chore: convert console resource to tool (#193)

This commit is contained in:
Pavel Feldman 2025-04-15 18:01:59 -07:00 committed by GitHub
parent e4331313f9
commit 5c2e11017d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 66 additions and 61 deletions

View File

@ -298,6 +298,12 @@ server.connect(transport);
- Parameters: - Parameters:
- `key` (string): Name of the key to press or a character to generate, such as `ArrowLeft` or `a` - `key` (string): Name of the key to press or a character to generate, such as `ArrowLeft` or `a`
### Console
- **browser_console_messages**
- Description: Returns all console messages
- Parameters: None
### Files and Media ### Files and Media
- **browser_file_upload** - **browser_file_upload**

View File

@ -111,18 +111,21 @@ export class Context {
if (this._currentTab === tab) if (this._currentTab === tab)
this._currentTab = this._tabs[Math.min(index, this._tabs.length - 1)]; this._currentTab = this._tabs[Math.min(index, this._tabs.length - 1)];
const browser = this._browser; if (this._browserContext && !this._tabs.length)
if (this._browserContext && !this._tabs.length) { void this.close();
void this._browserContext.close().then(() => browser?.close()).catch(() => {});
this._browser = undefined;
this._browserContext = undefined;
}
} }
async close() { async close() {
if (!this._browserContext) if (!this._browserContext)
return; return;
await this._browserContext.close(); const browserContext = this._browserContext;
const browser = this._browser;
this._browserContext = undefined;
this._browser = undefined;
await browserContext?.close().then(async () => {
await browser?.close();
}).catch(() => {});
} }
private async _ensureBrowserContext() { private async _ensureBrowserContext() {

View File

@ -20,6 +20,7 @@ import fs from 'fs';
import { createServerWithTools } from './server'; import { createServerWithTools } from './server';
import common from './tools/common'; import common from './tools/common';
import console from './tools/console';
import files from './tools/files'; 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';
@ -28,15 +29,14 @@ 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';
import screen from './tools/screen'; import screen from './tools/screen';
import { console as consoleResource } from './resources/console';
import type { Tool, ToolCapability } from './tools/tool'; import type { Tool, ToolCapability } from './tools/tool';
import type { Resource } from './resources/resource';
import type { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
import type { LaunchOptions } from 'playwright'; import type { LaunchOptions } from 'playwright';
const snapshotTools: Tool[] = [ const snapshotTools: Tool[] = [
...common(true), ...common(true),
...console,
...files(true), ...files(true),
...install, ...install,
...keyboard(true), ...keyboard(true),
@ -48,6 +48,7 @@ const snapshotTools: Tool[] = [
const screenshotTools: Tool[] = [ const screenshotTools: Tool[] = [
...common(false), ...common(false),
...console,
...files(false), ...files(false),
...install, ...install,
...keyboard(false), ...keyboard(false),
@ -57,10 +58,6 @@ const screenshotTools: Tool[] = [
...tabs(false), ...tabs(false),
]; ];
const resources: Resource[] = [
consoleResource,
];
type Options = { type Options = {
browser?: string; browser?: string;
userDataDir?: string; userDataDir?: string;
@ -115,7 +112,7 @@ export async function createServer(options?: Options): Promise<Server> {
name: 'Playwright', name: 'Playwright',
version: packageJSON.version, version: packageJSON.version,
tools, tools,
resources, resources: [],
browserName, browserName,
userDataDir, userDataDir,
launchOptions, launchOptions,

View File

@ -14,22 +14,32 @@
* limitations under the License. * limitations under the License.
*/ */
import type { Resource } from './resource'; import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
export const console: Resource = { import type { Tool } from './tool';
const consoleSchema = z.object({});
const console: Tool = {
capability: 'core',
schema: { schema: {
uri: 'browser://console', name: 'browser_console_messages',
name: 'Page console', description: 'Returns all console messages',
mimeType: 'text/plain', inputSchema: zodToJsonSchema(consoleSchema),
}, },
handle: async context => {
read: async (context, uri) => {
const messages = await context.currentTab().console(); const messages = await context.currentTab().console();
const log = messages.map(message => `[${message.type().toUpperCase()}] ${message.text()}`).join('\n'); const log = messages.map(message => `[${message.type().toUpperCase()}] ${message.text()}`).join('\n');
return [{ return {
uri, content: [{
mimeType: 'text/plain', type: 'text',
text: log text: log
}]; }],
};
}, },
}; };
export default [
console,
];

View File

@ -211,14 +211,10 @@ test('browser_type', async ({ client }) => {
submit: true, submit: true,
}, },
}); });
const resource = await client.readResource({ expect(await client.callTool({
uri: 'browser://console', name: 'browser_console_messages',
}); arguments: {},
expect(resource.contents).toEqual([{ })).toHaveTextContent('[LOG] Key pressed: Enter , Text: Hi!');
uri: 'browser://console',
mimeType: 'text/plain',
text: '[LOG] Key pressed: Enter , Text: Hi!',
}]);
}); });
test('browser_type (slowly)', async ({ client }) => { test('browser_type (slowly)', async ({ client }) => {
@ -238,19 +234,15 @@ test('browser_type (slowly)', async ({ client }) => {
slowly: true, slowly: true,
}, },
}); });
const resource = await client.readResource({ expect(await client.callTool({
uri: 'browser://console', name: 'browser_console_messages',
}); arguments: {},
expect(resource.contents).toEqual([{ })).toHaveTextContent([
uri: 'browser://console',
mimeType: 'text/plain',
text: [
'[LOG] Key pressed: H Text: ', '[LOG] Key pressed: H Text: ',
'[LOG] Key pressed: i Text: H', '[LOG] Key pressed: i Text: H',
'[LOG] Key pressed: ! Text: Hi', '[LOG] Key pressed: ! Text: Hi',
'[LOG] Key pressed: Enter Text: Hi!', '[LOG] Key pressed: Enter Text: Hi!',
].join('\n'), ].join('\n'));
}]);
}); });
test('browser_resize', async ({ client }) => { test('browser_resize', async ({ client }) => {

View File

@ -20,6 +20,7 @@ test('test snapshot tool list', async ({ client }) => {
const { tools } = await client.listTools(); const { tools } = await client.listTools();
expect(new Set(tools.map(t => t.name))).toEqual(new Set([ expect(new Set(tools.map(t => t.name))).toEqual(new Set([
'browser_click', 'browser_click',
'browser_console_messages',
'browser_drag', 'browser_drag',
'browser_file_upload', 'browser_file_upload',
'browser_hover', 'browser_hover',
@ -47,6 +48,7 @@ test('test vision tool list', async ({ visionClient }) => {
const { tools: visionTools } = await visionClient.listTools(); const { tools: visionTools } = await visionClient.listTools();
expect(new Set(visionTools.map(t => t.name))).toEqual(new Set([ expect(new Set(visionTools.map(t => t.name))).toEqual(new Set([
'browser_close', 'browser_close',
'browser_console_messages',
'browser_file_upload', 'browser_file_upload',
'browser_install', 'browser_install',
'browser_navigate_back', 'browser_navigate_back',
@ -70,12 +72,7 @@ test('test vision tool list', async ({ visionClient }) => {
test('test resources list', async ({ client }) => { test('test resources list', async ({ client }) => {
const { resources } = await client.listResources(); const { resources } = await client.listResources();
expect(resources).toEqual([ expect(resources).toEqual([]);
expect.objectContaining({
uri: 'browser://console',
mimeType: 'text/plain',
}),
]);
}); });
test('test capabilities', async ({ startClient }) => { test('test capabilities', async ({ startClient }) => {

View File

@ -16,7 +16,7 @@
import { test, expect } from './fixtures'; import { test, expect } from './fixtures';
test('browser://console', async ({ client }) => { test('browser_console_messages', async ({ client }) => {
await client.callTool({ await client.callTool({
name: 'browser_navigate', name: 'browser_navigate',
arguments: { arguments: {
@ -24,12 +24,12 @@ test('browser://console', async ({ client }) => {
}, },
}); });
const resource = await client.readResource({ const resource = await client.callTool({
uri: 'browser://console', name: 'browser_console_messages',
arguments: {},
}); });
expect(resource.contents).toEqual([{ expect(resource).toHaveTextContent([
uri: 'browser://console', '[LOG] Hello, world!',
mimeType: 'text/plain', '[ERROR] Error',
text: '[LOG] Hello, world!\n[ERROR] Error', ].join('\n'));
}]);
}); });