Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions packages/playwright-core/src/tools/mcp/browserFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,16 @@ async function createCDPBrowser(config: FullConfig, clientInfo: ClientInfo): Pro

async function createRemoteBrowser(config: FullConfig): Promise<BrowserWithInfo> {
testDebug('create browser (remote)');
const descriptor = await serverRegistry.find(config.browser.remoteEndpoint!);
// `remoteEndpoint` may be a plain URL string or a ConnectOptions object that
// carries additional fields such as `exposeNetwork`, `headers`, `slowMo`, and
// `timeout`. Normalize once so the rest of the function deals with a single
// shape.
const remote = config.browser.remoteEndpoint!;
const remoteOptions = typeof remote === 'string'
? { endpoint: remote, headers: config.browser.remoteHeaders }
: remote;

const descriptor = await serverRegistry.find(remoteOptions.endpoint);
if (descriptor) {
const browser = await connectToBrowserAcrossVersions(descriptor);
return {
Expand All @@ -131,13 +140,9 @@ async function createRemoteBrowser(config: FullConfig): Promise<BrowserWithInfo>
};
}

const endpoint = config.browser.remoteEndpoint!;
const playwrightObject = playwright as Playwright;
// Use connectToBrowser instead of playwright[browserName].connect because we don't have browserName.
const browser = await connectToBrowser(playwrightObject, {
endpoint,
headers: config.browser.remoteHeaders,
});
const browser = await connectToBrowser(playwrightObject, remoteOptions);
browser._connectToBrowserType(playwrightObject[browser._browserName], {}, undefined);
return { browser, browserInfo: browserInfo(browser, config), canBind: false, ownership: 'attached' };
}
Expand Down
12 changes: 9 additions & 3 deletions packages/playwright-core/src/tools/mcp/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,18 @@ export type Config = {
cdpTimeout?: number;

/**
* Remote endpoint to connect to an existing Playwright server.
* Remote endpoint to connect to an existing Playwright server. May be a
* WebSocket URL string, or a [ConnectOptions] object that mirrors the
* `connectOptions` shape used by the test runner. When passed as an object,
* `exposeNetwork`, `headers`, `slowMo`, and `timeout` are forwarded to the
* underlying connect call.
*/
remoteEndpoint?: string;
remoteEndpoint?: string | playwright.ConnectOptions & { endpoint: string };

/**
* Headers to send with the remote endpoint connect request.
* Headers to send with the remote endpoint connect request. Ignored when
* `remoteEndpoint` is provided as a [ConnectOptions] object; supply
* `headers` on that object instead.
*/
remoteHeaders?: Record<string, string>;

Expand Down
22 changes: 22 additions & 0 deletions tests/mcp/remote-endpoint.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,25 @@ test('connect without remoteHeaders fails on run-server endpoint', async ({ star
error: expect.stringContaining(`reading 'launch'`),
});
});

test('remoteEndpoint accepts ConnectOptions object with headers', async ({ startClient, server, runServerEndpoint }) => {
const { client } = await startClient({
config: {
browser: {
remoteEndpoint: {
endpoint: runServerEndpoint,
headers: { 'x-playwright-browser': 'chromium' },
},
isolated: true,
},
},
});

const response = await client.callTool({
name: 'browser_navigate',
arguments: { url: server.HELLO_WORLD },
});
expect(response).toHaveResponse({
page: expect.stringContaining('Page Title: Title'),
});
});