@playwright/mcp 0.0.31 → 0.0.32

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 (49) hide show
  1. package/README.md +25 -4
  2. package/config.d.ts +5 -0
  3. package/index.d.ts +1 -6
  4. package/lib/browserServerBackend.js +54 -0
  5. package/lib/config.js +2 -1
  6. package/lib/context.js +48 -171
  7. package/lib/extension/cdpRelay.js +370 -0
  8. package/lib/extension/main.js +33 -0
  9. package/lib/httpServer.js +20 -182
  10. package/lib/index.js +3 -2
  11. package/lib/loop/loop.js +69 -0
  12. package/lib/loop/loopClaude.js +152 -0
  13. package/lib/loop/loopOpenAI.js +141 -0
  14. package/lib/loop/main.js +60 -0
  15. package/lib/loopTools/context.js +66 -0
  16. package/lib/loopTools/main.js +49 -0
  17. package/lib/loopTools/perform.js +32 -0
  18. package/lib/loopTools/snapshot.js +29 -0
  19. package/lib/loopTools/tool.js +18 -0
  20. package/lib/mcp/inProcessTransport.js +72 -0
  21. package/lib/mcp/server.js +88 -0
  22. package/lib/{transport.js → mcp/transport.js} +30 -42
  23. package/lib/package.js +3 -3
  24. package/lib/program.js +38 -9
  25. package/lib/response.js +98 -0
  26. package/lib/sessionLog.js +70 -0
  27. package/lib/tab.js +133 -22
  28. package/lib/tools/common.js +11 -23
  29. package/lib/tools/console.js +4 -15
  30. package/lib/tools/dialogs.js +12 -17
  31. package/lib/tools/evaluate.js +12 -21
  32. package/lib/tools/files.js +10 -16
  33. package/lib/tools/install.js +3 -7
  34. package/lib/tools/keyboard.js +30 -42
  35. package/lib/tools/mouse.js +27 -50
  36. package/lib/tools/navigate.js +15 -35
  37. package/lib/tools/network.js +5 -15
  38. package/lib/tools/pdf.js +8 -15
  39. package/lib/tools/screenshot.js +29 -30
  40. package/lib/tools/snapshot.js +45 -65
  41. package/lib/tools/tabs.js +10 -41
  42. package/lib/tools/tool.js +14 -0
  43. package/lib/tools/utils.js +2 -2
  44. package/lib/tools/wait.js +3 -6
  45. package/lib/tools.js +3 -0
  46. package/package.json +9 -3
  47. package/lib/connection.js +0 -81
  48. package/lib/pageSnapshot.js +0 -43
  49. package/lib/server.js +0 -48
package/lib/tab.js CHANGED
@@ -13,18 +13,25 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { PageSnapshot } from './pageSnapshot.js';
17
- import { callOnPageNoTrace } from './tools/utils.js';
16
+ import { EventEmitter } from 'events';
17
+ import { callOnPageNoTrace, waitForCompletion } from './tools/utils.js';
18
18
  import { logUnhandledError } from './log.js';
19
- export class Tab {
19
+ import { ManualPromise } from './manualPromise.js';
20
+ import { outputFile } from './config.js';
21
+ export const TabEvents = {
22
+ modalState: 'modalState'
23
+ };
24
+ export class Tab extends EventEmitter {
20
25
  context;
21
26
  page;
22
27
  _consoleMessages = [];
23
28
  _recentConsoleMessages = [];
24
29
  _requests = new Map();
25
- _snapshot;
26
30
  _onPageClose;
31
+ _modalStates = [];
32
+ _downloads = [];
27
33
  constructor(context, page, onPageClose) {
34
+ super();
28
35
  this.context = context;
29
36
  this.page = page;
30
37
  this._onPageClose = onPageClose;
@@ -34,19 +41,56 @@ export class Tab {
34
41
  page.on('response', response => this._requests.set(response.request(), response));
35
42
  page.on('close', () => this._onClose());
36
43
  page.on('filechooser', chooser => {
37
- this.context.setModalState({
44
+ this.setModalState({
38
45
  type: 'fileChooser',
39
46
  description: 'File chooser',
40
47
  fileChooser: chooser,
41
- }, this);
48
+ });
42
49
  });
43
- page.on('dialog', dialog => this.context.dialogShown(this, dialog));
50
+ page.on('dialog', dialog => this._dialogShown(dialog));
44
51
  page.on('download', download => {
45
- void this.context.downloadStarted(this, download);
52
+ void this._downloadStarted(download);
46
53
  });
47
54
  page.setDefaultNavigationTimeout(60000);
48
55
  page.setDefaultTimeout(5000);
49
56
  }
57
+ modalStates() {
58
+ return this._modalStates;
59
+ }
60
+ setModalState(modalState) {
61
+ this._modalStates.push(modalState);
62
+ this.emit(TabEvents.modalState, modalState);
63
+ }
64
+ clearModalState(modalState) {
65
+ this._modalStates = this._modalStates.filter(state => state !== modalState);
66
+ }
67
+ modalStatesMarkdown() {
68
+ const result = ['### Modal state'];
69
+ if (this._modalStates.length === 0)
70
+ result.push('- There is no modal state present');
71
+ for (const state of this._modalStates) {
72
+ const tool = this.context.tools.filter(tool => 'clearsModalState' in tool).find(tool => tool.clearsModalState === state.type);
73
+ result.push(`- [${state.description}]: can be handled by the "${tool?.schema.name}" tool`);
74
+ }
75
+ return result;
76
+ }
77
+ _dialogShown(dialog) {
78
+ this.setModalState({
79
+ type: 'dialog',
80
+ description: `"${dialog.type()}" dialog with message "${dialog.message()}"`,
81
+ dialog,
82
+ });
83
+ }
84
+ async _downloadStarted(download) {
85
+ const entry = {
86
+ download,
87
+ finished: false,
88
+ outputFile: await outputFile(this.context.config, download.suggestedFilename())
89
+ };
90
+ this._downloads.push(entry);
91
+ await download.saveAs(entry.outputFile);
92
+ entry.finished = true;
93
+ }
50
94
  _clearCollectedArtifacts() {
51
95
  this._consoleMessages.length = 0;
52
96
  this._recentConsoleMessages.length = 0;
@@ -81,36 +125,98 @@ export class Tab {
81
125
  // on chromium, the download event is fired *after* page.goto rejects, so we wait a lil bit
82
126
  const download = await Promise.race([
83
127
  downloadEvent,
84
- new Promise(resolve => setTimeout(resolve, 1000)),
128
+ new Promise(resolve => setTimeout(resolve, 3000)),
85
129
  ]);
86
130
  if (!download)
87
131
  throw e;
132
+ // Make sure other "download" listeners are notified first.
133
+ await new Promise(resolve => setTimeout(resolve, 500));
134
+ return;
88
135
  }
89
136
  // Cap load event to 5 seconds, the page is operational at this point.
90
137
  await this.waitForLoadState('load', { timeout: 5000 });
91
138
  }
92
- hasSnapshot() {
93
- return !!this._snapshot;
94
- }
95
- snapshotOrDie() {
96
- if (!this._snapshot)
97
- throw new Error('No snapshot available');
98
- return this._snapshot;
99
- }
100
139
  consoleMessages() {
101
140
  return this._consoleMessages;
102
141
  }
103
142
  requests() {
104
143
  return this._requests;
105
144
  }
106
- async captureSnapshot() {
107
- this._snapshot = await PageSnapshot.create(this.page);
145
+ _takeRecentConsoleMarkdown() {
146
+ if (!this._recentConsoleMessages.length)
147
+ return [];
148
+ const result = this._recentConsoleMessages.map(message => {
149
+ return `- ${trim(message.toString(), 100)}`;
150
+ });
151
+ return [`### New console messages`, ...result, ''];
108
152
  }
109
- takeRecentConsoleMessages() {
110
- const result = this._recentConsoleMessages.slice();
111
- this._recentConsoleMessages.length = 0;
153
+ _listDownloadsMarkdown() {
154
+ if (!this._downloads.length)
155
+ return [];
156
+ const result = ['### Downloads'];
157
+ for (const entry of this._downloads) {
158
+ if (entry.finished)
159
+ result.push(`- Downloaded file ${entry.download.suggestedFilename()} to ${entry.outputFile}`);
160
+ else
161
+ result.push(`- Downloading file ${entry.download.suggestedFilename()} ...`);
162
+ }
163
+ result.push('');
112
164
  return result;
113
165
  }
166
+ async captureSnapshot() {
167
+ const result = [];
168
+ if (this.modalStates().length) {
169
+ result.push(...this.modalStatesMarkdown());
170
+ return result.join('\n');
171
+ }
172
+ result.push(...this._takeRecentConsoleMarkdown());
173
+ result.push(...this._listDownloadsMarkdown());
174
+ await this._raceAgainstModalStates(async () => {
175
+ const snapshot = await this.page._snapshotForAI();
176
+ result.push(`### Page state`, `- Page URL: ${this.page.url()}`, `- Page Title: ${await this.page.title()}`, `- Page Snapshot:`, '```yaml', snapshot, '```');
177
+ });
178
+ return result.join('\n');
179
+ }
180
+ _javaScriptBlocked() {
181
+ return this._modalStates.some(state => state.type === 'dialog');
182
+ }
183
+ async _raceAgainstModalStates(action) {
184
+ if (this.modalStates().length)
185
+ return this.modalStates()[0];
186
+ const promise = new ManualPromise();
187
+ const listener = (modalState) => promise.resolve(modalState);
188
+ this.once(TabEvents.modalState, listener);
189
+ return await Promise.race([
190
+ action().then(() => {
191
+ this.off(TabEvents.modalState, listener);
192
+ return undefined;
193
+ }),
194
+ promise,
195
+ ]);
196
+ }
197
+ async waitForCompletion(callback) {
198
+ await this._raceAgainstModalStates(() => waitForCompletion(this, callback));
199
+ }
200
+ async refLocator(params) {
201
+ return (await this.refLocators([params]))[0];
202
+ }
203
+ async refLocators(params) {
204
+ const snapshot = await this.page._snapshotForAI();
205
+ return params.map(param => {
206
+ if (!snapshot.includes(`[ref=${param.ref}]`))
207
+ throw new Error(`Ref ${param.ref} not found in the current page snapshot. Try capturing new snapshot.`);
208
+ return this.page.locator(`aria-ref=${param.ref}`).describe(param.element);
209
+ });
210
+ }
211
+ async waitForTimeout(time) {
212
+ if (this._javaScriptBlocked()) {
213
+ await new Promise(f => setTimeout(f, time));
214
+ return;
215
+ }
216
+ await callOnPageNoTrace(this.page, page => {
217
+ return page.evaluate(() => new Promise(f => setTimeout(f, 1000)));
218
+ });
219
+ }
114
220
  }
115
221
  function messageToConsoleMessage(message) {
116
222
  return {
@@ -133,3 +239,8 @@ function pageErrorToConsoleMessage(errorOrValue) {
133
239
  toString: () => String(errorOrValue),
134
240
  };
135
241
  }
242
+ function trim(text, maxLength) {
243
+ if (text.length <= maxLength)
244
+ return text;
245
+ return text.slice(0, maxLength) + '...';
246
+ }
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { z } from 'zod';
17
- import { defineTool } from './tool.js';
17
+ import { defineTabTool, defineTool } from './tool.js';
18
18
  const close = defineTool({
19
19
  capability: 'core',
20
20
  schema: {
@@ -24,16 +24,13 @@ const close = defineTool({
24
24
  inputSchema: z.object({}),
25
25
  type: 'readOnly',
26
26
  },
27
- handle: async (context) => {
28
- await context.close();
29
- return {
30
- code: [`await page.close()`],
31
- captureSnapshot: false,
32
- waitForNetwork: false,
33
- };
27
+ handle: async (context, params, response) => {
28
+ await context.closeBrowserContext();
29
+ response.setIncludeTabs();
30
+ response.addCode(`await page.close()`);
34
31
  },
35
32
  });
36
- const resize = defineTool({
33
+ const resize = defineTabTool({
37
34
  capability: 'core',
38
35
  schema: {
39
36
  name: 'browser_resize',
@@ -45,21 +42,12 @@ const resize = defineTool({
45
42
  }),
46
43
  type: 'readOnly',
47
44
  },
48
- handle: async (context, params) => {
49
- const tab = context.currentTabOrDie();
50
- const code = [
51
- `// Resize browser window to ${params.width}x${params.height}`,
52
- `await page.setViewportSize({ width: ${params.width}, height: ${params.height} });`
53
- ];
54
- const action = async () => {
45
+ handle: async (tab, params, response) => {
46
+ response.addCode(`// Resize browser window to ${params.width}x${params.height}`);
47
+ response.addCode(`await page.setViewportSize({ width: ${params.width}, height: ${params.height} });`);
48
+ await tab.waitForCompletion(async () => {
55
49
  await tab.page.setViewportSize({ width: params.width, height: params.height });
56
- };
57
- return {
58
- code,
59
- action,
60
- captureSnapshot: true,
61
- waitForNetwork: true
62
- };
50
+ });
63
51
  },
64
52
  });
65
53
  export default [
@@ -14,8 +14,8 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { z } from 'zod';
17
- import { defineTool } from './tool.js';
18
- const console = defineTool({
17
+ import { defineTabTool } from './tool.js';
18
+ const console = defineTabTool({
19
19
  capability: 'core',
20
20
  schema: {
21
21
  name: 'browser_console_messages',
@@ -24,19 +24,8 @@ const console = defineTool({
24
24
  inputSchema: z.object({}),
25
25
  type: 'readOnly',
26
26
  },
27
- handle: async (context) => {
28
- const messages = context.currentTabOrDie().consoleMessages();
29
- const log = messages.map(message => message.toString()).join('\n');
30
- return {
31
- code: [`// <internal code to get console messages>`],
32
- action: async () => {
33
- return {
34
- content: [{ type: 'text', text: log }]
35
- };
36
- },
37
- captureSnapshot: false,
38
- waitForNetwork: false,
39
- };
27
+ handle: async (tab, params, response) => {
28
+ tab.consoleMessages().map(message => response.addResult(message.toString()));
40
29
  },
41
30
  });
42
31
  export default [
@@ -14,8 +14,8 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { z } from 'zod';
17
- import { defineTool } from './tool.js';
18
- const handleDialog = defineTool({
17
+ import { defineTabTool } from './tool.js';
18
+ const handleDialog = defineTabTool({
19
19
  capability: 'core',
20
20
  schema: {
21
21
  name: 'browser_handle_dialog',
@@ -27,23 +27,18 @@ const handleDialog = defineTool({
27
27
  }),
28
28
  type: 'destructive',
29
29
  },
30
- handle: async (context, params) => {
31
- const dialogState = context.modalStates().find(state => state.type === 'dialog');
30
+ handle: async (tab, params, response) => {
31
+ response.setIncludeSnapshot();
32
+ const dialogState = tab.modalStates().find(state => state.type === 'dialog');
32
33
  if (!dialogState)
33
34
  throw new Error('No dialog visible');
34
- if (params.accept)
35
- await dialogState.dialog.accept(params.promptText);
36
- else
37
- await dialogState.dialog.dismiss();
38
- context.clearModalState(dialogState);
39
- const code = [
40
- `// <internal code to handle "${dialogState.dialog.type()}" dialog>`,
41
- ];
42
- return {
43
- code,
44
- captureSnapshot: true,
45
- waitForNetwork: false,
46
- };
35
+ tab.clearModalState(dialogState);
36
+ await tab.waitForCompletion(async () => {
37
+ if (params.accept)
38
+ await dialogState.dialog.accept(params.promptText);
39
+ else
40
+ await dialogState.dialog.dismiss();
41
+ });
47
42
  },
48
43
  clearsModalState: 'dialog',
49
44
  });
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { z } from 'zod';
17
- import { defineTool } from './tool.js';
17
+ import { defineTabTool } from './tool.js';
18
18
  import * as javascript from '../javascript.js';
19
19
  import { generateLocator } from './utils.js';
20
20
  const evaluateSchema = z.object({
@@ -22,7 +22,7 @@ const evaluateSchema = z.object({
22
22
  element: z.string().optional().describe('Human-readable element description used to obtain permission to interact with the element'),
23
23
  ref: z.string().optional().describe('Exact target element reference from the page snapshot'),
24
24
  });
25
- const evaluate = defineTool({
25
+ const evaluate = defineTabTool({
26
26
  capability: 'core',
27
27
  schema: {
28
28
  name: 'browser_evaluate',
@@ -31,30 +31,21 @@ const evaluate = defineTool({
31
31
  inputSchema: evaluateSchema,
32
32
  type: 'destructive',
33
33
  },
34
- handle: async (context, params) => {
35
- const tab = context.currentTabOrDie();
36
- const code = [];
34
+ handle: async (tab, params, response) => {
35
+ response.setIncludeSnapshot();
37
36
  let locator;
38
37
  if (params.ref && params.element) {
39
- const snapshot = tab.snapshotOrDie();
40
- locator = snapshot.refLocator({ ref: params.ref, element: params.element });
41
- code.push(`await page.${await generateLocator(locator)}.evaluate(${javascript.quote(params.function)});`);
38
+ locator = await tab.refLocator({ ref: params.ref, element: params.element });
39
+ response.addCode(`await page.${await generateLocator(locator)}.evaluate(${javascript.quote(params.function)});`);
42
40
  }
43
41
  else {
44
- code.push(`await page.evaluate(${javascript.quote(params.function)});`);
42
+ response.addCode(`await page.evaluate(${javascript.quote(params.function)});`);
45
43
  }
46
- return {
47
- code,
48
- action: async () => {
49
- const receiver = locator ?? tab.page;
50
- const result = await receiver._evaluateFunction(params.function);
51
- return {
52
- content: [{ type: 'text', text: '- Result: ' + (JSON.stringify(result, null, 2) || 'undefined') }],
53
- };
54
- },
55
- captureSnapshot: false,
56
- waitForNetwork: false,
57
- };
44
+ await tab.waitForCompletion(async () => {
45
+ const receiver = locator ?? tab.page;
46
+ const result = await receiver._evaluateFunction(params.function);
47
+ response.addResult(JSON.stringify(result, null, 2) || 'undefined');
48
+ });
58
49
  },
59
50
  });
60
51
  export default [
@@ -14,8 +14,8 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { z } from 'zod';
17
- import { defineTool } from './tool.js';
18
- const uploadFile = defineTool({
17
+ import { defineTabTool } from './tool.js';
18
+ const uploadFile = defineTabTool({
19
19
  capability: 'core',
20
20
  schema: {
21
21
  name: 'browser_file_upload',
@@ -26,23 +26,17 @@ const uploadFile = defineTool({
26
26
  }),
27
27
  type: 'destructive',
28
28
  },
29
- handle: async (context, params) => {
30
- const modalState = context.modalStates().find(state => state.type === 'fileChooser');
29
+ handle: async (tab, params, response) => {
30
+ response.setIncludeSnapshot();
31
+ const modalState = tab.modalStates().find(state => state.type === 'fileChooser');
31
32
  if (!modalState)
32
33
  throw new Error('No file chooser visible');
33
- const code = [
34
- `// <internal code to chose files ${params.paths.join(', ')}`,
35
- ];
36
- const action = async () => {
34
+ response.addCode(`// Select files for upload`);
35
+ response.addCode(`await fileChooser.setFiles(${JSON.stringify(params.paths)})`);
36
+ tab.clearModalState(modalState);
37
+ await tab.waitForCompletion(async () => {
37
38
  await modalState.fileChooser.setFiles(params.paths);
38
- context.clearModalState(modalState);
39
- };
40
- return {
41
- code,
42
- action,
43
- captureSnapshot: true,
44
- waitForNetwork: true,
45
- };
39
+ });
46
40
  },
47
41
  clearsModalState: 'fileChooser',
48
42
  });
@@ -15,9 +15,9 @@
15
15
  */
16
16
  import { fork } from 'child_process';
17
17
  import path from 'path';
18
+ import { fileURLToPath } from 'url';
18
19
  import { z } from 'zod';
19
20
  import { defineTool } from './tool.js';
20
- import { fileURLToPath } from 'node:url';
21
21
  const install = defineTool({
22
22
  capability: 'core-install',
23
23
  schema: {
@@ -27,7 +27,7 @@ const install = defineTool({
27
27
  inputSchema: z.object({}),
28
28
  type: 'destructive',
29
29
  },
30
- handle: async (context) => {
30
+ handle: async (context, params, response) => {
31
31
  const channel = context.config.browser?.launchOptions?.channel ?? context.config.browser?.browserName ?? 'chrome';
32
32
  const cliUrl = import.meta.resolve('playwright/package.json');
33
33
  const cliPath = path.join(fileURLToPath(cliUrl), '..', 'cli.js');
@@ -45,11 +45,7 @@ const install = defineTool({
45
45
  reject(new Error(`Failed to install browser: ${output.join('')}`));
46
46
  });
47
47
  });
48
- return {
49
- code: [`// Browser ${channel} installed`],
50
- captureSnapshot: false,
51
- waitForNetwork: false,
52
- };
48
+ response.setIncludeTabs();
53
49
  },
54
50
  });
55
51
  export default [
@@ -14,11 +14,11 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { z } from 'zod';
17
- import { defineTool } from './tool.js';
17
+ import { defineTabTool } from './tool.js';
18
18
  import { elementSchema } from './snapshot.js';
19
19
  import { generateLocator } from './utils.js';
20
20
  import * as javascript from '../javascript.js';
21
- const pressKey = defineTool({
21
+ const pressKey = defineTabTool({
22
22
  capability: 'core',
23
23
  schema: {
24
24
  name: 'browser_press_key',
@@ -29,19 +29,13 @@ const pressKey = defineTool({
29
29
  }),
30
30
  type: 'destructive',
31
31
  },
32
- handle: async (context, params) => {
33
- const tab = context.currentTabOrDie();
34
- const code = [
35
- `// Press ${params.key}`,
36
- `await page.keyboard.press('${params.key}');`,
37
- ];
38
- const action = () => tab.page.keyboard.press(params.key);
39
- return {
40
- code,
41
- action,
42
- captureSnapshot: true,
43
- waitForNetwork: true
44
- };
32
+ handle: async (tab, params, response) => {
33
+ response.setIncludeSnapshot();
34
+ response.addCode(`// Press ${params.key}`);
35
+ response.addCode(`await page.keyboard.press('${params.key}');`);
36
+ await tab.waitForCompletion(async () => {
37
+ await tab.page.keyboard.press(params.key);
38
+ });
45
39
  },
46
40
  });
47
41
  const typeSchema = elementSchema.extend({
@@ -49,7 +43,7 @@ const typeSchema = elementSchema.extend({
49
43
  submit: z.boolean().optional().describe('Whether to submit entered text (press Enter after)'),
50
44
  slowly: z.boolean().optional().describe('Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.'),
51
45
  });
52
- const type = defineTool({
46
+ const type = defineTabTool({
53
47
  capability: 'core',
54
48
  schema: {
55
49
  name: 'browser_type',
@@ -58,32 +52,26 @@ const type = defineTool({
58
52
  inputSchema: typeSchema,
59
53
  type: 'destructive',
60
54
  },
61
- handle: async (context, params) => {
62
- const snapshot = context.currentTabOrDie().snapshotOrDie();
63
- const locator = snapshot.refLocator(params);
64
- const code = [];
65
- const steps = [];
66
- if (params.slowly) {
67
- code.push(`// Press "${params.text}" sequentially into "${params.element}"`);
68
- code.push(`await page.${await generateLocator(locator)}.pressSequentially(${javascript.quote(params.text)});`);
69
- steps.push(() => locator.pressSequentially(params.text));
70
- }
71
- else {
72
- code.push(`// Fill "${params.text}" into "${params.element}"`);
73
- code.push(`await page.${await generateLocator(locator)}.fill(${javascript.quote(params.text)});`);
74
- steps.push(() => locator.fill(params.text));
75
- }
76
- if (params.submit) {
77
- code.push(`// Submit text`);
78
- code.push(`await page.${await generateLocator(locator)}.press('Enter');`);
79
- steps.push(() => locator.press('Enter'));
80
- }
81
- return {
82
- code,
83
- action: () => steps.reduce((acc, step) => acc.then(step), Promise.resolve()),
84
- captureSnapshot: true,
85
- waitForNetwork: true,
86
- };
55
+ handle: async (tab, params, response) => {
56
+ const locator = await tab.refLocator(params);
57
+ await tab.waitForCompletion(async () => {
58
+ if (params.slowly) {
59
+ response.setIncludeSnapshot();
60
+ response.addCode(`// Press "${params.text}" sequentially into "${params.element}"`);
61
+ response.addCode(`await page.${await generateLocator(locator)}.pressSequentially(${javascript.quote(params.text)});`);
62
+ await locator.pressSequentially(params.text);
63
+ }
64
+ else {
65
+ response.addCode(`// Fill "${params.text}" into "${params.element}"`);
66
+ response.addCode(`await page.${await generateLocator(locator)}.fill(${javascript.quote(params.text)});`);
67
+ await locator.fill(params.text);
68
+ }
69
+ if (params.submit) {
70
+ response.setIncludeSnapshot();
71
+ response.addCode(`await page.${await generateLocator(locator)}.press('Enter');`);
72
+ await locator.press('Enter');
73
+ }
74
+ });
87
75
  },
88
76
  });
89
77
  export default [