@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.
- package/README.md +25 -4
- package/config.d.ts +5 -0
- package/index.d.ts +1 -6
- package/lib/browserServerBackend.js +54 -0
- package/lib/config.js +2 -1
- package/lib/context.js +48 -171
- package/lib/extension/cdpRelay.js +370 -0
- package/lib/extension/main.js +33 -0
- package/lib/httpServer.js +20 -182
- package/lib/index.js +3 -2
- package/lib/loop/loop.js +69 -0
- package/lib/loop/loopClaude.js +152 -0
- package/lib/loop/loopOpenAI.js +141 -0
- package/lib/loop/main.js +60 -0
- package/lib/loopTools/context.js +66 -0
- package/lib/loopTools/main.js +49 -0
- package/lib/loopTools/perform.js +32 -0
- package/lib/loopTools/snapshot.js +29 -0
- package/lib/loopTools/tool.js +18 -0
- package/lib/mcp/inProcessTransport.js +72 -0
- package/lib/mcp/server.js +88 -0
- package/lib/{transport.js → mcp/transport.js} +30 -42
- package/lib/package.js +3 -3
- package/lib/program.js +38 -9
- package/lib/response.js +98 -0
- package/lib/sessionLog.js +70 -0
- package/lib/tab.js +133 -22
- package/lib/tools/common.js +11 -23
- package/lib/tools/console.js +4 -15
- package/lib/tools/dialogs.js +12 -17
- package/lib/tools/evaluate.js +12 -21
- package/lib/tools/files.js +10 -16
- package/lib/tools/install.js +3 -7
- package/lib/tools/keyboard.js +30 -42
- package/lib/tools/mouse.js +27 -50
- package/lib/tools/navigate.js +15 -35
- package/lib/tools/network.js +5 -15
- package/lib/tools/pdf.js +8 -15
- package/lib/tools/screenshot.js +29 -30
- package/lib/tools/snapshot.js +45 -65
- package/lib/tools/tabs.js +10 -41
- package/lib/tools/tool.js +14 -0
- package/lib/tools/utils.js +2 -2
- package/lib/tools/wait.js +3 -6
- package/lib/tools.js +3 -0
- package/package.json +9 -3
- package/lib/connection.js +0 -81
- package/lib/pageSnapshot.js +0 -43
- 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 {
|
|
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
|
-
|
|
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.
|
|
44
|
+
this.setModalState({
|
|
38
45
|
type: 'fileChooser',
|
|
39
46
|
description: 'File chooser',
|
|
40
47
|
fileChooser: chooser,
|
|
41
|
-
}
|
|
48
|
+
});
|
|
42
49
|
});
|
|
43
|
-
page.on('dialog', dialog => this.
|
|
50
|
+
page.on('dialog', dialog => this._dialogShown(dialog));
|
|
44
51
|
page.on('download', download => {
|
|
45
|
-
void this.
|
|
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,
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
+
}
|
package/lib/tools/common.js
CHANGED
|
@@ -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.
|
|
29
|
-
|
|
30
|
-
|
|
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 =
|
|
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 (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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 [
|
package/lib/tools/console.js
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import { z } from 'zod';
|
|
17
|
-
import {
|
|
18
|
-
const console =
|
|
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 (
|
|
28
|
-
|
|
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 [
|
package/lib/tools/dialogs.js
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import { z } from 'zod';
|
|
17
|
-
import {
|
|
18
|
-
const handleDialog =
|
|
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 (
|
|
31
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
});
|
package/lib/tools/evaluate.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import { z } from 'zod';
|
|
17
|
-
import {
|
|
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 =
|
|
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 (
|
|
35
|
-
|
|
36
|
-
const code = [];
|
|
34
|
+
handle: async (tab, params, response) => {
|
|
35
|
+
response.setIncludeSnapshot();
|
|
37
36
|
let locator;
|
|
38
37
|
if (params.ref && params.element) {
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
42
|
+
response.addCode(`await page.evaluate(${javascript.quote(params.function)});`);
|
|
45
43
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 [
|
package/lib/tools/files.js
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import { z } from 'zod';
|
|
17
|
-
import {
|
|
18
|
-
const uploadFile =
|
|
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 (
|
|
30
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
};
|
|
40
|
-
return {
|
|
41
|
-
code,
|
|
42
|
-
action,
|
|
43
|
-
captureSnapshot: true,
|
|
44
|
-
waitForNetwork: true,
|
|
45
|
-
};
|
|
39
|
+
});
|
|
46
40
|
},
|
|
47
41
|
clearsModalState: 'fileChooser',
|
|
48
42
|
});
|
package/lib/tools/install.js
CHANGED
|
@@ -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
|
-
|
|
49
|
-
code: [`// Browser ${channel} installed`],
|
|
50
|
-
captureSnapshot: false,
|
|
51
|
-
waitForNetwork: false,
|
|
52
|
-
};
|
|
48
|
+
response.setIncludeTabs();
|
|
53
49
|
},
|
|
54
50
|
});
|
|
55
51
|
export default [
|
package/lib/tools/keyboard.js
CHANGED
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import { z } from 'zod';
|
|
17
|
-
import {
|
|
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 =
|
|
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 (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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 =
|
|
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 (
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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 [
|