microsoft/playwright · error · Error

Malformed endpoint. Did you use BrowserType.launchServer met

Error message

Malformed endpoint. Did you use BrowserType.launchServer method?

What it means

Thrown by connectToEndpoint after the WebSocket handshake succeeds and Playwright is initialized, but playwright._initializer.preLaunchedBrowser is absent. browserType.connect() expects a server that was started with browserType.launchServer(), which pre-launches a browser and ships its reference in the initializer. The endpoint you reached is a valid Playwright server but not one that exposes a pre-launched browser.

Source

Thrown at packages/playwright-core/src/client/connect.ts:61

  connection.on('close', () => {
    // Emulate all pages, contexts and the browser closing upon disconnect.
    for (const context of browser?.contexts() || []) {
      for (const page of context.pages())
        page._onClose();
      context._onClose();
    }
    setTimeout(() => browser?._didClose(), 0);
  });

  const result = await raceAgainstDeadline(async () => {
    // For tests.
    if ((params as any).__testHookBeforeCreateBrowser)
      await (params as any).__testHookBeforeCreateBrowser();

    const playwright = await connection!.initializePlaywright();
    if (!playwright._initializer.preLaunchedBrowser) {
      connection.close();
      throw new Error('Malformed endpoint. Did you use BrowserType.launchServer method?');
    }
    playwright.selectors = playwright.selectors;
    browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
    browser._shouldCloseConnectionOnClose = true;
    browser.on(Events.Browser.Disconnected, () => connection.close());
    return browser;
  }, deadline);
  if (!result.timedOut) {
    return result.result;
  } else {
    connection.close();
    throw new Error(`Timeout ${params.timeout}ms exceeded`);
  }
}

export async function connectToEndpoint(parentConnection: Connection, params: channels.LocalUtilsConnectParams, timeout: channels.TimeoutOptions): Promise<Connection> {
  const localUtils = parentConnection.localUtils();
  const transport = localUtils ? new JsonPipeTransport(localUtils) : new WebSocketTransport();

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Start the server with browserType.launchServer({ wsEndpoint, host, port }) (or `playwright launch-server chromium`) and connect to the wsEndpoint it prints.
  2. If the target is a raw Chrome/Edge CDP endpoint (e.g. --remote-debugging-port=9222), use browserType.connectOverCDP(httpUrl) instead of connect().
  3. Verify the server log actually prints 'Listening at ws://...' from launchServer; if not, switch to launchServer.
  4. Ensure client and server run the same Playwright version to avoid an empty/missing preLaunchedBrowser initializer.

Example fix

// before
const browser = await chromium.connect({ wsEndpoint: 'ws://localhost:9222/devtools/browser/...' });

// after — server side
const server = await chromium.launchServer({ host: '127.0.0.1', port: 9333 });
console.log(server.wsEndpoint());
// client side
const browser = await chromium.connect({ wsEndpoint: server.wsEndpoint() });
// — or, for a raw CDP target —
const browser = await chromium.connectOverCDP('http://localhost:9222');
Defensive patterns

Strategy: validation

Validate before calling

// Before connect(), confirm the endpoint is a launchServer endpoint.
// launchServer endpoints respond to the Playwright protocol with a preLaunchedBrowser initializer.
// Quick reachability + identity check (does NOT prove launchServer, but catches CDP/raw WS):
async function isPlaywrightServer(wsEndpoint) {
  const { WebSocket } = await import('ws');
  return await new Promise(resolve => {
    const ws = new WebSocket(wsEndpoint);
    const t = setTimeout(() => { ws.terminate(); resolve(false); }, 5000);
    ws.on('open', () => { clearTimeout(t); ws.close(); resolve(true); });
    ws.on('error', () => { clearTimeout(t); resolve(false); });
  });
}
if (!(await isPlaywrightServer(wsEndpoint))) throw new Error('Use connectOverCDP for raw CDP, or launchServer() on the server.');

Type guard

import type { Browser, BrowserType } from 'playwright-core';

// launchServer is the producer; its return type is the proof.
async function assertLaunchServer(bt: BrowserType, opts: Parameters<BrowserType['launchServer']>[0]): Promise<string> {
  const s = await bt.launchServer(opts);
  return s.wsEndpoint(); // only launchServer exposes wsEndpoint() on a BrowserServer
}

Try / catch

try {
  browser = await chromium.connect({ wsEndpoint, timeout: 30_000 });
} catch (e) {
  if (/Malformed endpoint\. Did you use BrowserType\.launchServer method\?/.test(String(e?.message))) {
    // Distinguish raw CDP from a genuinely wrong server before retrying.
    browser = await chromium.connectOverCDP(wsEndpoint.replace(/^ws/, 'http'));
  } else throw e;
}

Prevention

When it happens

Trigger: Calling chromium.firefox.webkit.connect({ wsEndpoint }) against: (a) a raw browser CDP/WebSocket endpoint, (b) a server started via `playwright launch-server` without launchServer() in code, (c) a playwright codegen/inspector server, or (d) any endpoint where the server did not call launchServer({ wsEndpoint }). initializePlaywright() resolves, but the initializer lacks preLaunchedBrowser, so the client closes the connection and throws.

Common situations: Pointing connect() at a Chrome DevTools Protocol target (you want connectOverCDP instead); version skew where the server is an older/newer build that does not populate preLaunchedBrowser; connecting to an orchestrator that proxies WebSocket but strips the browser initializer; copy-pasting a wsEndpoint from launch() instead of launchServer().

Understand the failure class

Related errors


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