@playwright/mcp 0.0.10 → 0.0.13

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/README.md CHANGED
@@ -43,7 +43,7 @@ const urlForWebsites = `vscode:mcp/install?${encodeURIComponent(config)}`;
43
43
  const urlForGithub = `https://insiders.vscode.dev/redirect?url=${encodeURIComponent(urlForWebsites)}`;
44
44
  -->
45
45
 
46
- [<img alt="Install in VS Code Insiders" src="https://img.shields.io/badge/VS_Code_Insiders-VS_Code_Insiders?style=flat-square&label=Install%20Server&color=24bfa5">](https://insiders.vscode.dev/redirect?url=vscode-insiders%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522playwright%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522%2540playwright%252Fmcp%2540latest%2522%255D%257D)
46
+ [<img src="https://img.shields.io/badge/VS_Code-VS_Code?style=flat-square&label=Install%20Server&color=0098FF" alt="Install in VS Code">](https://insiders.vscode.dev/redirect?url=vscode%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522playwright%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522%2540playwright%252Fmcp%2540latest%2522%255D%257D) [<img alt="Install in VS Code Insiders" src="https://img.shields.io/badge/VS_Code_Insiders-VS_Code_Insiders?style=flat-square&label=Install%20Server&color=24bfa5">](https://insiders.vscode.dev/redirect?url=vscode-insiders%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522playwright%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522%2540playwright%252Fmcp%2540latest%2522%255D%257D)
47
47
 
48
48
  Alternatively, you can install the Playwright MCP server using the VS Code CLI:
49
49
 
@@ -323,6 +323,3 @@ server.connect(transport);
323
323
  - **browser_install**
324
324
  - Description: Install the browser specified in the config. Call this if you get an error about the browser not being installed.
325
325
  - Parameters: None
326
-
327
- ### Vision Mode
328
-
package/lib/context.js CHANGED
@@ -52,6 +52,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
52
52
  };
53
53
  Object.defineProperty(exports, "__esModule", { value: true });
54
54
  exports.Context = void 0;
55
+ exports.generateLocator = generateLocator;
55
56
  const playwright = __importStar(require("playwright"));
56
57
  const yaml_1 = __importDefault(require("yaml"));
57
58
  const utils_1 = require("./tools/utils");
@@ -69,7 +70,7 @@ class Context {
69
70
  }
70
71
  currentTab() {
71
72
  if (!this._currentTab)
72
- throw new Error('Navigate to a location to create a tab');
73
+ throw new Error('No current snapshot available. Capture a snapshot of navigate to a new location first.');
73
74
  return this._currentTab;
74
75
  }
75
76
  async newTab() {
@@ -90,8 +91,8 @@ class Context {
90
91
  }
91
92
  async listTabs() {
92
93
  if (!this._tabs.length)
93
- return 'No tabs open';
94
- const lines = ['Open tabs:'];
94
+ return '### No tabs open';
95
+ const lines = ['### Open tabs'];
95
96
  for (let i = 0; i < this._tabs.length; i++) {
96
97
  const tab = this._tabs[i];
97
98
  const title = await tab.page.title();
@@ -206,24 +207,36 @@ class Tab {
206
207
  await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => { });
207
208
  }
208
209
  async run(callback, options) {
210
+ let runResult;
209
211
  try {
210
212
  if (!options?.noClearFileChooser)
211
213
  this._fileChooser = undefined;
212
214
  if (options?.waitForCompletion)
213
- await (0, utils_1.waitForCompletion)(this.page, () => callback(this));
215
+ runResult = await (0, utils_1.waitForCompletion)(this.page, () => callback(this)) ?? undefined;
214
216
  else
215
- await callback(this);
217
+ runResult = await callback(this) ?? undefined;
216
218
  }
217
219
  finally {
218
220
  if (options?.captureSnapshot)
219
221
  this._snapshot = await PageSnapshot.create(this.page);
220
222
  }
221
- const tabList = this.context.tabs().length > 1 ? await this.context.listTabs() + '\n\nCurrent tab:' + '\n' : '';
222
- const snapshot = this._snapshot?.text({ status: options?.status, hasFileChooser: !!this._fileChooser }) ?? options?.status ?? '';
223
+ const result = [];
224
+ result.push(`- Ran code:
225
+ \`\`\`js
226
+ ${runResult.code.join('\n')}
227
+ \`\`\`
228
+ `);
229
+ if (this.context.tabs().length > 1)
230
+ result.push(await this.context.listTabs(), '');
231
+ if (this._snapshot) {
232
+ if (this.context.tabs().length > 1)
233
+ result.push('### Current tab');
234
+ result.push(this._snapshot.text({ hasFileChooser: !!this._fileChooser }));
235
+ }
223
236
  return {
224
237
  content: [{
225
238
  type: 'text',
226
- text: tabList + snapshot,
239
+ text: result.join('\n'),
227
240
  }],
228
241
  };
229
242
  }
@@ -234,7 +247,7 @@ class Tab {
234
247
  });
235
248
  }
236
249
  async runAndWaitWithSnapshot(callback, options) {
237
- return await this.run(callback, {
250
+ return await this.run(tab => callback(tab.lastSnapshot()), {
238
251
  captureSnapshot: true,
239
252
  waitForCompletion: true,
240
253
  ...options,
@@ -267,11 +280,7 @@ class PageSnapshot {
267
280
  }
268
281
  text(options) {
269
282
  const results = [];
270
- if (options?.status) {
271
- results.push(options.status);
272
- results.push('');
273
- }
274
- if (options?.hasFileChooser) {
283
+ if (options.hasFileChooser) {
275
284
  results.push('- There is a file chooser visible that requires browser_file_upload to be called');
276
285
  results.push('');
277
286
  }
@@ -336,3 +345,6 @@ class PageSnapshot {
336
345
  return frame.locator(`aria-ref=${ref}`);
337
346
  }
338
347
  }
348
+ async function generateLocator(locator) {
349
+ return locator._generateLocatorString();
350
+ }
package/lib/index.js CHANGED
@@ -31,7 +31,7 @@ const tabs_1 = __importDefault(require("./tools/tabs"));
31
31
  const screen_1 = __importDefault(require("./tools/screen"));
32
32
  const console_1 = require("./resources/console");
33
33
  const snapshotTools = [
34
- ...common_1.default,
34
+ ...(0, common_1.default)(true),
35
35
  ...(0, files_1.default)(true),
36
36
  ...install_1.default,
37
37
  ...(0, keyboard_1.default)(true),
@@ -41,7 +41,7 @@ const snapshotTools = [
41
41
  ...(0, tabs_1.default)(true),
42
42
  ];
43
43
  const screenshotTools = [
44
- ...common_1.default,
44
+ ...(0, common_1.default)(false),
45
45
  ...(0, files_1.default)(false),
46
46
  ...install_1.default,
47
47
  ...(0, keyboard_1.default)(false),
@@ -0,0 +1,54 @@
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.escapeWithQuotes = escapeWithQuotes;
19
+ exports.quote = quote;
20
+ exports.formatObject = formatObject;
21
+ // adapted from:
22
+ // - https://github.com/microsoft/playwright/blob/76ee48dc9d4034536e3ec5b2c7ce8be3b79418a8/packages/playwright-core/src/utils/isomorphic/stringUtils.ts
23
+ // - https://github.com/microsoft/playwright/blob/76ee48dc9d4034536e3ec5b2c7ce8be3b79418a8/packages/playwright-core/src/server/codegen/javascript.ts
24
+ // NOTE: this function should not be used to escape any selectors.
25
+ function escapeWithQuotes(text, char = '\'') {
26
+ const stringified = JSON.stringify(text);
27
+ const escapedText = stringified.substring(1, stringified.length - 1).replace(/\\"/g, '"');
28
+ if (char === '\'')
29
+ return char + escapedText.replace(/[']/g, '\\\'') + char;
30
+ if (char === '"')
31
+ return char + escapedText.replace(/["]/g, '\\"') + char;
32
+ if (char === '`')
33
+ return char + escapedText.replace(/[`]/g, '`') + char;
34
+ throw new Error('Invalid escape char');
35
+ }
36
+ function quote(text) {
37
+ return escapeWithQuotes(text, '\'');
38
+ }
39
+ function formatObject(value, indent = ' ') {
40
+ if (typeof value === 'string')
41
+ return quote(value);
42
+ if (Array.isArray(value))
43
+ return `[${value.map(o => formatObject(o)).join(', ')}]`;
44
+ if (typeof value === 'object') {
45
+ const keys = Object.keys(value).filter(key => value[key] !== undefined).sort();
46
+ if (!keys.length)
47
+ return '{}';
48
+ const tokens = [];
49
+ for (const key of keys)
50
+ tokens.push(`${key}: ${formatObject(value[key])}`);
51
+ return `{\n${indent}${tokens.join(`,\n${indent}`)}\n}`;
52
+ }
53
+ return String(value);
54
+ }
package/lib/program.js CHANGED
@@ -69,7 +69,7 @@ commander_1.program
69
69
  channel = 'chrome';
70
70
  }
71
71
  const launchOptions = {
72
- headless: !!options.headless,
72
+ headless: !!(options.headless ?? (os_1.default.platform() === 'linux' && !process.env.DISPLAY)),
73
73
  channel,
74
74
  executablePath: options.executablePath,
75
75
  };
@@ -92,11 +92,14 @@ commander_1.program
92
92
  }
93
93
  });
94
94
  function setupExitWatchdog(serverList) {
95
- process.stdin.on('close', async () => {
95
+ const handleExit = async () => {
96
96
  setTimeout(() => process.exit(0), 15000);
97
97
  await serverList.closeAll();
98
98
  process.exit(0);
99
- });
99
+ };
100
+ process.stdin.on('close', handleExit);
101
+ process.on('SIGINT', handleExit);
102
+ process.on('SIGTERM', handleExit);
100
103
  }
101
104
  commander_1.program.parse(process.argv);
102
105
  async function createUserDataDir(browserName) {
@@ -56,7 +56,34 @@ const close = {
56
56
  };
57
57
  },
58
58
  };
59
- exports.default = [
59
+ const resizeSchema = zod_1.z.object({
60
+ width: zod_1.z.number().describe('Width of the browser window'),
61
+ height: zod_1.z.number().describe('Height of the browser window'),
62
+ });
63
+ const resize = captureSnapshot => ({
64
+ capability: 'core',
65
+ schema: {
66
+ name: 'browser_resize',
67
+ description: 'Resize the browser window',
68
+ inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(resizeSchema),
69
+ },
70
+ handle: async (context, params) => {
71
+ const validatedParams = resizeSchema.parse(params);
72
+ const tab = context.currentTab();
73
+ return await tab.run(async (tab) => {
74
+ await tab.page.setViewportSize({ width: validatedParams.width, height: validatedParams.height });
75
+ const code = [
76
+ `// Resize browser window to ${validatedParams.width}x${validatedParams.height}`,
77
+ `await page.setViewportSize({ width: ${validatedParams.width}, height: ${validatedParams.height} });`
78
+ ];
79
+ return { code };
80
+ }, {
81
+ captureSnapshot,
82
+ });
83
+ },
84
+ });
85
+ exports.default = (captureSnapshot) => [
60
86
  close,
61
87
  wait,
88
+ resize(captureSnapshot)
62
89
  ];
@@ -32,8 +32,11 @@ const uploadFile = captureSnapshot => ({
32
32
  const tab = context.currentTab();
33
33
  return await tab.runAndWait(async () => {
34
34
  await tab.submitFileChooser(validatedParams.paths);
35
+ const code = [
36
+ `// <internal code to chose files ${validatedParams.paths.join(', ')}`,
37
+ ];
38
+ return { code };
35
39
  }, {
36
- status: `Chose files ${validatedParams.paths.join(', ')}`,
37
40
  captureSnapshot,
38
41
  noClearFileChooser: true,
39
42
  });
@@ -34,8 +34,12 @@ const pressKey = captureSnapshot => ({
34
34
  const validatedParams = pressKeySchema.parse(params);
35
35
  return await context.currentTab().runAndWait(async (tab) => {
36
36
  await tab.page.keyboard.press(validatedParams.key);
37
+ const code = [
38
+ `// Press ${validatedParams.key}`,
39
+ `await page.keyboard.press('${validatedParams.key}');`,
40
+ ];
41
+ return { code };
37
42
  }, {
38
- status: `Pressed key ${validatedParams.key}`,
39
43
  captureSnapshot,
40
44
  });
41
45
  },
@@ -32,8 +32,12 @@ const navigate = captureSnapshot => ({
32
32
  const currentTab = await context.ensureTab();
33
33
  return await currentTab.run(async (tab) => {
34
34
  await tab.navigate(validatedParams.url);
35
+ const code = [
36
+ `// Navigate to ${validatedParams.url}`,
37
+ `await page.goto('${validatedParams.url}');`,
38
+ ];
39
+ return { code };
35
40
  }, {
36
- status: `Navigated to ${validatedParams.url}`,
37
41
  captureSnapshot,
38
42
  });
39
43
  },
@@ -49,8 +53,12 @@ const goBack = snapshot => ({
49
53
  handle: async (context) => {
50
54
  return await context.currentTab().runAndWait(async (tab) => {
51
55
  await tab.page.goBack();
56
+ const code = [
57
+ `// Navigate back`,
58
+ `await page.goBack();`,
59
+ ];
60
+ return { code };
52
61
  }, {
53
- status: 'Navigated back',
54
62
  captureSnapshot: snapshot,
55
63
  });
56
64
  },
@@ -66,8 +74,12 @@ const goForward = snapshot => ({
66
74
  handle: async (context) => {
67
75
  return await context.currentTab().runAndWait(async (tab) => {
68
76
  await tab.page.goForward();
77
+ const code = [
78
+ `// Navigate forward`,
79
+ `await page.goForward();`,
80
+ ];
81
+ return { code };
69
82
  }, {
70
- status: 'Navigated forward',
71
83
  captureSnapshot: snapshot,
72
84
  });
73
85
  },
@@ -25,7 +25,7 @@ const screenshot = {
25
25
  inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(zod_1.z.object({})),
26
26
  },
27
27
  handle: async (context) => {
28
- const tab = context.currentTab();
28
+ const tab = await context.ensureTab();
29
29
  const screenshot = await tab.page.screenshot({ type: 'jpeg', quality: 50, scale: 'css' });
30
30
  return {
31
31
  content: [{ type: 'image', data: screenshot.toString('base64'), mimeType: 'image/jpeg' }],
@@ -69,11 +69,16 @@ const click = {
69
69
  handle: async (context, params) => {
70
70
  return await context.currentTab().runAndWait(async (tab) => {
71
71
  const validatedParams = clickSchema.parse(params);
72
+ const code = [
73
+ `// Click mouse at coordinates (${validatedParams.x}, ${validatedParams.y})`,
74
+ `await page.mouse.move(${validatedParams.x}, ${validatedParams.y});`,
75
+ `await page.mouse.down();`,
76
+ `await page.mouse.up();`,
77
+ ];
72
78
  await tab.page.mouse.move(validatedParams.x, validatedParams.y);
73
79
  await tab.page.mouse.down();
74
80
  await tab.page.mouse.up();
75
- }, {
76
- status: 'Clicked mouse',
81
+ return { code };
77
82
  });
78
83
  },
79
84
  };
@@ -97,8 +102,14 @@ const drag = {
97
102
  await tab.page.mouse.down();
98
103
  await tab.page.mouse.move(validatedParams.endX, validatedParams.endY);
99
104
  await tab.page.mouse.up();
100
- }, {
101
- status: `Dragged mouse from (${validatedParams.startX}, ${validatedParams.startY}) to (${validatedParams.endX}, ${validatedParams.endY})`,
105
+ const code = [
106
+ `// Drag mouse from (${validatedParams.startX}, ${validatedParams.startY}) to (${validatedParams.endX}, ${validatedParams.endY})`,
107
+ `await page.mouse.move(${validatedParams.startX}, ${validatedParams.startY});`,
108
+ `await page.mouse.down();`,
109
+ `await page.mouse.move(${validatedParams.endX}, ${validatedParams.endY});`,
110
+ `await page.mouse.up();`,
111
+ ];
112
+ return { code };
102
113
  });
103
114
  },
104
115
  };
@@ -116,11 +127,17 @@ const type = {
116
127
  handle: async (context, params) => {
117
128
  const validatedParams = typeSchema.parse(params);
118
129
  return await context.currentTab().runAndWait(async (tab) => {
130
+ const code = [
131
+ `// Type ${validatedParams.text}`,
132
+ `await page.keyboard.type('${validatedParams.text}');`,
133
+ ];
119
134
  await tab.page.keyboard.type(validatedParams.text);
120
- if (validatedParams.submit)
135
+ if (validatedParams.submit) {
136
+ code.push(`// Submit text`);
137
+ code.push(`await page.keyboard.press('Enter');`);
121
138
  await tab.page.keyboard.press('Enter');
122
- }, {
123
- status: `Typed text "${validatedParams.text}"`,
139
+ }
140
+ return { code };
124
141
  });
125
142
  },
126
143
  };
@@ -14,12 +14,47 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
17
50
  var __importDefault = (this && this.__importDefault) || function (mod) {
18
51
  return (mod && mod.__esModule) ? mod : { "default": mod };
19
52
  };
20
53
  Object.defineProperty(exports, "__esModule", { value: true });
21
54
  const zod_1 = require("zod");
22
55
  const zod_to_json_schema_1 = __importDefault(require("zod-to-json-schema"));
56
+ const context_1 = require("../context");
57
+ const javascript = __importStar(require("../javascript"));
23
58
  const snapshot = {
24
59
  capability: 'core',
25
60
  schema: {
@@ -28,7 +63,11 @@ const snapshot = {
28
63
  inputSchema: (0, zod_to_json_schema_1.default)(zod_1.z.object({})),
29
64
  },
30
65
  handle: async (context) => {
31
- return await context.currentTab().run(async () => { }, { captureSnapshot: true });
66
+ const tab = await context.ensureTab();
67
+ return await tab.run(async () => {
68
+ const code = [`// <internal code to capture accessibility snapshot>`];
69
+ return { code };
70
+ }, { captureSnapshot: true });
32
71
  },
33
72
  };
34
73
  const elementSchema = zod_1.z.object({
@@ -44,11 +83,14 @@ const click = {
44
83
  },
45
84
  handle: async (context, params) => {
46
85
  const validatedParams = elementSchema.parse(params);
47
- return await context.currentTab().runAndWaitWithSnapshot(async (tab) => {
48
- const locator = tab.lastSnapshot().refLocator(validatedParams.ref);
86
+ return await context.currentTab().runAndWaitWithSnapshot(async (snapshot) => {
87
+ const locator = snapshot.refLocator(validatedParams.ref);
88
+ const code = [
89
+ `// Click ${validatedParams.element}`,
90
+ `await page.${await (0, context_1.generateLocator)(locator)}.click();`
91
+ ];
49
92
  await locator.click();
50
- }, {
51
- status: `Clicked "${validatedParams.element}"`,
93
+ return { code };
52
94
  });
53
95
  },
54
96
  };
@@ -67,12 +109,15 @@ const drag = {
67
109
  },
68
110
  handle: async (context, params) => {
69
111
  const validatedParams = dragSchema.parse(params);
70
- return await context.currentTab().runAndWaitWithSnapshot(async (tab) => {
71
- const startLocator = tab.lastSnapshot().refLocator(validatedParams.startRef);
72
- const endLocator = tab.lastSnapshot().refLocator(validatedParams.endRef);
112
+ return await context.currentTab().runAndWaitWithSnapshot(async (snapshot) => {
113
+ const startLocator = snapshot.refLocator(validatedParams.startRef);
114
+ const endLocator = snapshot.refLocator(validatedParams.endRef);
115
+ const code = [
116
+ `// Drag ${validatedParams.startElement} to ${validatedParams.endElement}`,
117
+ `await page.${await (0, context_1.generateLocator)(startLocator)}.dragTo(page.${await (0, context_1.generateLocator)(endLocator)});`
118
+ ];
73
119
  await startLocator.dragTo(endLocator);
74
- }, {
75
- status: `Dragged "${validatedParams.startElement}" to "${validatedParams.endElement}"`,
120
+ return { code };
76
121
  });
77
122
  },
78
123
  };
@@ -85,11 +130,14 @@ const hover = {
85
130
  },
86
131
  handle: async (context, params) => {
87
132
  const validatedParams = elementSchema.parse(params);
88
- return await context.currentTab().runAndWaitWithSnapshot(async (tab) => {
89
- const locator = tab.lastSnapshot().refLocator(validatedParams.ref);
133
+ return await context.currentTab().runAndWaitWithSnapshot(async (snapshot) => {
134
+ const locator = snapshot.refLocator(validatedParams.ref);
135
+ const code = [
136
+ `// Hover over ${validatedParams.element}`,
137
+ `await page.${await (0, context_1.generateLocator)(locator)}.hover();`
138
+ ];
90
139
  await locator.hover();
91
- }, {
92
- status: `Hovered over "${validatedParams.element}"`,
140
+ return { code };
93
141
  });
94
142
  },
95
143
  };
@@ -107,16 +155,25 @@ const type = {
107
155
  },
108
156
  handle: async (context, params) => {
109
157
  const validatedParams = typeSchema.parse(params);
110
- return await context.currentTab().runAndWaitWithSnapshot(async (tab) => {
111
- const locator = tab.lastSnapshot().refLocator(validatedParams.ref);
112
- if (validatedParams.slowly)
158
+ return await context.currentTab().runAndWaitWithSnapshot(async (snapshot) => {
159
+ const locator = snapshot.refLocator(validatedParams.ref);
160
+ const code = [];
161
+ if (validatedParams.slowly) {
162
+ code.push(`// Press "${validatedParams.text}" sequentially into "${validatedParams.element}"`);
163
+ code.push(`await page.${await (0, context_1.generateLocator)(locator)}.pressSequentially(${javascript.quote(validatedParams.text)});`);
113
164
  await locator.pressSequentially(validatedParams.text);
114
- else
165
+ }
166
+ else {
167
+ code.push(`// Fill "${validatedParams.text}" into "${validatedParams.element}"`);
168
+ code.push(`await page.${await (0, context_1.generateLocator)(locator)}.fill(${javascript.quote(validatedParams.text)});`);
115
169
  await locator.fill(validatedParams.text);
116
- if (validatedParams.submit)
170
+ }
171
+ if (validatedParams.submit) {
172
+ code.push(`// Submit text`);
173
+ code.push(`await page.${await (0, context_1.generateLocator)(locator)}.press('Enter');`);
117
174
  await locator.press('Enter');
118
- }, {
119
- status: `Typed "${validatedParams.text}" into "${validatedParams.element}"`,
175
+ }
176
+ return { code };
120
177
  });
121
178
  },
122
179
  };
@@ -132,11 +189,14 @@ const selectOption = {
132
189
  },
133
190
  handle: async (context, params) => {
134
191
  const validatedParams = selectOptionSchema.parse(params);
135
- return await context.currentTab().runAndWaitWithSnapshot(async (tab) => {
136
- const locator = tab.lastSnapshot().refLocator(validatedParams.ref);
192
+ return await context.currentTab().runAndWaitWithSnapshot(async (snapshot) => {
193
+ const locator = snapshot.refLocator(validatedParams.ref);
194
+ const code = [
195
+ `// Select options [${validatedParams.values.join(', ')}] in ${validatedParams.element}`,
196
+ `await page.${await (0, context_1.generateLocator)(locator)}.selectOption(${javascript.formatObject(validatedParams.values)});`
197
+ ];
137
198
  await locator.selectOption(validatedParams.values);
138
- }, {
139
- status: `Selected option in "${validatedParams.element}"`,
199
+ return { code };
140
200
  });
141
201
  },
142
202
  };
package/lib/tools/tabs.js CHANGED
@@ -47,7 +47,12 @@ const selectTab = captureSnapshot => ({
47
47
  const validatedParams = selectTabSchema.parse(params);
48
48
  await context.selectTab(validatedParams.index);
49
49
  const currentTab = await context.ensureTab();
50
- return await currentTab.run(async () => { }, { captureSnapshot });
50
+ return await currentTab.run(async () => {
51
+ const code = [
52
+ `// <internal code to select tab ${validatedParams.index}>`,
53
+ ];
54
+ return { code };
55
+ }, { captureSnapshot });
51
56
  },
52
57
  });
53
58
  const newTabSchema = zod_1.z.object({
@@ -65,7 +70,12 @@ const newTab = {
65
70
  await context.newTab();
66
71
  if (validatedParams.url)
67
72
  await context.currentTab().navigate(validatedParams.url);
68
- return await context.currentTab().run(async () => { }, { captureSnapshot: true });
73
+ return await context.currentTab().run(async () => {
74
+ const code = [
75
+ `// <internal code to open a new tab>`,
76
+ ];
77
+ return { code };
78
+ }, { captureSnapshot: true });
69
79
  },
70
80
  };
71
81
  const closeTabSchema = zod_1.z.object({
@@ -81,9 +91,15 @@ const closeTab = captureSnapshot => ({
81
91
  handle: async (context, params) => {
82
92
  const validatedParams = closeTabSchema.parse(params);
83
93
  await context.closeTab(validatedParams.index);
84
- const currentTab = await context.currentTab();
85
- if (currentTab)
86
- return await currentTab.run(async () => { }, { captureSnapshot });
94
+ const currentTab = context.currentTab();
95
+ if (currentTab) {
96
+ return await currentTab.run(async () => {
97
+ const code = [
98
+ `// <internal code to close tab ${validatedParams.index}>`,
99
+ ];
100
+ return { code };
101
+ }, { captureSnapshot });
102
+ }
87
103
  return {
88
104
  content: [{
89
105
  type: 'text',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playwright/mcp",
3
- "version": "0.0.10",
3
+ "version": "0.0.13",
4
4
  "description": "Playwright Tools for MCP",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,6 +19,7 @@
19
19
  "lint": "eslint .",
20
20
  "watch": "tsc --watch",
21
21
  "test": "playwright test",
22
+ "ctest": "playwright test --project=chrome",
22
23
  "clean": "rm -rf lib",
23
24
  "npm-publish": "npm run clean && npm run build && npm run test && npm publish"
24
25
  },