@playwright/mcp 0.0.33 → 0.0.35-alpha-2025-08-27

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.
Files changed (42) hide show
  1. package/README.md +60 -39
  2. package/config.d.ts +1 -1
  3. package/lib/browserContextFactory.js +32 -26
  4. package/lib/browserServerBackend.js +18 -62
  5. package/lib/config.js +1 -1
  6. package/lib/context.js +6 -6
  7. package/lib/extension/cdpRelay.js +33 -21
  8. package/lib/extension/extensionContextFactory.js +9 -9
  9. package/lib/extension/protocol.js +18 -0
  10. package/lib/index.js +2 -1
  11. package/lib/loopTools/context.js +3 -2
  12. package/lib/loopTools/main.js +15 -10
  13. package/lib/mcp/{transport.js → http.js} +51 -37
  14. package/lib/mcp/mdb.js +198 -0
  15. package/lib/mcp/proxyBackend.js +104 -0
  16. package/lib/mcp/server.js +63 -33
  17. package/lib/mcp/tool.js +32 -0
  18. package/lib/program.js +49 -20
  19. package/lib/sessionLog.js +1 -1
  20. package/lib/tab.js +2 -2
  21. package/lib/tools/evaluate.js +1 -1
  22. package/lib/tools/form.js +57 -0
  23. package/lib/tools/keyboard.js +1 -1
  24. package/lib/tools/navigate.js +0 -16
  25. package/lib/tools/pdf.js +1 -1
  26. package/lib/tools/screenshot.js +1 -1
  27. package/lib/tools/snapshot.js +1 -1
  28. package/lib/tools/tabs.js +31 -59
  29. package/lib/tools/verify.js +137 -0
  30. package/lib/tools/wait.js +3 -4
  31. package/lib/tools.js +5 -1
  32. package/lib/{javascript.js → utils/codegen.js} +1 -1
  33. package/lib/{fileUtils.js → utils/fileUtils.js} +6 -2
  34. package/lib/{utils.js → utils/guid.js} +3 -7
  35. package/lib/{package.js → utils/package.js} +1 -1
  36. package/lib/vscode/host.js +128 -0
  37. package/lib/vscode/main.js +62 -0
  38. package/package.json +6 -5
  39. package/lib/extension/main.js +0 -26
  40. package/lib/httpServer.js +0 -39
  41. /package/lib/{manualPromise.js → mcp/manualPromise.js} +0 -0
  42. /package/lib/{log.js → utils/log.js} +0 -0
package/lib/program.js CHANGED
@@ -14,16 +14,16 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { program, Option } from 'commander';
17
- // @ts-ignore
18
- import { startTraceViewerServer } from 'playwright-core/lib/server';
19
- import * as mcpTransport from './mcp/transport.js';
17
+ import * as mcpServer from './mcp/server.js';
20
18
  import { commaSeparatedList, resolveCLIConfig, semicolonSeparatedList } from './config.js';
21
- import { packageJSON } from './package.js';
22
- import { createExtensionContextFactory, runWithExtension } from './extension/main.js';
23
- import { BrowserServerBackend } from './browserServerBackend.js';
19
+ import { packageJSON } from './utils/package.js';
24
20
  import { Context } from './context.js';
25
21
  import { contextFactory } from './browserContextFactory.js';
26
22
  import { runLoopTools } from './loopTools/main.js';
23
+ import { ProxyBackend } from './mcp/proxyBackend.js';
24
+ import { BrowserServerBackend } from './browserServerBackend.js';
25
+ import { ExtensionContextFactory } from './extension/extensionContextFactory.js';
26
+ import { runVSCodeTools } from './vscode/host.js';
27
27
  program
28
28
  .version('Version ' + packageJSON.version)
29
29
  .name(packageJSON.name)
@@ -36,6 +36,7 @@ program
36
36
  .option('--config <path>', 'path to the configuration file.')
37
37
  .option('--device <device>', 'device to emulate, for example: "iPhone 15"')
38
38
  .option('--executable-path <path>', 'path to the browser executable.')
39
+ .option('--extension', 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.')
39
40
  .option('--headless', 'run browser in headless mode, headed by default')
40
41
  .option('--host <host>', 'host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.')
41
42
  .option('--ignore-https-errors', 'ignore https errors')
@@ -52,8 +53,8 @@ program
52
53
  .option('--user-agent <ua string>', 'specify user agent string')
53
54
  .option('--user-data-dir <path>', 'path to the user data directory. If not specified, a temporary directory will be created.')
54
55
  .option('--viewport-size <size>', 'specify browser viewport size in pixels, for example "1280, 720"')
55
- .addOption(new Option('--extension', 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.').hideHelp())
56
56
  .addOption(new Option('--connect-tool', 'Allow to switch between different browser connection methods.').hideHelp())
57
+ .addOption(new Option('--vscode', 'VS Code tools.').hideHelp())
57
58
  .addOption(new Option('--loop-tools', 'Run loop tools').hideHelp())
58
59
  .addOption(new Option('--vision', 'Legacy option, use --caps=vision instead').hideHelp())
59
60
  .action(async (options) => {
@@ -64,27 +65,55 @@ program
64
65
  options.caps = 'vision';
65
66
  }
66
67
  const config = await resolveCLIConfig(options);
68
+ const browserContextFactory = contextFactory(config);
69
+ const extensionContextFactory = new ExtensionContextFactory(config.browser.launchOptions.channel || 'chrome', config.browser.userDataDir, config.browser.launchOptions.executablePath);
67
70
  if (options.extension) {
68
- await runWithExtension(config);
71
+ const serverBackendFactory = {
72
+ name: 'Playwright w/ extension',
73
+ nameInConfig: 'playwright-extension',
74
+ version: packageJSON.version,
75
+ create: () => new BrowserServerBackend(config, extensionContextFactory)
76
+ };
77
+ await mcpServer.start(serverBackendFactory, config.server);
78
+ return;
79
+ }
80
+ if (options.vscode) {
81
+ await runVSCodeTools(config);
69
82
  return;
70
83
  }
71
84
  if (options.loopTools) {
72
85
  await runLoopTools(config);
73
86
  return;
74
87
  }
75
- const browserContextFactory = contextFactory(config);
76
- const factories = [browserContextFactory];
77
- if (options.connectTool)
78
- factories.push(createExtensionContextFactory(config));
79
- const serverBackendFactory = () => new BrowserServerBackend(config, factories);
80
- await mcpTransport.start(serverBackendFactory, config.server);
81
- if (config.saveTrace) {
82
- const server = await startTraceViewerServer();
83
- const urlPrefix = server.urlPrefix('human-readable');
84
- const url = urlPrefix + '/trace/index.html?trace=' + config.browser.launchOptions.tracesDir + '/trace.json';
85
- // eslint-disable-next-line no-console
86
- console.error('\nTrace viewer listening on ' + url);
88
+ if (options.connectTool) {
89
+ const providers = [
90
+ {
91
+ name: 'default',
92
+ description: 'Starts standalone browser',
93
+ connect: () => mcpServer.wrapInProcess(new BrowserServerBackend(config, browserContextFactory)),
94
+ },
95
+ {
96
+ name: 'extension',
97
+ description: 'Connect to a browser using the Playwright MCP extension',
98
+ connect: () => mcpServer.wrapInProcess(new BrowserServerBackend(config, extensionContextFactory)),
99
+ },
100
+ ];
101
+ const factory = {
102
+ name: 'Playwright w/ switch',
103
+ nameInConfig: 'playwright-switch',
104
+ version: packageJSON.version,
105
+ create: () => new ProxyBackend(providers),
106
+ };
107
+ await mcpServer.start(factory, config.server);
108
+ return;
87
109
  }
110
+ const factory = {
111
+ name: 'Playwright',
112
+ nameInConfig: 'playwright',
113
+ version: packageJSON.version,
114
+ create: () => new BrowserServerBackend(config, browserContextFactory)
115
+ };
116
+ await mcpServer.start(factory, config.server);
88
117
  });
89
118
  function setupExitWatchdog() {
90
119
  let isExiting = false;
package/lib/sessionLog.js CHANGED
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import fs from 'fs';
17
17
  import path from 'path';
18
- import { logUnhandledError } from './log.js';
18
+ import { logUnhandledError } from './utils/log.js';
19
19
  import { outputFile } from './config.js';
20
20
  export class SessionLog {
21
21
  _folder;
package/lib/tab.js CHANGED
@@ -15,8 +15,8 @@
15
15
  */
16
16
  import { EventEmitter } from 'events';
17
17
  import { callOnPageNoTrace, waitForCompletion } from './tools/utils.js';
18
- import { logUnhandledError } from './log.js';
19
- import { ManualPromise } from './manualPromise.js';
18
+ import { logUnhandledError } from './utils/log.js';
19
+ import { ManualPromise } from './mcp/manualPromise.js';
20
20
  export const TabEvents = {
21
21
  modalState: 'modalState'
22
22
  };
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { z } from 'zod';
17
17
  import { defineTabTool } from './tool.js';
18
- import * as javascript from '../javascript.js';
18
+ import * as javascript from '../utils/codegen.js';
19
19
  import { generateLocator } from './utils.js';
20
20
  const evaluateSchema = z.object({
21
21
  function: z.string().describe('() => { /* code */ } or (element) => { /* code */ } when element is provided'),
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { z } from 'zod';
17
+ import { defineTabTool } from './tool.js';
18
+ import { generateLocator } from './utils.js';
19
+ import * as javascript from '../utils/codegen.js';
20
+ const fillForm = defineTabTool({
21
+ capability: 'core',
22
+ schema: {
23
+ name: 'browser_fill_form',
24
+ title: 'Fill form',
25
+ description: 'Fill multiple form fields',
26
+ inputSchema: z.object({
27
+ fields: z.array(z.object({
28
+ name: z.string().describe('Human-readable field name'),
29
+ type: z.enum(['textbox', 'checkbox', 'radio', 'combobox', 'slider']).describe('Type of the field'),
30
+ ref: z.string().describe('Exact target field reference from the page snapshot'),
31
+ value: z.string().describe('Value to fill in the field. If the field is a checkbox, the value should be `true` or `false`. If the field is a combobox, the value should be the text of the option.'),
32
+ })).describe('Fields to fill in'),
33
+ }),
34
+ type: 'destructive',
35
+ },
36
+ handle: async (tab, params, response) => {
37
+ for (const field of params.fields) {
38
+ const locator = await tab.refLocator({ element: field.name, ref: field.ref });
39
+ const locatorSource = `await page.${await generateLocator(locator)}`;
40
+ if (field.type === 'textbox' || field.type === 'slider') {
41
+ await locator.fill(field.value);
42
+ response.addCode(`${locatorSource}.fill(${javascript.quote(field.value)});`);
43
+ }
44
+ else if (field.type === 'checkbox' || field.type === 'radio') {
45
+ await locator.setChecked(field.value === 'true');
46
+ response.addCode(`${locatorSource}.setChecked(${javascript.quote(field.value)});`);
47
+ }
48
+ else if (field.type === 'combobox') {
49
+ await locator.selectOption({ label: field.value });
50
+ response.addCode(`${locatorSource}.selectOption(${javascript.quote(field.value)});`);
51
+ }
52
+ }
53
+ },
54
+ });
55
+ export default [
56
+ fillForm,
57
+ ];
@@ -17,7 +17,7 @@ import { z } from 'zod';
17
17
  import { defineTabTool } from './tool.js';
18
18
  import { elementSchema } from './snapshot.js';
19
19
  import { generateLocator } from './utils.js';
20
- import * as javascript from '../javascript.js';
20
+ import * as javascript from '../utils/codegen.js';
21
21
  const pressKey = defineTabTool({
22
22
  capability: 'core',
23
23
  schema: {
@@ -48,23 +48,7 @@ const goBack = defineTabTool({
48
48
  response.addCode(`await page.goBack();`);
49
49
  },
50
50
  });
51
- const goForward = defineTabTool({
52
- capability: 'core',
53
- schema: {
54
- name: 'browser_navigate_forward',
55
- title: 'Go forward',
56
- description: 'Go forward to the next page',
57
- inputSchema: z.object({}),
58
- type: 'readOnly',
59
- },
60
- handle: async (tab, params, response) => {
61
- await tab.page.goForward();
62
- response.setIncludeSnapshot();
63
- response.addCode(`await page.goForward();`);
64
- },
65
- });
66
51
  export default [
67
52
  navigate,
68
53
  goBack,
69
- goForward,
70
54
  ];
package/lib/tools/pdf.js CHANGED
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { z } from 'zod';
17
17
  import { defineTabTool } from './tool.js';
18
- import * as javascript from '../javascript.js';
18
+ import * as javascript from '../utils/codegen.js';
19
19
  const pdfSchema = z.object({
20
20
  filename: z.string().optional().describe('File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified.'),
21
21
  });
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { z } from 'zod';
17
17
  import { defineTabTool } from './tool.js';
18
- import * as javascript from '../javascript.js';
18
+ import * as javascript from '../utils/codegen.js';
19
19
  import { generateLocator } from './utils.js';
20
20
  const screenshotSchema = z.object({
21
21
  type: z.enum(['png', 'jpeg']).default('png').describe('Image format for the screenshot. Default is png.'),
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { z } from 'zod';
17
17
  import { defineTabTool, defineTool } from './tool.js';
18
- import * as javascript from '../javascript.js';
18
+ import * as javascript from '../utils/codegen.js';
19
19
  import { generateLocator } from './utils.js';
20
20
  const snapshot = defineTool({
21
21
  capability: 'core',
package/lib/tools/tabs.js CHANGED
@@ -15,73 +15,45 @@
15
15
  */
16
16
  import { z } from 'zod';
17
17
  import { defineTool } from './tool.js';
18
- const listTabs = defineTool({
18
+ const browserTabs = defineTool({
19
19
  capability: 'core-tabs',
20
20
  schema: {
21
- name: 'browser_tab_list',
22
- title: 'List tabs',
23
- description: 'List browser tabs',
24
- inputSchema: z.object({}),
25
- type: 'readOnly',
26
- },
27
- handle: async (context, params, response) => {
28
- await context.ensureTab();
29
- response.setIncludeTabs();
30
- },
31
- });
32
- const selectTab = defineTool({
33
- capability: 'core-tabs',
34
- schema: {
35
- name: 'browser_tab_select',
36
- title: 'Select a tab',
37
- description: 'Select a tab by index',
38
- inputSchema: z.object({
39
- index: z.number().describe('The index of the tab to select'),
40
- }),
41
- type: 'readOnly',
42
- },
43
- handle: async (context, params, response) => {
44
- await context.selectTab(params.index);
45
- response.setIncludeSnapshot();
46
- },
47
- });
48
- const newTab = defineTool({
49
- capability: 'core-tabs',
50
- schema: {
51
- name: 'browser_tab_new',
52
- title: 'Open a new tab',
53
- description: 'Open a new tab',
54
- inputSchema: z.object({
55
- url: z.string().optional().describe('The URL to navigate to in the new tab. If not provided, the new tab will be blank.'),
56
- }),
57
- type: 'readOnly',
58
- },
59
- handle: async (context, params, response) => {
60
- const tab = await context.newTab();
61
- if (params.url)
62
- await tab.navigate(params.url);
63
- response.setIncludeSnapshot();
64
- },
65
- });
66
- const closeTab = defineTool({
67
- capability: 'core-tabs',
68
- schema: {
69
- name: 'browser_tab_close',
70
- title: 'Close a tab',
71
- description: 'Close a tab',
21
+ name: 'browser_tabs',
22
+ title: 'Manage tabs',
23
+ description: 'List, create, close, or select a browser tab.',
72
24
  inputSchema: z.object({
73
- index: z.number().optional().describe('The index of the tab to close. Closes current tab if not provided.'),
25
+ action: z.enum(['list', 'new', 'close', 'select']).describe('Operation to perform'),
26
+ index: z.number().optional().describe('Tab index, used for close/select. If omitted for close, current tab is closed.'),
74
27
  }),
75
28
  type: 'destructive',
76
29
  },
77
30
  handle: async (context, params, response) => {
78
- await context.closeTab(params.index);
79
- response.setIncludeSnapshot();
31
+ switch (params.action) {
32
+ case 'list': {
33
+ await context.ensureTab();
34
+ response.setIncludeTabs();
35
+ return;
36
+ }
37
+ case 'new': {
38
+ await context.newTab();
39
+ response.setIncludeTabs();
40
+ return;
41
+ }
42
+ case 'close': {
43
+ await context.closeTab(params.index);
44
+ response.setIncludeSnapshot();
45
+ return;
46
+ }
47
+ case 'select': {
48
+ if (!params.index)
49
+ throw new Error('Tab index is required');
50
+ await context.selectTab(params.index);
51
+ response.setIncludeSnapshot();
52
+ return;
53
+ }
54
+ }
80
55
  },
81
56
  });
82
57
  export default [
83
- listTabs,
84
- newTab,
85
- selectTab,
86
- closeTab,
58
+ browserTabs,
87
59
  ];
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { z } from 'zod';
17
+ import { defineTabTool } from './tool.js';
18
+ import * as javascript from '../utils/codegen.js';
19
+ import { generateLocator } from './utils.js';
20
+ const verifyElement = defineTabTool({
21
+ capability: 'verify',
22
+ schema: {
23
+ name: 'browser_verify_element_visible',
24
+ title: 'Verify element visible',
25
+ description: 'Verify element is visible on the page',
26
+ inputSchema: z.object({
27
+ role: z.string().describe('ROLE of the element. Can be found in the snapshot like this: \`- {ROLE} "Accessible Name":\`'),
28
+ accessibleName: z.string().describe('ACCESSIBLE_NAME of the element. Can be found in the snapshot like this: \`- role "{ACCESSIBLE_NAME}"\`'),
29
+ }),
30
+ type: 'readOnly',
31
+ },
32
+ handle: async (tab, params, response) => {
33
+ const locator = tab.page.getByRole(params.role, { name: params.accessibleName });
34
+ if (await locator.count() === 0) {
35
+ response.addError(`Element with role "${params.role}" and accessible name "${params.accessibleName}" not found`);
36
+ return;
37
+ }
38
+ response.addCode(`await expect(page.getByRole(${javascript.escapeWithQuotes(params.role)}, { name: ${javascript.escapeWithQuotes(params.accessibleName)} })).toBeVisible();`);
39
+ response.addResult('Done');
40
+ },
41
+ });
42
+ const verifyText = defineTabTool({
43
+ capability: 'verify',
44
+ schema: {
45
+ name: 'browser_verify_text_visible',
46
+ title: 'Verify text visible',
47
+ description: `Verify text is visible on the page. Prefer ${verifyElement.schema.name} if possible.`,
48
+ inputSchema: z.object({
49
+ text: z.string().describe('TEXT to verify. Can be found in the snapshot like this: \`- role "Accessible Name": {TEXT}\` or like this: \`- text: {TEXT}\`'),
50
+ }),
51
+ type: 'readOnly',
52
+ },
53
+ handle: async (tab, params, response) => {
54
+ const locator = tab.page.getByText(params.text).filter({ visible: true });
55
+ if (await locator.count() === 0) {
56
+ response.addError('Text not found');
57
+ return;
58
+ }
59
+ response.addCode(`await expect(page.getByText(${javascript.escapeWithQuotes(params.text)})).toBeVisible();`);
60
+ response.addResult('Done');
61
+ },
62
+ });
63
+ const verifyList = defineTabTool({
64
+ capability: 'verify',
65
+ schema: {
66
+ name: 'browser_verify_list_visible',
67
+ title: 'Verify list visible',
68
+ description: 'Verify list is visible on the page',
69
+ inputSchema: z.object({
70
+ element: z.string().describe('Human-readable list description'),
71
+ ref: z.string().describe('Exact target element reference that points to the list'),
72
+ items: z.array(z.string()).describe('Items to verify'),
73
+ }),
74
+ type: 'readOnly',
75
+ },
76
+ handle: async (tab, params, response) => {
77
+ const locator = await tab.refLocator({ ref: params.ref, element: params.element });
78
+ const itemTexts = [];
79
+ for (const item of params.items) {
80
+ const itemLocator = locator.getByText(item);
81
+ if (await itemLocator.count() === 0) {
82
+ response.addError(`Item "${item}" not found`);
83
+ return;
84
+ }
85
+ itemTexts.push((await itemLocator.textContent()));
86
+ }
87
+ const ariaSnapshot = `\`
88
+ - list:
89
+ ${itemTexts.map(t => ` - text: ${javascript.escapeWithQuotes(t, '"')}`).join('\n')}
90
+ \``;
91
+ response.addCode(`await expect(page.locator('body')).toMatchAriaSnapshot(${ariaSnapshot});`);
92
+ response.addResult('Done');
93
+ },
94
+ });
95
+ const verifyValue = defineTabTool({
96
+ capability: 'verify',
97
+ schema: {
98
+ name: 'browser_verify_value',
99
+ title: 'Verify value',
100
+ description: 'Verify element value',
101
+ inputSchema: z.object({
102
+ type: z.enum(['textbox', 'checkbox', 'radio', 'combobox', 'slider']).describe('Type of the element'),
103
+ element: z.string().describe('Human-readable element description'),
104
+ ref: z.string().describe('Exact target element reference that points to the element'),
105
+ value: z.string().describe('Value to verify. For checkbox, use "true" or "false".'),
106
+ }),
107
+ type: 'readOnly',
108
+ },
109
+ handle: async (tab, params, response) => {
110
+ const locator = await tab.refLocator({ ref: params.ref, element: params.element });
111
+ const locatorSource = `page.${await generateLocator(locator)}`;
112
+ if (params.type === 'textbox' || params.type === 'slider' || params.type === 'combobox') {
113
+ const value = await locator.inputValue();
114
+ if (value !== params.value) {
115
+ response.addError(`Expected value "${params.value}", but got "${value}"`);
116
+ return;
117
+ }
118
+ response.addCode(`await expect(${locatorSource}).toHaveValue(${javascript.quote(params.value)});`);
119
+ }
120
+ else if (params.type === 'checkbox' || params.type === 'radio') {
121
+ const value = await locator.isChecked();
122
+ if (value !== (params.value === 'true')) {
123
+ response.addError(`Expected value "${params.value}", but got "${value}"`);
124
+ return;
125
+ }
126
+ const matcher = value ? 'toBeChecked' : 'not.toBeChecked';
127
+ response.addCode(`await expect(${locatorSource}).${matcher}();`);
128
+ }
129
+ response.addResult('Done');
130
+ },
131
+ });
132
+ export default [
133
+ verifyElement,
134
+ verifyText,
135
+ verifyList,
136
+ verifyValue,
137
+ ];
package/lib/tools/wait.js CHANGED
@@ -31,20 +31,19 @@ const wait = defineTool({
31
31
  handle: async (context, params, response) => {
32
32
  if (!params.text && !params.textGone && !params.time)
33
33
  throw new Error('Either time, text or textGone must be provided');
34
- const code = [];
35
34
  if (params.time) {
36
- code.push(`await new Promise(f => setTimeout(f, ${params.time} * 1000));`);
35
+ response.addCode(`await new Promise(f => setTimeout(f, ${params.time} * 1000));`);
37
36
  await new Promise(f => setTimeout(f, Math.min(30000, params.time * 1000)));
38
37
  }
39
38
  const tab = context.currentTabOrDie();
40
39
  const locator = params.text ? tab.page.getByText(params.text).first() : undefined;
41
40
  const goneLocator = params.textGone ? tab.page.getByText(params.textGone).first() : undefined;
42
41
  if (goneLocator) {
43
- code.push(`await page.getByText(${JSON.stringify(params.textGone)}).first().waitFor({ state: 'hidden' });`);
42
+ response.addCode(`await page.getByText(${JSON.stringify(params.textGone)}).first().waitFor({ state: 'hidden' });`);
44
43
  await goneLocator.waitFor({ state: 'hidden' });
45
44
  }
46
45
  if (locator) {
47
- code.push(`await page.getByText(${JSON.stringify(params.text)}).first().waitFor({ state: 'visible' });`);
46
+ response.addCode(`await page.getByText(${JSON.stringify(params.text)}).first().waitFor({ state: 'visible' });`);
48
47
  await locator.waitFor({ state: 'visible' });
49
48
  }
50
49
  response.addResult(`Waited for ${params.text || params.textGone || params.time}`);
package/lib/tools.js CHANGED
@@ -18,8 +18,10 @@ import console from './tools/console.js';
18
18
  import dialogs from './tools/dialogs.js';
19
19
  import evaluate from './tools/evaluate.js';
20
20
  import files from './tools/files.js';
21
+ import form from './tools/form.js';
21
22
  import install from './tools/install.js';
22
23
  import keyboard from './tools/keyboard.js';
24
+ import mouse from './tools/mouse.js';
23
25
  import navigate from './tools/navigate.js';
24
26
  import network from './tools/network.js';
25
27
  import pdf from './tools/pdf.js';
@@ -27,13 +29,14 @@ import snapshot from './tools/snapshot.js';
27
29
  import tabs from './tools/tabs.js';
28
30
  import screenshot from './tools/screenshot.js';
29
31
  import wait from './tools/wait.js';
30
- import mouse from './tools/mouse.js';
32
+ import verify from './tools/verify.js';
31
33
  export const allTools = [
32
34
  ...common,
33
35
  ...console,
34
36
  ...dialogs,
35
37
  ...evaluate,
36
38
  ...files,
39
+ ...form,
37
40
  ...install,
38
41
  ...keyboard,
39
42
  ...navigate,
@@ -44,6 +47,7 @@ export const allTools = [
44
47
  ...snapshot,
45
48
  ...tabs,
46
49
  ...wait,
50
+ ...verify,
47
51
  ];
48
52
  export function filteredTools(config) {
49
53
  return allTools.filter(tool => tool.capability.startsWith('core') || config.capabilities?.includes(tool.capability));
@@ -25,7 +25,7 @@ export function escapeWithQuotes(text, char = '\'') {
25
25
  if (char === '"')
26
26
  return char + escapedText.replace(/["]/g, '\\"') + char;
27
27
  if (char === '`')
28
- return char + escapedText.replace(/[`]/g, '`') + char;
28
+ return char + escapedText.replace(/[`]/g, '\\`') + char;
29
29
  throw new Error('Invalid escape char');
30
30
  }
31
31
  export function quote(text) {
@@ -27,6 +27,10 @@ export function cacheDir() {
27
27
  throw new Error('Unsupported platform: ' + process.platform);
28
28
  return path.join(cacheDirectory, 'ms-playwright');
29
29
  }
30
- export async function userDataDir(browserConfig) {
31
- return path.join(cacheDir(), 'ms-playwright', `mcp-${browserConfig.launchOptions?.channel ?? browserConfig?.browserName}-profile`);
30
+ export function sanitizeForFilePath(s) {
31
+ const sanitize = (s) => s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
32
+ const separator = s.lastIndexOf('.');
33
+ if (separator === -1)
34
+ return sanitize(s);
35
+ return sanitize(s.substring(0, separator)) + '.' + sanitize(s.substring(separator + 1));
32
36
  }
@@ -14,13 +14,9 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import crypto from 'crypto';
17
+ export function createGuid() {
18
+ return crypto.randomBytes(16).toString('hex');
19
+ }
17
20
  export function createHash(data) {
18
21
  return crypto.createHash('sha256').update(data).digest('hex').slice(0, 7);
19
22
  }
20
- export function sanitizeForFilePath(s) {
21
- const sanitize = (s) => s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
22
- const separator = s.lastIndexOf('.');
23
- if (separator === -1)
24
- return sanitize(s);
25
- return sanitize(s.substring(0, separator)) + '.' + sanitize(s.substring(separator + 1));
26
- }
@@ -17,4 +17,4 @@ import fs from 'fs';
17
17
  import path from 'path';
18
18
  import url from 'url';
19
19
  const __filename = url.fileURLToPath(import.meta.url);
20
- export const packageJSON = JSON.parse(fs.readFileSync(path.join(path.dirname(__filename), '..', 'package.json'), 'utf8'));
20
+ export const packageJSON = JSON.parse(fs.readFileSync(path.join(path.dirname(__filename), '..', '..', 'package.json'), 'utf8'));