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
- Start the server with browserType.launchServer({ wsEndpoint, host, port }) (or `playwright launch-server chromium`) and connect to the wsEndpoint it prints.
- If the target is a raw Chrome/Edge CDP endpoint (e.g. --remote-debugging-port=9222), use browserType.connectOverCDP(httpUrl) instead of connect().
- Verify the server log actually prints 'Listening at ws://...' from launchServer; if not, switch to launchServer.
- 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
- Always obtain wsEndpoint from launchServer().wsEndpoint(); never hand-write it.
- Document at the call site which producer (launchServer vs CDP vs codegen) the endpoint came from.
- Pin client and server Playwright versions in lockfile.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Timeout ${params.timeout}ms exceeded
- Timeout ${options.timeout}ms exceeded
- The value of "n" is out of range. It must be a non-negative
- Socks4 proxy protocol does not support authentication
- Browser does not support socks5 proxy authentication
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/97271d8fffe7d0f5.
Report an issue: GitHub.