@playwright/mcp 0.0.7 → 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 CHANGED
@@ -59,9 +59,26 @@ code-insiders --add-mcp '{"name":"playwright","command":"npx","args":["@playwrig
59
59
 
60
60
  After installation, the Playwright MCP server will be available for use with your GitHub Copilot agent in VS Code.
61
61
 
62
+ ### CLI Options
63
+
64
+ The Playwright MCP server supports the following command-line options:
65
+
66
+ - `--browser <browser>`: Browser or chrome channel to use. Possible values:
67
+ - `chrome`, `firefox`, `webkit`, `msedge`
68
+ - Chrome channels: `chrome-beta`, `chrome-canary`, `chrome-dev`
69
+ - Edge channels: `msedge-beta`, `msedge-canary`, `msedge-dev`
70
+ - Default: `chrome`
71
+ - `--caps <caps>`: Comma-separated list of capabilities to enable, possible values: tabs, pdf, history, wait, files, install. Default is all.
72
+ - `--cdp-endpoint <endpoint>`: CDP endpoint to connect to
73
+ - `--executable-path <path>`: Path to the browser executable
74
+ - `--headless`: Run browser in headless mode (headed by default)
75
+ - `--port <port>`: Port to listen on for SSE transport
76
+ - `--user-data-dir <path>`: Path to the user data directory
77
+ - `--vision`: Run server that uses screenshots (Aria snapshots are used by default)
78
+
62
79
  ### User data directory
63
80
 
64
- Playwright MCP will launch Chrome browser with the new profile, located at
81
+ Playwright MCP will launch the browser with the new profile, located at
65
82
 
66
83
  ```
67
84
  - `%USERPROFILE%\AppData\Local\ms-playwright\mcp-chrome-profile` on Windows
@@ -69,7 +86,7 @@ Playwright MCP will launch Chrome browser with the new profile, located at
69
86
  - `~/.cache/ms-playwright/mcp-chrome-profile` on Linux
70
87
  ```
71
88
 
72
- All the logged in information will be stored in that profile, you can delete it between sessions if you'dlike to clear the offline state.
89
+ All the logged in information will be stored in that profile, you can delete it between sessions if you'd like to clear the offline state.
73
90
 
74
91
 
75
92
  ### Running headless browser (Browser without GUI).
@@ -151,22 +168,7 @@ transport = new SSEServerTransport("/messages", res);
151
168
  server.connect(transport);
152
169
  ```
153
170
 
154
- ### Snapshot Mode
155
-
156
- The Playwright MCP provides a set of tools for browser automation. Here are all available tools:
157
-
158
- - **browser_navigate**
159
- - Description: Navigate to a URL
160
- - Parameters:
161
- - `url` (string): The URL to navigate to
162
-
163
- - **browser_go_back**
164
- - Description: Go back to the previous page
165
- - Parameters: None
166
-
167
- - **browser_go_forward**
168
- - Description: Go forward to the next page
169
- - Parameters: None
171
+ ### Snapshot-based Interactions
170
172
 
171
173
  - **browser_click**
172
174
  - Description: Perform click on a web page
@@ -194,109 +196,121 @@ The Playwright MCP provides a set of tools for browser automation. Here are all
194
196
  - `element` (string): Human-readable element description used to obtain permission to interact with the element
195
197
  - `ref` (string): Exact target element reference from the page snapshot
196
198
  - `text` (string): Text to type into the element
197
- - `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.
198
201
 
199
202
  - **browser_select_option**
200
- - Description: Select option in a dropdown
203
+ - Description: Select an option in a dropdown
201
204
  - Parameters:
202
205
  - `element` (string): Human-readable element description used to obtain permission to interact with the element
203
206
  - `ref` (string): Exact target element reference from the page snapshot
204
- - `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.
205
208
 
206
- - **browser_choose_file**
207
- - 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.
208
215
  - Parameters:
209
- - `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.
210
217
 
211
- - **browser_press_key**
212
- - 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
213
222
  - Parameters:
214
- - `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
215
226
 
216
- - **browser_snapshot**
217
- - Description: Capture accessibility snapshot of the current page (better than screenshot)
227
+ - **browser_screen_capture**
228
+ - Description: Take a screenshot of the current page
218
229
  - Parameters: None
219
230
 
220
- - **browser_save_as_pdf**
221
- - Description: Save page as PDF
222
- - 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
223
237
 
224
- - **browser_take_screenshot**
225
- - Description: Capture screenshot of the page
238
+ - **browser_screen_drag**
239
+ - Description: Drag left mouse button
226
240
  - Parameters:
227
- - `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
228
246
 
229
- - **browser_wait**
230
- - Description: Wait for a specified time in seconds
247
+ - **browser_screen_type**
248
+ - Description: Type text
231
249
  - Parameters:
232
- - `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)
233
252
 
234
- - **browser_close**
235
- - 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
236
262
  - Parameters: None
237
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.
238
268
 
239
- ### 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
240
273
 
241
- Vision Mode provides tools for visual-based interactions using screenshots. Here are all available tools:
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.
278
+
279
+ ### Navigation
242
280
 
243
281
  - **browser_navigate**
244
282
  - Description: Navigate to a URL
245
283
  - Parameters:
246
284
  - `url` (string): The URL to navigate to
247
285
 
248
- - **browser_go_back**
286
+ - **browser_navigate_back**
249
287
  - Description: Go back to the previous page
250
288
  - Parameters: None
251
289
 
252
- - **browser_go_forward**
290
+ - **browser_navigate_forward**
253
291
  - Description: Go forward to the next page
254
292
  - Parameters: None
255
293
 
256
- - **browser_screenshot**
257
- - Description: Capture screenshot of the current page
258
- - Parameters: None
259
-
260
- - **browser_move_mouse**
261
- - Description: Move mouse to specified coordinates
262
- - Parameters:
263
- - `x` (number): X coordinate
264
- - `y` (number): Y coordinate
265
-
266
- - **browser_click**
267
- - Description: Click at specified coordinates
268
- - Parameters:
269
- - `x` (number): X coordinate to click at
270
- - `y` (number): Y coordinate to click at
271
-
272
- - **browser_drag**
273
- - Description: Perform drag and drop operation
274
- - Parameters:
275
- - `startX` (number): Start X coordinate
276
- - `startY` (number): Start Y coordinate
277
- - `endX` (number): End X coordinate
278
- - `endY` (number): End Y coordinate
279
-
280
- - **browser_type**
281
- - Description: Type text at specified coordinates
282
- - Parameters:
283
- - `text` (string): Text to type
284
- - `submit` (boolean): Whether to submit entered text (press Enter after)
294
+ ### Keyboard
285
295
 
286
296
  - **browser_press_key**
287
297
  - Description: Press a key on the keyboard
288
298
  - Parameters:
289
299
  - `key` (string): Name of the key to press or a character to generate, such as `ArrowLeft` or `a`
290
300
 
291
- - **browser_choose_file**
301
+ ### Files and Media
302
+
303
+ - **browser_file_upload**
292
304
  - Description: Choose one or multiple files to upload
293
305
  - Parameters:
294
306
  - `paths` (array): The absolute paths to the files to upload. Can be a single file or multiple files.
295
307
 
296
- - **browser_save_as_pdf**
308
+ - **browser_pdf_save**
297
309
  - Description: Save page as PDF
298
310
  - Parameters: None
299
311
 
312
+ ### Utilities
313
+
300
314
  - **browser_wait**
301
315
  - Description: Wait for a specified time in seconds
302
316
  - Parameters:
@@ -305,3 +319,10 @@ Vision Mode provides tools for visual-based interactions using screenshots. Here
305
319
  - **browser_close**
306
320
  - Description: Close the page
307
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
@@ -47,122 +47,292 @@ var __importStar = (this && this.__importStar) || (function () {
47
47
  return result;
48
48
  };
49
49
  })();
50
+ var __importDefault = (this && this.__importDefault) || function (mod) {
51
+ return (mod && mod.__esModule) ? mod : { "default": mod };
52
+ };
50
53
  Object.defineProperty(exports, "__esModule", { value: true });
51
54
  exports.Context = void 0;
52
55
  const playwright = __importStar(require("playwright"));
56
+ const yaml_1 = __importDefault(require("yaml"));
57
+ const utils_1 = require("./tools/utils");
53
58
  class Context {
54
- _userDataDir;
55
- _launchOptions;
59
+ options;
56
60
  _browser;
57
- _page;
61
+ _browserContext;
62
+ _tabs = [];
63
+ _currentTab;
64
+ constructor(options) {
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)];
122
+ const browser = this._browser;
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;
58
180
  _console = [];
59
- _createPagePromise;
60
181
  _fileChooser;
61
- _lastSnapshotFrames = [];
62
- constructor(userDataDir, launchOptions) {
63
- this._userDataDir = userDataDir;
64
- this._launchOptions = launchOptions;
65
- }
66
- async createPage() {
67
- if (this._createPagePromise)
68
- return this._createPagePromise;
69
- this._createPagePromise = (async () => {
70
- const { browser, page } = await this._createPage();
71
- page.on('console', event => this._console.push(event));
72
- page.on('framenavigated', frame => {
73
- if (!frame.parentFrame())
74
- this._console.length = 0;
75
- });
76
- page.on('close', () => this._onPageClose());
77
- page.on('filechooser', chooser => this._fileChooser = chooser);
78
- page.setDefaultNavigationTimeout(60000);
79
- page.setDefaultTimeout(5000);
80
- this._page = page;
81
- this._browser = browser;
82
- return page;
83
- })();
84
- return this._createPagePromise;
85
- }
86
- _onPageClose() {
87
- const browser = this._browser;
88
- const page = this._page;
89
- void page?.context()?.close().then(() => browser?.close()).catch(() => { });
90
- this._createPagePromise = undefined;
91
- this._browser = undefined;
92
- this._page = undefined;
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() {
93
199
  this._fileChooser = undefined;
94
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(() => { });
207
+ }
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,
234
+ });
95
235
  }
96
- existingPage() {
97
- if (!this._page)
98
- throw new Error('Navigate to a location to create a page');
99
- return this._page;
236
+ async runAndWaitWithSnapshot(callback, options) {
237
+ return await this.run(callback, {
238
+ captureSnapshot: true,
239
+ waitForCompletion: true,
240
+ ...options,
241
+ });
242
+ }
243
+ lastSnapshot() {
244
+ if (!this._snapshot)
245
+ throw new Error('No snapshot available');
246
+ return this._snapshot;
100
247
  }
101
248
  async console() {
102
249
  return this._console;
103
250
  }
104
- async close() {
105
- if (!this._page)
106
- return;
107
- await this._page.close();
108
- }
109
251
  async submitFileChooser(paths) {
110
252
  if (!this._fileChooser)
111
253
  throw new Error('No file chooser visible');
112
254
  await this._fileChooser.setFiles(paths);
113
255
  this._fileChooser = undefined;
114
256
  }
115
- hasFileChooser() {
116
- return !!this._fileChooser;
257
+ }
258
+ class PageSnapshot {
259
+ _frameLocators = [];
260
+ _text;
261
+ constructor() {
117
262
  }
118
- clearFileChooser() {
119
- this._fileChooser = undefined;
263
+ static async create(page) {
264
+ const snapshot = new PageSnapshot();
265
+ await snapshot._build(page);
266
+ return snapshot;
120
267
  }
121
- async _createPage() {
122
- if (process.env.PLAYWRIGHT_WS_ENDPOINT) {
123
- const url = new URL(process.env.PLAYWRIGHT_WS_ENDPOINT);
124
- if (this._launchOptions)
125
- url.searchParams.set('launch-options', JSON.stringify(this._launchOptions));
126
- const browser = await playwright.chromium.connect(String(url));
127
- const page = await browser.newPage();
128
- return { browser, page };
268
+ text(options) {
269
+ const results = [];
270
+ if (options?.status) {
271
+ results.push(options.status);
272
+ results.push('');
129
273
  }
130
- const context = await playwright.chromium.launchPersistentContext(this._userDataDir, this._launchOptions);
131
- const [page] = context.pages();
132
- return { page };
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');
274
+ if (options?.hasFileChooser) {
275
+ results.push('- There is a file chooser visible that requires browser_file_upload to be called');
276
+ results.push('');
277
+ }
278
+ results.push(this._text);
279
+ return results.join('\n');
280
+ }
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');
287
+ }
288
+ async _snapshotFrame(frame) {
289
+ const frameIndex = this._frameLocators.push(frame) - 1;
290
+ const snapshotString = await frame.locator('body').ariaSnapshot({ ref: true });
291
+ const snapshot = yaml_1.default.parseDocument(snapshotString);
292
+ const visit = async (node) => {
293
+ if (yaml_1.default.isPair(node)) {
294
+ await Promise.all([
295
+ visit(node.key).then(k => node.key = k),
296
+ visit(node.value).then(v => node.value = v)
297
+ ]);
298
+ }
299
+ else if (yaml_1.default.isSeq(node) || yaml_1.default.isMap(node)) {
300
+ node.items = await Promise.all(node.items.map(visit));
301
+ }
302
+ else if (yaml_1.default.isScalar(node)) {
303
+ if (typeof node.value === 'string') {
304
+ const value = node.value;
305
+ if (frameIndex > 0)
306
+ node.value = value.replace('[ref=', `[ref=f${frameIndex}`);
307
+ if (value.startsWith('iframe ')) {
308
+ const ref = value.match(/\[ref=(.*)\]/)?.[1];
309
+ if (ref) {
310
+ try {
311
+ const childSnapshot = await this._snapshotFrame(frame.frameLocator(`aria-ref=${ref}`));
312
+ return snapshot.createPair(node.value, childSnapshot);
313
+ }
314
+ catch (error) {
315
+ return snapshot.createPair(node.value, '<could not take iframe snapshot>');
316
+ }
317
+ }
318
+ }
319
+ }
320
+ }
321
+ return node;
322
+ };
323
+ await visit(snapshot.contents);
324
+ return snapshot;
153
325
  }
154
326
  refLocator(ref) {
155
- const page = this.existingPage();
156
- let frame = page.mainFrame();
327
+ let frame = this._frameLocators[0];
157
328
  const match = ref.match(/^f(\d+)(.*)/);
158
329
  if (match) {
159
330
  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];
331
+ frame = this._frameLocators[frameIndex];
163
332
  ref = match[2];
164
333
  }
334
+ if (!frame)
335
+ throw new Error(`Frame does not exist. Provide ref from the most current snapshot.`);
165
336
  return frame.locator(`aria-ref=${ref}`);
166
337
  }
167
338
  }
168
- exports.Context = Context;