@playwright/mcp 0.0.19 → 0.0.22

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/index.js CHANGED
@@ -13,55 +13,6 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { createServerWithTools } from './server.js';
17
- import common from './tools/common.js';
18
- import console from './tools/console.js';
19
- import dialogs from './tools/dialogs.js';
20
- import files from './tools/files.js';
21
- import install from './tools/install.js';
22
- import keyboard from './tools/keyboard.js';
23
- import navigate from './tools/navigate.js';
24
- import network from './tools/network.js';
25
- import pdf from './tools/pdf.js';
26
- import snapshot from './tools/snapshot.js';
27
- import tabs from './tools/tabs.js';
28
- import screen from './tools/screen.js';
29
- import testing from './tools/testing.js';
30
- const snapshotTools = [
31
- ...common(true),
32
- ...console,
33
- ...dialogs(true),
34
- ...files(true),
35
- ...install,
36
- ...keyboard(true),
37
- ...navigate(true),
38
- ...network,
39
- ...pdf,
40
- ...snapshot,
41
- ...tabs(true),
42
- ...testing,
43
- ];
44
- const screenshotTools = [
45
- ...common(false),
46
- ...console,
47
- ...dialogs(false),
48
- ...files(false),
49
- ...install,
50
- ...keyboard(false),
51
- ...navigate(false),
52
- ...network,
53
- ...pdf,
54
- ...screen,
55
- ...tabs(false),
56
- ...testing,
57
- ];
58
- import packageJSON from '../package.json' with { type: 'json' };
59
- export async function createServer(config = {}) {
60
- const allTools = config.vision ? screenshotTools : snapshotTools;
61
- const tools = allTools.filter(tool => !config.capabilities || tool.capability === 'core' || config.capabilities.includes(tool.capability));
62
- return createServerWithTools({
63
- name: 'Playwright',
64
- version: packageJSON.version,
65
- tools,
66
- }, config);
16
+ export async function createConnection(config = {}) {
17
+ return createConnection(config);
67
18
  }
package/lib/program.js CHANGED
@@ -14,11 +14,9 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { program } from 'commander';
17
- import { createServer } from './index.js';
18
- import { ServerList } from './server.js';
19
17
  import { startHttpTransport, startStdioTransport } from './transport.js';
20
18
  import { resolveConfig } from './config.js';
21
- import packageJSON from '../package.json' with { type: 'json' };
19
+ import { packageJSON } from './context.js';
22
20
  program
23
21
  .version('Version ' + packageJSON.version)
24
22
  .name(packageJSON.name)
@@ -31,25 +29,33 @@ program
31
29
  .option('--user-data-dir <path>', 'Path to the user data directory')
32
30
  .option('--port <port>', 'Port to listen on for SSE transport.')
33
31
  .option('--host <host>', 'Host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.')
32
+ .option('--allowed-origins <origins>', 'Semicolon-separated list of origins to allow the browser to request. Default is to allow all.', semicolonSeparatedList)
33
+ .option('--blocked-origins <origins>', 'Semicolon-separated list of origins to block the browser from requesting. Blocklist is evaluated before allowlist. If used without the allowlist, requests not matching the blocklist are still allowed.', semicolonSeparatedList)
34
34
  .option('--vision', 'Run server that uses screenshots (Aria snapshots are used by default)')
35
+ .option('--no-image-responses', 'Do not send image responses to the client.')
36
+ .option('--output-dir <path>', 'Path to the directory for output files.')
35
37
  .option('--config <path>', 'Path to the configuration file.')
36
38
  .action(async (options) => {
37
39
  const config = await resolveConfig(options);
38
- const serverList = new ServerList(() => createServer(config));
39
- setupExitWatchdog(serverList);
40
+ const connectionList = [];
41
+ setupExitWatchdog(connectionList);
40
42
  if (options.port)
41
- startHttpTransport(+options.port, options.host, serverList);
43
+ startHttpTransport(config, +options.port, options.host, connectionList);
42
44
  else
43
- await startStdioTransport(serverList);
45
+ await startStdioTransport(config, connectionList);
44
46
  });
45
- function setupExitWatchdog(serverList) {
47
+ function setupExitWatchdog(connectionList) {
46
48
  const handleExit = async () => {
47
49
  setTimeout(() => process.exit(0), 15000);
48
- await serverList.closeAll();
50
+ for (const connection of connectionList)
51
+ await connection.close();
49
52
  process.exit(0);
50
53
  };
51
54
  process.stdin.on('close', handleExit);
52
55
  process.on('SIGINT', handleExit);
53
56
  process.on('SIGTERM', handleExit);
54
57
  }
58
+ function semicolonSeparatedList(value) {
59
+ return value.split(';').map(v => v.trim());
60
+ }
55
61
  program.parse(process.argv);
package/lib/tab.js CHANGED
@@ -56,7 +56,24 @@ export class Tab {
56
56
  this._onPageClose(this);
57
57
  }
58
58
  async navigate(url) {
59
- await this.page.goto(url, { waitUntil: 'domcontentloaded' });
59
+ const downloadEvent = this.page.waitForEvent('download').catch(() => { });
60
+ try {
61
+ await this.page.goto(url, { waitUntil: 'domcontentloaded' });
62
+ }
63
+ catch (_e) {
64
+ const e = _e;
65
+ const mightBeDownload = e.message.includes('net::ERR_ABORTED') // chromium
66
+ || e.message.includes('Download is starting'); // firefox + webkit
67
+ if (!mightBeDownload)
68
+ throw e;
69
+ // on chromium, the download event is fired *after* page.goto rejects, so we wait a lil bit
70
+ const download = await Promise.race([
71
+ downloadEvent,
72
+ new Promise(resolve => setTimeout(resolve, 500)),
73
+ ]);
74
+ if (!download)
75
+ throw e;
76
+ }
60
77
  // Cap load event to 5 seconds, the page is operational at this point.
61
78
  await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => { });
62
79
  }
@@ -19,10 +19,12 @@ const wait = captureSnapshot => defineTool({
19
19
  capability: 'wait',
20
20
  schema: {
21
21
  name: 'browser_wait',
22
+ title: 'Wait',
22
23
  description: 'Wait for a specified time in seconds',
23
24
  inputSchema: z.object({
24
25
  time: z.number().describe('The time to wait in seconds'),
25
26
  }),
27
+ type: 'readOnly',
26
28
  },
27
29
  handle: async (context, params) => {
28
30
  await new Promise(f => setTimeout(f, Math.min(10000, params.time * 1000)));
@@ -37,8 +39,10 @@ const close = defineTool({
37
39
  capability: 'core',
38
40
  schema: {
39
41
  name: 'browser_close',
42
+ title: 'Close browser',
40
43
  description: 'Close the page',
41
44
  inputSchema: z.object({}),
45
+ type: 'readOnly',
42
46
  },
43
47
  handle: async (context) => {
44
48
  await context.close();
@@ -53,11 +57,13 @@ const resize = captureSnapshot => defineTool({
53
57
  capability: 'core',
54
58
  schema: {
55
59
  name: 'browser_resize',
60
+ title: 'Resize browser window',
56
61
  description: 'Resize the browser window',
57
62
  inputSchema: z.object({
58
63
  width: z.number().describe('Width of the browser window'),
59
64
  height: z.number().describe('Height of the browser window'),
60
65
  }),
66
+ type: 'readOnly',
61
67
  },
62
68
  handle: async (context, params) => {
63
69
  const tab = context.currentTabOrDie();
@@ -19,8 +19,10 @@ const console = defineTool({
19
19
  capability: 'core',
20
20
  schema: {
21
21
  name: 'browser_console_messages',
22
+ title: 'Get console messages',
22
23
  description: 'Returns all console messages',
23
24
  inputSchema: z.object({}),
25
+ type: 'readOnly',
24
26
  },
25
27
  handle: async (context) => {
26
28
  const messages = context.currentTabOrDie().console();
@@ -19,11 +19,13 @@ const handleDialog = captureSnapshot => defineTool({
19
19
  capability: 'core',
20
20
  schema: {
21
21
  name: 'browser_handle_dialog',
22
+ title: 'Handle a dialog',
22
23
  description: 'Handle a dialog',
23
24
  inputSchema: z.object({
24
25
  accept: z.boolean().describe('Whether to accept the dialog.'),
25
26
  promptText: z.string().optional().describe('The text of the prompt in case of a prompt dialog.'),
26
27
  }),
28
+ type: 'destructive',
27
29
  },
28
30
  handle: async (context, params) => {
29
31
  const dialogState = context.modalStates().find(state => state.type === 'dialog');
@@ -19,10 +19,12 @@ const uploadFile = captureSnapshot => defineTool({
19
19
  capability: 'files',
20
20
  schema: {
21
21
  name: 'browser_file_upload',
22
+ title: 'Upload files',
22
23
  description: 'Upload one or multiple files',
23
24
  inputSchema: z.object({
24
25
  paths: z.array(z.string()).describe('The absolute paths to the files to upload. Can be a single file or multiple files.'),
25
26
  }),
27
+ type: 'destructive',
26
28
  },
27
29
  handle: async (context, params) => {
28
30
  const modalState = context.modalStates().find(state => state.type === 'fileChooser');
@@ -17,17 +17,21 @@ import { fork } from 'child_process';
17
17
  import path from 'path';
18
18
  import { z } from 'zod';
19
19
  import { defineTool } from './tool.js';
20
+ import { fileURLToPath } from 'node:url';
20
21
  const install = defineTool({
21
22
  capability: 'install',
22
23
  schema: {
23
24
  name: 'browser_install',
25
+ title: 'Install the browser specified in the config',
24
26
  description: 'Install the browser specified in the config. Call this if you get an error about the browser not being installed.',
25
27
  inputSchema: z.object({}),
28
+ type: 'destructive',
26
29
  },
27
30
  handle: async (context) => {
28
31
  const channel = context.config.browser?.launchOptions?.channel ?? context.config.browser?.launchOptions.browserName ?? 'chrome';
29
- const cli = path.join(require.resolve('playwright/package.json'), '..', 'cli.js');
30
- const child = fork(cli, ['install', channel], {
32
+ const cliUrl = import.meta.resolve('playwright/package.json');
33
+ const cliPath = path.join(fileURLToPath(cliUrl), '..', 'cli.js');
34
+ const child = fork(cliPath, ['install', channel], {
31
35
  stdio: 'pipe',
32
36
  });
33
37
  const output = [];
@@ -19,10 +19,12 @@ const pressKey = captureSnapshot => defineTool({
19
19
  capability: 'core',
20
20
  schema: {
21
21
  name: 'browser_press_key',
22
+ title: 'Press a key',
22
23
  description: 'Press a key on the keyboard',
23
24
  inputSchema: z.object({
24
25
  key: z.string().describe('Name of the key to press or a character to generate, such as `ArrowLeft` or `a`'),
25
26
  }),
27
+ type: 'destructive',
26
28
  },
27
29
  handle: async (context, params) => {
28
30
  const tab = context.currentTabOrDie();
@@ -19,10 +19,12 @@ const navigate = captureSnapshot => defineTool({
19
19
  capability: 'core',
20
20
  schema: {
21
21
  name: 'browser_navigate',
22
+ title: 'Navigate to a URL',
22
23
  description: 'Navigate to a URL',
23
24
  inputSchema: z.object({
24
25
  url: z.string().describe('The URL to navigate to'),
25
26
  }),
27
+ type: 'destructive',
26
28
  },
27
29
  handle: async (context, params) => {
28
30
  const tab = await context.ensureTab();
@@ -42,8 +44,10 @@ const goBack = captureSnapshot => defineTool({
42
44
  capability: 'history',
43
45
  schema: {
44
46
  name: 'browser_navigate_back',
47
+ title: 'Go back',
45
48
  description: 'Go back to the previous page',
46
49
  inputSchema: z.object({}),
50
+ type: 'readOnly',
47
51
  },
48
52
  handle: async (context) => {
49
53
  const tab = await context.ensureTab();
@@ -63,8 +67,10 @@ const goForward = captureSnapshot => defineTool({
63
67
  capability: 'history',
64
68
  schema: {
65
69
  name: 'browser_navigate_forward',
70
+ title: 'Go forward',
66
71
  description: 'Go forward to the next page',
67
72
  inputSchema: z.object({}),
73
+ type: 'readOnly',
68
74
  },
69
75
  handle: async (context) => {
70
76
  const tab = context.currentTabOrDie();
@@ -19,8 +19,10 @@ const requests = defineTool({
19
19
  capability: 'core',
20
20
  schema: {
21
21
  name: 'browser_network_requests',
22
+ title: 'List network requests',
22
23
  description: 'Returns all network requests since loading the page',
23
24
  inputSchema: z.object({}),
25
+ type: 'readOnly',
24
26
  },
25
27
  handle: async (context) => {
26
28
  const requests = context.currentTabOrDie().requests();
package/lib/tools/pdf.js CHANGED
@@ -21,8 +21,10 @@ const pdf = defineTool({
21
21
  capability: 'pdf',
22
22
  schema: {
23
23
  name: 'browser_pdf_save',
24
+ title: 'Save as PDF',
24
25
  description: 'Save page as PDF',
25
26
  inputSchema: z.object({}),
27
+ type: 'readOnly',
26
28
  },
27
29
  handle: async (context) => {
28
30
  const tab = context.currentTabOrDie();
@@ -23,8 +23,10 @@ const screenshot = defineTool({
23
23
  capability: 'core',
24
24
  schema: {
25
25
  name: 'browser_screen_capture',
26
+ title: 'Take a screenshot',
26
27
  description: 'Take a screenshot of the current page',
27
28
  inputSchema: z.object({}),
29
+ type: 'readOnly',
28
30
  },
29
31
  handle: async (context) => {
30
32
  const tab = await context.ensureTab();
@@ -50,11 +52,13 @@ const moveMouse = defineTool({
50
52
  capability: 'core',
51
53
  schema: {
52
54
  name: 'browser_screen_move_mouse',
55
+ title: 'Move mouse',
53
56
  description: 'Move mouse to a given position',
54
57
  inputSchema: elementSchema.extend({
55
58
  x: z.number().describe('X coordinate'),
56
59
  y: z.number().describe('Y coordinate'),
57
60
  }),
61
+ type: 'readOnly',
58
62
  },
59
63
  handle: async (context, params) => {
60
64
  const tab = context.currentTabOrDie();
@@ -75,11 +79,13 @@ const click = defineTool({
75
79
  capability: 'core',
76
80
  schema: {
77
81
  name: 'browser_screen_click',
82
+ title: 'Click',
78
83
  description: 'Click left mouse button',
79
84
  inputSchema: elementSchema.extend({
80
85
  x: z.number().describe('X coordinate'),
81
86
  y: z.number().describe('Y coordinate'),
82
87
  }),
88
+ type: 'destructive',
83
89
  },
84
90
  handle: async (context, params) => {
85
91
  const tab = context.currentTabOrDie();
@@ -106,6 +112,7 @@ const drag = defineTool({
106
112
  capability: 'core',
107
113
  schema: {
108
114
  name: 'browser_screen_drag',
115
+ title: 'Drag mouse',
109
116
  description: 'Drag left mouse button',
110
117
  inputSchema: elementSchema.extend({
111
118
  startX: z.number().describe('Start X coordinate'),
@@ -113,6 +120,7 @@ const drag = defineTool({
113
120
  endX: z.number().describe('End X coordinate'),
114
121
  endY: z.number().describe('End Y coordinate'),
115
122
  }),
123
+ type: 'destructive',
116
124
  },
117
125
  handle: async (context, params) => {
118
126
  const tab = context.currentTabOrDie();
@@ -141,11 +149,13 @@ const type = defineTool({
141
149
  capability: 'core',
142
150
  schema: {
143
151
  name: 'browser_screen_type',
152
+ title: 'Type text',
144
153
  description: 'Type text',
145
154
  inputSchema: z.object({
146
155
  text: z.string().describe('Text to type into the element'),
147
156
  submit: z.boolean().optional().describe('Whether to submit entered text (press Enter after)'),
148
157
  }),
158
+ type: 'destructive',
149
159
  },
150
160
  handle: async (context, params) => {
151
161
  const tab = context.currentTabOrDie();
@@ -21,8 +21,10 @@ const snapshot = defineTool({
21
21
  capability: 'core',
22
22
  schema: {
23
23
  name: 'browser_snapshot',
24
+ title: 'Page snapshot',
24
25
  description: 'Capture accessibility snapshot of the current page, this is better than screenshot',
25
26
  inputSchema: z.object({}),
27
+ type: 'readOnly',
26
28
  },
27
29
  handle: async (context) => {
28
30
  await context.ensureTab();
@@ -41,8 +43,10 @@ const click = defineTool({
41
43
  capability: 'core',
42
44
  schema: {
43
45
  name: 'browser_click',
46
+ title: 'Click',
44
47
  description: 'Perform click on a web page',
45
48
  inputSchema: elementSchema,
49
+ type: 'destructive',
46
50
  },
47
51
  handle: async (context, params) => {
48
52
  const tab = context.currentTabOrDie();
@@ -63,6 +67,7 @@ const drag = defineTool({
63
67
  capability: 'core',
64
68
  schema: {
65
69
  name: 'browser_drag',
70
+ title: 'Drag mouse',
66
71
  description: 'Perform drag and drop between two elements',
67
72
  inputSchema: z.object({
68
73
  startElement: z.string().describe('Human-readable source element description used to obtain the permission to interact with the element'),
@@ -70,6 +75,7 @@ const drag = defineTool({
70
75
  endElement: z.string().describe('Human-readable target element description used to obtain the permission to interact with the element'),
71
76
  endRef: z.string().describe('Exact target element reference from the page snapshot'),
72
77
  }),
78
+ type: 'destructive',
73
79
  },
74
80
  handle: async (context, params) => {
75
81
  const snapshot = context.currentTabOrDie().snapshotOrDie();
@@ -91,8 +97,10 @@ const hover = defineTool({
91
97
  capability: 'core',
92
98
  schema: {
93
99
  name: 'browser_hover',
100
+ title: 'Hover mouse',
94
101
  description: 'Hover over element on page',
95
102
  inputSchema: elementSchema,
103
+ type: 'readOnly',
96
104
  },
97
105
  handle: async (context, params) => {
98
106
  const snapshot = context.currentTabOrDie().snapshotOrDie();
@@ -118,8 +126,10 @@ const type = defineTool({
118
126
  capability: 'core',
119
127
  schema: {
120
128
  name: 'browser_type',
129
+ title: 'Type text',
121
130
  description: 'Type text into editable element',
122
131
  inputSchema: typeSchema,
132
+ type: 'destructive',
123
133
  },
124
134
  handle: async (context, params) => {
125
135
  const snapshot = context.currentTabOrDie().snapshotOrDie();
@@ -156,8 +166,10 @@ const selectOption = defineTool({
156
166
  capability: 'core',
157
167
  schema: {
158
168
  name: 'browser_select_option',
169
+ title: 'Select option',
159
170
  description: 'Select an option in a dropdown',
160
171
  inputSchema: selectOptionSchema,
172
+ type: 'destructive',
161
173
  },
162
174
  handle: async (context, params) => {
163
175
  const snapshot = context.currentTabOrDie().snapshotOrDie();
@@ -188,8 +200,10 @@ const screenshot = defineTool({
188
200
  capability: 'core',
189
201
  schema: {
190
202
  name: 'browser_take_screenshot',
203
+ title: 'Take a screenshot',
191
204
  description: `Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions.`,
192
205
  inputSchema: screenshotSchema,
206
+ type: 'readOnly',
193
207
  },
194
208
  handle: async (context, params) => {
195
209
  const tab = context.currentTabOrDie();
@@ -206,7 +220,7 @@ const screenshot = defineTool({
206
220
  code.push(`await page.${await generateLocator(locator)}.screenshot(${javascript.formatObject(options)});`);
207
221
  else
208
222
  code.push(`await page.screenshot(${javascript.formatObject(options)});`);
209
- const includeBase64 = !context.config.tools?.browser_take_screenshot?.omitBase64;
223
+ const includeBase64 = !context.config.noImageResponses;
210
224
  const action = async () => {
211
225
  const screenshot = locator ? await locator.screenshot(options) : await tab.page.screenshot(options);
212
226
  return {
package/lib/tools/tabs.js CHANGED
@@ -19,8 +19,10 @@ const listTabs = defineTool({
19
19
  capability: 'tabs',
20
20
  schema: {
21
21
  name: 'browser_tab_list',
22
+ title: 'List tabs',
22
23
  description: 'List browser tabs',
23
24
  inputSchema: z.object({}),
25
+ type: 'readOnly',
24
26
  },
25
27
  handle: async (context) => {
26
28
  await context.ensureTab();
@@ -41,10 +43,12 @@ const selectTab = captureSnapshot => defineTool({
41
43
  capability: 'tabs',
42
44
  schema: {
43
45
  name: 'browser_tab_select',
46
+ title: 'Select a tab',
44
47
  description: 'Select a tab by index',
45
48
  inputSchema: z.object({
46
49
  index: z.number().describe('The index of the tab to select'),
47
50
  }),
51
+ type: 'readOnly',
48
52
  },
49
53
  handle: async (context, params) => {
50
54
  await context.selectTab(params.index);
@@ -62,10 +66,12 @@ const newTab = captureSnapshot => defineTool({
62
66
  capability: 'tabs',
63
67
  schema: {
64
68
  name: 'browser_tab_new',
69
+ title: 'Open a new tab',
65
70
  description: 'Open a new tab',
66
71
  inputSchema: z.object({
67
72
  url: z.string().optional().describe('The URL to navigate to in the new tab. If not provided, the new tab will be blank.'),
68
73
  }),
74
+ type: 'readOnly',
69
75
  },
70
76
  handle: async (context, params) => {
71
77
  await context.newTab();
@@ -85,10 +91,12 @@ const closeTab = captureSnapshot => defineTool({
85
91
  capability: 'tabs',
86
92
  schema: {
87
93
  name: 'browser_tab_close',
94
+ title: 'Close a tab',
88
95
  description: 'Close a tab',
89
96
  inputSchema: z.object({
90
97
  index: z.number().optional().describe('The index of the tab to close. Closes current tab if not provided.'),
91
98
  }),
99
+ type: 'destructive',
92
100
  },
93
101
  handle: async (context, params) => {
94
102
  await context.closeTab(params.index);
@@ -24,8 +24,10 @@ const generateTest = defineTool({
24
24
  capability: 'testing',
25
25
  schema: {
26
26
  name: 'browser_generate_playwright_test',
27
+ title: 'Generate a Playwright test',
27
28
  description: 'Generate a Playwright test for given scenario',
28
29
  inputSchema: generateTestSchema,
30
+ type: 'readOnly',
29
31
  },
30
32
  handle: async (context, params) => {
31
33
  return {
package/lib/tools.js ADDED
@@ -0,0 +1,56 @@
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 common from './tools/common.js';
17
+ import console from './tools/console.js';
18
+ import dialogs from './tools/dialogs.js';
19
+ import files from './tools/files.js';
20
+ import install from './tools/install.js';
21
+ import keyboard from './tools/keyboard.js';
22
+ import navigate from './tools/navigate.js';
23
+ import network from './tools/network.js';
24
+ import pdf from './tools/pdf.js';
25
+ import snapshot from './tools/snapshot.js';
26
+ import tabs from './tools/tabs.js';
27
+ import screen from './tools/screen.js';
28
+ import testing from './tools/testing.js';
29
+ export const snapshotTools = [
30
+ ...common(true),
31
+ ...console,
32
+ ...dialogs(true),
33
+ ...files(true),
34
+ ...install,
35
+ ...keyboard(true),
36
+ ...navigate(true),
37
+ ...network,
38
+ ...pdf,
39
+ ...snapshot,
40
+ ...tabs(true),
41
+ ...testing,
42
+ ];
43
+ export const screenshotTools = [
44
+ ...common(false),
45
+ ...console,
46
+ ...dialogs(false),
47
+ ...files(false),
48
+ ...install,
49
+ ...keyboard(false),
50
+ ...navigate(false),
51
+ ...network,
52
+ ...pdf,
53
+ ...screen,
54
+ ...tabs(false),
55
+ ...testing,
56
+ ];