BLOG
Playwright Chrome Extension Install: What to Do Instead
Playwright Chrome extension install isn't a real setup step. Learn what the extension actually is, and how to connect Playwright to Chrome with CDP.
# Playwright Chrome Extension Install: What to Do Instead
If you searched for a Playwright Chrome extension install, you are probably trying to get Playwright to drive the Chrome window you already have open. There is no official Playwright Chrome extension to install. Playwright ships as an npm package plus browser binaries, and it controls browsers through the Chrome DevTools Protocol (CDP) or its own launcher — not through a browser extension.
This guide explains what people usually mean by "Playwright Chrome extension," why the install path does not exist, and the connection methods that actually work: connectOverCDP, launchOptions.args, and hosted Chromium sessions you can attach to from anywhere.
Why there is no Playwright Chrome extension to install
Playwright is a Node.js (and Python/.NET/Java) library. When you run npm i -D @playwright/test and npx playwright install, you get:
- The Playwright client library in
node_modules. - Browser binaries (Chromium, Firefox, WebKit) in a local cache directory.
Neither of those is a Chrome extension. Playwright talks to browsers over a debugging protocol. For Chromium-based browsers that protocol is CDP; for Firefox and WebKit, Playwright uses its own patched builds and a similar out-of-process control channel.
The confusion comes from a few places:
- Chrome extensions that automate tabs. Tools like Selenium IDE or various "AI browser" extensions live inside Chrome and drive the active tab. That is a different architecture from Playwright.
- Playwright MCP servers. The Playwright MCP project exposes browser control to LLM clients. It is an MCP server, not a Chrome extension, though some clients wrap it in a UI that looks extension-like.
- Old Puppeteer-era tutorials. Puppeteer also has no Chrome extension; it connects over CDP. People search for the same thing under both names.
So the honest answer to "how do I install the Playwright Chrome extension" is: you don't. You install Playwright, then decide how it will reach a browser.
What you actually install
| Goal | What to install | Command / artifact |
|---|---|---|
| Run Playwright tests locally | Playwright package + browsers | npm i -D @playwright/test then npx playwright install |
| Drive your own Chrome via CDP | Playwright package only | npm i playwright, launch Chrome with --remote-debugging-port |
| Connect to a hosted browser | Playwright package only | chromium.connectOverCDP(wsEndpoint) |
| Give an LLM browser control | Playwright MCP server | npx @playwright/mcp@latest |
| Automate from a Chrome extension | A different tool | Not Playwright |
If you only need to attach to a browser that already exists — local or remote — you do not need npx playwright install at all. The client library is enough. That is a meaningful difference when you are building CI images or agent containers and do not want a few hundred megabytes of browser binaries in every layer.
Connecting Playwright to an existing Chrome over CDP
This is the closest thing to what people imagine a "Chrome extension" would do: take the browser that is already running and drive it.
Start Chrome with a debugging port:
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-profile
# Linux
google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-profileThen connect from Playwright:
import { chromium, Browser, BrowserContext, Page } from 'playwright';
async function attachToChrome(): Promise<void> {
// connectOverCDP returns a Browser bound to the running Chrome instance.
const browser: Browser = await chromium.connectOverCDP(
'http://127.0.0.1:9222'
);
// Existing tabs live in the default context. Reuse it; do not create a new one.
const contexts: BrowserContext[] = browser.contexts();
const context: BrowserContext = contexts[0] ?? (await browser.newContext());
const pages: Page[] = context.pages();
const page: Page = pages[0] ?? (await context.newPage());
await page.goto('https://example.com');
console.log(await page.title());
// Do NOT call browser.close() here — it would kill the user's Chrome.
await browser.close();
}
attachToChrome().catch((err) => {
console.error('CDP connect failed:', err);
process.exit(1);
});Three things trip people up here:
- `connectOverCDP` is Chromium-only. It will not attach to Firefox or WebKit. If you need those, you must launch them through Playwright.
- `browser.close()` disconnects and can terminate the browser. If you attached to a user's session, close the connection without killing the process, or just let the script exit.
- The default context already exists. Calling
newContext()on a CDP-connected browser creates an incognito-style context, not a second window in the same profile.
For a deeper walkthrough of the attach flow, see Playwright attach to existing browser.
When connectOverCDP fails
The most common failure is a connection refused or a WebSocket handshake error. Causes, roughly in order of frequency:
- Chrome was already running. A second launch with
--remote-debugging-portis ignored because the existing process owns the profile. Quit Chrome fully, or use a separate--user-data-dir. - The port is bound to localhost only. If Playwright runs in a container and Chrome runs on the host,
127.0.0.1inside the container is not the host. Usehost.docker.internalor a tunnel. - You are passing an HTTP URL where a WS URL is expected, or vice versa.
connectOverCDPaccepts bothhttp://host:portand a fullws://endpoint, but the endpoint must be the browser-level one, not a page target. - Chrome updated and changed the debugging surface. Rare, but pinning a Chrome version in CI avoids surprise.
If you are hitting browserWSEndpoint not working errors from Puppeteer-era code, the same diagnosis applies: the endpoint is either stale, wrong-scoped, or unreachable from the caller's network.
launchOptions.args: controlling the browser you launch
If you do not need to attach to an existing Chrome, launching your own with explicit args is more predictable. This is where launchOptions.args matters.
import { chromium } from 'playwright';
const browser = await chromium.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--remote-debugging-port=0', // let the OS pick a free port
],
});
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
locale: 'en-US',
timezoneId: 'America/New_York',
});
const page = await context.newPage();
await page.goto('https://example.com');
await browser.close();Notes on the args above:
--no-sandboxis common in containers but weakens isolation. Only use it where the container itself is the security boundary.--disable-dev-shm-usageavoids/dev/shmexhaustion in Docker, which otherwise causes random tab crashes.--remote-debugging-port=0lets the OS assign a port, which is useful when you want to expose the CDP endpoint to another process without hardcoding a number.
For a fuller treatment of launch flags and their trade-offs, see Playwright browser launch options.
Local Chrome vs hosted Chromium
Once you move past a single developer machine, the local-Chrome approach starts to cost more than it saves.
| Dimension | Local Chrome + CDP | Hosted Chromium session |
|---|---|---|
| Setup | Install Chrome, manage profile dirs, open ports | Get a CDP endpoint, connect |
| Scaling | One browser per machine, manual | Sessions provisioned per request |
| Isolation | Shared profile unless you script it | Session-scoped by default |
| CI friendliness | Needs a display or headless flags | Headless by default |
| Debugging | Local DevTools only | Live viewer plus CDP |
| Profile persistence | Manual --user-data-dir management | Configurable persistent profiles |
| Network egress | Your machine's IP | Configurable proxy settings |
The trade-off is control versus operational overhead. Local Chrome gives you the exact build and flags you chose. A hosted runtime gives you a consistent, disposable browser you can reach from any worker, which is usually what you want once more than one process needs to drive a browser.
Remote Browser provides hosted Chromium sessions with CDP access, so the same connectOverCDP call works against a remote endpoint:
import { chromium } from 'playwright';
// wsEndpoint comes from your session provisioning call.
const browser = await chromium.connectOverCDP(process.env.BROWSER_WS_ENDPOINT!);
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com');
await browser.close();The client code is identical to the local case. What changes is where the browser lives and who is responsible for keeping it alive, patched, and isolated. See Remote Browser for AI agents for the runtime model, and Remote Browser online if you want to try a session without installing anything locally.
Playwright MCP and the "extension" confusion
The Playwright MCP server is the other thing people mean when they search for a Chrome extension. It exposes Playwright's browser control as MCP tools so an LLM client can call browser_navigate, browser_click, and so on.
It is not a Chrome extension. It is a process that owns a browser and speaks MCP to the client. You can point it at a hosted browser the same way you would point a script at one, which is often the right move for agents that need a stable, long-lived session rather than a browser tied to a developer's laptop.
If your actual goal is "let an agent drive a browser," the MCP route is usually more direct than trying to bolt automation onto a Chrome extension. For the connection-level details, see Playwright MCP Chrome extension.
Production criteria before you commit
Whichever path you pick, check these before you build on it:
- Endpoint stability. Does the CDP endpoint survive a worker restart, or do you need to re-provision? Long-running agents need the latter.
- Session isolation. Can two concurrent jobs share a profile by accident? They should not be able to.
- Profile persistence. If your workflow logs in once and reuses cookies, you need a profile that outlives a single session.
- Network controls. Proxy and egress settings matter for anything touching geo-restricted or rate-limited sites.
- Observability. A live viewer plus CDP access makes debugging a failed run far cheaper than log archaeology.
- Cost model. Browser time is metered differently across providers. Check pricing for current details rather than assuming a per-request model.
None of these are Playwright problems. They are runtime problems, and they are the reason teams move from "Chrome on my laptop" to a hosted browser once automation leaves the prototype stage.
Summary
There is no Playwright Chrome extension to install. Playwright is a library that controls browsers over CDP or its own protocol. If you want to drive an existing Chrome, launch it with --remote-debugging-port and call chromium.connectOverCDP(). If you want a browser you can reach from anywhere, connect the same client to a hosted Chromium endpoint and let the runtime handle isolation, profiles, and lifecycle.
The install step you were looking for does not exist. The connection step does, and it is one line of code. For the full API surface, see the documentation.