← Blog

BLOG

How To Use Playwright Chrome Extension

Learn how to use Playwright Chrome extension workflows, why connectOverCDP matters, and how to run Playwright against hosted Chromium.

September 17, 202610 min readRemote Browser

# How To Use Playwright Chrome Extension

If you searched for "how to use Playwright Chrome extension," you probably want one of two things: a recorder-style tool that captures clicks into Playwright code, or a way to drive a real Chrome instance that already has extensions loaded. Playwright itself does not ship a Chrome extension for automation. What it ships is a CDP client that can attach to a running Chromium-based browser — including one with extensions installed — via chromium.connectOverCDP(). That distinction is the whole answer, and it changes how you set up your stack.

This guide covers the practical paths: the Playwright Chrome Recorder extension, connecting Playwright to a browser that has extensions, and running the same code against hosted Chromium so you are not maintaining a local Chrome install on every worker. If you want the runtime layer first, see Remote Browser for AI agents.

What the "Playwright Chrome Extension" actually refers to

There are three different things people mean when they use that phrase, and conflating them causes most of the confusion:

  1. Playwright Chrome Recorder — a Chrome extension published by the Playwright team that records user interactions and exports them as Playwright test code. It is a codegen tool, not a runtime.
  2. Playwright MCP / browser extensions in the target browser — extensions loaded into the Chrome instance you are automating. Playwright can drive a browser that has extensions, but it cannot install or manage them for you through the extension APIs.
  3. A hypothetical "Playwright extension" for remote control — this does not exist. Remote control is done over the Chrome DevTools Protocol (CDP), not through a browser extension.

If you are looking for a download link to a Playwright automation extension, that is the wrong mental model. The correct primitive is CDP. The Chrome DevTools Protocol documentation is the authoritative reference for what that connection exposes.

Playwright Chrome Recorder: what it does and does not do

The Playwright Chrome Recorder is genuinely useful for one job: turning a manual click-through into a starting test file. You install it from the Chrome Web Store, hit record, perform the flow, and export a .spec.ts file.

What it does not do:

  • It does not run your tests. Export is the end of its involvement.
  • It does not handle authentication state, network mocking, or fixtures.
  • It does not produce production-grade selectors. Recorded selectors are often brittle and need editing.
  • It does not connect to remote browsers.

Treat the recorder as a scaffolding tool. Record once, then rewrite the selectors and add assertions. Do not ship recorded code as-is.

When the recorder is worth using

  • Bootstrapping a new test suite where you do not know the DOM yet.
  • Capturing a complex multi-step flow so you can see which locators Playwright prefers.
  • Handing a reproducible repro to a teammate.

When to skip it

  • You already have a page object model.
  • The flow involves iframes, shadow DOM, or canvas — the recorder struggles with all three.
  • You need to run the same flow across many sessions in parallel. That is a runtime problem, not a recording problem.

Connecting Playwright to a browser with extensions

If your goal is to automate a Chrome instance that has extensions loaded — an ad blocker, a wallet, an internal helper — you cannot launch that browser through Playwright's bundled Chromium and expect your extensions to be there. You have two options.

Option A: Launch Chrome with a persistent context

Playwright can launch your installed Chrome with channel: 'chrome' and a persistent user data directory. Extensions installed in that profile are available to the automation session.

import { chromium } from 'playwright';

const context = await chromium.launchPersistentContext(
  '/tmp/chrome-profile',
  {
    channel: 'chrome',
    headless: false,
    args: [
      '--disable-extensions-except=/path/to/extension',
      '--load-extension=/path/to/extension',
    ],
  }
);

const page = await context.newPage();
await page.goto('https://example.com');

This works, but it ties your automation to a machine that has Chrome installed, a profile directory that persists, and a display server if you run headed. Scaling that across workers is painful.

Option B: Connect over CDP to a browser that already has extensions

This is the pattern that scales. You start Chrome (or a hosted Chromium session) with the extensions you need, expose the CDP endpoint, and connect Playwright to it.

import { chromium } from 'playwright';

// The endpoint comes from your browser runtime, not from Playwright.
const browser = await chromium.connectOverCDP(
  'wss://your-browser-runtime.example/cdp'
);

const context = browser.contexts()[0];
const page = await context.newPage();

await page.goto('https://example.com');
await page.getByRole('button', { name: 'Sign in' }).click();

// Close the connection, not the browser, if the runtime owns the lifecycle.
await browser.close();

Two things to note. First, connectOverCDP returns a Browser whose contexts already exist — you call browser.contexts()[0] rather than browser.newContext(). Second, browser.close() on a CDP connection disconnects; whether it also terminates the remote browser depends on the runtime. With hosted Chromium, the session lifecycle is usually managed separately.

The official Playwright reference for this method is BrowserType.connectOverCDP.

Local Chrome vs hosted Chromium for Playwright

The decision that matters most is not which extension you use. It is where the browser runs.

CriterionLocal Chrome + extensionsHosted Chromium over CDP
Extension supportFull, but per-machineConfigurable per session
Setup per workerInstall Chrome, profile, extensionsConnect to an endpoint
ScalingManual, brittleSession-per-request
DebuggingLocal DevToolsLive viewer + CDP
Profile persistenceLocal directoryManaged persistent profiles
Proxy configurationPer-machine flagsConfigurable browser settings
CI friendlinessRequires Chrome in the imageEndpoint + credentials
Failure isolationShared machine stateIsolated sessions

If you are running one script on your laptop, local Chrome is fine. If you are running many sessions a day, the local model breaks down at the point where you need a second machine.

What connectOverCDP does not solve

CDP is a control protocol, not a runtime. Connecting Playwright to a remote browser solves the "where does Chrome run" problem. It does not solve:

  • Session lifecycle. Who starts the browser, who stops it, what happens when a worker dies mid-session.
  • Profile persistence. Cookies, localStorage, and logged-in state need a home that survives the session.
  • Proxy and network egress. IP reputation matters for many sites; that is a runtime concern.
  • Observability. You need to see what the agent saw when a run fails.
  • Concurrency limits. Every browser session costs memory and CPU somewhere.

Those are the reasons teams move from "connect Playwright to a Chrome I started" to "connect Playwright to a browser runtime." The Remote Browser documentation covers how sessions, profiles, and CDP endpoints are exposed.

A production-shaped Playwright + CDP setup

Here is a pattern that holds up when you move from a script to a service. The key idea: your Playwright code should not know or care whether the browser is local or remote. It receives a CDP endpoint and connects.

import { chromium, Browser, Page } from 'playwright';

interface SessionHandle {
  cdpUrl: string;
  close: () => Promise<void>;
}

async function withBrowser<T>(
  session: SessionHandle,
  fn: (page: Page) => Promise<T>
): Promise<T> {
  const browser: Browser = await chromium.connectOverCDP(session.cdpUrl, {
    timeout: 30_000,
  });

  try {
    const context = browser.contexts()[0] ?? (await browser.newContext());
    const page = await context.newPage();
    return await fn(page);
  } finally {
    await browser.close();
    await session.close();
  }
}

// Usage
await withBrowser(await createSession(), async (page) => {
  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  await page.getByLabel('Search').fill('playwright cdp');
  await page.keyboard.press('Enter');
  await page.waitForLoadState('networkidle');
});

Three details worth calling out:

  • Timeout on connect. A CDP handshake that hangs is worse than one that fails. Set an explicit timeout.
  • Context reuse. Hosted runtimes often pre-create a context. Reusing it preserves any profile state the runtime loaded.
  • Separate close semantics. Closing the Playwright Browser disconnects. Closing the session is a runtime call. Keep them separate so a disconnect does not leak a session.

If you are wiring this into an agent loop rather than a test suite, the same shape applies — the agent calls a tool that opens a session, runs actions, and closes. See Remote Browser online for the runtime view.

Extensions, stealth, and what you can actually configure

A common reason people want "the Playwright Chrome extension" is to load something that changes how the browser looks to the site — an ad blocker, a fingerprint tool, a wallet. Be precise about what is and is not supported.

  • Loading extensions into a hosted session depends on the runtime. Some runtimes allow a fixed set of pre-installed extensions; others do not allow arbitrary extension loading at all. Check the runtime's docs rather than assuming.
  • Configurable browser settings — user agent, locale, timezone, viewport, proxy — are standard and usually exposed as session parameters. These are not the same as fingerprint spoofing.
  • Fingerprint spoofing is a stronger claim. Do not assume it unless the runtime documents it explicitly.

For most automation, the settings that matter are proxy egress, locale, timezone, and a consistent user agent. Those are boring and effective. Exotic fingerprint manipulation is a maintenance burden and often counterproductive.

Debugging: the part extensions do not help with

When a Playwright run fails against a remote browser, you need three things:

  1. The CDP endpoint so you can attach DevTools or a second Playwright client.
  2. A live viewer so a human can watch the session in real time.
  3. Session artifacts — console logs, network HAR, screenshots — captured at the runtime level.

A local Chrome with extensions gives you the first one for free and nothing else. A hosted runtime should give you all three. This is the practical reason teams move off local Chrome even when extensions are not the bottleneck. The remote control browser post goes deeper on the debugging workflow.

Common mistakes

  • Calling `browser.newContext()` after `connectOverCDP`. The remote browser already has contexts. Use them, or you will fight state.
  • Assuming `browser.close()` kills the remote browser. It usually does not. Manage the session separately.
  • Hardcoding the CDP URL. Endpoints are per-session. Read them from your runtime.
  • Recording with the Chrome Recorder and shipping the output. Recorded selectors break. Rewrite them.
  • Trying to install extensions at runtime via CDP. Extension installation is not a CDP feature. It is a launch-time concern.
  • Ignoring proxy configuration until production. IP reputation affects success rates long before it affects your local tests.

Where Remote Browser fits

Remote Browser provides hosted Chromium sessions with CDP access, so Playwright, Puppeteer, and Selenium clients connect the same way they would to a local Chrome. Sessions are isolated, profiles can persist across runs, and browser settings like proxy and locale are configurable per session. There is a live viewer for debugging and usage controls so you can see what you are spending.

It is not a replacement for the Playwright Chrome Recorder — that tool still has a place for scaffolding. It is the runtime layer underneath, so the code you export from the recorder can run against a browser you did not have to install. For the broader picture, see remote web browser.

Pricing and current limits are on the pricing page. If you want to try the connection path first, the documentation has the endpoint and session details.

Summary

  • There is no Playwright automation Chrome extension. There is a Chrome Recorder extension for codegen, and there is CDP for control.
  • To automate a browser with extensions, either launch Chrome with a persistent context and --load-extension, or connect over CDP to a browser that already has them.
  • chromium.connectOverCDP() is the production path. It decouples your Playwright code from where Chrome runs.
  • CDP solves control, not lifecycle. Session management, profiles, proxies, and observability are runtime concerns.
  • Hosted Chromium removes the per-machine Chrome install and gives you a consistent endpoint for every worker.

Start with the recorder if you need scaffolding. Ship with CDP against a hosted runtime.