mirror of
https://github.com/microsoft/playwright-mcp.git
synced 2025-07-26 16:42:27 +08:00
chore: fix operation over cdp (#440)
Ref https://github.com/microsoft/playwright-mcp/issues/439
This commit is contained in:
parent
c2b7fb29de
commit
1318e39fac
@ -40,6 +40,9 @@ export class PageSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _build() {
|
private async _build() {
|
||||||
|
// FIXME: Rountrip evaluate to ensure _snapshotForAI works.
|
||||||
|
// This probably broke once we moved off locator snapshots
|
||||||
|
await this._page.evaluate(() => 1);
|
||||||
const snapshot = await callOnPageNoTrace(this._page, page => (page as PageEx)._snapshotForAI());
|
const snapshot = await callOnPageNoTrace(this._page, page => (page as PageEx)._snapshotForAI());
|
||||||
this._text = [
|
this._text = [
|
||||||
`- Page Snapshot`,
|
`- Page Snapshot`,
|
||||||
|
@ -16,16 +16,21 @@
|
|||||||
|
|
||||||
import { test, expect } from './fixtures.js';
|
import { test, expect } from './fixtures.js';
|
||||||
|
|
||||||
test('cdp server', async ({ cdpEndpoint, startClient, server }) => {
|
test('cdp server', async ({ cdpServer, startClient, server }) => {
|
||||||
const client = await startClient({ args: [`--cdp-endpoint=${await cdpEndpoint()}`] });
|
await cdpServer.start();
|
||||||
|
const client = await startClient({ args: [`--cdp-endpoint=${cdpServer.endpoint}`] });
|
||||||
expect(await client.callTool({
|
expect(await client.callTool({
|
||||||
name: 'browser_navigate',
|
name: 'browser_navigate',
|
||||||
arguments: { url: server.HELLO_WORLD },
|
arguments: { url: server.HELLO_WORLD },
|
||||||
})).toContainTextContent(`- generic [ref=e1]: Hello, world!`);
|
})).toContainTextContent(`- generic [ref=e1]: Hello, world!`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cdp server reuse tab', async ({ cdpEndpoint, startClient }) => {
|
test('cdp server reuse tab', async ({ cdpServer, startClient, server }) => {
|
||||||
const client = await startClient({ args: [`--cdp-endpoint=${await cdpEndpoint()}`] });
|
const browserContext = await cdpServer.start();
|
||||||
|
const client = await startClient({ args: [`--cdp-endpoint=${cdpServer.endpoint}`] });
|
||||||
|
|
||||||
|
const [page] = browserContext.pages();
|
||||||
|
await page.goto(server.HELLO_WORLD);
|
||||||
|
|
||||||
expect(await client.callTool({
|
expect(await client.callTool({
|
||||||
name: 'browser_click',
|
name: 'browser_click',
|
||||||
@ -43,18 +48,17 @@ test('cdp server reuse tab', async ({ cdpEndpoint, startClient }) => {
|
|||||||
// <internal code to capture accessibility snapshot>
|
// <internal code to capture accessibility snapshot>
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
- Page URL: data:text/html,hello world
|
- Page URL: ${server.HELLO_WORLD}
|
||||||
- Page Title:
|
- Page Title: Title
|
||||||
- Page Snapshot
|
- Page Snapshot
|
||||||
\`\`\`yaml
|
\`\`\`yaml
|
||||||
- generic [ref=e1]: hello world
|
- generic [ref=e1]: Hello, world!
|
||||||
\`\`\`
|
\`\`\`
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should throw connection error and allow re-connecting', async ({ cdpEndpoint, startClient, server }) => {
|
test('should throw connection error and allow re-connecting', async ({ cdpServer, startClient, server }) => {
|
||||||
const port = 3200 + test.info().parallelIndex;
|
const client = await startClient({ args: [`--cdp-endpoint=${cdpServer.endpoint}`] });
|
||||||
const client = await startClient({ args: [`--cdp-endpoint=http://localhost:${port}`] });
|
|
||||||
|
|
||||||
server.setContent('/', `
|
server.setContent('/', `
|
||||||
<title>Title</title>
|
<title>Title</title>
|
||||||
@ -65,7 +69,7 @@ test('should throw connection error and allow re-connecting', async ({ cdpEndpoi
|
|||||||
name: 'browser_navigate',
|
name: 'browser_navigate',
|
||||||
arguments: { url: server.PREFIX },
|
arguments: { url: server.PREFIX },
|
||||||
})).toContainTextContent(`Error: browserType.connectOverCDP: connect ECONNREFUSED`);
|
})).toContainTextContent(`Error: browserType.connectOverCDP: connect ECONNREFUSED`);
|
||||||
await cdpEndpoint(port);
|
await cdpServer.start();
|
||||||
expect(await client.callTool({
|
expect(await client.callTool({
|
||||||
name: 'browser_navigate',
|
name: 'browser_navigate',
|
||||||
arguments: { url: server.PREFIX },
|
arguments: { url: server.PREFIX },
|
||||||
|
@ -22,22 +22,27 @@ import { chromium } from 'playwright';
|
|||||||
import { test as baseTest, expect as baseExpect } from '@playwright/test';
|
import { test as baseTest, expect as baseExpect } from '@playwright/test';
|
||||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||||
import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
|
|
||||||
import { TestServer } from './testserver/index.ts';
|
import { TestServer } from './testserver/index.ts';
|
||||||
|
|
||||||
import type { Config } from '../config';
|
import type { Config } from '../config';
|
||||||
|
import type { BrowserContext } from 'playwright';
|
||||||
|
|
||||||
export type TestOptions = {
|
export type TestOptions = {
|
||||||
mcpBrowser: string | undefined;
|
mcpBrowser: string | undefined;
|
||||||
mcpMode: 'docker' | undefined;
|
mcpMode: 'docker' | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CDPServer = {
|
||||||
|
endpoint: string;
|
||||||
|
start: () => Promise<BrowserContext>;
|
||||||
|
};
|
||||||
|
|
||||||
type TestFixtures = {
|
type TestFixtures = {
|
||||||
client: Client;
|
client: Client;
|
||||||
visionClient: Client;
|
visionClient: Client;
|
||||||
startClient: (options?: { clientName?: string, args?: string[], config?: Config }) => Promise<Client>;
|
startClient: (options?: { clientName?: string, args?: string[], config?: Config }) => Promise<Client>;
|
||||||
wsEndpoint: string;
|
wsEndpoint: string;
|
||||||
cdpEndpoint: (port?: number) => Promise<string>;
|
cdpServer: CDPServer;
|
||||||
server: TestServer;
|
server: TestServer;
|
||||||
httpsServer: TestServer;
|
httpsServer: TestServer;
|
||||||
mcpHeadless: boolean;
|
mcpHeadless: boolean;
|
||||||
@ -95,39 +100,25 @@ export const test = baseTest.extend<TestFixtures & TestOptions, WorkerFixtures>(
|
|||||||
await browserServer.close();
|
await browserServer.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
cdpEndpoint: async ({ }, use, testInfo) => {
|
cdpServer: async ({ mcpBrowser }, use, testInfo) => {
|
||||||
let browserProcess: ChildProcessWithoutNullStreams | undefined;
|
test.skip(!['chrome', 'msedge', 'chromium'].includes(mcpBrowser!), 'CDP is not supported for non-Chromium browsers');
|
||||||
|
|
||||||
await use(async port => {
|
let browserContext: BrowserContext | undefined;
|
||||||
if (!port)
|
const port = 3200 + test.info().parallelIndex;
|
||||||
port = 3200 + test.info().parallelIndex;
|
await use({
|
||||||
if (browserProcess)
|
endpoint: `http://localhost:${port}`,
|
||||||
return `http://localhost:${port}`;
|
start: async () => {
|
||||||
browserProcess = spawn(chromium.executablePath(), [
|
browserContext = await chromium.launchPersistentContext(testInfo.outputPath('cdp-user-data-dir'), {
|
||||||
`--user-data-dir=${testInfo.outputPath('user-data-dir')}`,
|
channel: mcpBrowser,
|
||||||
`--remote-debugging-port=${port}`,
|
headless: true,
|
||||||
`--no-first-run`,
|
args: [
|
||||||
`--no-sandbox`,
|
`--remote-debugging-port=${port}`,
|
||||||
`--headless`,
|
],
|
||||||
'--use-mock-keychain',
|
|
||||||
`data:text/html,hello world`,
|
|
||||||
], {
|
|
||||||
stdio: 'pipe',
|
|
||||||
});
|
|
||||||
await new Promise<void>(resolve => {
|
|
||||||
browserProcess!.stderr.on('data', data => {
|
|
||||||
if (data.toString().includes('DevTools listening on '))
|
|
||||||
resolve();
|
|
||||||
});
|
});
|
||||||
});
|
return browserContext;
|
||||||
return `http://localhost:${port}`;
|
}
|
||||||
});
|
|
||||||
await new Promise<void>(resolve => {
|
|
||||||
if (!browserProcess)
|
|
||||||
return resolve();
|
|
||||||
browserProcess.on('exit', () => resolve());
|
|
||||||
browserProcess.kill();
|
|
||||||
});
|
});
|
||||||
|
await browserContext?.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
mcpHeadless: async ({ headless }, use) => {
|
mcpHeadless: async ({ headless }, use) => {
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { chromium } from 'playwright';
|
|
||||||
|
|
||||||
import { test, expect } from './fixtures.js';
|
import { test, expect } from './fixtures.js';
|
||||||
|
|
||||||
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||||
@ -139,17 +137,14 @@ test('close tab', async ({ client }) => {
|
|||||||
\`\`\``);
|
\`\`\``);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('reuse first tab when navigating', async ({ startClient, cdpEndpoint, server }) => {
|
test('reuse first tab when navigating', async ({ startClient, cdpServer, server }) => {
|
||||||
server.setContent('/', `<title>Title</title><body>Body</body>`, 'text/html');
|
const browserContext = await cdpServer.start();
|
||||||
|
const pages = browserContext.pages();
|
||||||
|
|
||||||
const browser = await chromium.connectOverCDP(await cdpEndpoint());
|
const client = await startClient({ args: [`--cdp-endpoint=${cdpServer.endpoint}`] });
|
||||||
const [context] = browser.contexts();
|
|
||||||
const pages = context.pages();
|
|
||||||
|
|
||||||
const client = await startClient({ args: [`--cdp-endpoint=${await cdpEndpoint()}`] });
|
|
||||||
await client.callTool({
|
await client.callTool({
|
||||||
name: 'browser_navigate',
|
name: 'browser_navigate',
|
||||||
arguments: { url: server.PREFIX },
|
arguments: { url: server.HELLO_WORLD },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(pages.length).toBe(1);
|
expect(pages.length).toBe(1);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user