@playwright/mcp 0.0.9 → 0.0.13

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 CHANGED
@@ -43,7 +43,7 @@ const urlForWebsites = `vscode:mcp/install?${encodeURIComponent(config)}`;
43
43
  const urlForGithub = `https://insiders.vscode.dev/redirect?url=${encodeURIComponent(urlForWebsites)}`;
44
44
  -->
45
45
 
46
- [<img alt="Install in VS Code Insiders" src="https://img.shields.io/badge/VS_Code_Insiders-VS_Code_Insiders?style=flat-square&label=Install%20Server&color=24bfa5">](https://insiders.vscode.dev/redirect?url=vscode-insiders%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522playwright%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522%2540playwright%252Fmcp%2540latest%2522%255D%257D)
46
+ [<img src="https://img.shields.io/badge/VS_Code-VS_Code?style=flat-square&label=Install%20Server&color=0098FF" alt="Install in VS Code">](https://insiders.vscode.dev/redirect?url=vscode%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522playwright%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522%2540playwright%252Fmcp%2540latest%2522%255D%257D) [<img alt="Install in VS Code Insiders" src="https://img.shields.io/badge/VS_Code_Insiders-VS_Code_Insiders?style=flat-square&label=Install%20Server&color=24bfa5">](https://insiders.vscode.dev/redirect?url=vscode-insiders%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522playwright%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522%2540playwright%252Fmcp%2540latest%2522%255D%257D)
47
47
 
48
48
  Alternatively, you can install the Playwright MCP server using the VS Code CLI:
49
49
 
@@ -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 Mode
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
- - **browser_choose_file**
223
- - Description: Choose one or multiple files to upload
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
- - `paths` (array): The absolute paths to the files to upload. Can be a single file or multiple files.
216
+ - `raw` (boolean, optional): Whether to return without compression (in PNG format). Default is false, which returns a JPEG image.
226
217
 
227
- - **browser_press_key**
228
- - Description: Press a key on the keyboard
218
+ ### Vision-based Interactions
219
+
220
+ - **browser_screen_move_mouse**
221
+ - Description: Move mouse to a given position
229
222
  - Parameters:
230
- - `key` (string): Name of the key to press or a character to generate, such as `ArrowLeft` or `a`
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
- - **browser_snapshot**
233
- - Description: Capture accessibility snapshot of the current page (better than screenshot)
227
+ - **browser_screen_capture**
228
+ - Description: Take a screenshot of the current page
234
229
  - Parameters: None
235
230
 
236
- - **browser_save_as_pdf**
237
- - Description: Save page as PDF
238
- - Parameters: None
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
- - **browser_take_screenshot**
241
- - Description: Capture screenshot of the page
238
+ - **browser_screen_drag**
239
+ - Description: Drag left mouse button
242
240
  - Parameters:
243
- - `raw` (string): Optionally returns lossless PNG screenshot. JPEG by default.
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
- - **browser_wait**
246
- - Description: Wait for a specified time in seconds
247
+ - **browser_screen_type**
248
+ - Description: Type text
247
249
  - Parameters:
248
- - `time` (number): The time to wait in seconds (capped at 10 seconds)
250
+ - `text` (string): Text to type
251
+ - `submit` (boolean, optional): Whether to submit entered text (press Enter after)
249
252
 
250
- - **browser_close**
251
- - Description: Close the page
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.
254
268
 
255
- ### Vision Mode
269
+ - **browser_tab_select**
270
+ - Description: Select a tab by index
271
+ - Parameters:
272
+ - `index` (number): The index of the tab to select
273
+
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
- Vision Mode provides tools for visual-based interactions using screenshots. Here are all available tools:
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
- - **browser_go_back**
286
+ - **browser_navigate_back**
265
287
  - Description: Go back to the previous page
266
288
  - Parameters: None
267
289
 
268
- - **browser_go_forward**
290
+ - **browser_navigate_forward**
269
291
  - Description: Go forward to the next page
270
292
  - Parameters: None
271
293
 
272
- - **browser_screenshot**
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
- - **browser_choose_file**
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
- - **browser_save_as_pdf**
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,7 @@ 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
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,121 +52,120 @@ 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"));
55
+ exports.generateLocator = generateLocator;
57
56
  const playwright = __importStar(require("playwright"));
58
57
  const yaml_1 = __importDefault(require("yaml"));
58
+ const utils_1 = require("./tools/utils");
59
59
  class Context {
60
- _options;
60
+ options;
61
61
  _browser;
62
- _page;
63
- _console = [];
64
- _createPagePromise;
65
- _fileChooser;
66
- _lastSnapshotFrames = [];
62
+ _browserContext;
63
+ _tabs = [];
64
+ _currentTab;
67
65
  constructor(options) {
68
- this._options = options;
69
- }
70
- async createPage() {
71
- if (this._createPagePromise)
72
- return this._createPagePromise;
73
- this._createPagePromise = (async () => {
74
- const { browser, page } = await this._createPage();
75
- page.on('console', event => this._console.push(event));
76
- page.on('framenavigated', frame => {
77
- if (!frame.parentFrame())
78
- this._console.length = 0;
79
- });
80
- page.on('close', () => this._onPageClose());
81
- page.on('filechooser', chooser => this._fileChooser = chooser);
82
- page.setDefaultNavigationTimeout(60000);
83
- page.setDefaultTimeout(5000);
84
- this._page = page;
85
- this._browser = browser;
86
- return page;
87
- })();
88
- return this._createPagePromise;
89
- }
90
- _onPageClose() {
91
- const browser = this._browser;
92
- const page = this._page;
93
- void page?.context()?.close().then(() => browser?.close()).catch(() => { });
94
- this._createPagePromise = undefined;
95
- this._browser = undefined;
96
- this._page = undefined;
97
- this._fileChooser = undefined;
98
- this._console.length = 0;
66
+ this.options = options;
99
67
  }
100
- async install() {
101
- const channel = this._options.launchOptions?.channel ?? this._options.browserName ?? 'chrome';
102
- const cli = path_1.default.join(require.resolve('playwright/package.json'), '..', 'cli.js');
103
- const child = (0, child_process_1.fork)(cli, ['install', channel], {
104
- stdio: 'pipe',
105
- });
106
- const output = [];
107
- child.stdout?.on('data', data => output.push(data.toString()));
108
- child.stderr?.on('data', data => output.push(data.toString()));
109
- return new Promise((resolve, reject) => {
110
- child.on('close', code => {
111
- if (code === 0)
112
- resolve(channel);
113
- else
114
- reject(new Error(`Failed to install browser: ${output.join('')}`));
115
- });
116
- });
68
+ tabs() {
69
+ return this._tabs;
117
70
  }
118
- existingPage() {
119
- if (!this._page)
120
- throw new Error('Navigate to a location to create a page');
121
- return this._page;
71
+ currentTab() {
72
+ if (!this._currentTab)
73
+ throw new Error('No current snapshot available. Capture a snapshot of navigate to a new location first.');
74
+ return this._currentTab;
122
75
  }
123
- async console() {
124
- return this._console;
76
+ async newTab() {
77
+ const browserContext = await this._ensureBrowserContext();
78
+ const page = await browserContext.newPage();
79
+ this._currentTab = this._tabs.find(t => t.page === page);
80
+ return this._currentTab;
125
81
  }
126
- async close() {
127
- if (!this._page)
128
- return;
129
- await this._page.close();
82
+ async selectTab(index) {
83
+ this._currentTab = this._tabs[index - 1];
84
+ await this._currentTab.page.bringToFront();
130
85
  }
131
- async submitFileChooser(paths) {
132
- if (!this._fileChooser)
133
- throw new Error('No file chooser visible');
134
- await this._fileChooser.setFiles(paths);
135
- this._fileChooser = undefined;
86
+ async ensureTab() {
87
+ const context = await this._ensureBrowserContext();
88
+ if (!this._currentTab)
89
+ await context.newPage();
90
+ return this._currentTab;
136
91
  }
137
- hasFileChooser() {
138
- return !!this._fileChooser;
92
+ async listTabs() {
93
+ if (!this._tabs.length)
94
+ return '### No tabs open';
95
+ const lines = ['### Open tabs'];
96
+ for (let i = 0; i < this._tabs.length; i++) {
97
+ const tab = this._tabs[i];
98
+ const title = await tab.page.title();
99
+ const url = tab.page.url();
100
+ const current = tab === this._currentTab ? ' (current)' : '';
101
+ lines.push(`- ${i + 1}:${current} [${title}] (${url})`);
102
+ }
103
+ return lines.join('\n');
139
104
  }
140
- clearFileChooser() {
141
- this._fileChooser = undefined;
105
+ async closeTab(index) {
106
+ const tab = index === undefined ? this.currentTab() : this._tabs[index - 1];
107
+ await tab.page.close();
108
+ return await this.listTabs();
109
+ }
110
+ _onPageCreated(page) {
111
+ const tab = new Tab(this, page, tab => this._onPageClosed(tab));
112
+ this._tabs.push(tab);
113
+ if (!this._currentTab)
114
+ this._currentTab = tab;
115
+ }
116
+ _onPageClosed(tab) {
117
+ const index = this._tabs.indexOf(tab);
118
+ if (index === -1)
119
+ return;
120
+ this._tabs.splice(index, 1);
121
+ if (this._currentTab === tab)
122
+ this._currentTab = this._tabs[Math.min(index, this._tabs.length - 1)];
123
+ const browser = this._browser;
124
+ if (this._browserContext && !this._tabs.length) {
125
+ void this._browserContext.close().then(() => browser?.close()).catch(() => { });
126
+ this._browser = undefined;
127
+ this._browserContext = undefined;
128
+ }
129
+ }
130
+ async close() {
131
+ if (!this._browserContext)
132
+ return;
133
+ await this._browserContext.close();
134
+ }
135
+ async _ensureBrowserContext() {
136
+ if (!this._browserContext) {
137
+ const context = await this._createBrowserContext();
138
+ this._browser = context.browser;
139
+ this._browserContext = context.browserContext;
140
+ for (const page of this._browserContext.pages())
141
+ this._onPageCreated(page);
142
+ this._browserContext.on('page', page => this._onPageCreated(page));
143
+ }
144
+ return this._browserContext;
142
145
  }
143
- async _createPage() {
144
- if (this._options.remoteEndpoint) {
145
- const url = new URL(this._options.remoteEndpoint);
146
- if (this._options.browserName)
147
- url.searchParams.set('browser', this._options.browserName);
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 };
146
+ async _createBrowserContext() {
147
+ if (this.options.remoteEndpoint) {
148
+ const url = new URL(this.options.remoteEndpoint);
149
+ if (this.options.browserName)
150
+ url.searchParams.set('browser', this.options.browserName);
151
+ if (this.options.launchOptions)
152
+ url.searchParams.set('launch-options', JSON.stringify(this.options.launchOptions));
153
+ const browser = await playwright[this.options.browserName ?? 'chromium'].connect(String(url));
154
+ const browserContext = await browser.newContext();
155
+ return { browser, browserContext };
153
156
  }
154
- if (this._options.cdpEndpoint) {
155
- const browser = await playwright.chromium.connectOverCDP(this._options.cdpEndpoint);
157
+ if (this.options.cdpEndpoint) {
158
+ const browser = await playwright.chromium.connectOverCDP(this.options.cdpEndpoint);
156
159
  const browserContext = browser.contexts()[0];
157
- let [page] = browserContext.pages();
158
- if (!page)
159
- page = await browserContext.newPage();
160
- return { browser, page };
160
+ return { browser, browserContext };
161
161
  }
162
- const context = await this._launchPersistentContext();
163
- const [page] = context.pages();
164
- return { page };
162
+ const browserContext = await this._launchPersistentContext();
163
+ return { browserContext };
165
164
  }
166
165
  async _launchPersistentContext() {
167
166
  try {
168
- const browserType = this._options.browserName ? playwright[this._options.browserName] : playwright.chromium;
169
- return await browserType.launchPersistentContext(this._options.userDataDir, this._options.launchOptions);
167
+ const browserType = this.options.browserName ? playwright[this.options.browserName] : playwright.chromium;
168
+ return await browserType.launchPersistentContext(this.options.userDataDir, this.options.launchOptions);
170
169
  }
171
170
  catch (error) {
172
171
  if (error.message.includes('Executable doesn\'t exist'))
@@ -174,13 +173,129 @@ class Context {
174
173
  throw error;
175
174
  }
176
175
  }
177
- async allFramesSnapshot() {
178
- this._lastSnapshotFrames = [];
179
- const yaml = await this._allFramesSnapshot(this.existingPage());
180
- return yaml.toString().trim();
176
+ }
177
+ exports.Context = Context;
178
+ class Tab {
179
+ context;
180
+ page;
181
+ _console = [];
182
+ _fileChooser;
183
+ _snapshot;
184
+ _onPageClose;
185
+ constructor(context, page, onPageClose) {
186
+ this.context = context;
187
+ this.page = page;
188
+ this._onPageClose = onPageClose;
189
+ page.on('console', event => this._console.push(event));
190
+ page.on('framenavigated', frame => {
191
+ if (!frame.parentFrame())
192
+ this._console.length = 0;
193
+ });
194
+ page.on('close', () => this._onClose());
195
+ page.on('filechooser', chooser => this._fileChooser = chooser);
196
+ page.setDefaultNavigationTimeout(60000);
197
+ page.setDefaultTimeout(5000);
198
+ }
199
+ _onClose() {
200
+ this._fileChooser = undefined;
201
+ this._console.length = 0;
202
+ this._onPageClose(this);
203
+ }
204
+ async navigate(url) {
205
+ await this.page.goto(url, { waitUntil: 'domcontentloaded' });
206
+ // Cap load event to 5 seconds, the page is operational at this point.
207
+ await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => { });
208
+ }
209
+ async run(callback, options) {
210
+ let runResult;
211
+ try {
212
+ if (!options?.noClearFileChooser)
213
+ this._fileChooser = undefined;
214
+ if (options?.waitForCompletion)
215
+ runResult = await (0, utils_1.waitForCompletion)(this.page, () => callback(this)) ?? undefined;
216
+ else
217
+ runResult = await callback(this) ?? undefined;
218
+ }
219
+ finally {
220
+ if (options?.captureSnapshot)
221
+ this._snapshot = await PageSnapshot.create(this.page);
222
+ }
223
+ const result = [];
224
+ result.push(`- Ran code:
225
+ \`\`\`js
226
+ ${runResult.code.join('\n')}
227
+ \`\`\`
228
+ `);
229
+ if (this.context.tabs().length > 1)
230
+ result.push(await this.context.listTabs(), '');
231
+ if (this._snapshot) {
232
+ if (this.context.tabs().length > 1)
233
+ result.push('### Current tab');
234
+ result.push(this._snapshot.text({ hasFileChooser: !!this._fileChooser }));
235
+ }
236
+ return {
237
+ content: [{
238
+ type: 'text',
239
+ text: result.join('\n'),
240
+ }],
241
+ };
242
+ }
243
+ async runAndWait(callback, options) {
244
+ return await this.run(callback, {
245
+ waitForCompletion: true,
246
+ ...options,
247
+ });
248
+ }
249
+ async runAndWaitWithSnapshot(callback, options) {
250
+ return await this.run(tab => callback(tab.lastSnapshot()), {
251
+ captureSnapshot: true,
252
+ waitForCompletion: true,
253
+ ...options,
254
+ });
255
+ }
256
+ lastSnapshot() {
257
+ if (!this._snapshot)
258
+ throw new Error('No snapshot available');
259
+ return this._snapshot;
181
260
  }
182
- async _allFramesSnapshot(frame) {
183
- const frameIndex = this._lastSnapshotFrames.push(frame) - 1;
261
+ async console() {
262
+ return this._console;
263
+ }
264
+ async submitFileChooser(paths) {
265
+ if (!this._fileChooser)
266
+ throw new Error('No file chooser visible');
267
+ await this._fileChooser.setFiles(paths);
268
+ this._fileChooser = undefined;
269
+ }
270
+ }
271
+ class PageSnapshot {
272
+ _frameLocators = [];
273
+ _text;
274
+ constructor() {
275
+ }
276
+ static async create(page) {
277
+ const snapshot = new PageSnapshot();
278
+ await snapshot._build(page);
279
+ return snapshot;
280
+ }
281
+ text(options) {
282
+ const results = [];
283
+ if (options.hasFileChooser) {
284
+ results.push('- There is a file chooser visible that requires browser_file_upload to be called');
285
+ results.push('');
286
+ }
287
+ results.push(this._text);
288
+ return results.join('\n');
289
+ }
290
+ async _build(page) {
291
+ const yamlDocument = await this._snapshotFrame(page);
292
+ const lines = [];
293
+ lines.push(`- Page URL: ${page.url()}`, `- Page Title: ${await page.title()}`);
294
+ lines.push(`- Page Snapshot`, '```yaml', yamlDocument.toString().trim(), '```', '');
295
+ this._text = lines.join('\n');
296
+ }
297
+ async _snapshotFrame(frame) {
298
+ const frameIndex = this._frameLocators.push(frame) - 1;
184
299
  const snapshotString = await frame.locator('body').ariaSnapshot({ ref: true });
185
300
  const snapshot = yaml_1.default.parseDocument(snapshotString);
186
301
  const visit = async (node) => {
@@ -202,7 +317,7 @@ class Context {
202
317
  const ref = value.match(/\[ref=(.*)\]/)?.[1];
203
318
  if (ref) {
204
319
  try {
205
- const childSnapshot = await this._allFramesSnapshot(frame.frameLocator(`aria-ref=${ref}`));
320
+ const childSnapshot = await this._snapshotFrame(frame.frameLocator(`aria-ref=${ref}`));
206
321
  return snapshot.createPair(node.value, childSnapshot);
207
322
  }
208
323
  catch (error) {
@@ -218,11 +333,11 @@ class Context {
218
333
  return snapshot;
219
334
  }
220
335
  refLocator(ref) {
221
- let frame = this._lastSnapshotFrames[0];
336
+ let frame = this._frameLocators[0];
222
337
  const match = ref.match(/^f(\d+)(.*)/);
223
338
  if (match) {
224
339
  const frameIndex = parseInt(match[1], 10);
225
- frame = this._lastSnapshotFrames[frameIndex];
340
+ frame = this._frameLocators[frameIndex];
226
341
  ref = match[2];
227
342
  }
228
343
  if (!frame)
@@ -230,4 +345,6 @@ class Context {
230
345
  return frame.locator(`aria-ref=${ref}`);
231
346
  }
232
347
  }
233
- exports.Context = Context;
348
+ async function generateLocator(locator) {
349
+ return locator._generateLocatorString();
350
+ }