microsoft/playwright · error · Error

Extension not connected

Error message

Extension not connected

What it means

Thrown inside the CdpRelay's `sendCommand` closure whenever a command is dispatched before `this._extensionConnection` has been set (the extension WebSocket has not yet connected). The relay routes every Playwright→extension command through that connection, so a missing connection is a hard precondition failure.

Source

Thrown at packages/playwright-core/src/tools/mcp/cdpRelay.ts:83

  private _profileDirectory?: string;
  private _cdpPath: string;
  private _extensionPath: string;
  private _cdpConnection: WebSocket | null = null;
  private _extensionConnection: ExtensionConnection | null = null;
  private _protocolVersion: number;
  private _handler: ExtensionProtocolV2;
  private _extensionConnectionPromise = new ManualPromise<void>();

  constructor(browserChannel: string, executablePath?: string, customUserDataDir?: string, profileDirectory?: string) {
    this._browserChannel = browserChannel;
    this._executablePath = executablePath;
    this._customUserDataDir = customUserDataDir;
    this._profileDirectory = profileDirectory;
    this._protocolVersion = parseInt(process.env.PLAYWRIGHT_EXTENSION_PROTOCOL ?? protocol.VERSION.toString(), 10);

    const sendCommand = (method: string, params: any): Promise<any> => {
      if (!this._extensionConnection)
        throw new Error('Extension not connected');
      return this._extensionConnection.send(method as keyof ExtensionCommandV2, params);
    };
    this._handler = new ExtensionProtocolV2(sendCommand);

    const uuid = crypto.randomUUID();
    this._cdpPath = `/cdp/${uuid}`;
    this._extensionPath = `/extension/${uuid}`;

    void this._extensionConnectionPromise.catch(logUnhandledError);
    this._wsServer = new WSServer({
      onRequest: (request, response) => {
        response.statusCode = 404;
        response.end();
      },
      onHeaders: () => {},
      onUpgrade: () => undefined,
      isAllowedPathname: pathname => pathname === this._cdpPath || pathname === this._extensionPath,
      onConnection: (request, url, ws) => {

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Complete the extension connection flow first — `await relay.establishExtensionConnection(clientName)` — before driving the browser.
  2. Verify the extension is installed, enabled, and that the connect page was accepted by the user.
  3. If a token is configured (`PLAYWRIGHT_MCP_EXTENSION_TOKEN`), ensure the extension sends the matching token.
Defensive patterns

Strategy: validation

Validate before calling

// Only send commands once the extension connection is established.
let extensionReady = false;
relay.onExtensionConnected(() => { extensionReady = true; });
function assertExtensionReady() {
  if (!extensionReady) throw new Error('Extension not connected yet; complete the connect flow first.');
}

Try / catch

try {
  await cdpRelay.someCommand(...);
} catch (e) {
  if (/Extension not connected/.test((e as Error).message)) {
    await relay.establishExtensionConnection(clientName); // guide user through connect
    await cdpRelay.someCommand(...);
  } else throw e;
}

Prevention

When it happens

Trigger: Any CDP/command traffic is generated before `_handleExtensionConnection` has wired up `_extensionConnection`, i.e. before `establishExtensionConnection` resolves. This can happen if Playwright connects to the CDP endpoint and starts sending commands before the extension side has dialed in.

Common situations: The user has not yet approved/connected the browser extension; the extension failed to connect (network blocked, wrong token, extension disabled); a race where the CDP client connects and issues commands immediately while the extension handshake is still pending.

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/24fe1ae2ef147ed2. Report an issue: GitHub.