mirror of
https://github.com/microsoft/playwright-mcp.git
synced 2025-07-27 00:52:27 +08:00
feat(network): implement listing network requests (#247)
Fixes: https://github.com/microsoft/playwright-mcp/issues/242
This commit is contained in:
parent
c80f7cf222
commit
1bc3c761de
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -58,4 +58,4 @@ jobs:
|
|||||||
run: npx playwright install --with-deps
|
run: npx playwright install --with-deps
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: npm test
|
run: npm test -- --forbid-only
|
||||||
|
@ -317,6 +317,7 @@ export class Tab {
|
|||||||
readonly context: Context;
|
readonly context: Context;
|
||||||
readonly page: playwright.Page;
|
readonly page: playwright.Page;
|
||||||
private _console: playwright.ConsoleMessage[] = [];
|
private _console: playwright.ConsoleMessage[] = [];
|
||||||
|
private _requests: Map<playwright.Request, playwright.Response | null> = new Map();
|
||||||
private _snapshot: PageSnapshot | undefined;
|
private _snapshot: PageSnapshot | undefined;
|
||||||
private _onPageClose: (tab: Tab) => void;
|
private _onPageClose: (tab: Tab) => void;
|
||||||
|
|
||||||
@ -325,9 +326,11 @@ export class Tab {
|
|||||||
this.page = page;
|
this.page = page;
|
||||||
this._onPageClose = onPageClose;
|
this._onPageClose = onPageClose;
|
||||||
page.on('console', event => this._console.push(event));
|
page.on('console', event => this._console.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 => {
|
page.on('framenavigated', frame => {
|
||||||
if (!frame.parentFrame())
|
if (!frame.parentFrame())
|
||||||
this._console.length = 0;
|
this._clearCollectedArtifacts();
|
||||||
});
|
});
|
||||||
page.on('close', () => this._onClose());
|
page.on('close', () => this._onClose());
|
||||||
page.on('filechooser', chooser => {
|
page.on('filechooser', chooser => {
|
||||||
@ -342,8 +345,13 @@ export class Tab {
|
|||||||
page.setDefaultTimeout(5000);
|
page.setDefaultTimeout(5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onClose() {
|
private _clearCollectedArtifacts() {
|
||||||
this._console.length = 0;
|
this._console.length = 0;
|
||||||
|
this._requests.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onClose() {
|
||||||
|
this._clearCollectedArtifacts();
|
||||||
this._onPageClose(this);
|
this._onPageClose(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,6 +375,10 @@ export class Tab {
|
|||||||
return this._console;
|
return this._console;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async requests(): Promise<Map<playwright.Request, playwright.Response | null>> {
|
||||||
|
return this._requests;
|
||||||
|
}
|
||||||
|
|
||||||
async captureSnapshot() {
|
async captureSnapshot() {
|
||||||
this._snapshot = await PageSnapshot.create(this.page);
|
this._snapshot = await PageSnapshot.create(this.page);
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import files from './tools/files';
|
|||||||
import install from './tools/install';
|
import install from './tools/install';
|
||||||
import keyboard from './tools/keyboard';
|
import keyboard from './tools/keyboard';
|
||||||
import navigate from './tools/navigate';
|
import navigate from './tools/navigate';
|
||||||
|
import network from './tools/network';
|
||||||
import pdf from './tools/pdf';
|
import pdf from './tools/pdf';
|
||||||
import snapshot from './tools/snapshot';
|
import snapshot from './tools/snapshot';
|
||||||
import tabs from './tools/tabs';
|
import tabs from './tools/tabs';
|
||||||
@ -43,6 +44,7 @@ const snapshotTools: Tool<any>[] = [
|
|||||||
...install,
|
...install,
|
||||||
...keyboard(true),
|
...keyboard(true),
|
||||||
...navigate(true),
|
...navigate(true),
|
||||||
|
...network,
|
||||||
...pdf,
|
...pdf,
|
||||||
...snapshot,
|
...snapshot,
|
||||||
...tabs(true),
|
...tabs(true),
|
||||||
@ -56,6 +58,7 @@ const screenshotTools: Tool<any>[] = [
|
|||||||
...install,
|
...install,
|
||||||
...keyboard(false),
|
...keyboard(false),
|
||||||
...navigate(false),
|
...navigate(false),
|
||||||
|
...network,
|
||||||
...pdf,
|
...pdf,
|
||||||
...screen,
|
...screen,
|
||||||
...tabs(false),
|
...tabs(false),
|
||||||
|
57
src/tools/network.ts
Normal file
57
src/tools/network.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { defineTool } from './tool';
|
||||||
|
|
||||||
|
import type * as playwright from 'playwright';
|
||||||
|
|
||||||
|
const requests = defineTool({
|
||||||
|
capability: 'core',
|
||||||
|
|
||||||
|
schema: {
|
||||||
|
name: 'browser_network_requests',
|
||||||
|
description: 'Returns all network requests since loading the page',
|
||||||
|
inputSchema: z.object({}),
|
||||||
|
},
|
||||||
|
|
||||||
|
handle: async context => {
|
||||||
|
const requests = await context.currentTabOrDie().requests();
|
||||||
|
const log = [...requests.entries()].map(([request, response]) => renderRequest(request, response)).join('\n');
|
||||||
|
return {
|
||||||
|
code: [`// <internal code to list network requests>`],
|
||||||
|
action: async () => {
|
||||||
|
return {
|
||||||
|
content: [{ type: 'text', text: log }]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
captureSnapshot: false,
|
||||||
|
waitForNetwork: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderRequest(request: playwright.Request, response: playwright.Response | null) {
|
||||||
|
const result: string[] = [];
|
||||||
|
result.push(`[${request.method().toUpperCase()}] ${request.url()}`);
|
||||||
|
if (response)
|
||||||
|
result.push(`=> [${response.status()}] ${response.statusText()}`);
|
||||||
|
return result.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default [
|
||||||
|
requests,
|
||||||
|
];
|
@ -32,6 +32,7 @@ test('test snapshot tool list', async ({ client }) => {
|
|||||||
'browser_navigate_back',
|
'browser_navigate_back',
|
||||||
'browser_navigate_forward',
|
'browser_navigate_forward',
|
||||||
'browser_navigate',
|
'browser_navigate',
|
||||||
|
'browser_network_requests',
|
||||||
'browser_pdf_save',
|
'browser_pdf_save',
|
||||||
'browser_press_key',
|
'browser_press_key',
|
||||||
'browser_resize',
|
'browser_resize',
|
||||||
@ -56,6 +57,7 @@ test('test vision tool list', async ({ visionClient }) => {
|
|||||||
'browser_navigate_back',
|
'browser_navigate_back',
|
||||||
'browser_navigate_forward',
|
'browser_navigate_forward',
|
||||||
'browser_navigate',
|
'browser_navigate',
|
||||||
|
'browser_network_requests',
|
||||||
'browser_pdf_save',
|
'browser_pdf_save',
|
||||||
'browser_press_key',
|
'browser_press_key',
|
||||||
'browser_resize',
|
'browser_resize',
|
||||||
|
@ -21,6 +21,7 @@ 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 { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
|
import { TestServer } from './testserver';
|
||||||
|
|
||||||
type TestFixtures = {
|
type TestFixtures = {
|
||||||
client: Client;
|
client: Client;
|
||||||
@ -28,11 +29,14 @@ type TestFixtures = {
|
|||||||
startClient: (options?: { args?: string[] }) => Promise<Client>;
|
startClient: (options?: { args?: string[] }) => Promise<Client>;
|
||||||
wsEndpoint: string;
|
wsEndpoint: string;
|
||||||
cdpEndpoint: string;
|
cdpEndpoint: string;
|
||||||
|
server: TestServer;
|
||||||
|
httpsServer: TestServer;
|
||||||
};
|
};
|
||||||
|
|
||||||
type WorkerFixtures = {
|
type WorkerFixtures = {
|
||||||
mcpHeadless: boolean;
|
mcpHeadless: boolean;
|
||||||
mcpBrowser: string | undefined;
|
mcpBrowser: string | undefined;
|
||||||
|
_workerServers: { server: TestServer, httpsServer: TestServer };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const test = baseTest.extend<TestFixtures, WorkerFixtures>({
|
export const test = baseTest.extend<TestFixtures, WorkerFixtures>({
|
||||||
@ -103,7 +107,32 @@ export const test = baseTest.extend<TestFixtures, WorkerFixtures>({
|
|||||||
await use(headless);
|
await use(headless);
|
||||||
}, { scope: 'worker' }],
|
}, { scope: 'worker' }],
|
||||||
|
|
||||||
mcpBrowser: ['chromium', { option: true, scope: 'worker' }],
|
mcpBrowser: ['chrome', { option: true, scope: 'worker' }],
|
||||||
|
|
||||||
|
_workerServers: [async ({}, use, workerInfo) => {
|
||||||
|
const port = 8907 + workerInfo.workerIndex * 4;
|
||||||
|
const server = await TestServer.create(port);
|
||||||
|
|
||||||
|
const httpsPort = port + 1;
|
||||||
|
const httpsServer = await TestServer.createHTTPS(httpsPort);
|
||||||
|
|
||||||
|
await use({ server, httpsServer });
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
server.stop(),
|
||||||
|
httpsServer.stop(),
|
||||||
|
]);
|
||||||
|
}, { scope: 'worker' }],
|
||||||
|
|
||||||
|
server: async ({ _workerServers }, use) => {
|
||||||
|
_workerServers.server.reset();
|
||||||
|
await use(_workerServers.server);
|
||||||
|
},
|
||||||
|
|
||||||
|
httpsServer: async ({ _workerServers }, use) => {
|
||||||
|
_workerServers.httpsServer.reset();
|
||||||
|
await use(_workerServers.httpsServer);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
type Response = Awaited<ReturnType<Client['callTool']>>;
|
type Response = Awaited<ReturnType<Client['callTool']>>;
|
||||||
|
49
tests/network.spec.ts
Normal file
49
tests/network.spec.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test, expect } from './fixtures';
|
||||||
|
|
||||||
|
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.route('/json', (req, res) => {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ name: 'John Doe' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.callTool({
|
||||||
|
name: 'browser_navigate',
|
||||||
|
arguments: {
|
||||||
|
url: server.PREFIX,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.callTool({
|
||||||
|
name: 'browser_click',
|
||||||
|
arguments: {
|
||||||
|
element: 'Click me button',
|
||||||
|
ref: 's1e3',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect.poll(() => client.callTool({
|
||||||
|
name: 'browser_network_requests',
|
||||||
|
arguments: {},
|
||||||
|
})).toHaveTextContent(`[GET] http://localhost:8907/json => [200] OK`);
|
||||||
|
});
|
29
tests/testserver/cert.pem
Normal file
29
tests/testserver/cert.pem
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
145
tests/testserver/index.ts
Normal file
145
tests/testserver/index.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
* Modifications copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import http from 'http';
|
||||||
|
import https from 'https';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const fulfillSymbol = Symbol('fulfil callback');
|
||||||
|
const rejectSymbol = Symbol('reject callback');
|
||||||
|
|
||||||
|
export class TestServer {
|
||||||
|
private _server: http.Server;
|
||||||
|
readonly debugServer: any;
|
||||||
|
private _routes = new Map<string, (request: http.IncomingMessage, response: http.ServerResponse) => any>();
|
||||||
|
private _csp = new Map<string, string>();
|
||||||
|
private _extraHeaders = new Map<string, object>();
|
||||||
|
private _requestSubscribers = new Map<string, Promise<any>>();
|
||||||
|
readonly PORT: number;
|
||||||
|
readonly PREFIX: string;
|
||||||
|
readonly CROSS_PROCESS_PREFIX: string;
|
||||||
|
|
||||||
|
static async create(port: number): Promise<TestServer> {
|
||||||
|
const server = new TestServer(port);
|
||||||
|
await new Promise(x => server._server.once('listening', x));
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async createHTTPS(port: number): Promise<TestServer> {
|
||||||
|
const server = new TestServer(port, {
|
||||||
|
key: await fs.promises.readFile(path.join(__dirname, 'key.pem')),
|
||||||
|
cert: await fs.promises.readFile(path.join(__dirname, 'cert.pem')),
|
||||||
|
passphrase: 'aaaa',
|
||||||
|
});
|
||||||
|
await new Promise(x => server._server.once('listening', x));
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(port: number, sslOptions?: object) {
|
||||||
|
if (sslOptions)
|
||||||
|
this._server = https.createServer(sslOptions, this._onRequest.bind(this));
|
||||||
|
else
|
||||||
|
this._server = http.createServer(this._onRequest.bind(this));
|
||||||
|
this._server.listen(port);
|
||||||
|
this.debugServer = require('debug')('pw:testserver');
|
||||||
|
|
||||||
|
const cross_origin = '127.0.0.1';
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCSP(path: string, csp: string) {
|
||||||
|
this._csp.set(path, csp);
|
||||||
|
}
|
||||||
|
|
||||||
|
setExtraHeaders(path: string, object: Record<string, string>) {
|
||||||
|
this._extraHeaders.set(path, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop() {
|
||||||
|
this.reset();
|
||||||
|
await new Promise(x => this._server.close(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
route(path: string, handler: (request: http.IncomingMessage, response: http.ServerResponse) => any) {
|
||||||
|
this._routes.set(path, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect(from: string, to: string) {
|
||||||
|
this.route(from, (req, res) => {
|
||||||
|
const headers = this._extraHeaders.get(req.url!) || {};
|
||||||
|
res.writeHead(302, { ...headers, location: to });
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForRequest(path: string): Promise<http.IncomingMessage> {
|
||||||
|
let promise = this._requestSubscribers.get(path);
|
||||||
|
if (promise)
|
||||||
|
return promise;
|
||||||
|
let fulfill, reject;
|
||||||
|
promise = new Promise((f, r) => {
|
||||||
|
fulfill = f;
|
||||||
|
reject = r;
|
||||||
|
});
|
||||||
|
promise[fulfillSymbol] = fulfill;
|
||||||
|
promise[rejectSymbol] = reject;
|
||||||
|
this._requestSubscribers.set(path, promise);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this._routes.clear();
|
||||||
|
this._csp.clear();
|
||||||
|
this._extraHeaders.clear();
|
||||||
|
this._server.closeAllConnections();
|
||||||
|
const error = new Error('Static Server has been reset');
|
||||||
|
for (const subscriber of this._requestSubscribers.values())
|
||||||
|
subscriber[rejectSymbol].call(null, error);
|
||||||
|
this._requestSubscribers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRequest(request: http.IncomingMessage, response: http.ServerResponse) {
|
||||||
|
request.on('error', error => {
|
||||||
|
if ((error as any).code === 'ECONNRESET')
|
||||||
|
response.end();
|
||||||
|
else
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
(request as any).postBody = new Promise(resolve => {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
request.on('data', chunk => {
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
request.on('end', () => resolve(Buffer.concat(chunks)));
|
||||||
|
});
|
||||||
|
const path = request.url || '/';
|
||||||
|
this.debugServer(`request ${request.method} ${path}`);
|
||||||
|
// Notify request subscriber.
|
||||||
|
if (this._requestSubscribers.has(path)) {
|
||||||
|
this._requestSubscribers.get(path)![fulfillSymbol].call(null, request);
|
||||||
|
this._requestSubscribers.delete(path);
|
||||||
|
}
|
||||||
|
const handler = this._routes.get(path);
|
||||||
|
if (handler)
|
||||||
|
handler.call(null, request, response);
|
||||||
|
}
|
||||||
|
}
|
52
tests/testserver/key.pem
Normal file
52
tests/testserver/key.pem
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
ZkZVSOEp+sYBf/tmptlKr49nO+dTjQ==
|
||||||
|
-----END PRIVATE KEY-----
|
19
tests/testserver/san.cnf
Normal file
19
tests/testserver/san.cnf
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# openssl req -new -x509 -days 3650 -key key.pem -out cert.pem -config san.cnf -extensions v3_req
|
||||||
|
|
||||||
|
[req]
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
req_extensions = v3_req
|
||||||
|
prompt = no
|
||||||
|
|
||||||
|
[req_distinguished_name]
|
||||||
|
CN = playwright-test
|
||||||
|
|
||||||
|
[v3_req]
|
||||||
|
basicConstraints = CA:FALSE
|
||||||
|
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = localhost
|
||||||
|
IP.1 = 127.0.0.1
|
||||||
|
IP.2 = ::1
|
Loading…
x
Reference in New Issue
Block a user