BLOG
Playwright Chrome Extension Download: What You Actually Need
Playwright Chrome extension download options explained, plus how to run Playwright against hosted Chromium with CDP instead of local extensions.
# Playwright Chrome Extension Download: What You Actually Need
If you searched for a "Playwright Chrome extension download," you probably want one of two things: the Playwright extension for Chrome DevTools, or a way to drive a real Chrome instance from Playwright without fighting local setup. Those are different problems, and only one of them has a downloadable extension. This guide covers both, then shows how to connect Playwright to a hosted Chromium session over CDP so you can skip the local browser entirely.
The short version: Playwright does not ship a Chrome extension you install from the Web Store. It ships a Chrome DevTools extension that adds a "Playwright" panel to DevTools for recording and inspecting selectors. If your actual goal is running Playwright against a remote browser, the extension is irrelevant — you want CDP, and you want a runtime that exposes it.
What "Playwright Chrome Extension" Usually Means
There are three distinct things people conflate under this search term:
- The Playwright DevTools extension. A small Chrome extension maintained by the Playwright team that adds a Playwright tab to Chrome DevTools. It records user interactions and generates selector code you can paste into a test.
- Playwright's browser binaries. When you run
npx playwright install, Playwright downloads its own Chromium, Firefox, and WebKit builds. These are not extensions and do not appear inchrome://extensions. - **Chrome extensions used *inside* Playwright tests.** If you want to load a real
.crxextension into a Playwright-controlled browser, you uselaunchPersistentContextwith--load-extension. This is a Playwright feature, not a download.
Most searches land on #1. Most production needs are actually #3 or a remote-browser variant of it.
The Playwright DevTools Extension
The DevTools extension is genuinely useful for one job: writing selectors. You open DevTools, click the Playwright tab, hit record, interact with the page, and it emits code like:
await page.getByRole('button', { name: 'Sign in' }).click();That output is better than hand-written CSS selectors because it uses Playwright's role-based locators, which survive DOM refactors. The extension is a development aid. It does not run your tests, does not connect to remote browsers, and does not help with CI.
If that is all you need, install it from the Playwright documentation's extension page and move on. If you need Playwright to drive a browser that is not on your laptop, keep reading.
Loading Real Extensions Into Playwright
If you actually need a Chrome extension (an ad blocker, an auth helper, a corporate SSO extension) loaded inside a Playwright session, the pattern is:
import { chromium } from 'playwright';
const context = await chromium.launchPersistentContext('/tmp/profile', {
headless: false,
args: [
`--disable-extensions-except=/path/to/extension`,
`--load-extension=/path/to/extension`,
],
});Two constraints matter here:
- Chromium only.
--load-extensionworks with Chromium and Chrome, not Firefox or WebKit. - Persistent context required. Extensions need a profile directory, so you cannot use the default ephemeral context.
This works fine locally. It gets awkward the moment you move to CI, because you now need to ship the .crx or unpacked extension directory into every worker, keep it in sync, and deal with the fact that headless Chromium historically had limited extension support (the new headless mode improved this, but behavior still varies by version).
Why Local Extension Setup Breaks Down in Production
The extension itself is rarely the hard part. The hard part is everything around it:
| Concern | Local Playwright + extension | Hosted Chromium runtime |
|---|---|---|
| Browser binary management | You pin and patch Chromium versions per worker | Runtime owns the binary |
| Extension distribution | Copy .crx into every container image | Configurable per session |
| Profile persistence | Local disk, lost on container recycle | Persistent profiles across sessions |
| Scaling | One browser per worker, memory-bound | Sessions provisioned on demand |
| Debugging | Screenshots and traces after the fact | Live viewer during the run |
| Proxy / network config | Per-worker env vars | Session-level configuration |
| CDP access | Local only | Remote endpoint you connect to |
The pattern that scales is to stop treating the browser as a local process and start treating it as a service you connect to. That is what a hosted runtime like Remote Browser provides: a Chromium session with a CDP endpoint, Playwright-compatible, with persistent profiles and a live viewer.
Connecting Playwright to a Remote Browser Over CDP
Playwright's connectOverCDP method attaches to an existing Chromium instance instead of launching one. The remote runtime hands you a WebSocket debugger URL, and Playwright drives it exactly like a local browser.
import { chromium, Browser, BrowserContext, Page } from 'playwright';
interface SessionInfo {
cdpUrl: string;
sessionId: string;
}
async function connectToHostedBrowser(): Promise<{
browser: Browser;
context: BrowserContext;
page: Page;
}> {
// Your runtime returns a CDP WebSocket URL for the session.
const res = await fetch('https://api.remote-browser.dev/v1/sessions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.REMOTE_BROWSER_API_KEY}`,
},
body: JSON.stringify({
// Configurable browser settings: region, proxy, profile, viewport.
profile: 'checkout-agent',
viewport: { width: 1280, height: 800 },
}),
});
const session: SessionInfo = await res.json();
// Attach Playwright to the hosted Chromium instance.
const browser = await chromium.connectOverCDP(session.cdpUrl);
// Reuse the default context so persistent profile state is preserved.
const context = browser.contexts()[0] ?? (await browser.newContext());
const page = context.pages()[0] ?? (await context.newPage());
return { browser, context, page };
}
async function run() {
const { browser, page } = await connectToHostedBrowser();
await page.goto('https://example.com/login');
await page.getByLabel('Email').fill('agent@example.com');
await page.getByRole('button', { name: 'Continue' }).click();
// Disconnect without killing the remote session if you want to resume later.
await browser.close();
}
run().catch(console.error);A few things worth noting about this pattern:
- `connectOverCDP` is Chromium-only. That is fine for most agent and automation workloads, but if you need Firefox or WebKit, you need a different transport. See the Playwright CDP documentation for the exact contract.
- `browser.close()` on a CDP connection disconnects; it does not necessarily terminate the remote browser. Whether the session persists depends on the runtime. On Remote Browser, session lifecycle is explicit — you can keep a session alive, reconnect, or tear it down.
- Persistent profiles matter. If your workflow logs in once and reuses the session, the profile has to survive across connections. That is a runtime feature, not a Playwright feature.
If you are migrating an existing local Playwright suite, the change is usually small: replace chromium.launch() with chromium.connectOverCDP(url). The rest of your test code — locators, assertions, waits — stays the same.
Extensions, Profiles, and What Hosted Runtimes Actually Support
Here is where you need to be precise about what a hosted runtime can and cannot do, because marketing pages tend to blur this.
What hosted Chromium handles well:
- Persistent profiles so cookies, localStorage, and auth state survive across sessions.
- Configurable browser settings at the session level — viewport, user agent, locale, timezone, proxy.
- Session isolation so one agent's cookies never leak into another's.
- Live viewer for watching a session in real time, which is far more useful than post-hoc traces when an agent goes off the rails.
- CDP access for Playwright, Puppeteer, and Selenium clients.
What is genuinely harder:
- Arbitrary Chrome extensions. Some runtimes support loading extensions; many do not, and support varies by Chromium version. If your workflow depends on a specific extension, verify it works before committing.
- Fingerprint-level customization. "Stealth" is a spectrum, not a checkbox. Prefer runtimes that describe their settings concretely rather than promising undetectability.
For AI agent workloads specifically, the extension question usually disappears. Agents do not need your ad blocker; they need a clean, isolated, persistent browser they can reconnect to. That is a runtime problem, and it is why the remote browser for AI agents pattern exists.
When You Still Want a Local Browser
Hosted runtimes are not the right answer for everything. Keep Playwright local when:
- You are writing and debugging selectors. The DevTools extension and headed local Chromium are faster to iterate against.
- You need to test a Chrome extension you are developing. Loading an unpacked extension into a local persistent context is the standard workflow.
- Your test suite is small, runs on one machine, and does not need to scale.
- You are doing visual regression against a specific local rendering environment.
Move to a hosted runtime when:
- Your CI workers are memory-constrained and browser-per-worker does not fit.
- You need sessions that outlive a single process — long-running agents, human-in-the-loop approvals, scheduled jobs.
- You need to debug a production run live rather than reconstruct it from a trace.
- You are running AI agents that need a browser they can reconnect to across steps.
The trade-off is control versus operational burden. Local gives you full control and full responsibility. Hosted gives you a stable endpoint and takes the binary management, profile storage, and scaling off your plate.
Practical Migration Checklist
If you are moving from local Playwright to a hosted Chromium runtime, work through this in order:
- Audit your launch options. Every flag you pass to
chromium.launch()needs a hosted equivalent. Viewport, locale, timezone, and proxy usually map cleanly. Extension flags often do not. - Decide on profile strategy. Ephemeral sessions are simpler; persistent profiles are required for anything involving login state. Pick per workflow, not globally.
- Replace launch with connect. Swap
chromium.launch()forchromium.connectOverCDP(cdpUrl). Keep the rest of your code unchanged until tests pass. - Handle session lifecycle explicitly. Decide when a session ends: on disconnect, on explicit teardown, or on a timeout. Ambiguity here causes leaked sessions and surprise bills.
- Add a live viewer to your debugging loop. Watching a failing run in real time beats reading a stack trace.
- Check pricing against your actual usage pattern. Browser time is metered, and agent workloads are bursty. See pricing for current rates rather than assuming a per-seat model.
For a broader look at what running Chromium without local setup involves, the remote browser online guide covers the operational side in more depth.
The Bottom Line
There is no Playwright Chrome extension download that turns Playwright into a remote browser controller. The DevTools extension helps you write selectors. Loading extensions into Playwright requires a persistent Chromium context. And running Playwright against a browser that is not on your machine requires CDP — specifically connectOverCDP against a Chromium instance that someone else operates.
If that last case is yours, the useful question is not "which extension do I download" but "which runtime gives me a stable CDP endpoint, persistent profiles, and a way to watch sessions live." That is the problem Remote Browser is built to solve, and the documentation walks through session creation, connection, and teardown with working code.