@playwright/mcp 0.0.15 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/tab.js ADDED
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Microsoft Corporation.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.Tab = void 0;
19
+ const pageSnapshot_1 = require("./pageSnapshot");
20
+ class Tab {
21
+ context;
22
+ page;
23
+ _console = [];
24
+ _requests = new Map();
25
+ _snapshot;
26
+ _onPageClose;
27
+ constructor(context, page, onPageClose) {
28
+ this.context = context;
29
+ this.page = page;
30
+ this._onPageClose = onPageClose;
31
+ page.on('console', event => this._console.push(event));
32
+ page.on('request', request => this._requests.set(request, null));
33
+ page.on('response', response => this._requests.set(response.request(), response));
34
+ page.on('framenavigated', frame => {
35
+ if (!frame.parentFrame())
36
+ this._clearCollectedArtifacts();
37
+ });
38
+ page.on('close', () => this._onClose());
39
+ page.on('filechooser', chooser => {
40
+ this.context.setModalState({
41
+ type: 'fileChooser',
42
+ description: 'File chooser',
43
+ fileChooser: chooser,
44
+ }, this);
45
+ });
46
+ page.on('dialog', dialog => this.context.dialogShown(this, dialog));
47
+ page.setDefaultNavigationTimeout(60000);
48
+ page.setDefaultTimeout(5000);
49
+ }
50
+ _clearCollectedArtifacts() {
51
+ this._console.length = 0;
52
+ this._requests.clear();
53
+ }
54
+ _onClose() {
55
+ this._clearCollectedArtifacts();
56
+ this._onPageClose(this);
57
+ }
58
+ async navigate(url) {
59
+ await this.page.goto(url, { waitUntil: 'domcontentloaded' });
60
+ // Cap load event to 5 seconds, the page is operational at this point.
61
+ await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => { });
62
+ }
63
+ hasSnapshot() {
64
+ return !!this._snapshot;
65
+ }
66
+ snapshotOrDie() {
67
+ if (!this._snapshot)
68
+ throw new Error('No snapshot available');
69
+ return this._snapshot;
70
+ }
71
+ console() {
72
+ return this._console;
73
+ }
74
+ requests() {
75
+ return this._requests;
76
+ }
77
+ async captureSnapshot() {
78
+ this._snapshot = await pageSnapshot_1.PageSnapshot.create(this.page);
79
+ }
80
+ }
81
+ exports.Tab = Tab;
@@ -30,7 +30,7 @@ const install = (0, tool_1.defineTool)({
30
30
  inputSchema: zod_1.z.object({}),
31
31
  },
32
32
  handle: async (context) => {
33
- const channel = context.options.launchOptions?.channel ?? context.options.browserName ?? 'chrome';
33
+ const channel = context.config.browser?.launchOptions?.channel ?? context.config.browser?.launchOptions.browserName ?? 'chrome';
34
34
  const cli = path_1.default.join(require.resolve('playwright/package.json'), '..', 'cli.js');
35
35
  const child = (0, child_process_1.fork)(cli, ['install', channel], {
36
36
  stdio: 'pipe',
package/lib/tools/pdf.js CHANGED
@@ -47,16 +47,11 @@ var __importStar = (this && this.__importStar) || (function () {
47
47
  return result;
48
48
  };
49
49
  })();
50
- var __importDefault = (this && this.__importDefault) || function (mod) {
51
- return (mod && mod.__esModule) ? mod : { "default": mod };
52
- };
53
50
  Object.defineProperty(exports, "__esModule", { value: true });
54
- const os_1 = __importDefault(require("os"));
55
- const path_1 = __importDefault(require("path"));
56
51
  const zod_1 = require("zod");
57
52
  const tool_1 = require("./tool");
58
- const utils_1 = require("./utils");
59
53
  const javascript = __importStar(require("../javascript"));
54
+ const config_1 = require("../config");
60
55
  const pdf = (0, tool_1.defineTool)({
61
56
  capability: 'pdf',
62
57
  schema: {
@@ -66,7 +61,7 @@ const pdf = (0, tool_1.defineTool)({
66
61
  },
67
62
  handle: async (context) => {
68
63
  const tab = context.currentTabOrDie();
69
- const fileName = path_1.default.join(os_1.default.tmpdir(), (0, utils_1.sanitizeForFilePath)(`page-${new Date().toISOString()}`)) + '.pdf';
64
+ const fileName = await (0, config_1.outputFile)(context.config, `page-${new Date().toISOString()}'.pdf'`);
70
65
  const code = [
71
66
  `// Save page as ${fileName}`,
72
67
  `await page.pdf(${javascript.formatObject({ path: fileName })});`,
@@ -47,17 +47,12 @@ var __importStar = (this && this.__importStar) || (function () {
47
47
  return result;
48
48
  };
49
49
  })();
50
- var __importDefault = (this && this.__importDefault) || function (mod) {
51
- return (mod && mod.__esModule) ? mod : { "default": mod };
52
- };
53
50
  Object.defineProperty(exports, "__esModule", { value: true });
54
- const path_1 = __importDefault(require("path"));
55
- const os_1 = __importDefault(require("os"));
51
+ exports.generateLocator = generateLocator;
56
52
  const zod_1 = require("zod");
57
- const utils_1 = require("./utils");
58
- const context_1 = require("../context");
59
- const javascript = __importStar(require("../javascript"));
60
53
  const tool_1 = require("./tool");
54
+ const javascript = __importStar(require("../javascript"));
55
+ const config_1 = require("../config");
61
56
  const snapshot = (0, tool_1.defineTool)({
62
57
  capability: 'core',
63
58
  schema: {
@@ -90,7 +85,7 @@ const click = (0, tool_1.defineTool)({
90
85
  const locator = tab.snapshotOrDie().refLocator(params.ref);
91
86
  const code = [
92
87
  `// Click ${params.element}`,
93
- `await page.${await (0, context_1.generateLocator)(locator)}.click();`
88
+ `await page.${await generateLocator(locator)}.click();`
94
89
  ];
95
90
  return {
96
91
  code,
@@ -118,7 +113,7 @@ const drag = (0, tool_1.defineTool)({
118
113
  const endLocator = snapshot.refLocator(params.endRef);
119
114
  const code = [
120
115
  `// Drag ${params.startElement} to ${params.endElement}`,
121
- `await page.${await (0, context_1.generateLocator)(startLocator)}.dragTo(page.${await (0, context_1.generateLocator)(endLocator)});`
116
+ `await page.${await generateLocator(startLocator)}.dragTo(page.${await generateLocator(endLocator)});`
122
117
  ];
123
118
  return {
124
119
  code,
@@ -140,7 +135,7 @@ const hover = (0, tool_1.defineTool)({
140
135
  const locator = snapshot.refLocator(params.ref);
141
136
  const code = [
142
137
  `// Hover over ${params.element}`,
143
- `await page.${await (0, context_1.generateLocator)(locator)}.hover();`
138
+ `await page.${await generateLocator(locator)}.hover();`
144
139
  ];
145
140
  return {
146
141
  code,
@@ -169,17 +164,17 @@ const type = (0, tool_1.defineTool)({
169
164
  const steps = [];
170
165
  if (params.slowly) {
171
166
  code.push(`// Press "${params.text}" sequentially into "${params.element}"`);
172
- code.push(`await page.${await (0, context_1.generateLocator)(locator)}.pressSequentially(${javascript.quote(params.text)});`);
167
+ code.push(`await page.${await generateLocator(locator)}.pressSequentially(${javascript.quote(params.text)});`);
173
168
  steps.push(() => locator.pressSequentially(params.text));
174
169
  }
175
170
  else {
176
171
  code.push(`// Fill "${params.text}" into "${params.element}"`);
177
- code.push(`await page.${await (0, context_1.generateLocator)(locator)}.fill(${javascript.quote(params.text)});`);
172
+ code.push(`await page.${await generateLocator(locator)}.fill(${javascript.quote(params.text)});`);
178
173
  steps.push(() => locator.fill(params.text));
179
174
  }
180
175
  if (params.submit) {
181
176
  code.push(`// Submit text`);
182
- code.push(`await page.${await (0, context_1.generateLocator)(locator)}.press('Enter');`);
177
+ code.push(`await page.${await generateLocator(locator)}.press('Enter');`);
183
178
  steps.push(() => locator.press('Enter'));
184
179
  }
185
180
  return {
@@ -205,7 +200,7 @@ const selectOption = (0, tool_1.defineTool)({
205
200
  const locator = snapshot.refLocator(params.ref);
206
201
  const code = [
207
202
  `// Select options [${params.values.join(', ')}] in ${params.element}`,
208
- `await page.${await (0, context_1.generateLocator)(locator)}.selectOption(${javascript.formatObject(params.values)});`
203
+ `await page.${await generateLocator(locator)}.selectOption(${javascript.formatObject(params.values)});`
209
204
  ];
210
205
  return {
211
206
  code,
@@ -236,7 +231,7 @@ const screenshot = (0, tool_1.defineTool)({
236
231
  const tab = context.currentTabOrDie();
237
232
  const snapshot = tab.snapshotOrDie();
238
233
  const fileType = params.raw ? 'png' : 'jpeg';
239
- const fileName = path_1.default.join(os_1.default.tmpdir(), (0, utils_1.sanitizeForFilePath)(`page-${new Date().toISOString()}`)) + `.${fileType}`;
234
+ const fileName = await (0, config_1.outputFile)(context.config, `page-${new Date().toISOString()}.${fileType}`);
240
235
  const options = { type: fileType, quality: fileType === 'png' ? undefined : 50, scale: 'css', path: fileName };
241
236
  const isElementScreenshot = params.element && params.ref;
242
237
  const code = [
@@ -244,17 +239,18 @@ const screenshot = (0, tool_1.defineTool)({
244
239
  ];
245
240
  const locator = params.ref ? snapshot.refLocator(params.ref) : null;
246
241
  if (locator)
247
- code.push(`await page.${await (0, context_1.generateLocator)(locator)}.screenshot(${javascript.formatObject(options)});`);
242
+ code.push(`await page.${await generateLocator(locator)}.screenshot(${javascript.formatObject(options)});`);
248
243
  else
249
244
  code.push(`await page.screenshot(${javascript.formatObject(options)});`);
245
+ const includeBase64 = !context.config.tools?.browser_take_screenshot?.omitBase64;
250
246
  const action = async () => {
251
247
  const screenshot = locator ? await locator.screenshot(options) : await tab.page.screenshot(options);
252
248
  return {
253
- content: [{
249
+ content: includeBase64 ? [{
254
250
  type: 'image',
255
251
  data: screenshot.toString('base64'),
256
252
  mimeType: fileType === 'png' ? 'image/png' : 'image/jpeg',
257
- }]
253
+ }] : []
258
254
  };
259
255
  };
260
256
  return {
@@ -265,6 +261,9 @@ const screenshot = (0, tool_1.defineTool)({
265
261
  };
266
262
  }
267
263
  });
264
+ async function generateLocator(locator) {
265
+ return locator._generateLocatorString();
266
+ }
268
267
  exports.default = [
269
268
  snapshot,
270
269
  click,
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Microsoft Corporation.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ var __importDefault = (this && this.__importDefault) || function (mod) {
18
+ return (mod && mod.__esModule) ? mod : { "default": mod };
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.startStdioTransport = startStdioTransport;
22
+ exports.startHttpTransport = startHttpTransport;
23
+ const node_http_1 = __importDefault(require("node:http"));
24
+ const node_assert_1 = __importDefault(require("node:assert"));
25
+ const node_crypto_1 = __importDefault(require("node:crypto"));
26
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
27
+ const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
28
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
29
+ async function startStdioTransport(serverList) {
30
+ const server = await serverList.create();
31
+ await server.connect(new stdio_js_1.StdioServerTransport());
32
+ }
33
+ async function handleSSE(req, res, url, serverList, sessions) {
34
+ if (req.method === 'POST') {
35
+ const sessionId = url.searchParams.get('sessionId');
36
+ if (!sessionId) {
37
+ res.statusCode = 400;
38
+ return res.end('Missing sessionId');
39
+ }
40
+ const transport = sessions.get(sessionId);
41
+ if (!transport) {
42
+ res.statusCode = 404;
43
+ return res.end('Session not found');
44
+ }
45
+ return await transport.handlePostMessage(req, res);
46
+ }
47
+ else if (req.method === 'GET') {
48
+ const transport = new sse_js_1.SSEServerTransport('/sse', res);
49
+ sessions.set(transport.sessionId, transport);
50
+ const server = await serverList.create();
51
+ res.on('close', () => {
52
+ sessions.delete(transport.sessionId);
53
+ serverList.close(server).catch(e => console.error(e));
54
+ });
55
+ return await server.connect(transport);
56
+ }
57
+ res.statusCode = 405;
58
+ res.end('Method not allowed');
59
+ }
60
+ async function handleStreamable(req, res, serverList, sessions) {
61
+ const sessionId = req.headers['mcp-session-id'];
62
+ if (sessionId) {
63
+ const transport = sessions.get(sessionId);
64
+ if (!transport) {
65
+ res.statusCode = 404;
66
+ res.end('Session not found');
67
+ return;
68
+ }
69
+ return await transport.handleRequest(req, res);
70
+ }
71
+ if (req.method === 'POST') {
72
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
73
+ sessionIdGenerator: () => node_crypto_1.default.randomUUID(),
74
+ onsessioninitialized: sessionId => {
75
+ sessions.set(sessionId, transport);
76
+ }
77
+ });
78
+ transport.onclose = () => {
79
+ if (transport.sessionId)
80
+ sessions.delete(transport.sessionId);
81
+ };
82
+ const server = await serverList.create();
83
+ await server.connect(transport);
84
+ return await transport.handleRequest(req, res);
85
+ }
86
+ res.statusCode = 400;
87
+ res.end('Invalid request');
88
+ }
89
+ function startHttpTransport(port, hostname, serverList) {
90
+ const sseSessions = new Map();
91
+ const streamableSessions = new Map();
92
+ const httpServer = node_http_1.default.createServer(async (req, res) => {
93
+ const url = new URL(`http://localhost${req.url}`);
94
+ if (url.pathname.startsWith('/mcp'))
95
+ await handleStreamable(req, res, serverList, streamableSessions);
96
+ else
97
+ await handleSSE(req, res, url, serverList, sseSessions);
98
+ });
99
+ httpServer.listen(port, hostname, () => {
100
+ const address = httpServer.address();
101
+ (0, node_assert_1.default)(address, 'Could not bind server socket');
102
+ let url;
103
+ if (typeof address === 'string') {
104
+ url = address;
105
+ }
106
+ else {
107
+ const resolvedPort = address.port;
108
+ let resolvedHost = address.family === 'IPv4' ? address.address : `[${address.address}]`;
109
+ if (resolvedHost === '0.0.0.0' || resolvedHost === '[::]')
110
+ resolvedHost = 'localhost';
111
+ url = `http://${resolvedHost}:${resolvedPort}`;
112
+ }
113
+ console.log(`Listening on ${url}`);
114
+ console.log('Put this in your client config:');
115
+ console.log(JSON.stringify({
116
+ 'mcpServers': {
117
+ 'playwright': {
118
+ 'url': `${url}/sse`
119
+ }
120
+ }
121
+ }, undefined, 2));
122
+ console.log('If your client supports streamable HTTP, you can use the /mcp endpoint instead.');
123
+ });
124
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playwright/mcp",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "Playwright Tools for MCP",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,7 +16,7 @@
16
16
  "license": "Apache-2.0",
17
17
  "scripts": {
18
18
  "build": "tsc",
19
- "lint": "eslint .",
19
+ "lint": "npm run update-readme && eslint .",
20
20
  "update-readme": "node utils/update-readme.js",
21
21
  "watch": "tsc --watch",
22
22
  "test": "playwright test",
@@ -34,16 +34,16 @@
34
34
  }
35
35
  },
36
36
  "dependencies": {
37
- "@modelcontextprotocol/sdk": "^1.6.1",
37
+ "@modelcontextprotocol/sdk": "^1.10.1",
38
38
  "commander": "^13.1.0",
39
- "playwright": "1.53.0-alpha-1745357020000",
39
+ "playwright": "1.53.0-alpha-2025-04-25",
40
40
  "yaml": "^2.7.1",
41
41
  "zod-to-json-schema": "^3.24.4"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@eslint/eslintrc": "^3.2.0",
45
45
  "@eslint/js": "^9.19.0",
46
- "@playwright/test": "1.53.0-alpha-1745357020000",
46
+ "@playwright/test": "1.53.0-alpha-2025-04-25",
47
47
  "@stylistic/eslint-plugin": "^3.0.1",
48
48
  "@types/node": "^22.13.10",
49
49
  "@typescript-eslint/eslint-plugin": "^8.26.1",