chore: roll playwright 5/9 (#394)

This commit is contained in:
Pavel Feldman 2025-05-09 18:01:17 -07:00 committed by GitHub
parent c28b480b51
commit 445170a76b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 88 additions and 149 deletions

39
package-lock.json generated
View File

@ -11,8 +11,7 @@
"dependencies": {
"@modelcontextprotocol/sdk": "^1.11.0",
"commander": "^13.1.0",
"playwright": "1.53.0-alpha-1746218818000",
"yaml": "^2.7.1",
"playwright": "1.53.0-alpha-1746832516000",
"zod-to-json-schema": "^3.24.4"
},
"bin": {
@ -21,7 +20,7 @@
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
"@playwright/test": "1.53.0-alpha-1746218818000",
"@playwright/test": "1.53.0-alpha-1746832516000",
"@stylistic/eslint-plugin": "^3.0.1",
"@types/node": "^22.13.10",
"@typescript-eslint/eslint-plugin": "^8.26.1",
@ -287,13 +286,13 @@
}
},
"node_modules/@playwright/test": {
"version": "1.53.0-alpha-1746218818000",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0-alpha-1746218818000.tgz",
"integrity": "sha512-J05FD0oOCVbjbp4IjQi5tOPKywchi5EENS9jRjgkA5N9jd/+BaZ3jT8HlLMIgALdk/eLsprQa7vh9h45Q1FOPA==",
"version": "1.53.0-alpha-1746832516000",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0-alpha-1746832516000.tgz",
"integrity": "sha512-Sec+6uzpA4MfwmQqJFBFVazffynqVwLO5swDxG7WoqgpUdn9gQX4K4tDG64SV6f4nOpwdM5LKTasPSXu02nn/Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.53.0-alpha-1746218818000"
"playwright": "1.53.0-alpha-1746832516000"
},
"bin": {
"playwright": "cli.js"
@ -3299,12 +3298,12 @@
}
},
"node_modules/playwright": {
"version": "1.53.0-alpha-1746218818000",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0-alpha-1746218818000.tgz",
"integrity": "sha512-mVIjtdqIawIqWVyvCaLmV6XTALCT4oWWrbMjoHyyWRln3jQjnm3RUO9LkaINz+Yh88O3FkuY6RfjGXPXeFeJ4Q==",
"version": "1.53.0-alpha-1746832516000",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0-alpha-1746832516000.tgz",
"integrity": "sha512-kcC1B2XJr4VaDAcVzi61SbYGkodq1QIqQXuPieXsNgZZ7cEKWzO2sI42yp2yie6wlCx0oLkSS2Q6jWSRVRLeaw==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.53.0-alpha-1746218818000"
"playwright-core": "1.53.0-alpha-1746832516000"
},
"bin": {
"playwright": "cli.js"
@ -3317,9 +3316,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.53.0-alpha-1746218818000",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0-alpha-1746218818000.tgz",
"integrity": "sha512-iaIZmhO/psGssWpxIprJkFrn2h4xFjgL0jZsKGtReAMZ/XhlqMUJxtSitwWM4BV+wxJIptsZD0s5Ml2KU62Z3w==",
"version": "1.53.0-alpha-1746832516000",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0-alpha-1746832516000.tgz",
"integrity": "sha512-4O98y4zV0rOP6CepMLC/VGuzqGaR1sS9AVh+i0CghWMQHM/8bxPJI8W38QndO0JU0V5nBD6j7DQeNt1mJ+CZ+g==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
@ -4350,18 +4349,6 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@ -37,14 +37,13 @@
"dependencies": {
"@modelcontextprotocol/sdk": "^1.11.0",
"commander": "^13.1.0",
"playwright": "1.53.0-alpha-1746218818000",
"yaml": "^2.7.1",
"playwright": "1.53.0-alpha-1746832516000",
"zod-to-json-schema": "^3.24.4"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
"@playwright/test": "1.53.0-alpha-1746218818000",
"@playwright/test": "1.53.0-alpha-1746832516000",
"@stylistic/eslint-plugin": "^3.0.1",
"@types/node": "^22.13.10",
"@typescript-eslint/eslint-plugin": "^8.26.1",

View File

@ -99,7 +99,7 @@ export async function configFromCLIOptions(cliOptions: CLIOptions): Promise<Conf
};
if (browserName === 'chromium')
(launchOptions as any).webSocketPort = await findFreePort();
(launchOptions as any).cdpPort = await findFreePort();
const contextOptions: BrowserContextOptions | undefined = cliOptions.device ? devices[cliOptions.device] : undefined;

View File

@ -15,20 +15,18 @@
*/
import * as playwright from 'playwright';
import yaml from 'yaml';
type PageOrFrameLocator = playwright.Page | playwright.FrameLocator;
export class PageSnapshot {
private _frameLocators: PageOrFrameLocator[] = [];
private _page: playwright.Page;
private _text!: string;
constructor() {
constructor(page: playwright.Page) {
this._page = page;
}
static async create(page: playwright.Page): Promise<PageSnapshot> {
const snapshot = new PageSnapshot();
await snapshot._build(page);
const snapshot = new PageSnapshot(page);
await snapshot._build();
return snapshot;
}
@ -36,8 +34,8 @@ export class PageSnapshot {
return this._text;
}
private async _build(page: playwright.Page) {
const yamlDocument = await this._snapshotFrame(page);
private async _build() {
const yamlDocument = await (this._page as any)._snapshotForAI();
this._text = [
`- Page Snapshot`,
'```yaml',
@ -46,56 +44,7 @@ export class PageSnapshot {
].join('\n');
}
private async _snapshotFrame(frame: playwright.Page | playwright.FrameLocator) {
const frameIndex = this._frameLocators.push(frame) - 1;
const snapshotString = await frame.locator('body').ariaSnapshot({ ref: true, emitGeneric: true });
const snapshot = yaml.parseDocument(snapshotString);
const visit = async (node: any): Promise<unknown> => {
if (yaml.isPair(node)) {
await Promise.all([
visit(node.key).then(k => node.key = k),
visit(node.value).then(v => node.value = v)
]);
} else if (yaml.isSeq(node) || yaml.isMap(node)) {
node.items = await Promise.all(node.items.map(visit));
} else if (yaml.isScalar(node)) {
if (typeof node.value === 'string') {
const value = node.value;
if (frameIndex > 0)
node.value = value.replace('[ref=', `[ref=f${frameIndex}`);
if (value.startsWith('iframe ')) {
const ref = value.match(/\[ref=(.*)\]/)?.[1];
if (ref) {
try {
const childSnapshot = await this._snapshotFrame(frame.frameLocator(`aria-ref=${ref}`));
return snapshot.createPair(node.value, childSnapshot);
} catch (error) {
return snapshot.createPair(node.value, '<could not take iframe snapshot>');
}
}
}
}
}
return node;
};
await visit(snapshot.contents);
return snapshot;
}
refLocator(ref: string): playwright.Locator {
let frame = this._frameLocators[0];
const match = ref.match(/^f(\d+)(.*)/);
if (match) {
const frameIndex = parseInt(match[1], 10);
frame = this._frameLocators[frameIndex];
ref = match[2];
}
if (!frame)
throw new Error(`Frame does not exist. Provide ref from the most current snapshot.`);
return frame.locator(`aria-ref=${ref}`);
return this._page.locator(`aria-ref=${ref}`);
}
}

View File

@ -21,7 +21,7 @@ test('cdp server', async ({ cdpEndpoint, startClient, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`- generic [ref=s1e2]: Hello, world!`);
})).toContainTextContent(`- generic [ref=e1]: Hello, world!`);
});
test('cdp server reuse tab', async ({ cdpEndpoint, startClient }) => {
@ -48,7 +48,7 @@ test('cdp server reuse tab', async ({ cdpEndpoint, startClient }) => {
- Page Title:
- Page Snapshot
\`\`\`yaml
- generic [ref=s1e2]: hello world
- generic [ref=e1]: hello world
\`\`\`
`);
});
@ -70,5 +70,5 @@ test('should throw connection error and allow re-connecting', async ({ cdpEndpoi
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.PREFIX },
})).toContainTextContent(`- generic [ref=s1e2]: Hello, world!`);
})).toContainTextContent(`- generic [ref=e1]: Hello, world!`);
});

View File

@ -31,7 +31,7 @@ await page.goto('${server.HELLO_WORLD}');
- Page Title: Title
- Page Snapshot
\`\`\`yaml
- generic [ref=s1e2]: Hello, world!
- generic [ref=e1]: Hello, world!
\`\`\`
`
);
@ -52,7 +52,7 @@ test('browser_click', async ({ client, server }) => {
name: 'browser_click',
arguments: {
element: 'Submit button',
ref: 's1e3',
ref: 'e2',
},
})).toHaveTextContent(`
- Ran Playwright code:
@ -65,7 +65,7 @@ await page.getByRole('button', { name: 'Submit' }).click();
- Page Title: Title
- Page Snapshot
\`\`\`yaml
- button "Submit" [ref=s2e3]
- button "Submit" [ref=e2]
\`\`\`
`);
});
@ -88,7 +88,7 @@ test('browser_select_option', async ({ client, server }) => {
name: 'browser_select_option',
arguments: {
element: 'Select',
ref: 's1e3',
ref: 'e2',
values: ['bar'],
},
})).toHaveTextContent(`
@ -102,7 +102,7 @@ await page.getByRole('combobox').selectOption(['bar']);
- Page Title: Title
- Page Snapshot
\`\`\`yaml
- combobox [ref=s2e3]:
- combobox [ref=e2]:
- option "Foo"
- option "Bar" [selected]
\`\`\`
@ -128,7 +128,7 @@ test('browser_select_option (multiple)', async ({ client, server }) => {
name: 'browser_select_option',
arguments: {
element: 'Select',
ref: 's1e3',
ref: 'e2',
values: ['bar', 'baz'],
},
})).toHaveTextContent(`
@ -142,10 +142,10 @@ await page.getByRole('listbox').selectOption(['bar', 'baz']);
- Page Title: Title
- Page Snapshot
\`\`\`yaml
- listbox [ref=s2e3]:
- option "Foo" [ref=s2e4]
- option "Bar" [selected] [ref=s2e5]
- option "Baz" [selected] [ref=s2e6]
- listbox [ref=e2]:
- option "Foo" [ref=e3]
- option "Bar" [selected] [ref=e4]
- option "Baz" [selected] [ref=e5]
\`\`\`
`);
});
@ -168,7 +168,7 @@ test('browser_type', async ({ client, server }) => {
name: 'browser_type',
arguments: {
element: 'textbox',
ref: 's1e3',
ref: 'e2',
text: 'Hi!',
submit: true,
},
@ -194,7 +194,7 @@ test('browser_type (slowly)', async ({ client, server }) => {
name: 'browser_type',
arguments: {
element: 'textbox',
ref: 's1e3',
ref: 'e2',
text: 'Hi!',
submit: true,
slowly: true,

View File

@ -24,13 +24,13 @@ test('alert dialog', async ({ client, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.PREFIX },
})).toContainTextContent('- button "Button" [ref=s1e3]');
})).toContainTextContent('- button "Button" [ref=e2]');
expect(await client.callTool({
name: 'browser_click',
arguments: {
element: 'Button',
ref: 's1e3',
ref: 'e2',
},
})).toHaveTextContent(`- Ran Playwright code:
\`\`\`js
@ -58,7 +58,7 @@ await page.getByRole('button', { name: 'Button' }).click();
- Page Title:
- Page Snapshot
\`\`\`yaml
- button "Button" [ref=s2e3]
- button "Button" [ref=e2]
\`\`\`
`);
});
@ -76,13 +76,13 @@ test('two alert dialogs', async ({ client, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.PREFIX },
})).toContainTextContent('- button "Button" [ref=s1e3]');
})).toContainTextContent('- button "Button" [ref=e2]');
expect(await client.callTool({
name: 'browser_click',
arguments: {
element: 'Button',
ref: 's1e3',
ref: 'e2',
},
})).toHaveTextContent(`- Ran Playwright code:
\`\`\`js
@ -114,13 +114,13 @@ test('confirm dialog (true)', async ({ client, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.PREFIX },
})).toContainTextContent('- button "Button" [ref=s1e3]');
})).toContainTextContent('- button "Button" [ref=e2]');
expect(await client.callTool({
name: 'browser_click',
arguments: {
element: 'Button',
ref: 's1e3',
ref: 'e2',
},
})).toContainTextContent(`### Modal state
- ["confirm" dialog with message "Confirm"]: can be handled by the "browser_handle_dialog" tool`);
@ -136,7 +136,7 @@ test('confirm dialog (true)', async ({ client, server }) => {
expect(result).toContainTextContent('// <internal code to handle "confirm" dialog>');
expect(result).toContainTextContent(`- Page Snapshot
\`\`\`yaml
- generic [ref=s2e2]: "true"
- generic [ref=e1]: "true"
\`\`\``);
});
@ -151,13 +151,13 @@ test('confirm dialog (false)', async ({ client, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.PREFIX },
})).toContainTextContent('- button "Button" [ref=s1e3]');
})).toContainTextContent('- button "Button" [ref=e2]');
expect(await client.callTool({
name: 'browser_click',
arguments: {
element: 'Button',
ref: 's1e3',
ref: 'e2',
},
})).toContainTextContent(`### Modal state
- ["confirm" dialog with message "Confirm"]: can be handled by the "browser_handle_dialog" tool`);
@ -171,7 +171,7 @@ test('confirm dialog (false)', async ({ client, server }) => {
expect(result).toContainTextContent(`- Page Snapshot
\`\`\`yaml
- generic [ref=s2e2]: "false"
- generic [ref=e1]: "false"
\`\`\``);
});
@ -186,13 +186,13 @@ test('prompt dialog', async ({ client, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.PREFIX },
})).toContainTextContent('- button "Button" [ref=s1e3]');
})).toContainTextContent('- button "Button" [ref=e2]');
expect(await client.callTool({
name: 'browser_click',
arguments: {
element: 'Button',
ref: 's1e3',
ref: 'e2',
},
})).toContainTextContent(`### Modal state
- ["prompt" dialog with message "Prompt"]: can be handled by the "browser_handle_dialog" tool`);
@ -207,6 +207,6 @@ test('prompt dialog', async ({ client, server }) => {
expect(result).toContainTextContent(`- Page Snapshot
\`\`\`yaml
- generic [ref=s2e2]: Answer
- generic [ref=e1]: Answer
\`\`\``);
});

View File

@ -29,8 +29,9 @@ test('browser_file_upload', async ({ client, localOutputPath, server }) => {
arguments: { url: server.PREFIX },
})).toContainTextContent(`
\`\`\`yaml
- button "Choose File" [ref=s1e3]
- button "Button" [ref=s1e4]
- generic [ref=e1]:
- button "Choose File" [ref=e2]
- button "Button" [ref=e3]
\`\`\``);
{
@ -48,7 +49,7 @@ The tool "browser_file_upload" can only be used when there is related modal stat
name: 'browser_click',
arguments: {
element: 'Textbox',
ref: 's1e3',
ref: 'e2',
},
})).toContainTextContent(`### Modal state
- [File chooser]: can be handled by the "browser_file_upload" tool`);
@ -67,8 +68,9 @@ The tool "browser_file_upload" can only be used when there is related modal stat
expect(response).not.toContainTextContent('### Modal state');
expect(response).toContainTextContent(`
\`\`\`yaml
- button "Choose File" [ref=s3e3]
- button "Button" [ref=s3e4]
- generic [ref=e1]:
- button "Choose File" [ref=e2]
- button "Button" [ref=e3]
\`\`\``);
}
@ -77,7 +79,7 @@ The tool "browser_file_upload" can only be used when there is related modal stat
name: 'browser_click',
arguments: {
element: 'Textbox',
ref: 's3e3',
ref: 'e2',
},
});
@ -89,7 +91,7 @@ The tool "browser_file_upload" can only be used when there is related modal stat
name: 'browser_click',
arguments: {
element: 'Button',
ref: 's4e4',
ref: 'e3',
},
});
@ -111,12 +113,12 @@ test('clicking on download link emits download', async ({ startClient, localOutp
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.PREFIX },
})).toContainTextContent('- link "Download" [ref=s1e3]');
})).toContainTextContent('- link "Download" [ref=e2]');
await client.callTool({
name: 'browser_click',
arguments: {
element: 'Download link',
ref: 's1e3',
ref: 'e2',
},
});
await expect.poll(() => client.callTool({ name: 'browser_snapshot', arguments: {} })).toContainTextContent(`

View File

@ -24,19 +24,21 @@ test('stitched aria frames', async ({ client }) => {
},
})).toContainTextContent(`
\`\`\`yaml
- heading "Hello" [level=1] [ref=s1e3]
- iframe [ref=s1e4]:
- button "World" [ref=f1s1e3]
- main [ref=f1s1e4]:
- iframe [ref=f1s1e5]:
- paragraph [ref=f2s1e3]: Nested
- generic [ref=e1]:
- heading "Hello" [level=1] [ref=e2]
- iframe [ref=e3]:
- generic [ref=f1e1]:
- button "World" [ref=f1e2]
- main [ref=f1e3]:
- iframe [ref=f1e4]:
- paragraph [ref=f2e2]: Nested
\`\`\``);
expect(await client.callTool({
name: 'browser_click',
arguments: {
element: 'World',
ref: 'f1s1e3',
ref: 'f1e2',
},
})).toContainTextContent(`// Click World`);
});

View File

@ -30,7 +30,7 @@ test('test reopen browser', async ({ client, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`- generic [ref=s1e2]: Hello, world!`);
})).toContainTextContent(`- generic [ref=e1]: Hello, world!`);
});
test('executable path', async ({ startClient, server }) => {

View File

@ -34,7 +34,7 @@ test('browser_network_requests', async ({ client, server }) => {
name: 'browser_click',
arguments: {
element: 'Click me button',
ref: 's1e3',
ref: 'e2',
},
});

View File

@ -36,7 +36,7 @@ test('save as pdf', async ({ client, mcpBrowser, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`- generic [ref=s1e2]: Hello, world!`);
})).toContainTextContent(`- generic [ref=e1]: Hello, world!`);
const response = await client.callTool({
name: 'browser_pdf_save',
@ -55,7 +55,7 @@ test('save as pdf (filename: output.pdf)', async ({ startClient, mcpBrowser, ser
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`- generic [ref=s1e2]: Hello, world!`);
})).toContainTextContent(`- generic [ref=e1]: Hello, world!`);
expect(await client.callTool({
name: 'browser_pdf_save',

View File

@ -46,13 +46,13 @@ test('browser_take_screenshot (element)', async ({ client, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`[ref=s1e2]`);
})).toContainTextContent(`[ref=e1]`);
expect(await client.callTool({
name: 'browser_take_screenshot',
arguments: {
element: 'hello button',
ref: 's1e2',
ref: 'e1',
},
})).toEqual({
content: [

View File

@ -97,7 +97,7 @@ test('sse transport via public API', async ({ server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`- generic [ref=s1e2]: Hello, world!`);
})).toContainTextContent(`- generic [ref=e1]: Hello, world!`);
await client.close();
mcpServer.close();
});

View File

@ -63,7 +63,7 @@ test('create new tab', async ({ client }) => {
- Page Title: Tab one
- Page Snapshot
\`\`\`yaml
- generic [ref=s1e2]: Body one
- generic [ref=e1]: Body one
\`\`\``);
expect(await createTab(client, 'Tab two', 'Body two')).toHaveTextContent(`
@ -82,7 +82,7 @@ test('create new tab', async ({ client }) => {
- Page Title: Tab two
- Page Snapshot
\`\`\`yaml
- generic [ref=s1e2]: Body two
- generic [ref=e1]: Body two
\`\`\``);
});
@ -110,7 +110,7 @@ test('select tab', async ({ client }) => {
- Page Title: Tab one
- Page Snapshot
\`\`\`yaml
- generic [ref=s2e2]: Body one
- generic [ref=e1]: Body one
\`\`\``);
});
@ -137,7 +137,7 @@ test('close tab', async ({ client }) => {
- Page Title: Tab one
- Page Snapshot
\`\`\`yaml
- generic [ref=s2e2]: Body one
- generic [ref=e1]: Body one
\`\`\``);
});

View File

@ -40,14 +40,14 @@ test('browser_wait_for(text)', async ({ client, server }) => {
name: 'browser_click',
arguments: {
element: 'Click me',
ref: 's1e3',
ref: 'e2',
},
});
expect(await client.callTool({
name: 'browser_wait_for',
arguments: { text: 'Text to appear' },
})).toContainTextContent(`- generic [ref=s3e4]: Text to appear`);
})).toContainTextContent(`- generic [ref=e3]: Text to appear`);
});
test('browser_wait_for(textGone)', async ({ client, server }) => {
@ -74,12 +74,12 @@ test('browser_wait_for(textGone)', async ({ client, server }) => {
name: 'browser_click',
arguments: {
element: 'Click me',
ref: 's1e3',
ref: 'e2',
},
});
expect(await client.callTool({
name: 'browser_wait_for',
arguments: { textGone: 'Text to disappear' },
})).toContainTextContent(`- generic [ref=s3e4]: Text to appear`);
})).toContainTextContent(`- generic [ref=e3]: Text to appear`);
});