Skip to content

Commit 6848ab8

Browse files
committed
test-mcp
1 parent 23b31b5 commit 6848ab8

File tree

7 files changed

+190
-18
lines changed

7 files changed

+190
-18
lines changed

packages/playwright/src/mcp/browser/tools/tabs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ const browserTabs = defineTool({
4545
}
4646
case 'close': {
4747
await context.closeTab(params.index);
48-
response.setIncludeSnapshot();
48+
response.setIncludeSnapshot('full');
4949
return;
5050
}
5151
case 'select': {
5252
if (params.index === undefined)
5353
throw new Error('Tab index is required');
5454
await context.selectTab(params.index);
55-
response.setIncludeSnapshot();
55+
response.setIncludeSnapshot('full');
5656
return;
5757
}
5858
}

tests/mcp/click.spec.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@
1616

1717
import { test, expect } from './fixtures';
1818

19-
test('browser_click', async ({ client, server, mcpBrowser }) => {
19+
test('browser_click', async ({ client, server }) => {
2020
server.setContent('/', `
2121
<title>Title</title>
2222
<button>Submit</button>
23+
<script>
24+
const button = document.querySelector('button');
25+
button.addEventListener('click', () => {
26+
button.focus(); // without manual focus, webkit focuses body
27+
});
28+
</script>
2329
`, 'text/html');
2430

2531
await client.callTool({
@@ -35,7 +41,7 @@ test('browser_click', async ({ client, server, mcpBrowser }) => {
3541
},
3642
})).toHaveResponse({
3743
code: `await page.getByRole('button', { name: 'Submit' }).click();`,
38-
pageState: expect.stringContaining(`- button "Submit" ${mcpBrowser !== 'webkit' || process.platform === 'linux' ? '[active] ' : ''}[ref=e2]`),
44+
pageState: expect.stringContaining(`- button "Submit" [active] [ref=e2]`),
3945
});
4046
});
4147

tests/mcp/core.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,6 @@ test('browser_select_option (multiple)', async ({ client, server }) => {
9090
})).toHaveResponse({
9191
code: `await page.getByRole('listbox').selectOption(['bar', 'baz']);`,
9292
pageState: expect.stringContaining(`
93-
- listbox [ref=e2]:
94-
- option "Foo" [ref=e3]
9593
- option "Bar" [selected] [ref=e4]
9694
- option "Baz" [selected] [ref=e5]`),
9795
});

tests/mcp/dialogs.spec.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import { test, expect } from './fixtures';
1818

1919
test('alert dialog', async ({ client, server }) => {
20-
server.setContent('/', `<button onclick="alert('Alert')">Button</button>`, 'text/html');
20+
server.setContent('/', `<title>Title</title><button onclick="alert('Alert')">Button</button>`, 'text/html');
2121
expect(await client.callTool({
2222
name: 'browser_navigate',
2323
arguments: { url: server.PREFIX },
@@ -54,7 +54,7 @@ test('alert dialog', async ({ client, server }) => {
5454
},
5555
})).toHaveResponse({
5656
modalState: undefined,
57-
pageState: expect.stringContaining(`- button "Button"`),
57+
pageState: expect.stringContaining(`Page Title: Title`),
5858
});
5959
});
6060

@@ -218,7 +218,7 @@ test('prompt dialog', async ({ client, server }) => {
218218
});
219219

220220
test('alert dialog w/ race', async ({ client, server }) => {
221-
server.setContent('/', `<button onclick="setTimeout(() => alert('Alert'), 100)">Button</button>`, 'text/html');
221+
server.setContent('/', `<title>Title</title><button onclick="setTimeout(() => alert('Alert'), 100)">Button</button>`, 'text/html');
222222
expect(await client.callTool({
223223
name: 'browser_navigate',
224224
arguments: { url: server.PREFIX },
@@ -247,9 +247,7 @@ test('alert dialog w/ race', async ({ client, server }) => {
247247
expect(result).toHaveResponse({
248248
modalState: undefined,
249249
pageState: expect.stringContaining(`- Page URL: ${server.PREFIX}/
250-
- Page Title:
251-
- Page Snapshot:
252-
\`\`\`yaml
253-
- button "Button"`),
250+
- Page Title: Title
251+
- Page Snapshot:`),
254252
});
255253
});

tests/mcp/session-log.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ test('session log should record tool calls', async ({ startClient, server }, tes
4242
},
4343
})).toHaveResponse({
4444
code: `await page.getByRole('button', { name: 'Submit' }).click();`,
45-
pageState: expect.stringContaining(`- button "Submit"`),
45+
pageState: expect.stringContaining(`Page Title: Title`),
4646
});
4747

4848
const output = stderr().split('\n').filter(line => line.startsWith('Session: '))[0];

tests/mcp/snapshot-diff.spec.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { test, expect } from './fixtures';
18+
19+
test('should return aria snapshot diff', async ({ client, server }) => {
20+
server.setContent('/', `
21+
<button>Button 1</button>
22+
<button>Button 2</button>
23+
<ul id=filler></ul>
24+
<script>
25+
const filler = document.getElementById('filler');
26+
for (let i = 0; i < 100; i++) {
27+
const li = document.createElement('li');
28+
li.textContent = 'Filler ' + i;
29+
filler.appendChild(li);
30+
}
31+
const [button1, button2] = document.querySelectorAll('button');
32+
button1.addEventListener('click', () => {
33+
const span = document.createElement('span');
34+
span.textContent = 'new text';
35+
button2.appendChild(span);
36+
button1.focus(); // without manual focus, webkit focuses body
37+
});
38+
button2.focus();
39+
button2.addEventListener('click', () => {
40+
button2.focus(); // without manual focus, webkit focuses body
41+
});
42+
</script>
43+
`, 'text/html');
44+
45+
const listitems = new Array(100).fill(0).map((_, i) => `\n - listitem [ref=e${5 + i}]: Filler ${i}`).join('');
46+
47+
expect(await client.callTool({
48+
name: 'browser_navigate',
49+
arguments: {
50+
url: server.PREFIX,
51+
},
52+
})).toHaveResponse({
53+
pageState: expect.stringContaining(`
54+
- button "Button 1" [ref=e2]
55+
- button "Button 2" [active] [ref=e3]
56+
- list [ref=e4]:${listitems}`),
57+
});
58+
59+
expect(await client.callTool({
60+
name: 'browser_click',
61+
arguments: {
62+
element: 'Button 2',
63+
ref: 'e3',
64+
},
65+
})).toHaveResponse({
66+
pageState: expect.stringContaining(`Page Snapshot:
67+
\`\`\`yaml
68+
- ref=e1 [unchanged]
69+
\`\`\``),
70+
});
71+
72+
expect(await client.callTool({
73+
name: 'browser_click',
74+
arguments: {
75+
element: 'Button 1',
76+
ref: 'e2',
77+
},
78+
})).toHaveResponse({
79+
pageState: expect.stringContaining(`Page Snapshot:
80+
\`\`\`yaml
81+
- generic [ref=e1]:
82+
- button "Button 1" [active] [ref=e2]
83+
- button "Button 2new text" [ref=e105]
84+
- ref=e4 [unchanged]
85+
\`\`\``),
86+
});
87+
88+
// browser_snapshot forces a full snapshot.
89+
expect(await client.callTool({
90+
name: 'browser_snapshot',
91+
})).toHaveResponse({
92+
pageState: expect.stringContaining(`Page Snapshot:
93+
\`\`\`yaml
94+
- generic [ref=e1]:
95+
- button "Button 1" [active] [ref=e2]
96+
- button "Button 2new text" [ref=e105]
97+
- list [ref=e4]:${listitems}
98+
\`\`\``),
99+
});
100+
});
101+
102+
test('should reset aria snapshot diff upon navigation', async ({ client, server }) => {
103+
server.setContent('/before', `
104+
<button>Button 1</button>
105+
<button>Button 2</button>
106+
<ul id=filler></ul>
107+
<script>
108+
const filler = document.getElementById('filler');
109+
for (let i = 0; i < 100; i++) {
110+
const li = document.createElement('li');
111+
li.textContent = 'Filler ' + i;
112+
filler.appendChild(li);
113+
}
114+
document.querySelector('button').addEventListener('click', () => {
115+
window.location.href = '/after';
116+
});
117+
document.querySelectorAll('button')[1].focus();
118+
</script>
119+
`, 'text/html');
120+
121+
server.setContent('/after', `
122+
<button>Button 1</button>
123+
<button>Button 2</button>
124+
<ul id=filler></ul>
125+
<script>
126+
const filler = document.getElementById('filler');
127+
for (let i = 0; i < 100; i++) {
128+
const li = document.createElement('li');
129+
li.textContent = 'Filler ' + i;
130+
filler.appendChild(li);
131+
}
132+
document.querySelectorAll('button')[1].focus();
133+
</script>
134+
`, 'text/html');
135+
136+
expect(await client.callTool({
137+
name: 'browser_navigate',
138+
arguments: {
139+
url: server.PREFIX + '/before',
140+
},
141+
})).toHaveResponse({
142+
pageState: expect.stringContaining(`
143+
- button "Button 1" [ref=e2]
144+
- button "Button 2" [active] [ref=e3]`),
145+
});
146+
147+
expect(await client.callTool({
148+
name: 'browser_click',
149+
arguments: {
150+
element: 'Button 1',
151+
ref: 'e2',
152+
},
153+
})).toHaveResponse({
154+
pageState: expect.stringContaining(`
155+
- button "Button 1" [ref=e2]
156+
- button "Button 2" [active] [ref=e3]`),
157+
});
158+
});

tests/mcp/wait.spec.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ test('browser_wait_for(text)', async ({ client, server }) => {
3131
</body>
3232
`, 'text/html');
3333

34-
await client.callTool({
34+
expect(await client.callTool({
3535
name: 'browser_navigate',
3636
arguments: { url: server.PREFIX },
37+
})).toHaveResponse({
38+
pageState: expect.stringContaining(`- generic [ref=e3]: Text to disappear`),
3739
});
3840

3941
await client.callTool({
@@ -44,10 +46,14 @@ test('browser_wait_for(text)', async ({ client, server }) => {
4446
},
4547
});
4648

47-
expect(await client.callTool({
49+
await client.callTool({
4850
name: 'browser_wait_for',
4951
arguments: { text: 'Text to appear' },
5052
code: `await page.getByText("Text to appear").first().waitFor({ state: 'visible' });`,
53+
});
54+
55+
expect(await client.callTool({
56+
name: 'browser_snapshot',
5157
})).toHaveResponse({
5258
pageState: expect.stringContaining(`- generic [ref=e3]: Text to appear`),
5359
});
@@ -68,9 +74,11 @@ test('browser_wait_for(textGone)', async ({ client, server }) => {
6874
</body>
6975
`, 'text/html');
7076

71-
await client.callTool({
77+
expect(await client.callTool({
7278
name: 'browser_navigate',
7379
arguments: { url: server.PREFIX },
80+
})).toHaveResponse({
81+
pageState: expect.stringContaining(`- generic [ref=e3]: Text to disappear`),
7482
});
7583

7684
await client.callTool({
@@ -81,10 +89,14 @@ test('browser_wait_for(textGone)', async ({ client, server }) => {
8189
},
8290
});
8391

84-
expect(await client.callTool({
92+
await client.callTool({
8593
name: 'browser_wait_for',
8694
arguments: { textGone: 'Text to disappear' },
8795
code: `await page.getByText("Text to disappear").first().waitFor({ state: 'hidden' });`,
96+
});
97+
98+
expect(await client.callTool({
99+
name: 'browser_snapshot',
88100
})).toHaveResponse({
89101
pageState: expect.stringContaining(`- generic [ref=e3]: Text to appear`),
90102
});

0 commit comments

Comments
 (0)