chore: only reset network log upon explicit navigation (#377)

Fixes https://github.com/microsoft/playwright-mcp/issues/376
This commit is contained in:
Pavel Feldman 2025-05-08 17:02:09 -07:00 committed by GitHub
parent 85c85bd2fb
commit 57b3c14276
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 228 additions and 179 deletions

View File

@ -35,10 +35,6 @@ export class Tab {
page.on('console', event => this._consoleMessages.push(event));
page.on('request', request => this._requests.set(request, null));
page.on('response', response => this._requests.set(response.request(), response));
page.on('framenavigated', frame => {
if (!frame.parentFrame())
this._clearCollectedArtifacts();
});
page.on('close', () => this._onClose());
page.on('filechooser', chooser => {
this.context.setModalState({
@ -66,6 +62,8 @@ export class Tab {
}
async navigate(url: string) {
this._clearCollectedArtifacts();
const downloadEvent = this.page.waitForEvent('download').catch(() => {});
try {
await this.page.goto(url, { waitUntil: 'domcontentloaded' });

View File

@ -16,13 +16,11 @@
import { test, expect } from './fixtures.js';
test('cdp server', async ({ cdpEndpoint, startClient }) => {
test('cdp server', async ({ cdpEndpoint, startClient, server }) => {
const client = await startClient({ args: [`--cdp-endpoint=${await cdpEndpoint()}`] });
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`- generic [ref=s1e2]: Hello, world!`);
});
@ -55,20 +53,22 @@ test('cdp server reuse tab', async ({ cdpEndpoint, startClient }) => {
`);
});
test('should throw connection error and allow re-connecting', async ({ cdpEndpoint, startClient }) => {
test('should throw connection error and allow re-connecting', async ({ cdpEndpoint, startClient, server }) => {
const port = 3200 + test.info().parallelIndex;
const client = await startClient({ args: [`--cdp-endpoint=http://localhost:${port}`] });
server.setContent('/', `
<title>Title</title>
<body>Hello, world!</body>
`, 'text/html');
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
arguments: { url: server.PREFIX },
})).toContainTextContent(`Error: browserType.connectOverCDP: connect ECONNREFUSED`);
await cdpEndpoint(port);
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
arguments: { url: server.PREFIX },
})).toContainTextContent(`- generic [ref=s1e2]: Hello, world!`);
});

View File

@ -19,7 +19,12 @@ import fs from 'node:fs';
import { Config } from '../config.js';
import { test, expect } from './fixtures.js';
test('config user data dir', async ({ startClient, localOutputPath }) => {
test('config user data dir', async ({ startClient, localOutputPath, server }) => {
server.setContent('/', `
<title>Title</title>
<body>Hello, world!</body>
`, 'text/html');
const config: Config = {
browser: {
userDataDir: localOutputPath('user-data-dir'),
@ -31,9 +36,7 @@ test('config user data dir', async ({ startClient, localOutputPath }) => {
const client = await startClient({ args: ['--config', configPath] });
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><body>Hello, world!</body></html>',
},
arguments: { url: server.PREFIX },
})).toContainTextContent(`Hello, world!`);
const files = await fs.promises.readdir(config.browser!.userDataDir!);

View File

@ -16,11 +16,21 @@
import { test, expect } from './fixtures.js';
test('browser_console_messages', async ({ client }) => {
test('browser_console_messages', async ({ client, server }) => {
server.setContent('/', `
<!DOCTYPE html>
<html>
<script>
console.log("Hello, world!");
console.error("Error");
</script>
</html>
`, 'text/html');
await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><script>console.log("Hello, world!");console.error("Error"); </script></html>',
url: server.PREFIX,
},
});

View File

@ -16,20 +16,18 @@
import { test, expect } from './fixtures.js';
test('browser_navigate', async ({ client }) => {
test('browser_navigate', async ({ client, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
arguments: { url: server.HELLO_WORLD },
})).toHaveTextContent(`
- Ran Playwright code:
\`\`\`js
// Navigate to data:text/html,<html><title>Title</title><body>Hello, world!</body></html>
await page.goto('data:text/html,<html><title>Title</title><body>Hello, world!</body></html>');
// Navigate to ${server.HELLO_WORLD}
await page.goto('${server.HELLO_WORLD}');
\`\`\`
- Page URL: data:text/html,<html><title>Title</title><body>Hello, world!</body></html>
- Page URL: ${server.HELLO_WORLD}
- Page Title: Title
- Page Snapshot
\`\`\`yaml
@ -39,12 +37,15 @@ await page.goto('data:text/html,<html><title>Title</title><body>Hello, world!</b
);
});
test('browser_click', async ({ client }) => {
test('browser_click', async ({ client, server }) => {
server.setContent('/', `
<title>Title</title>
<button>Submit</button>
`, 'text/html');
await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><button>Submit</button></html>',
},
arguments: { url: server.PREFIX },
});
expect(await client.callTool({
@ -60,7 +61,7 @@ test('browser_click', async ({ client }) => {
await page.getByRole('button', { name: 'Submit' }).click();
\`\`\`
- Page URL: data:text/html,<html><title>Title</title><button>Submit</button></html>
- Page URL: ${server.PREFIX}
- Page Title: Title
- Page Snapshot
\`\`\`yaml
@ -69,12 +70,18 @@ await page.getByRole('button', { name: 'Submit' }).click();
`);
});
test('browser_select_option', async ({ client }) => {
test('browser_select_option', async ({ client, server }) => {
server.setContent('/', `
<title>Title</title>
<select>
<option value="foo">Foo</option>
<option value="bar">Bar</option>
</select>
`, 'text/html');
await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><select><option value="foo">Foo</option><option value="bar">Bar</option></select></html>',
},
arguments: { url: server.PREFIX },
});
expect(await client.callTool({
@ -91,7 +98,7 @@ test('browser_select_option', async ({ client }) => {
await page.getByRole('combobox').selectOption(['bar']);
\`\`\`
- Page URL: data:text/html,<html><title>Title</title><select><option value="foo">Foo</option><option value="bar">Bar</option></select></html>
- Page URL: ${server.PREFIX}
- Page Title: Title
- Page Snapshot
\`\`\`yaml
@ -102,12 +109,19 @@ await page.getByRole('combobox').selectOption(['bar']);
`);
});
test('browser_select_option (multiple)', async ({ client }) => {
test('browser_select_option (multiple)', async ({ client, server }) => {
server.setContent('/', `
<title>Title</title>
<select multiple>
<option value="foo">Foo</option>
<option value="bar">Bar</option>
<option value="baz">Baz</option>
</select>
`, 'text/html');
await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><select multiple><option value="foo">Foo</option><option value="bar">Bar</option><option value="baz">Baz</option></select></html>',
},
arguments: { url: server.PREFIX },
});
expect(await client.callTool({
@ -124,7 +138,7 @@ test('browser_select_option (multiple)', async ({ client }) => {
await page.getByRole('listbox').selectOption(['bar', 'baz']);
\`\`\`
- Page URL: data:text/html,<html><title>Title</title><select multiple><option value="foo">Foo</option><option value="bar">Bar</option><option value="baz">Baz</option></select></html>
- Page URL: ${server.PREFIX}
- Page Title: Title
- Page Snapshot
\`\`\`yaml
@ -136,11 +150,18 @@ await page.getByRole('listbox').selectOption(['bar', 'baz']);
`);
});
test('browser_type', async ({ client }) => {
test('browser_type', async ({ client, server }) => {
server.setContent('/', `
<!DOCTYPE html>
<html>
<input type='keypress' onkeypress="console.log('Key pressed:', event.key, ', Text:', event.target.value)"></input>
</html>
`, 'text/html');
await client.callTool({
name: 'browser_navigate',
arguments: {
url: `data:text/html,<input type='keypress' onkeypress="console.log('Key pressed:', event.key, ', Text:', event.target.value)"></input>`,
url: server.PREFIX,
},
});
await client.callTool({
@ -158,11 +179,15 @@ test('browser_type', async ({ client }) => {
})).toHaveTextContent('[LOG] Key pressed: Enter , Text: Hi!');
});
test('browser_type (slowly)', async ({ client }) => {
test('browser_type (slowly)', async ({ client, server }) => {
server.setContent('/', `
<input type='text' onkeydown="console.log('Key pressed:', event.key, 'Text:', event.target.value)"></input>
`, 'text/html');
await client.callTool({
name: 'browser_navigate',
arguments: {
url: `data:text/html,<input type='text' onkeydown="console.log('Key pressed:', event.key, 'Text:', event.target.value)"></input>`,
url: server.PREFIX,
},
});
await client.callTool({
@ -186,12 +211,18 @@ test('browser_type (slowly)', async ({ client }) => {
].join('\n'));
});
test('browser_resize', async ({ client }) => {
test('browser_resize', async ({ client, server }) => {
server.setContent('/', `
<title>Resize Test</title>
<body>
<div id="size">Waiting for resize...</div>
<script>new ResizeObserver(() => { document.getElementById("size").textContent = \`Window size: \${window.innerWidth}x\${window.innerHeight}\`; }).observe(document.body);
</script>
</body>
`, 'text/html');
await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Resize Test</title><body><div id="size">Waiting for resize...</div><script>new ResizeObserver(() => { document.getElementById("size").textContent = `Window size: ${window.innerWidth}x${window.innerHeight}`; }).observe(document.body);</script></body></html>',
},
arguments: { url: server.PREFIX },
});
const response = await client.callTool({

View File

@ -19,12 +19,11 @@ import { test, expect } from './fixtures.js';
// https://github.com/microsoft/playwright/issues/35663
test.skip(({ mcpBrowser, mcpHeadless }) => mcpBrowser === 'webkit' && mcpHeadless);
test('alert dialog', async ({ client }) => {
test('alert dialog', async ({ client, server }) => {
server.setContent('/', `<button onclick="alert('Alert')">Button</button>`, 'text/html');
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><button onclick="alert(\'Alert\')">Button</button></html>',
},
arguments: { url: server.PREFIX },
})).toContainTextContent('- button "Button" [ref=s1e3]');
expect(await client.callTool({
@ -55,8 +54,8 @@ await page.getByRole('button', { name: 'Button' }).click();
// <internal code to handle "alert" dialog>
\`\`\`
- Page URL: data:text/html,<html><title>Title</title><button onclick="alert('Alert')">Button</button></html>
- Page Title: Title
- Page URL: ${server.PREFIX}
- Page Title:
- Page Snapshot
\`\`\`yaml
- button "Button" [ref=s2e3]
@ -64,13 +63,19 @@ await page.getByRole('button', { name: 'Button' }).click();
`);
});
test('two alert dialogs', async ({ client }) => {
test('two alert dialogs', async ({ client, server }) => {
test.fixme(true, 'Race between the dialog and ariaSnapshot');
server.setContent('/', `
<title>Title</title>
<body>
<button onclick="alert('Alert 1');alert('Alert 2');">Button</button>
</body>
`, 'text/html');
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><button onclick="alert(\'Alert 1\');alert(\'Alert 2\');">Button</button></html>',
},
arguments: { url: server.PREFIX },
})).toContainTextContent('- button "Button" [ref=s1e3]');
expect(await client.callTool({
@ -98,12 +103,17 @@ await page.getByRole('button', { name: 'Button' }).click();
expect(result).not.toContainTextContent('### Modal state');
});
test('confirm dialog (true)', async ({ client }) => {
test('confirm dialog (true)', async ({ client, server }) => {
server.setContent('/', `
<title>Title</title>
<body>
<button onclick="document.body.textContent = confirm('Confirm')">Button</button>
</body>
`, 'text/html');
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><button onclick="document.body.textContent = confirm(\'Confirm\')">Button</button></html>',
},
arguments: { url: server.PREFIX },
})).toContainTextContent('- button "Button" [ref=s1e3]');
expect(await client.callTool({
@ -130,12 +140,17 @@ test('confirm dialog (true)', async ({ client }) => {
\`\`\``);
});
test('confirm dialog (false)', async ({ client }) => {
test('confirm dialog (false)', async ({ client, server }) => {
server.setContent('/', `
<title>Title</title>
<body>
<button onclick="document.body.textContent = confirm('Confirm')">Button</button>
</body>
`, 'text/html');
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><button onclick="document.body.textContent = confirm(\'Confirm\')">Button</button></html>',
},
arguments: { url: server.PREFIX },
})).toContainTextContent('- button "Button" [ref=s1e3]');
expect(await client.callTool({
@ -160,12 +175,17 @@ test('confirm dialog (false)', async ({ client }) => {
\`\`\``);
});
test('prompt dialog', async ({ client }) => {
test('prompt dialog', async ({ client, server }) => {
server.setContent('/', `
<title>Title</title>
<body>
<button onclick="document.body.textContent = prompt('Prompt')">Button</button>
</body>
`, 'text/html');
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><button onclick="document.body.textContent = prompt(\'Prompt\')">Button</button></html>',
},
arguments: { url: server.PREFIX },
})).toContainTextContent('- button "Button" [ref=s1e3]');
expect(await client.callTool({

View File

@ -18,12 +18,15 @@ import { test, expect } from './fixtures.js';
import fs from 'fs/promises';
import path from 'path';
test('browser_file_upload', async ({ client, localOutputPath }) => {
test('browser_file_upload', async ({ client, localOutputPath, server }) => {
server.setContent('/', `
<input type="file" />
<button>Button</button>
`, 'text/html');
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><input type="file" /><button>Button</button></html>',
},
arguments: { url: server.PREFIX },
})).toContainTextContent(`
\`\`\`yaml
- button "Choose File" [ref=s1e3]
@ -96,17 +99,18 @@ The tool "browser_file_upload" can only be used when there is related modal stat
}
});
test('clicking on download link emits download', async ({ startClient, localOutputPath }) => {
test('clicking on download link emits download', async ({ startClient, localOutputPath, server }) => {
const outputDir = localOutputPath('output');
const client = await startClient({
config: { outputDir },
});
server.setContent('/', `<a href="/download" download="test.txt">Download</a>`, 'text/html');
server.setContent('/download', 'Data', 'text/plain');
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<a href="data:text/plain,Hello world!" download="test.txt">Download</a>',
},
arguments: { url: server.PREFIX },
})).toContainTextContent('- link "Download" [ref=s1e3]');
await client.callTool({
name: 'browser_click',
@ -133,7 +137,7 @@ test('navigating to download link emits download', async ({ client, server, mcpB
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: server.PREFIX + '/download',
url: server.PREFIX + 'download',
},
})).toContainTextContent('### Downloads');
});

View File

@ -16,12 +16,10 @@
import { test, expect } from './fixtures.js';
test('test reopen browser', async ({ client }) => {
test('test reopen browser', async ({ client, server }) => {
await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
arguments: { url: server.HELLO_WORLD },
});
expect(await client.callTool({
@ -31,19 +29,15 @@ test('test reopen browser', async ({ client }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`- generic [ref=s1e2]: Hello, world!`);
});
test('executable path', async ({ startClient }) => {
test('executable path', async ({ startClient, server }) => {
const client = await startClient({ args: [`--executable-path=bogus`] });
const response = await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
arguments: { url: server.HELLO_WORLD },
});
expect(response).toContainTextContent(`executable doesn't exist`);
});

View File

@ -17,15 +17,11 @@
import { test, expect } from './fixtures.js';
test('browser_network_requests', async ({ client, server }) => {
server.route('/', (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`<button onclick="fetch('/json')">Click me</button>`);
});
server.setContent('/', `
<button onclick="fetch('/json')">Click me</button>
`, 'text/html');
server.route('/json', (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ name: 'John Doe' }));
});
server.setContent('/json', JSON.stringify({ name: 'John Doe' }), 'application/json');
await client.callTool({
name: 'browser_navigate',
@ -45,5 +41,6 @@ test('browser_network_requests', async ({ client, server }) => {
await expect.poll(() => client.callTool({
name: 'browser_network_requests',
arguments: {},
})).toHaveTextContent(`[GET] ${`${server.PREFIX}/json`} => [200] OK`);
})).toHaveTextContent(`[GET] ${`${server.PREFIX}`} => [200] OK
[GET] ${`${server.PREFIX}json`} => [200] OK`);
});

View File

@ -18,13 +18,11 @@ import fs from 'fs';
import { test, expect } from './fixtures.js';
test('save as pdf unavailable', async ({ startClient }) => {
test('save as pdf unavailable', async ({ startClient, server }) => {
const client = await startClient({ args: ['--caps="no-pdf"'] });
await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
arguments: { url: server.HELLO_WORLD },
});
expect(await client.callTool({
@ -32,13 +30,12 @@ test('save as pdf unavailable', async ({ startClient }) => {
})).toHaveTextContent(/Tool \"browser_pdf_save\" not found/);
});
test('save as pdf', async ({ client, mcpBrowser }) => {
test('save as pdf', async ({ client, mcpBrowser, server }) => {
test.skip(!!mcpBrowser && !['chromium', 'chrome', 'msedge'].includes(mcpBrowser), 'Save as PDF is only supported in Chromium.');
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`- generic [ref=s1e2]: Hello, world!`);
const response = await client.callTool({
@ -48,18 +45,16 @@ test('save as pdf', async ({ client, mcpBrowser }) => {
expect(response).toHaveTextContent(/Save page as.*page-[^:]+.pdf/);
});
test('save as pdf (filename: output.pdf)', async ({ startClient, mcpBrowser }, testInfo) => {
test('save as pdf (filename: output.pdf)', async ({ startClient, mcpBrowser, server, localOutputPath }) => {
test.skip(!!mcpBrowser && !['chromium', 'chrome', 'msedge'].includes(mcpBrowser), 'Save as PDF is only supported in Chromium.');
const outputDir = testInfo.outputPath('output');
const outputDir = localOutputPath('output');
const client = await startClient({
config: { outputDir },
});
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`- generic [ref=s1e2]: Hello, world!`);
expect(await client.callTool({

View File

@ -31,11 +31,8 @@ const fetchPage = async (client: Client, url: string) => {
};
test('default to allow all', async ({ server, client }) => {
server.route('/ppp', (_req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('content:PPP');
});
const result = await fetchPage(client, server.PREFIX + '/ppp');
server.setContent('/ppp', 'content:PPP', 'text/html');
const result = await fetchPage(client, server.PREFIX + 'ppp');
expect(result).toContain('content:PPP');
});
@ -48,14 +45,11 @@ test('blocked works', async ({ startClient }) => {
});
test('allowed works', async ({ server, startClient }) => {
server.route('/ppp', (_req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('content:PPP');
});
server.setContent('/ppp', 'content:PPP', 'text/html');
const client = await startClient({
args: ['--allowed-origins', `microsoft.com;${new URL(server.PREFIX).host};playwright.dev`]
});
const result = await fetchPage(client, server.PREFIX + '/ppp');
const result = await fetchPage(client, server.PREFIX + 'ppp');
expect(result).toContain('content:PPP');
});
@ -79,13 +73,10 @@ test('allowed without blocked blocks all non-explicitly specified origins', asyn
});
test('blocked without allowed allows non-explicitly specified origins', async ({ server, startClient }) => {
server.route('/ppp', (_req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('content:PPP');
});
server.setContent('/ppp', 'content:PPP', 'text/html');
const client = await startClient({
args: ['--blocked-origins', 'example.com'],
});
const result = await fetchPage(client, server.PREFIX + '/ppp');
const result = await fetchPage(client, server.PREFIX + 'ppp');
expect(result).toContain('content:PPP');
});

View File

@ -18,13 +18,11 @@ import fs from 'fs';
import { test, expect } from './fixtures.js';
test('browser_take_screenshot (viewport)', async ({ client }) => {
test('browser_take_screenshot (viewport)', async ({ client, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
})).toContainTextContent(`Navigate to data:text/html`);
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`Navigate to http://localhost`);
expect(await client.callTool({
name: 'browser_take_screenshot',
@ -44,19 +42,17 @@ test('browser_take_screenshot (viewport)', async ({ client }) => {
});
});
test('browser_take_screenshot (element)', async ({ client }) => {
test('browser_take_screenshot (element)', async ({ client, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><button>Hello, world!</button></html>',
},
})).toContainTextContent(`[ref=s1e3]`);
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`[ref=s1e2]`);
expect(await client.callTool({
name: 'browser_take_screenshot',
arguments: {
element: 'hello button',
ref: 's1e3',
ref: 's1e2',
},
})).toEqual({
content: [
@ -66,24 +62,22 @@ test('browser_take_screenshot (element)', async ({ client }) => {
type: 'image',
},
{
text: expect.stringContaining(`page.getByRole('button', { name: 'Hello, world!' }).screenshot`),
text: expect.stringContaining(`page.getByText('Hello, world!').screenshot`),
type: 'text',
},
],
});
});
test('--output-dir should work', async ({ startClient, localOutputPath }) => {
test('--output-dir should work', async ({ startClient, localOutputPath, server }) => {
const outputDir = localOutputPath('output');
const client = await startClient({
args: ['--output-dir', outputDir],
});
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
})).toContainTextContent(`Navigate to data:text/html`);
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`Navigate to http://localhost`);
await client.callTool({
name: 'browser_take_screenshot',
@ -95,24 +89,20 @@ test('--output-dir should work', async ({ startClient, localOutputPath }) => {
});
for (const raw of [undefined, true]) {
test(`browser_take_screenshot (raw: ${raw})`, async ({ startClient }, testInfo) => {
test(`browser_take_screenshot (raw: ${raw})`, async ({ startClient, localOutputPath, server }) => {
const ext = raw ? 'png' : 'jpeg';
const outputDir = testInfo.outputPath('output');
const outputDir = localOutputPath('output');
const client = await startClient({
config: { outputDir },
});
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
})).toContainTextContent(`Navigate to data:text/html`);
arguments: { url: server.PREFIX },
})).toContainTextContent(`Navigate to http://localhost`);
expect(await client.callTool({
name: 'browser_take_screenshot',
arguments: {
raw,
},
arguments: { raw },
})).toEqual({
content: [
{
@ -140,17 +130,15 @@ for (const raw of [undefined, true]) {
}
test('browser_take_screenshot (filename: "output.jpeg")', async ({ startClient }, testInfo) => {
const outputDir = testInfo.outputPath('output');
test('browser_take_screenshot (filename: "output.jpeg")', async ({ startClient, localOutputPath, server }) => {
const outputDir = localOutputPath('output');
const client = await startClient({
config: { outputDir },
});
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
})).toContainTextContent(`Navigate to data:text/html`);
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`Navigate to http://localhost`);
expect(await client.callTool({
name: 'browser_take_screenshot',
@ -178,7 +166,7 @@ test('browser_take_screenshot (filename: "output.jpeg")', async ({ startClient }
expect(files[0]).toMatch(/^output.jpeg$/);
});
test('browser_take_screenshot (noImageResponses)', async ({ startClient }) => {
test('browser_take_screenshot (noImageResponses)', async ({ startClient, server }) => {
const client = await startClient({
config: {
noImageResponses: true,
@ -187,10 +175,8 @@ test('browser_take_screenshot (noImageResponses)', async ({ startClient }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
})).toContainTextContent(`Navigate to data:text/html`);
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`Navigate to http://localhost`);
await client.callTool({
name: 'browser_take_screenshot',
@ -210,15 +196,13 @@ test('browser_take_screenshot (noImageResponses)', async ({ startClient }) => {
});
});
test('browser_take_screenshot (cursor)', async ({ startClient }) => {
test('browser_take_screenshot (cursor)', async ({ startClient, server }) => {
const client = await startClient({ clientName: 'cursor:vscode' });
expect(await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
},
})).toContainTextContent(`Navigate to data:text/html`);
arguments: { url: server.HELLO_WORLD },
})).toContainTextContent(`Navigate to http://localhost`);
await client.callTool({
name: 'browser_take_screenshot',

View File

@ -141,7 +141,9 @@ test('close tab', async ({ client }) => {
\`\`\``);
});
test('reuse first tab when navigating', async ({ startClient, cdpEndpoint }) => {
test('reuse first tab when navigating', async ({ startClient, cdpEndpoint, server }) => {
server.setContent('/', `<title>Title</title><body>Body</body>`, 'text/html');
const browser = await chromium.connectOverCDP(await cdpEndpoint());
const [context] = browser.contexts();
const pages = context.pages();
@ -149,9 +151,7 @@ test('reuse first tab when navigating', async ({ startClient, cdpEndpoint }) =>
const client = await startClient({ args: [`--cdp-endpoint=${await cdpEndpoint()}`] });
await client.callTool({
name: 'browser_navigate',
arguments: {
url: 'data:text/html,<title>Title</title><body>Body</body>',
},
arguments: { url: server.PREFIX },
});
expect(pages.length).toBe(1);

View File

@ -38,6 +38,7 @@ export class TestServer {
readonly PORT: number;
readonly PREFIX: string;
readonly CROSS_PROCESS_PREFIX: string;
readonly HELLO_WORLD: string;
static async create(port: number): Promise<TestServer> {
const server = new TestServer(port);
@ -67,8 +68,9 @@ export class TestServer {
const same_origin = 'localhost';
const protocol = sslOptions ? 'https' : 'http';
this.PORT = port;
this.PREFIX = `${protocol}://${same_origin}:${port}`;
this.CROSS_PROCESS_PREFIX = `${protocol}://${cross_origin}:${port}`;
this.PREFIX = `${protocol}://${same_origin}:${port}/`;
this.CROSS_PROCESS_PREFIX = `${protocol}://${cross_origin}:${port}/`;
this.HELLO_WORLD = `${this.PREFIX}hello-world`;
}
setCSP(path: string, csp: string) {
@ -88,6 +90,13 @@ export class TestServer {
this._routes.set(path, handler);
}
setContent(path: string, content: string, mimeType: string) {
this.route(path, (req, res) => {
res.writeHead(200, { 'Content-Type': mimeType });
res.end(mimeType === 'text/html' ? `<!DOCTYPE html>${content}` : content);
});
}
redirect(from: string, to: string) {
this.route(from, (req, res) => {
const headers = this._extraHeaders.get(req.url!) || {};
@ -120,6 +129,15 @@ export class TestServer {
for (const subscriber of this._requestSubscribers.values())
subscriber[rejectSymbol].call(null, error);
this._requestSubscribers.clear();
this.setContent('/favicon.ico', '', 'image/x-icon');
this.setContent('/', ``, 'text/html');
this.setContent('/hello-world', `
<title>Title</title>
<body>Hello, world!</body>
`, 'text/html');
}
_onRequest(request: http.IncomingMessage, response: http.ServerResponse) {
@ -144,7 +162,11 @@ export class TestServer {
this._requestSubscribers.delete(path);
}
const handler = this._routes.get(path);
if (handler)
if (handler) {
handler.call(null, request, response);
} else {
response.writeHead(404);
response.end();
}
}
}