@playwright/mcp 0.0.5 → 0.0.6
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 +15 -13
- package/lib/context.js +51 -0
- package/lib/index.js +2 -0
- package/lib/program.js +67 -2
- package/lib/server.js +2 -1
- package/lib/tools/common.js +19 -2
- package/lib/tools/snapshot.js +10 -20
- package/lib/tools/utils.js +15 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,27 +93,19 @@ This mode is useful for background or batch operations.
|
|
|
93
93
|
### Running headed browser on Linux w/o DISPLAY
|
|
94
94
|
|
|
95
95
|
When running headed browser on system w/o display or from worker processes of the IDEs,
|
|
96
|
-
|
|
97
|
-
from environment with the DISPLAY
|
|
96
|
+
run the MCP server from environment with the DISPLAY and pass the `--port` flag to enable SSE transport.
|
|
98
97
|
|
|
99
|
-
```
|
|
100
|
-
npx playwright
|
|
98
|
+
```bash
|
|
99
|
+
npx @playwright/mcp@latest --port 8931
|
|
101
100
|
```
|
|
102
101
|
|
|
103
|
-
And then in MCP config,
|
|
102
|
+
And then in MCP client config, set the `url` to the SSE endpoint:
|
|
104
103
|
|
|
105
104
|
```js
|
|
106
105
|
{
|
|
107
106
|
"mcpServers": {
|
|
108
107
|
"playwright": {
|
|
109
|
-
"
|
|
110
|
-
"args": [
|
|
111
|
-
"@playwright/mcp@latest"
|
|
112
|
-
],
|
|
113
|
-
"env": {
|
|
114
|
-
// Use the endpoint from the output of the server above.
|
|
115
|
-
"PLAYWRIGHT_WS_ENDPOINT": "ws://localhost:<port>/"
|
|
116
|
-
}
|
|
108
|
+
"url": "http://localhost:8931/sse"
|
|
117
109
|
}
|
|
118
110
|
}
|
|
119
111
|
}
|
|
@@ -211,6 +203,11 @@ The Playwright MCP provides a set of tools for browser automation. Here are all
|
|
|
211
203
|
- `ref` (string): Exact target element reference from the page snapshot
|
|
212
204
|
- `values` (array): Array of values to select in the dropdown.
|
|
213
205
|
|
|
206
|
+
- **browser_choose_file**
|
|
207
|
+
- Description: Choose one or multiple files to upload
|
|
208
|
+
- Parameters:
|
|
209
|
+
- `paths` (array): The absolute paths to the files to upload. Can be a single file or multiple files.
|
|
210
|
+
|
|
214
211
|
- **browser_press_key**
|
|
215
212
|
- Description: Press a key on the keyboard
|
|
216
213
|
- Parameters:
|
|
@@ -291,6 +288,11 @@ Vision Mode provides tools for visual-based interactions using screenshots. Here
|
|
|
291
288
|
- Parameters:
|
|
292
289
|
- `key` (string): Name of the key to press or a character to generate, such as `ArrowLeft` or `a`
|
|
293
290
|
|
|
291
|
+
- **browser_choose_file**
|
|
292
|
+
- Description: Choose one or multiple files to upload
|
|
293
|
+
- Parameters:
|
|
294
|
+
- `paths` (array): The absolute paths to the files to upload. Can be a single file or multiple files.
|
|
295
|
+
|
|
294
296
|
- **browser_save_as_pdf**
|
|
295
297
|
- Description: Save page as PDF
|
|
296
298
|
- Parameters: None
|
package/lib/context.js
CHANGED
|
@@ -57,6 +57,8 @@ class Context {
|
|
|
57
57
|
_page;
|
|
58
58
|
_console = [];
|
|
59
59
|
_createPagePromise;
|
|
60
|
+
_fileChooser;
|
|
61
|
+
_lastSnapshotFrames = [];
|
|
60
62
|
constructor(userDataDir, launchOptions) {
|
|
61
63
|
this._userDataDir = userDataDir;
|
|
62
64
|
this._launchOptions = launchOptions;
|
|
@@ -72,6 +74,9 @@ class Context {
|
|
|
72
74
|
this._console.length = 0;
|
|
73
75
|
});
|
|
74
76
|
page.on('close', () => this._onPageClose());
|
|
77
|
+
page.on('filechooser', chooser => this._fileChooser = chooser);
|
|
78
|
+
page.setDefaultNavigationTimeout(60000);
|
|
79
|
+
page.setDefaultTimeout(5000);
|
|
75
80
|
this._page = page;
|
|
76
81
|
this._browser = browser;
|
|
77
82
|
return page;
|
|
@@ -85,6 +90,7 @@ class Context {
|
|
|
85
90
|
this._createPagePromise = undefined;
|
|
86
91
|
this._browser = undefined;
|
|
87
92
|
this._page = undefined;
|
|
93
|
+
this._fileChooser = undefined;
|
|
88
94
|
this._console.length = 0;
|
|
89
95
|
}
|
|
90
96
|
existingPage() {
|
|
@@ -100,6 +106,18 @@ class Context {
|
|
|
100
106
|
return;
|
|
101
107
|
await this._page.close();
|
|
102
108
|
}
|
|
109
|
+
async submitFileChooser(paths) {
|
|
110
|
+
if (!this._fileChooser)
|
|
111
|
+
throw new Error('No file chooser visible');
|
|
112
|
+
await this._fileChooser.setFiles(paths);
|
|
113
|
+
this._fileChooser = undefined;
|
|
114
|
+
}
|
|
115
|
+
hasFileChooser() {
|
|
116
|
+
return !!this._fileChooser;
|
|
117
|
+
}
|
|
118
|
+
clearFileChooser() {
|
|
119
|
+
this._fileChooser = undefined;
|
|
120
|
+
}
|
|
103
121
|
async _createPage() {
|
|
104
122
|
if (process.env.PLAYWRIGHT_WS_ENDPOINT) {
|
|
105
123
|
const url = new URL(process.env.PLAYWRIGHT_WS_ENDPOINT);
|
|
@@ -113,5 +131,38 @@ class Context {
|
|
|
113
131
|
const [page] = context.pages();
|
|
114
132
|
return { page };
|
|
115
133
|
}
|
|
134
|
+
async allFramesSnapshot() {
|
|
135
|
+
const page = this.existingPage();
|
|
136
|
+
const visibleFrames = await page.locator('iframe').filter({ visible: true }).all();
|
|
137
|
+
this._lastSnapshotFrames = visibleFrames.map(frame => frame.contentFrame());
|
|
138
|
+
const snapshots = await Promise.all([
|
|
139
|
+
page.locator('html').ariaSnapshot({ ref: true }),
|
|
140
|
+
...this._lastSnapshotFrames.map(async (frame, index) => {
|
|
141
|
+
const snapshot = await frame.locator('html').ariaSnapshot({ ref: true });
|
|
142
|
+
const args = [];
|
|
143
|
+
const src = await frame.owner().getAttribute('src');
|
|
144
|
+
if (src)
|
|
145
|
+
args.push(`src=${src}`);
|
|
146
|
+
const name = await frame.owner().getAttribute('name');
|
|
147
|
+
if (name)
|
|
148
|
+
args.push(`name=${name}`);
|
|
149
|
+
return `\n# iframe ${args.join(' ')}\n` + snapshot.replaceAll('[ref=', `[ref=f${index}`);
|
|
150
|
+
})
|
|
151
|
+
]);
|
|
152
|
+
return snapshots.join('\n');
|
|
153
|
+
}
|
|
154
|
+
refLocator(ref) {
|
|
155
|
+
const page = this.existingPage();
|
|
156
|
+
let frame = page.mainFrame();
|
|
157
|
+
const match = ref.match(/^f(\d+)(.*)/);
|
|
158
|
+
if (match) {
|
|
159
|
+
const frameIndex = parseInt(match[1], 10);
|
|
160
|
+
if (!this._lastSnapshotFrames[frameIndex])
|
|
161
|
+
throw new Error(`Frame does not exist. Provide ref from the most current snapshot.`);
|
|
162
|
+
frame = this._lastSnapshotFrames[frameIndex];
|
|
163
|
+
ref = match[2];
|
|
164
|
+
}
|
|
165
|
+
return frame.locator(`aria-ref=${ref}`);
|
|
166
|
+
}
|
|
116
167
|
}
|
|
117
168
|
exports.Context = Context;
|
package/lib/index.js
CHANGED
|
@@ -64,6 +64,7 @@ const snapshotTools = [
|
|
|
64
64
|
common.navigate(true),
|
|
65
65
|
common.goBack(true),
|
|
66
66
|
common.goForward(true),
|
|
67
|
+
common.chooseFile(true),
|
|
67
68
|
snapshot.snapshot,
|
|
68
69
|
snapshot.click,
|
|
69
70
|
snapshot.hover,
|
|
@@ -76,6 +77,7 @@ const screenshotTools = [
|
|
|
76
77
|
common.navigate(false),
|
|
77
78
|
common.goBack(false),
|
|
78
79
|
common.goForward(false),
|
|
80
|
+
common.chooseFile(false),
|
|
79
81
|
screenshot.screenshot,
|
|
80
82
|
screenshot.moveMouse,
|
|
81
83
|
screenshot.click,
|
package/lib/program.js
CHANGED
|
@@ -18,12 +18,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
const http_1 = __importDefault(require("http"));
|
|
21
22
|
const fs_1 = __importDefault(require("fs"));
|
|
22
23
|
const os_1 = __importDefault(require("os"));
|
|
23
24
|
const path_1 = __importDefault(require("path"));
|
|
24
25
|
const commander_1 = require("commander");
|
|
25
26
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
27
|
+
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
26
28
|
const index_1 = require("./index");
|
|
29
|
+
const assert_1 = __importDefault(require("assert"));
|
|
27
30
|
const packageJSON = require('../package.json');
|
|
28
31
|
commander_1.program
|
|
29
32
|
.version('Version ' + packageJSON.version)
|
|
@@ -31,6 +34,7 @@ commander_1.program
|
|
|
31
34
|
.option('--headless', 'Run browser in headless mode, headed by default')
|
|
32
35
|
.option('--user-data-dir <path>', 'Path to the user data directory')
|
|
33
36
|
.option('--vision', 'Run server that uses screenshots (Aria snapshots are used by default)')
|
|
37
|
+
.option('--port <port>', 'Port to listen on for SSE transport.')
|
|
34
38
|
.action(async (options) => {
|
|
35
39
|
const launchOptions = {
|
|
36
40
|
headless: !!options.headless,
|
|
@@ -42,8 +46,69 @@ commander_1.program
|
|
|
42
46
|
vision: !!options.vision,
|
|
43
47
|
});
|
|
44
48
|
setupExitWatchdog(server);
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
if (options.port) {
|
|
50
|
+
const sessions = new Map();
|
|
51
|
+
const httpServer = http_1.default.createServer(async (req, res) => {
|
|
52
|
+
if (req.method === 'POST') {
|
|
53
|
+
const host = req.headers.host ?? 'http://unknown';
|
|
54
|
+
const sessionId = new URL(host + req.url).searchParams.get('sessionId');
|
|
55
|
+
if (!sessionId) {
|
|
56
|
+
res.statusCode = 400;
|
|
57
|
+
res.end('Missing sessionId');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const transport = sessions.get(sessionId);
|
|
61
|
+
if (!transport) {
|
|
62
|
+
res.statusCode = 404;
|
|
63
|
+
res.end('Session not found');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
await transport.handlePostMessage(req, res);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
else if (req.method === 'GET') {
|
|
70
|
+
const transport = new sse_js_1.SSEServerTransport('/sse', res);
|
|
71
|
+
sessions.set(transport.sessionId, transport);
|
|
72
|
+
res.on('close', () => {
|
|
73
|
+
sessions.delete(transport.sessionId);
|
|
74
|
+
});
|
|
75
|
+
await server.connect(transport);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
res.statusCode = 405;
|
|
80
|
+
res.end('Method not allowed');
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
httpServer.listen(+options.port, () => {
|
|
84
|
+
const address = httpServer.address();
|
|
85
|
+
(0, assert_1.default)(address, 'Could not bind server socket');
|
|
86
|
+
let urlPrefixHumanReadable;
|
|
87
|
+
if (typeof address === 'string') {
|
|
88
|
+
urlPrefixHumanReadable = address;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const port = address.port;
|
|
92
|
+
let resolvedHost = address.family === 'IPv4' ? address.address : `[${address.address}]`;
|
|
93
|
+
if (resolvedHost === '0.0.0.0' || resolvedHost === '[::]')
|
|
94
|
+
resolvedHost = 'localhost';
|
|
95
|
+
urlPrefixHumanReadable = `http://${resolvedHost}:${port}`;
|
|
96
|
+
}
|
|
97
|
+
console.log(`Listening on ${urlPrefixHumanReadable}`);
|
|
98
|
+
console.log('Put this in your client config:');
|
|
99
|
+
console.log(JSON.stringify({
|
|
100
|
+
'mcpServers': {
|
|
101
|
+
'playwright': {
|
|
102
|
+
'url': `${urlPrefixHumanReadable}/sse`
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}, undefined, 2));
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
110
|
+
await server.connect(transport);
|
|
111
|
+
}
|
|
47
112
|
});
|
|
48
113
|
function setupExitWatchdog(server) {
|
|
49
114
|
process.stdin.on('close', async () => {
|
package/lib/server.js
CHANGED
|
@@ -60,8 +60,9 @@ function createServerWithTools(options) {
|
|
|
60
60
|
const contents = await resource.read(context, request.params.uri);
|
|
61
61
|
return { contents };
|
|
62
62
|
});
|
|
63
|
+
const oldClose = server.close.bind(server);
|
|
63
64
|
server.close = async () => {
|
|
64
|
-
await
|
|
65
|
+
await oldClose();
|
|
65
66
|
await context.close();
|
|
66
67
|
};
|
|
67
68
|
return server;
|
package/lib/tools/common.js
CHANGED
|
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.close = exports.pdf = exports.pressKey = exports.wait = exports.goForward = exports.goBack = exports.navigate = void 0;
|
|
21
|
+
exports.chooseFile = exports.close = exports.pdf = exports.pressKey = exports.wait = exports.goForward = exports.goBack = exports.navigate = void 0;
|
|
22
22
|
const os_1 = __importDefault(require("os"));
|
|
23
23
|
const path_1 = __importDefault(require("path"));
|
|
24
24
|
const zod_1 = require("zod");
|
|
@@ -40,7 +40,7 @@ const navigate = snapshot => ({
|
|
|
40
40
|
// Cap load event to 5 seconds, the page is operational at this point.
|
|
41
41
|
await page.waitForLoadState('load', { timeout: 5000 }).catch(() => { });
|
|
42
42
|
if (snapshot)
|
|
43
|
-
return (0, utils_1.captureAriaSnapshot)(
|
|
43
|
+
return (0, utils_1.captureAriaSnapshot)(context);
|
|
44
44
|
return {
|
|
45
45
|
content: [{
|
|
46
46
|
type: 'text',
|
|
@@ -146,3 +146,20 @@ exports.close = {
|
|
|
146
146
|
};
|
|
147
147
|
},
|
|
148
148
|
};
|
|
149
|
+
const chooseFileSchema = zod_1.z.object({
|
|
150
|
+
paths: zod_1.z.array(zod_1.z.string()).describe('The absolute paths to the files to upload. Can be a single file or multiple files.'),
|
|
151
|
+
});
|
|
152
|
+
const chooseFile = snapshot => ({
|
|
153
|
+
schema: {
|
|
154
|
+
name: 'browser_choose_file',
|
|
155
|
+
description: 'Choose one or multiple files to upload',
|
|
156
|
+
inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(chooseFileSchema),
|
|
157
|
+
},
|
|
158
|
+
handle: async (context, params) => {
|
|
159
|
+
const validatedParams = chooseFileSchema.parse(params);
|
|
160
|
+
return await (0, utils_1.runAndWait)(context, `Chose files ${validatedParams.paths.join(', ')}`, async () => {
|
|
161
|
+
await context.submitFileChooser(validatedParams.paths);
|
|
162
|
+
}, snapshot);
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
exports.chooseFile = chooseFile;
|
package/lib/tools/snapshot.js
CHANGED
|
@@ -29,7 +29,7 @@ exports.snapshot = {
|
|
|
29
29
|
inputSchema: (0, zod_to_json_schema_1.default)(zod_1.z.object({})),
|
|
30
30
|
},
|
|
31
31
|
handle: async (context) => {
|
|
32
|
-
return await (0, utils_1.captureAriaSnapshot)(context
|
|
32
|
+
return await (0, utils_1.captureAriaSnapshot)(context);
|
|
33
33
|
},
|
|
34
34
|
};
|
|
35
35
|
const elementSchema = zod_1.z.object({
|
|
@@ -44,7 +44,7 @@ exports.click = {
|
|
|
44
44
|
},
|
|
45
45
|
handle: async (context, params) => {
|
|
46
46
|
const validatedParams = elementSchema.parse(params);
|
|
47
|
-
return (0, utils_1.runAndWait)(context, `"${validatedParams.element}" clicked`,
|
|
47
|
+
return (0, utils_1.runAndWait)(context, `"${validatedParams.element}" clicked`, () => context.refLocator(validatedParams.ref).click(), true);
|
|
48
48
|
},
|
|
49
49
|
};
|
|
50
50
|
const dragSchema = zod_1.z.object({
|
|
@@ -61,9 +61,9 @@ exports.drag = {
|
|
|
61
61
|
},
|
|
62
62
|
handle: async (context, params) => {
|
|
63
63
|
const validatedParams = dragSchema.parse(params);
|
|
64
|
-
return (0, utils_1.runAndWait)(context, `Dragged "${validatedParams.startElement}" to "${validatedParams.endElement}"`, async (
|
|
65
|
-
const startLocator = refLocator(
|
|
66
|
-
const endLocator = refLocator(
|
|
64
|
+
return (0, utils_1.runAndWait)(context, `Dragged "${validatedParams.startElement}" to "${validatedParams.endElement}"`, async () => {
|
|
65
|
+
const startLocator = context.refLocator(validatedParams.startRef);
|
|
66
|
+
const endLocator = context.refLocator(validatedParams.endRef);
|
|
67
67
|
await startLocator.dragTo(endLocator);
|
|
68
68
|
}, true);
|
|
69
69
|
},
|
|
@@ -76,7 +76,7 @@ exports.hover = {
|
|
|
76
76
|
},
|
|
77
77
|
handle: async (context, params) => {
|
|
78
78
|
const validatedParams = elementSchema.parse(params);
|
|
79
|
-
return (0, utils_1.runAndWait)(context, `Hovered over "${validatedParams.element}"`,
|
|
79
|
+
return (0, utils_1.runAndWait)(context, `Hovered over "${validatedParams.element}"`, () => context.refLocator(validatedParams.ref).hover(), true);
|
|
80
80
|
},
|
|
81
81
|
};
|
|
82
82
|
const typeSchema = elementSchema.extend({
|
|
@@ -91,8 +91,8 @@ exports.type = {
|
|
|
91
91
|
},
|
|
92
92
|
handle: async (context, params) => {
|
|
93
93
|
const validatedParams = typeSchema.parse(params);
|
|
94
|
-
return await (0, utils_1.runAndWait)(context, `Typed "${validatedParams.text}" into "${validatedParams.element}"`, async (
|
|
95
|
-
const locator = refLocator(
|
|
94
|
+
return await (0, utils_1.runAndWait)(context, `Typed "${validatedParams.text}" into "${validatedParams.element}"`, async () => {
|
|
95
|
+
const locator = context.refLocator(validatedParams.ref);
|
|
96
96
|
await locator.fill(validatedParams.text);
|
|
97
97
|
if (validatedParams.submit)
|
|
98
98
|
await locator.press('Enter');
|
|
@@ -110,8 +110,8 @@ exports.selectOption = {
|
|
|
110
110
|
},
|
|
111
111
|
handle: async (context, params) => {
|
|
112
112
|
const validatedParams = selectOptionSchema.parse(params);
|
|
113
|
-
return await (0, utils_1.runAndWait)(context, `Selected option in "${validatedParams.element}"`, async (
|
|
114
|
-
const locator = refLocator(
|
|
113
|
+
return await (0, utils_1.runAndWait)(context, `Selected option in "${validatedParams.element}"`, async () => {
|
|
114
|
+
const locator = context.refLocator(validatedParams.ref);
|
|
115
115
|
await locator.selectOption(validatedParams.values);
|
|
116
116
|
}, true);
|
|
117
117
|
},
|
|
@@ -135,13 +135,3 @@ exports.screenshot = {
|
|
|
135
135
|
};
|
|
136
136
|
},
|
|
137
137
|
};
|
|
138
|
-
function refLocator(page, ref) {
|
|
139
|
-
let frame = page.frames()[0];
|
|
140
|
-
const match = ref.match(/^f(\d+)(.*)/);
|
|
141
|
-
if (match) {
|
|
142
|
-
const frameIndex = parseInt(match[1], 10);
|
|
143
|
-
frame = page.frames()[frameIndex];
|
|
144
|
-
ref = match[2];
|
|
145
|
-
}
|
|
146
|
-
return frame.locator(`aria-ref=${ref}`);
|
|
147
|
-
}
|
package/lib/tools/utils.js
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.runAndWait = runAndWait;
|
|
19
|
-
exports.captureAllFrameSnapshot = captureAllFrameSnapshot;
|
|
20
19
|
exports.captureAriaSnapshot = captureAriaSnapshot;
|
|
21
20
|
async function waitForCompletion(page, callback) {
|
|
22
21
|
const requests = new Set();
|
|
@@ -67,30 +66,25 @@ async function waitForCompletion(page, callback) {
|
|
|
67
66
|
}
|
|
68
67
|
async function runAndWait(context, status, callback, snapshot = false) {
|
|
69
68
|
const page = context.existingPage();
|
|
69
|
+
const dismissFileChooser = context.hasFileChooser();
|
|
70
70
|
await waitForCompletion(page, () => callback(page));
|
|
71
|
-
|
|
71
|
+
if (dismissFileChooser)
|
|
72
|
+
context.clearFileChooser();
|
|
73
|
+
const result = snapshot ? await captureAriaSnapshot(context, status) : {
|
|
72
74
|
content: [{ type: 'text', text: status }],
|
|
73
75
|
};
|
|
76
|
+
return result;
|
|
74
77
|
}
|
|
75
|
-
async function
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
async function captureAriaSnapshot(page, status = '') {
|
|
78
|
+
async function captureAriaSnapshot(context, status = '') {
|
|
79
|
+
const page = context.existingPage();
|
|
80
|
+
const lines = [];
|
|
81
|
+
if (status)
|
|
82
|
+
lines.push(`${status}`);
|
|
83
|
+
lines.push('', `- Page URL: ${page.url()}`, `- Page Title: ${await page.title()}`);
|
|
84
|
+
if (context.hasFileChooser())
|
|
85
|
+
lines.push(`- There is a file chooser visible that requires browser_choose_file to be called`);
|
|
86
|
+
lines.push(`- Page Snapshot`, '```yaml', await context.allFramesSnapshot(), '```', '');
|
|
85
87
|
return {
|
|
86
|
-
content: [{ type: 'text', text:
|
|
87
|
-
- Page URL: ${page.url()}
|
|
88
|
-
- Page Title: ${await page.title()}
|
|
89
|
-
- Page Snapshot
|
|
90
|
-
\`\`\`yaml
|
|
91
|
-
${await captureAllFrameSnapshot(page)}
|
|
92
|
-
\`\`\`
|
|
93
|
-
`
|
|
94
|
-
}],
|
|
88
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
95
89
|
};
|
|
96
90
|
}
|