@playwright/mcp 0.0.9 → 0.0.10
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 +78 -73
- package/index.d.ts +7 -0
- package/lib/context.js +210 -105
- package/lib/index.js +30 -65
- package/lib/program.js +2 -0
- package/lib/resources/console.js +1 -1
- package/lib/tools/common.js +8 -127
- package/lib/tools/files.js +44 -0
- package/lib/tools/install.js +59 -0
- package/lib/tools/keyboard.js +45 -0
- package/lib/tools/navigate.js +79 -0
- package/lib/tools/pdf.js +48 -0
- package/lib/tools/{screenshot.js → screen.js} +45 -29
- package/lib/tools/snapshot.js +60 -26
- package/lib/tools/tabs.js +100 -0
- package/lib/tools/utils.js +1 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,6 +68,7 @@ The Playwright MCP server supports the following command-line options:
|
|
|
68
68
|
- Chrome channels: `chrome-beta`, `chrome-canary`, `chrome-dev`
|
|
69
69
|
- Edge channels: `msedge-beta`, `msedge-canary`, `msedge-dev`
|
|
70
70
|
- Default: `chrome`
|
|
71
|
+
- `--caps <caps>`: Comma-separated list of capabilities to enable, possible values: tabs, pdf, history, wait, files, install. Default is all.
|
|
71
72
|
- `--cdp-endpoint <endpoint>`: CDP endpoint to connect to
|
|
72
73
|
- `--executable-path <path>`: Path to the browser executable
|
|
73
74
|
- `--headless`: Run browser in headless mode (headed by default)
|
|
@@ -167,22 +168,7 @@ transport = new SSEServerTransport("/messages", res);
|
|
|
167
168
|
server.connect(transport);
|
|
168
169
|
```
|
|
169
170
|
|
|
170
|
-
### Snapshot
|
|
171
|
-
|
|
172
|
-
The Playwright MCP provides a set of tools for browser automation. Here are all available tools:
|
|
173
|
-
|
|
174
|
-
- **browser_navigate**
|
|
175
|
-
- Description: Navigate to a URL
|
|
176
|
-
- Parameters:
|
|
177
|
-
- `url` (string): The URL to navigate to
|
|
178
|
-
|
|
179
|
-
- **browser_go_back**
|
|
180
|
-
- Description: Go back to the previous page
|
|
181
|
-
- Parameters: None
|
|
182
|
-
|
|
183
|
-
- **browser_go_forward**
|
|
184
|
-
- Description: Go forward to the next page
|
|
185
|
-
- Parameters: None
|
|
171
|
+
### Snapshot-based Interactions
|
|
186
172
|
|
|
187
173
|
- **browser_click**
|
|
188
174
|
- Description: Perform click on a web page
|
|
@@ -210,109 +196,121 @@ The Playwright MCP provides a set of tools for browser automation. Here are all
|
|
|
210
196
|
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
|
211
197
|
- `ref` (string): Exact target element reference from the page snapshot
|
|
212
198
|
- `text` (string): Text to type into the element
|
|
213
|
-
- `submit` (boolean): Whether to submit entered text (press Enter after)
|
|
199
|
+
- `submit` (boolean, optional): Whether to submit entered text (press Enter after)
|
|
200
|
+
- `slowly` (boolean, optional): 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.
|
|
214
201
|
|
|
215
202
|
- **browser_select_option**
|
|
216
|
-
- Description: Select option in a dropdown
|
|
203
|
+
- Description: Select an option in a dropdown
|
|
217
204
|
- Parameters:
|
|
218
205
|
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
|
219
206
|
- `ref` (string): Exact target element reference from the page snapshot
|
|
220
|
-
- `values` (array): Array of values to select in the dropdown.
|
|
207
|
+
- `values` (array): Array of values to select in the dropdown. This can be a single value or multiple values.
|
|
221
208
|
|
|
222
|
-
- **
|
|
223
|
-
- Description:
|
|
209
|
+
- **browser_snapshot**
|
|
210
|
+
- Description: Capture accessibility snapshot of the current page, this is better than screenshot
|
|
211
|
+
- Parameters: None
|
|
212
|
+
|
|
213
|
+
- **browser_take_screenshot**
|
|
214
|
+
- Description: Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions.
|
|
224
215
|
- Parameters:
|
|
225
|
-
- `
|
|
216
|
+
- `raw` (boolean, optional): Whether to return without compression (in PNG format). Default is false, which returns a JPEG image.
|
|
226
217
|
|
|
227
|
-
-
|
|
228
|
-
|
|
218
|
+
### Vision-based Interactions
|
|
219
|
+
|
|
220
|
+
- **browser_screen_move_mouse**
|
|
221
|
+
- Description: Move mouse to a given position
|
|
229
222
|
- Parameters:
|
|
230
|
-
- `
|
|
223
|
+
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
|
224
|
+
- `x` (number): X coordinate
|
|
225
|
+
- `y` (number): Y coordinate
|
|
231
226
|
|
|
232
|
-
- **
|
|
233
|
-
- Description:
|
|
227
|
+
- **browser_screen_capture**
|
|
228
|
+
- Description: Take a screenshot of the current page
|
|
234
229
|
- Parameters: None
|
|
235
230
|
|
|
236
|
-
- **
|
|
237
|
-
- Description:
|
|
238
|
-
- Parameters:
|
|
231
|
+
- **browser_screen_click**
|
|
232
|
+
- Description: Click left mouse button
|
|
233
|
+
- Parameters:
|
|
234
|
+
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
|
235
|
+
- `x` (number): X coordinate
|
|
236
|
+
- `y` (number): Y coordinate
|
|
239
237
|
|
|
240
|
-
- **
|
|
241
|
-
- Description:
|
|
238
|
+
- **browser_screen_drag**
|
|
239
|
+
- Description: Drag left mouse button
|
|
242
240
|
- Parameters:
|
|
243
|
-
- `
|
|
241
|
+
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
|
242
|
+
- `startX` (number): Start X coordinate
|
|
243
|
+
- `startY` (number): Start Y coordinate
|
|
244
|
+
- `endX` (number): End X coordinate
|
|
245
|
+
- `endY` (number): End Y coordinate
|
|
244
246
|
|
|
245
|
-
- **
|
|
246
|
-
- Description:
|
|
247
|
+
- **browser_screen_type**
|
|
248
|
+
- Description: Type text
|
|
247
249
|
- Parameters:
|
|
248
|
-
- `
|
|
250
|
+
- `text` (string): Text to type
|
|
251
|
+
- `submit` (boolean, optional): Whether to submit entered text (press Enter after)
|
|
249
252
|
|
|
250
|
-
- **
|
|
251
|
-
- Description:
|
|
253
|
+
- **browser_press_key**
|
|
254
|
+
- Description: Press a key on the keyboard
|
|
255
|
+
- Parameters:
|
|
256
|
+
- `key` (string): Name of the key to press or a character to generate, such as `ArrowLeft` or `a`
|
|
257
|
+
|
|
258
|
+
### Tab Management
|
|
259
|
+
|
|
260
|
+
- **browser_tab_list**
|
|
261
|
+
- Description: List browser tabs
|
|
252
262
|
- Parameters: None
|
|
253
263
|
|
|
264
|
+
- **browser_tab_new**
|
|
265
|
+
- Description: Open a new tab
|
|
266
|
+
- Parameters:
|
|
267
|
+
- `url` (string, optional): The URL to navigate to in the new tab. If not provided, the new tab will be blank.
|
|
268
|
+
|
|
269
|
+
- **browser_tab_select**
|
|
270
|
+
- Description: Select a tab by index
|
|
271
|
+
- Parameters:
|
|
272
|
+
- `index` (number): The index of the tab to select
|
|
254
273
|
|
|
255
|
-
|
|
274
|
+
- **browser_tab_close**
|
|
275
|
+
- Description: Close a tab
|
|
276
|
+
- Parameters:
|
|
277
|
+
- `index` (number, optional): The index of the tab to close. Closes current tab if not provided.
|
|
256
278
|
|
|
257
|
-
|
|
279
|
+
### Navigation
|
|
258
280
|
|
|
259
281
|
- **browser_navigate**
|
|
260
282
|
- Description: Navigate to a URL
|
|
261
283
|
- Parameters:
|
|
262
284
|
- `url` (string): The URL to navigate to
|
|
263
285
|
|
|
264
|
-
- **
|
|
286
|
+
- **browser_navigate_back**
|
|
265
287
|
- Description: Go back to the previous page
|
|
266
288
|
- Parameters: None
|
|
267
289
|
|
|
268
|
-
- **
|
|
290
|
+
- **browser_navigate_forward**
|
|
269
291
|
- Description: Go forward to the next page
|
|
270
292
|
- Parameters: None
|
|
271
293
|
|
|
272
|
-
|
|
273
|
-
- Description: Capture screenshot of the current page
|
|
274
|
-
- Parameters: None
|
|
275
|
-
|
|
276
|
-
- **browser_move_mouse**
|
|
277
|
-
- Description: Move mouse to specified coordinates
|
|
278
|
-
- Parameters:
|
|
279
|
-
- `x` (number): X coordinate
|
|
280
|
-
- `y` (number): Y coordinate
|
|
281
|
-
|
|
282
|
-
- **browser_click**
|
|
283
|
-
- Description: Click at specified coordinates
|
|
284
|
-
- Parameters:
|
|
285
|
-
- `x` (number): X coordinate to click at
|
|
286
|
-
- `y` (number): Y coordinate to click at
|
|
287
|
-
|
|
288
|
-
- **browser_drag**
|
|
289
|
-
- Description: Perform drag and drop operation
|
|
290
|
-
- Parameters:
|
|
291
|
-
- `startX` (number): Start X coordinate
|
|
292
|
-
- `startY` (number): Start Y coordinate
|
|
293
|
-
- `endX` (number): End X coordinate
|
|
294
|
-
- `endY` (number): End Y coordinate
|
|
295
|
-
|
|
296
|
-
- **browser_type**
|
|
297
|
-
- Description: Type text at specified coordinates
|
|
298
|
-
- Parameters:
|
|
299
|
-
- `text` (string): Text to type
|
|
300
|
-
- `submit` (boolean): Whether to submit entered text (press Enter after)
|
|
294
|
+
### Keyboard
|
|
301
295
|
|
|
302
296
|
- **browser_press_key**
|
|
303
297
|
- Description: Press a key on the keyboard
|
|
304
298
|
- Parameters:
|
|
305
299
|
- `key` (string): Name of the key to press or a character to generate, such as `ArrowLeft` or `a`
|
|
306
300
|
|
|
307
|
-
|
|
301
|
+
### Files and Media
|
|
302
|
+
|
|
303
|
+
- **browser_file_upload**
|
|
308
304
|
- Description: Choose one or multiple files to upload
|
|
309
305
|
- Parameters:
|
|
310
306
|
- `paths` (array): The absolute paths to the files to upload. Can be a single file or multiple files.
|
|
311
307
|
|
|
312
|
-
- **
|
|
308
|
+
- **browser_pdf_save**
|
|
313
309
|
- Description: Save page as PDF
|
|
314
310
|
- Parameters: None
|
|
315
311
|
|
|
312
|
+
### Utilities
|
|
313
|
+
|
|
316
314
|
- **browser_wait**
|
|
317
315
|
- Description: Wait for a specified time in seconds
|
|
318
316
|
- Parameters:
|
|
@@ -321,3 +319,10 @@ Vision Mode provides tools for visual-based interactions using screenshots. Here
|
|
|
321
319
|
- **browser_close**
|
|
322
320
|
- Description: Close the page
|
|
323
321
|
- Parameters: None
|
|
322
|
+
|
|
323
|
+
- **browser_install**
|
|
324
|
+
- Description: Install the browser specified in the config. Call this if you get an error about the browser not being installed.
|
|
325
|
+
- Parameters: None
|
|
326
|
+
|
|
327
|
+
### Vision Mode
|
|
328
|
+
|
package/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
import type { LaunchOptions } from 'playwright';
|
|
19
19
|
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
20
20
|
|
|
21
|
+
type ToolCapability = 'core' | 'tabs' | 'pdf' | 'history' | 'wait' | 'files' | 'install';
|
|
22
|
+
|
|
21
23
|
type Options = {
|
|
22
24
|
/**
|
|
23
25
|
* Path to the user data directory.
|
|
@@ -35,6 +37,11 @@ type Options = {
|
|
|
35
37
|
* @default false
|
|
36
38
|
*/
|
|
37
39
|
vision?: boolean;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Capabilities to enable.
|
|
43
|
+
*/
|
|
44
|
+
capabilities?: ToolCapability[];
|
|
38
45
|
};
|
|
39
46
|
|
|
40
47
|
export function createServer(options?: Options): Server;
|
package/lib/context.js
CHANGED
|
@@ -52,135 +52,241 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
52
52
|
};
|
|
53
53
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
54
|
exports.Context = void 0;
|
|
55
|
-
const child_process_1 = require("child_process");
|
|
56
|
-
const path_1 = __importDefault(require("path"));
|
|
57
55
|
const playwright = __importStar(require("playwright"));
|
|
58
56
|
const yaml_1 = __importDefault(require("yaml"));
|
|
57
|
+
const utils_1 = require("./tools/utils");
|
|
59
58
|
class Context {
|
|
60
|
-
|
|
59
|
+
options;
|
|
61
60
|
_browser;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
_fileChooser;
|
|
66
|
-
_lastSnapshotFrames = [];
|
|
61
|
+
_browserContext;
|
|
62
|
+
_tabs = [];
|
|
63
|
+
_currentTab;
|
|
67
64
|
constructor(options) {
|
|
68
|
-
this.
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
65
|
+
this.options = options;
|
|
66
|
+
}
|
|
67
|
+
tabs() {
|
|
68
|
+
return this._tabs;
|
|
69
|
+
}
|
|
70
|
+
currentTab() {
|
|
71
|
+
if (!this._currentTab)
|
|
72
|
+
throw new Error('Navigate to a location to create a tab');
|
|
73
|
+
return this._currentTab;
|
|
74
|
+
}
|
|
75
|
+
async newTab() {
|
|
76
|
+
const browserContext = await this._ensureBrowserContext();
|
|
77
|
+
const page = await browserContext.newPage();
|
|
78
|
+
this._currentTab = this._tabs.find(t => t.page === page);
|
|
79
|
+
return this._currentTab;
|
|
80
|
+
}
|
|
81
|
+
async selectTab(index) {
|
|
82
|
+
this._currentTab = this._tabs[index - 1];
|
|
83
|
+
await this._currentTab.page.bringToFront();
|
|
84
|
+
}
|
|
85
|
+
async ensureTab() {
|
|
86
|
+
const context = await this._ensureBrowserContext();
|
|
87
|
+
if (!this._currentTab)
|
|
88
|
+
await context.newPage();
|
|
89
|
+
return this._currentTab;
|
|
90
|
+
}
|
|
91
|
+
async listTabs() {
|
|
92
|
+
if (!this._tabs.length)
|
|
93
|
+
return 'No tabs open';
|
|
94
|
+
const lines = ['Open tabs:'];
|
|
95
|
+
for (let i = 0; i < this._tabs.length; i++) {
|
|
96
|
+
const tab = this._tabs[i];
|
|
97
|
+
const title = await tab.page.title();
|
|
98
|
+
const url = tab.page.url();
|
|
99
|
+
const current = tab === this._currentTab ? ' (current)' : '';
|
|
100
|
+
lines.push(`- ${i + 1}:${current} [${title}] (${url})`);
|
|
101
|
+
}
|
|
102
|
+
return lines.join('\n');
|
|
103
|
+
}
|
|
104
|
+
async closeTab(index) {
|
|
105
|
+
const tab = index === undefined ? this.currentTab() : this._tabs[index - 1];
|
|
106
|
+
await tab.page.close();
|
|
107
|
+
return await this.listTabs();
|
|
108
|
+
}
|
|
109
|
+
_onPageCreated(page) {
|
|
110
|
+
const tab = new Tab(this, page, tab => this._onPageClosed(tab));
|
|
111
|
+
this._tabs.push(tab);
|
|
112
|
+
if (!this._currentTab)
|
|
113
|
+
this._currentTab = tab;
|
|
114
|
+
}
|
|
115
|
+
_onPageClosed(tab) {
|
|
116
|
+
const index = this._tabs.indexOf(tab);
|
|
117
|
+
if (index === -1)
|
|
118
|
+
return;
|
|
119
|
+
this._tabs.splice(index, 1);
|
|
120
|
+
if (this._currentTab === tab)
|
|
121
|
+
this._currentTab = this._tabs[Math.min(index, this._tabs.length - 1)];
|
|
91
122
|
const browser = this._browser;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
123
|
+
if (this._browserContext && !this._tabs.length) {
|
|
124
|
+
void this._browserContext.close().then(() => browser?.close()).catch(() => { });
|
|
125
|
+
this._browser = undefined;
|
|
126
|
+
this._browserContext = undefined;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async close() {
|
|
130
|
+
if (!this._browserContext)
|
|
131
|
+
return;
|
|
132
|
+
await this._browserContext.close();
|
|
133
|
+
}
|
|
134
|
+
async _ensureBrowserContext() {
|
|
135
|
+
if (!this._browserContext) {
|
|
136
|
+
const context = await this._createBrowserContext();
|
|
137
|
+
this._browser = context.browser;
|
|
138
|
+
this._browserContext = context.browserContext;
|
|
139
|
+
for (const page of this._browserContext.pages())
|
|
140
|
+
this._onPageCreated(page);
|
|
141
|
+
this._browserContext.on('page', page => this._onPageCreated(page));
|
|
142
|
+
}
|
|
143
|
+
return this._browserContext;
|
|
144
|
+
}
|
|
145
|
+
async _createBrowserContext() {
|
|
146
|
+
if (this.options.remoteEndpoint) {
|
|
147
|
+
const url = new URL(this.options.remoteEndpoint);
|
|
148
|
+
if (this.options.browserName)
|
|
149
|
+
url.searchParams.set('browser', this.options.browserName);
|
|
150
|
+
if (this.options.launchOptions)
|
|
151
|
+
url.searchParams.set('launch-options', JSON.stringify(this.options.launchOptions));
|
|
152
|
+
const browser = await playwright[this.options.browserName ?? 'chromium'].connect(String(url));
|
|
153
|
+
const browserContext = await browser.newContext();
|
|
154
|
+
return { browser, browserContext };
|
|
155
|
+
}
|
|
156
|
+
if (this.options.cdpEndpoint) {
|
|
157
|
+
const browser = await playwright.chromium.connectOverCDP(this.options.cdpEndpoint);
|
|
158
|
+
const browserContext = browser.contexts()[0];
|
|
159
|
+
return { browser, browserContext };
|
|
160
|
+
}
|
|
161
|
+
const browserContext = await this._launchPersistentContext();
|
|
162
|
+
return { browserContext };
|
|
163
|
+
}
|
|
164
|
+
async _launchPersistentContext() {
|
|
165
|
+
try {
|
|
166
|
+
const browserType = this.options.browserName ? playwright[this.options.browserName] : playwright.chromium;
|
|
167
|
+
return await browserType.launchPersistentContext(this.options.userDataDir, this.options.launchOptions);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
if (error.message.includes('Executable doesn\'t exist'))
|
|
171
|
+
throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
exports.Context = Context;
|
|
177
|
+
class Tab {
|
|
178
|
+
context;
|
|
179
|
+
page;
|
|
180
|
+
_console = [];
|
|
181
|
+
_fileChooser;
|
|
182
|
+
_snapshot;
|
|
183
|
+
_onPageClose;
|
|
184
|
+
constructor(context, page, onPageClose) {
|
|
185
|
+
this.context = context;
|
|
186
|
+
this.page = page;
|
|
187
|
+
this._onPageClose = onPageClose;
|
|
188
|
+
page.on('console', event => this._console.push(event));
|
|
189
|
+
page.on('framenavigated', frame => {
|
|
190
|
+
if (!frame.parentFrame())
|
|
191
|
+
this._console.length = 0;
|
|
192
|
+
});
|
|
193
|
+
page.on('close', () => this._onClose());
|
|
194
|
+
page.on('filechooser', chooser => this._fileChooser = chooser);
|
|
195
|
+
page.setDefaultNavigationTimeout(60000);
|
|
196
|
+
page.setDefaultTimeout(5000);
|
|
197
|
+
}
|
|
198
|
+
_onClose() {
|
|
97
199
|
this._fileChooser = undefined;
|
|
98
200
|
this._console.length = 0;
|
|
201
|
+
this._onPageClose(this);
|
|
202
|
+
}
|
|
203
|
+
async navigate(url) {
|
|
204
|
+
await this.page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
205
|
+
// Cap load event to 5 seconds, the page is operational at this point.
|
|
206
|
+
await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => { });
|
|
99
207
|
}
|
|
100
|
-
async
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
208
|
+
async run(callback, options) {
|
|
209
|
+
try {
|
|
210
|
+
if (!options?.noClearFileChooser)
|
|
211
|
+
this._fileChooser = undefined;
|
|
212
|
+
if (options?.waitForCompletion)
|
|
213
|
+
await (0, utils_1.waitForCompletion)(this.page, () => callback(this));
|
|
214
|
+
else
|
|
215
|
+
await callback(this);
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
if (options?.captureSnapshot)
|
|
219
|
+
this._snapshot = await PageSnapshot.create(this.page);
|
|
220
|
+
}
|
|
221
|
+
const tabList = this.context.tabs().length > 1 ? await this.context.listTabs() + '\n\nCurrent tab:' + '\n' : '';
|
|
222
|
+
const snapshot = this._snapshot?.text({ status: options?.status, hasFileChooser: !!this._fileChooser }) ?? options?.status ?? '';
|
|
223
|
+
return {
|
|
224
|
+
content: [{
|
|
225
|
+
type: 'text',
|
|
226
|
+
text: tabList + snapshot,
|
|
227
|
+
}],
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
async runAndWait(callback, options) {
|
|
231
|
+
return await this.run(callback, {
|
|
232
|
+
waitForCompletion: true,
|
|
233
|
+
...options,
|
|
105
234
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
resolve(channel);
|
|
113
|
-
else
|
|
114
|
-
reject(new Error(`Failed to install browser: ${output.join('')}`));
|
|
115
|
-
});
|
|
235
|
+
}
|
|
236
|
+
async runAndWaitWithSnapshot(callback, options) {
|
|
237
|
+
return await this.run(callback, {
|
|
238
|
+
captureSnapshot: true,
|
|
239
|
+
waitForCompletion: true,
|
|
240
|
+
...options,
|
|
116
241
|
});
|
|
117
242
|
}
|
|
118
|
-
|
|
119
|
-
if (!this.
|
|
120
|
-
throw new Error('
|
|
121
|
-
return this.
|
|
243
|
+
lastSnapshot() {
|
|
244
|
+
if (!this._snapshot)
|
|
245
|
+
throw new Error('No snapshot available');
|
|
246
|
+
return this._snapshot;
|
|
122
247
|
}
|
|
123
248
|
async console() {
|
|
124
249
|
return this._console;
|
|
125
250
|
}
|
|
126
|
-
async close() {
|
|
127
|
-
if (!this._page)
|
|
128
|
-
return;
|
|
129
|
-
await this._page.close();
|
|
130
|
-
}
|
|
131
251
|
async submitFileChooser(paths) {
|
|
132
252
|
if (!this._fileChooser)
|
|
133
253
|
throw new Error('No file chooser visible');
|
|
134
254
|
await this._fileChooser.setFiles(paths);
|
|
135
255
|
this._fileChooser = undefined;
|
|
136
256
|
}
|
|
137
|
-
|
|
138
|
-
|
|
257
|
+
}
|
|
258
|
+
class PageSnapshot {
|
|
259
|
+
_frameLocators = [];
|
|
260
|
+
_text;
|
|
261
|
+
constructor() {
|
|
139
262
|
}
|
|
140
|
-
|
|
141
|
-
|
|
263
|
+
static async create(page) {
|
|
264
|
+
const snapshot = new PageSnapshot();
|
|
265
|
+
await snapshot._build(page);
|
|
266
|
+
return snapshot;
|
|
142
267
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (this._options.launchOptions)
|
|
149
|
-
url.searchParams.set('launch-options', JSON.stringify(this._options.launchOptions));
|
|
150
|
-
const browser = await playwright[this._options.browserName ?? 'chromium'].connect(String(url));
|
|
151
|
-
const page = await browser.newPage();
|
|
152
|
-
return { browser, page };
|
|
268
|
+
text(options) {
|
|
269
|
+
const results = [];
|
|
270
|
+
if (options?.status) {
|
|
271
|
+
results.push(options.status);
|
|
272
|
+
results.push('');
|
|
153
273
|
}
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
let [page] = browserContext.pages();
|
|
158
|
-
if (!page)
|
|
159
|
-
page = await browserContext.newPage();
|
|
160
|
-
return { browser, page };
|
|
274
|
+
if (options?.hasFileChooser) {
|
|
275
|
+
results.push('- There is a file chooser visible that requires browser_file_upload to be called');
|
|
276
|
+
results.push('');
|
|
161
277
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return { page };
|
|
278
|
+
results.push(this._text);
|
|
279
|
+
return results.join('\n');
|
|
165
280
|
}
|
|
166
|
-
async
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (error.message.includes('Executable doesn\'t exist'))
|
|
173
|
-
throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
|
|
174
|
-
throw error;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
async allFramesSnapshot() {
|
|
178
|
-
this._lastSnapshotFrames = [];
|
|
179
|
-
const yaml = await this._allFramesSnapshot(this.existingPage());
|
|
180
|
-
return yaml.toString().trim();
|
|
281
|
+
async _build(page) {
|
|
282
|
+
const yamlDocument = await this._snapshotFrame(page);
|
|
283
|
+
const lines = [];
|
|
284
|
+
lines.push(`- Page URL: ${page.url()}`, `- Page Title: ${await page.title()}`);
|
|
285
|
+
lines.push(`- Page Snapshot`, '```yaml', yamlDocument.toString().trim(), '```', '');
|
|
286
|
+
this._text = lines.join('\n');
|
|
181
287
|
}
|
|
182
|
-
async
|
|
183
|
-
const frameIndex = this.
|
|
288
|
+
async _snapshotFrame(frame) {
|
|
289
|
+
const frameIndex = this._frameLocators.push(frame) - 1;
|
|
184
290
|
const snapshotString = await frame.locator('body').ariaSnapshot({ ref: true });
|
|
185
291
|
const snapshot = yaml_1.default.parseDocument(snapshotString);
|
|
186
292
|
const visit = async (node) => {
|
|
@@ -202,7 +308,7 @@ class Context {
|
|
|
202
308
|
const ref = value.match(/\[ref=(.*)\]/)?.[1];
|
|
203
309
|
if (ref) {
|
|
204
310
|
try {
|
|
205
|
-
const childSnapshot = await this.
|
|
311
|
+
const childSnapshot = await this._snapshotFrame(frame.frameLocator(`aria-ref=${ref}`));
|
|
206
312
|
return snapshot.createPair(node.value, childSnapshot);
|
|
207
313
|
}
|
|
208
314
|
catch (error) {
|
|
@@ -218,11 +324,11 @@ class Context {
|
|
|
218
324
|
return snapshot;
|
|
219
325
|
}
|
|
220
326
|
refLocator(ref) {
|
|
221
|
-
let frame = this.
|
|
327
|
+
let frame = this._frameLocators[0];
|
|
222
328
|
const match = ref.match(/^f(\d+)(.*)/);
|
|
223
329
|
if (match) {
|
|
224
330
|
const frameIndex = parseInt(match[1], 10);
|
|
225
|
-
frame = this.
|
|
331
|
+
frame = this._frameLocators[frameIndex];
|
|
226
332
|
ref = match[2];
|
|
227
333
|
}
|
|
228
334
|
if (!frame)
|
|
@@ -230,4 +336,3 @@ class Context {
|
|
|
230
336
|
return frame.locator(`aria-ref=${ref}`);
|
|
231
337
|
}
|
|
232
338
|
}
|
|
233
|
-
exports.Context = Context;
|