Skip to content

Commit 1913cc7

Browse files
committed
cleanups
1 parent 6679996 commit 1913cc7

File tree

13 files changed

+61
-99
lines changed

13 files changed

+61
-99
lines changed

README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,6 @@ Playwright MCP server supports following arguments. They can be provided in the
188188
example "1280, 720"
189189
--vision Run server that uses screenshots (Aria snapshots
190190
are used by default)
191-
--extension Allow connecting to a running browser instance
192-
(Edge/Chrome only). Requires the 'Playwright MCP'
193-
browser extension to be installed.
194191
```
195192

196193
<!--- End of options generated section -->

config.d.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,6 @@ export type Config = {
104104
*/
105105
saveTrace?: boolean;
106106

107-
/**
108-
* Run server that is able to connect to the 'Playwright MCP' Chrome extension.
109-
*/
110-
extension?: boolean;
111-
112107
/**
113108
* The directory to save output files.
114109
*/

extension/background.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
function debugLog(...args) {
8-
const enabled = true;
8+
const enabled = false;
99
if (enabled) {
1010
console.log('[Extension]', ...args);
1111
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"ctest": "playwright test --project=chrome",
2525
"ftest": "playwright test --project=firefox",
2626
"wtest": "playwright test --project=webkit",
27+
"etest": "playwright test --project=chromium-extension",
2728
"run-server": "node lib/browserServer.js",
2829
"clean": "rm -rf lib",
2930
"npm-publish": "npm run clean && npm run build && npm run test && npm publish"

src/browserContextFactory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ import type { BrowserInfo, LaunchBrowserRequest } from './browserServer.js';
2828

2929
const testDebug = debug('pw:mcp:test');
3030

31-
export function contextFactory(browserConfig: FullConfig['browser']): BrowserContextFactory {
31+
export function contextFactory(browserConfig: FullConfig['browser'], { forceCdp }: { forceCdp?: boolean } = {}): BrowserContextFactory {
3232
if (browserConfig.remoteEndpoint)
3333
return new RemoteContextFactory(browserConfig);
34-
if (browserConfig.cdpEndpoint)
34+
if (browserConfig.cdpEndpoint || forceCdp)
3535
return new CdpContextFactory(browserConfig);
3636
if (browserConfig.isolated)
3737
return new IsolatedContextFactory(browserConfig);

src/cdp-relay.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ export class CDPBridgeServer extends EventEmitter {
3838
sessionId: string;
3939
} | undefined;
4040

41-
public readonly CDP_PATH = '/cdp';
42-
public readonly EXTENSION_PATH = '/extension';
41+
public static readonly CDP_PATH = '/cdp';
42+
public static readonly EXTENSION_PATH = '/extension';
4343

4444
constructor(server: http.Server) {
4545
super();
@@ -57,9 +57,9 @@ export class CDPBridgeServer extends EventEmitter {
5757

5858
debugLogger(`New connection to ${url.pathname}`);
5959

60-
if (url.pathname === this.CDP_PATH) {
60+
if (url.pathname === CDPBridgeServer.CDP_PATH) {
6161
this._handlePlaywrightConnection(ws);
62-
} else if (url.pathname === this.EXTENSION_PATH) {
62+
} else if (url.pathname === CDPBridgeServer.EXTENSION_PATH) {
6363
this._handleExtensionConnection(ws);
6464
} else {
6565
debugLogger(`Invalid path: ${url.pathname}`);
@@ -293,8 +293,8 @@ if (import.meta.url === `file://${process.argv[1]}`) {
293293
const server = new CDPBridgeServer(httpServer);
294294

295295
console.error(`CDP Bridge Server listening on ws://localhost:${port}`);
296-
console.error(`- Playwright MCP: ws://localhost:${port}${server.CDP_PATH}`);
297-
console.error(`- Extension: ws://localhost:${port}${server.EXTENSION_PATH}`);
296+
console.error(`- Playwright MCP: ws://localhost:${port}${CDPBridgeServer.CDP_PATH}`);
297+
console.error(`- Extension: ws://localhost:${port}${CDPBridgeServer.EXTENSION_PATH}`);
298298

299299
process.on('SIGINT', () => {
300300
debugLogger('\nShutting down bridge server...');

src/config.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,18 @@ import os from 'os';
1919
import path from 'path';
2020
import { devices } from 'playwright';
2121

22-
import type { Config, ToolCapability } from '../config.js';
22+
import type { Config as PublicConfig, ToolCapability } from '../config.js';
2323
import type { BrowserContextOptions, LaunchOptions } from 'playwright';
2424
import { sanitizeForFilePath } from './tools/utils.js';
2525

26+
type Config = PublicConfig & {
27+
/**
28+
* TODO: Move to PublicConfig once we are ready to release this feature.
29+
* Run server that is able to connect to the 'Playwright MCP' Chrome extension.
30+
*/
31+
extension?: boolean;
32+
};
33+
2634
export type CLIOptions = {
2735
allowedOrigins?: string[];
2836
blockedOrigins?: string[];

src/program.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,15 @@
1515
*/
1616

1717
import type http from 'http';
18-
import { program } from 'commander';
18+
import { Option, program } from 'commander';
1919
// @ts-ignore
2020
import { startTraceViewerServer } from 'playwright-core/lib/server';
2121

22-
import { startHttpTransport, startStdioTransport } from './transport.js';
22+
import { httpAddressToString, startHttpTransport, startStdioTransport } from './transport.js';
2323
import { resolveCLIConfig } from './config.js';
2424
import { Server } from './server.js';
2525
import { packageJSON } from './package.js';
2626
import { CDPBridgeServer } from './cdp-relay.js';
27-
import { AddressInfo } from 'net';
2827

2928
program
3029
.version('Version ' + packageJSON.version)
@@ -55,14 +54,10 @@ program
5554
.option('--user-data-dir <path>', 'path to the user data directory. If not specified, a temporary directory will be created.')
5655
.option('--viewport-size <size>', 'specify browser viewport size in pixels, for example "1280, 720"')
5756
.option('--vision', 'Run server that uses screenshots (Aria snapshots are used by default)')
58-
.option('--extension', 'Allow connecting to a running browser instance (Edge/Chrome only). Requires the \'Playwright MCP\' browser extension to be installed.')
57+
.addOption(new Option('--extension', 'Allow connecting to a running browser instance (Edge/Chrome only). Requires the \'Playwright MCP\' browser extension to be installed.').hideHelp())
5958
.action(async options => {
6059
const config = await resolveCLIConfig(options);
61-
if (config.extension)
62-
// TODO: The issue is that inside 'new Server' we create the ContextFactory
63-
// instances based on if there is a CDP address given.
64-
config.browser.cdpEndpoint = '1';
65-
const server = new Server(config);
60+
const server = new Server(config, { forceCdp: !!config.extension });
6661
server.setupExitWatchdog();
6762

6863
let httpServer: http.Server | undefined = undefined;
@@ -79,12 +74,12 @@ program
7974
console.error('\nTrace viewer listening on ' + url);
8075
}
8176
if (config.extension && httpServer) {
82-
config.browser.cdpEndpoint = `ws://127.0.0.1:${(httpServer.address() as AddressInfo).port}/cdp`;
77+
const wsAddress = httpAddressToString(httpServer.address()).replace('http', 'ws');
78+
config.browser.cdpEndpoint = `${wsAddress}${CDPBridgeServer.CDP_PATH}`;
8379
const cdpRelayServer = new CDPBridgeServer(httpServer);
84-
// TODO: use watchdog to stop the server on exit
8580
process.on('exit', () => cdpRelayServer.stop());
8681
// eslint-disable-next-line no-console
87-
console.error(`CDP relay server started on ws://127.0.0.1:${(httpServer.address() as AddressInfo).port}/extension - Connect to it using the browser extension.`);
82+
console.error(`CDP relay server started on ${wsAddress}${CDPBridgeServer.EXTENSION_PATH} - Connect to it using the browser extension.`);
8883
}
8984
});
9085

src/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ export class Server {
2828
private _browserConfig: FullConfig['browser'];
2929
private _contextFactory: BrowserContextFactory;
3030

31-
constructor(config: FullConfig) {
31+
constructor(config: FullConfig, { forceCdp }: { forceCdp: boolean }) {
3232
this.config = config;
3333
this._browserConfig = config.browser;
34-
this._contextFactory = contextFactory(this._browserConfig);
34+
this._contextFactory = contextFactory(this._browserConfig, { forceCdp });
3535
}
3636

3737
async createConnection(transport: Transport): Promise<Connection> {

src/transport.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import http from 'node:http';
18+
import type { AddressInfo } from 'node:net';
1819
import assert from 'node:assert';
1920
import crypto from 'node:crypto';
2021

@@ -108,18 +109,7 @@ export async function startHttpTransport(server: Server): Promise<http.Server> {
108109
});
109110
const { host, port } = server.config.server;
110111
await new Promise<void>(resolve => httpServer.listen(port, host, resolve));
111-
const address = httpServer.address();
112-
assert(address, 'Could not bind server socket');
113-
let url: string;
114-
if (typeof address === 'string') {
115-
url = address;
116-
} else {
117-
const resolvedPort = address.port;
118-
let resolvedHost = address.family === 'IPv4' ? address.address : `[${address.address}]`;
119-
if (resolvedHost === '0.0.0.0' || resolvedHost === '[::]')
120-
resolvedHost = 'localhost';
121-
url = `http://${resolvedHost}:${resolvedPort}`;
122-
}
112+
const url = httpAddressToString(httpServer.address());
123113
const message = [
124114
`Listening on ${url}`,
125115
'Put this in your client config:',
@@ -136,3 +126,14 @@ export async function startHttpTransport(server: Server): Promise<http.Server> {
136126
console.error(message);
137127
return httpServer;
138128
}
129+
130+
export function httpAddressToString(address: string | AddressInfo | null): string {
131+
assert(address, 'Could not bind server socket');
132+
if (typeof address === 'string')
133+
return address;
134+
const resolvedPort = address.port;
135+
let resolvedHost = address.family === 'IPv4' ? address.address : `[${address.address}]`;
136+
if (resolvedHost === '0.0.0.0' || resolvedHost === '[::]')
137+
resolvedHost = 'localhost';
138+
return `http://${resolvedHost}:${resolvedPort}`;
139+
}

tests/extension.spec.ts

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
import url from 'url';
1717
import path from 'path';
18-
import assert from 'assert';
1918
import { spawnSync } from 'child_process';
2019

2120
import { test, expect } from './fixtures.js';
@@ -24,44 +23,10 @@ import { createConnection } from '@playwright/mcp';
2423

2524
test.skip(({ mcpMode }) => mcpMode !== 'extension');
2625

27-
test.skip('allow re-connecting to a browser', async ({ client, mcpExtensionPage }) => {
28-
assert(mcpExtensionPage, 'mcpExtensionPage is required for this test');
29-
await client.callTool({
30-
name: 'browser_navigate',
31-
arguments: {
32-
url: 'data:text/html,<html><title>Title</title><body>Hello, planet!</body></html>',
33-
},
34-
});
35-
await expect(mcpExtensionPage.page.locator('body')).toHaveText('Hello, planet!');
36-
37-
expect(await client.callTool({
38-
name: 'browser_close',
39-
arguments: {},
40-
})).toContainTextContent('No open pages available');
41-
42-
// await mcpExtensionPage.connect();
43-
44-
expect(await client.callTool({
45-
name: 'browser_navigate',
46-
arguments: {
47-
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
48-
},
49-
})).toContainTextContent('Error: Please use browser_connect before launching the browser');
50-
await mcpExtensionPage.connect();
51-
52-
expect(await client.callTool({
53-
name: 'browser_navigate',
54-
arguments: {
55-
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
56-
},
57-
})).toContainTextContent(`- generic [ref=e1]: Hello, world!`);
58-
await expect(mcpExtensionPage.page.locator('body')).toHaveText('Hello, world!');
59-
});
60-
6126
test('does not allow --cdp-endpoint', async ({ startClient }) => {
6227
await expect(createConnection({
6328
browser: { browserName: 'firefox' },
64-
extension: true,
29+
...({ extension: true })
6530
})).rejects.toThrow(/Extension mode is only supported for Chromium browsers/);
6631
});
6732

tests/fixtures.ts

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@ export const test = baseTest.extend<TestFixtures & TestOptions, WorkerFixtures>(
7878
let dispose: (() => void) | undefined;
7979

8080
await use(async options => {
81-
if (client)
82-
throw new Error('Client already started');
8381
const args: string[] = [];
8482
if (userDataDir)
8583
args.push('--user-data-dir', userDataDir);
@@ -176,7 +174,7 @@ export const test = baseTest.extend<TestFixtures & TestOptions, WorkerFixtures>(
176174
await popupPage.getByRole('textbox', { name: 'Bridge Server URL:' }).clear();
177175
await popupPage.getByRole('textbox', { name: 'Bridge Server URL:' }).fill(test[kTransportPort]);
178176
await popupPage.getByRole('button', { name: 'Share This Tab' }).click();
179-
}
177+
},
180178
});
181179
await context?.close();
182180
},
@@ -222,9 +220,6 @@ async function createTransport(args: string[], mcpMode: TestOptions['mcpMode']):
222220
command: 'docker',
223221
args: [...dockerArgs, 'playwright-mcp-dev:latest', ...args],
224222
});
225-
transport.stderr?.on('data', data => {
226-
stderrBuffer += data.toString();
227-
});
228223
return {
229224
transport,
230225
stderr,
@@ -260,19 +255,23 @@ async function createTransport(args: string[], mcpMode: TestOptions['mcpMode']):
260255
};
261256
}
262257

258+
const transport = new StdioClientTransport({
259+
command: 'node',
260+
args: [path.join(path.dirname(__filename), '../cli.js'), ...args],
261+
cwd: path.join(path.dirname(__filename), '..'),
262+
stderr: 'pipe',
263+
env: {
264+
...process.env,
265+
DEBUG: 'pw:mcp:test',
266+
DEBUG_COLORS: '0',
267+
DEBUG_HIDE_DATE: '1',
268+
},
269+
});
270+
transport.stderr?.on('data', data => {
271+
stderrBuffer += data.toString();
272+
});
263273
return {
264-
transport: new StdioClientTransport({
265-
command: 'node',
266-
args: [path.join(path.dirname(__filename), '../cli.js'), ...args],
267-
cwd: path.join(path.dirname(__filename), '..'),
268-
stderr: 'pipe',
269-
env: {
270-
...process.env,
271-
DEBUG: 'pw:mcp:test',
272-
DEBUG_COLORS: '0',
273-
DEBUG_HIDE_DATE: '1',
274-
},
275-
}),
274+
transport,
276275
stderr,
277276
};
278277
}

tests/pdf.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ test('save as pdf unavailable', async ({ startClient, server }) => {
3030
})).toHaveTextContent(/Tool \"browser_pdf_save\" not found/);
3131
});
3232

33-
test('save as pdf', async ({ startClient, mcpBrowser, server, mcpExtensionPage }, testInfo) => {
33+
test('save as pdf', async ({ startClient, mcpBrowser, server }, testInfo) => {
3434
const { client } = await startClient({
3535
config: { outputDir: testInfo.outputPath('output') },
3636
});
@@ -41,6 +41,7 @@ test('save as pdf', async ({ startClient, mcpBrowser, server, mcpExtensionPage }
4141
name: 'browser_navigate',
4242
arguments: { url: server.HELLO_WORLD },
4343
})).toContainTextContent(`- generic [ref=e1]: Hello, world!`);
44+
4445
const response = await client.callTool({
4546
name: 'browser_pdf_save',
4647
});

0 commit comments

Comments
 (0)