diff --git a/src/server.ts b/src/server.ts index 7f984337a..83f03ad15 100644 --- a/src/server.ts +++ b/src/server.ts @@ -23,7 +23,7 @@ import { SetLevelRequestSchema, } from './third_party/index.js'; import {ToolCategory} from './tools/categories.js'; -import type {ToolDefinition} from './tools/ToolDefinition.js'; +import type {DefinedPageTool, ToolDefinition} from './tools/ToolDefinition.js'; import {pageIdSchema} from './tools/ToolDefinition.js'; import {createTools} from './tools/tools.js'; import {VERSION} from './version.js'; @@ -107,7 +107,7 @@ export async function createMcpServer( const toolMutex = new Mutex(); - function registerTool(tool: ToolDefinition): void { + function registerTool(tool: ToolDefinition | DefinedPageTool): void { if ( tool.annotations.category === ToolCategory.EMULATION && serverArgs.categoryEmulation === false @@ -151,7 +151,9 @@ export async function createMcpServer( return; } const schema = - tool.annotations.pageScoped && serverArgs.experimentalPageIdRouting + 'pageScoped' in tool && + tool.pageScoped && + serverArgs.experimentalPageIdRouting ? {...tool.schema, ...pageIdSchema} : tool.schema; @@ -174,22 +176,31 @@ export async function createMcpServer( const response = serverArgs.slim ? new SlimMcpResponse(serverArgs) : new McpResponse(serverArgs); - const page = - tool.annotations.pageScoped && serverArgs.experimentalPageIdRouting - ? context.resolvePageById(params.pageId as number | undefined) - : undefined; - if (page) { - context.setRequestPage(page); - } try { - await tool.handler( - { - params, - page, - }, - response, - context, - ); + if ('pageScoped' in tool && tool.pageScoped) { + const page = + serverArgs.experimentalPageIdRouting && params.pageId + ? context.resolvePageById(params.pageId) + : context.getSelectedPage(); + context.setRequestPage(page); + await tool.handler( + { + params, + page, + }, + response, + context, + ); + } else { + await tool.handler( + // @ts-expect-error types do not match. + { + params, + }, + response, + context, + ); + } const {content, structuredContent} = await response.handle( tool.name, context, diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index 717d3a332..3f441ee94 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -20,7 +20,7 @@ import type {PaginationOptions} from '../utils/types.js'; import type {ToolCategory} from './categories.js'; -export interface ToolDefinition< +export interface BaseToolDefinition< Schema extends zod.ZodRawShape = zod.ZodRawShape, > { name: string; @@ -33,14 +33,14 @@ export interface ToolDefinition< */ readOnlyHint: boolean; conditions?: string[]; - /** - * If true, the tool operates on a specific page. - * The `pageId` schema field is auto-injected and the resolved - * page is provided via `request.page`. - */ - pageScoped?: boolean; }; schema: Schema; +} + +export interface ToolDefinition< + Schema extends zod.ZodRawShape = zod.ZodRawShape, +> extends BaseToolDefinition { + schema: Schema; handler: ( request: Request, response: Response, @@ -50,8 +50,6 @@ export interface ToolDefinition< export interface Request { params: zod.objectOutputType; - /** Populated centrally for tools with `pageScoped: true`. */ - page?: Page; } export interface ImageContentData { @@ -215,28 +213,65 @@ export function defineTool< if (typeof definition === 'function') { const factory = definition; return (args: Args) => { - const tool = factory(args); - wrapPageScopedHandler(tool); - return tool; + return factory(args); }; } - wrapPageScopedHandler(definition); return definition; } -function wrapPageScopedHandler( - definition: ToolDefinition, -) { - if (definition.annotations.pageScoped) { - const originalHandler = definition.handler; - definition.handler = async (request, response, context) => { - // In production, main.ts resolves request.page centrally before calling - // the handler. This fallback exists for tests that invoke handlers - // directly without going through main.ts. - request.page ??= context.getSelectedPage(); - return originalHandler(request, response, context); +interface PageToolDefinition< + Schema extends zod.ZodRawShape = zod.ZodRawShape, +> extends BaseToolDefinition { + handler: ( + request: Request & {page: Page}, + response: Response, + context: Context, + ) => Promise; +} + +export type DefinedPageTool = + PageToolDefinition & { + pageScoped: true; + handler: ( + request: Request & {page: Page}, + response: Response, + context: Context, + ) => Promise; + }; + +export function definePageTool( + definition: PageToolDefinition, +): DefinedPageTool; + +export function definePageTool< + Schema extends zod.ZodRawShape, + Args extends ParsedArguments = ParsedArguments, +>( + definition: (args?: Args) => PageToolDefinition, +): (args?: Args) => DefinedPageTool; + +export function definePageTool< + Schema extends zod.ZodRawShape, + Args extends ParsedArguments = ParsedArguments, +>( + definition: + | PageToolDefinition + | ((args?: Args) => PageToolDefinition), +): DefinedPageTool | ((args?: Args) => DefinedPageTool) { + if (typeof definition === 'function') { + return (args?: Args): DefinedPageTool => { + const tool = definition(args); + return { + ...tool, + pageScoped: true, + }; }; } + + return { + ...definition, + pageScoped: true, + } as DefinedPageTool; } export const CLOSE_PAGE_ERROR = diff --git a/src/tools/console.ts b/src/tools/console.ts index 1f6aa3771..a6a1799a4 100644 --- a/src/tools/console.ts +++ b/src/tools/console.ts @@ -8,7 +8,7 @@ import {zod} from '../third_party/index.js'; import type {ConsoleMessageType} from '../third_party/index.js'; import {ToolCategory} from './categories.js'; -import {defineTool} from './ToolDefinition.js'; +import {definePageTool} from './ToolDefinition.js'; type ConsoleResponseType = ConsoleMessageType | 'issue'; const FILTERABLE_MESSAGE_TYPES: [ @@ -37,14 +37,13 @@ const FILTERABLE_MESSAGE_TYPES: [ 'issue', ]; -export const listConsoleMessages = defineTool({ +export const listConsoleMessages = definePageTool({ name: 'list_console_messages', description: 'List all console messages for the currently selected page since the last navigation.', annotations: { category: ToolCategory.DEBUGGING, readOnlyHint: true, - pageScoped: true, }, schema: { pageSize: zod @@ -87,13 +86,12 @@ export const listConsoleMessages = defineTool({ }, }); -export const getConsoleMessage = defineTool({ +export const getConsoleMessage = definePageTool({ name: 'get_console_message', description: `Gets a console message by its ID. You can get all messages by calling ${listConsoleMessages.name}.`, annotations: { category: ToolCategory.DEBUGGING, readOnlyHint: true, - pageScoped: true, }, schema: { msgid: zod diff --git a/src/tools/emulation.ts b/src/tools/emulation.ts index b05221f07..0e1485c83 100644 --- a/src/tools/emulation.ts +++ b/src/tools/emulation.ts @@ -8,7 +8,7 @@ import {zod, PredefinedNetworkConditions} from '../third_party/index.js'; import {ToolCategory} from './categories.js'; -import {defineTool} from './ToolDefinition.js'; +import {definePageTool} from './ToolDefinition.js'; const throttlingOptions: [string, ...string[]] = [ 'No emulation', @@ -16,13 +16,12 @@ const throttlingOptions: [string, ...string[]] = [ ...Object.keys(PredefinedNetworkConditions), ]; -export const emulate = defineTool({ +export const emulate = definePageTool({ name: 'emulate', description: `Emulates various features on the selected page.`, annotations: { category: ToolCategory.EMULATION, readOnlyHint: false, - pageScoped: true, }, schema: { networkConditions: zod @@ -105,7 +104,7 @@ export const emulate = defineTool({ ), }, handler: async (request, _response, context) => { - const page = request.page!; + const page = request.page; await context.emulate(request.params, page); }, }); diff --git a/src/tools/input.ts b/src/tools/input.ts index d88b707c4..b37839585 100644 --- a/src/tools/input.ts +++ b/src/tools/input.ts @@ -11,7 +11,7 @@ import type {ElementHandle, KeyInput, Page} from '../third_party/index.js'; import {parseKey} from '../utils/keyboard.js'; import {ToolCategory} from './categories.js'; -import {defineTool} from './ToolDefinition.js'; +import {definePageTool} from './ToolDefinition.js'; const dblClickSchema = zod .boolean() @@ -40,7 +40,7 @@ function handleActionError(error: unknown, uid: string) { ); } -export const click = defineTool({ +export const click = definePageTool({ name: 'click', description: `Clicks on the provided element`, annotations: { @@ -81,14 +81,13 @@ export const click = defineTool({ }, }); -export const clickAt = defineTool({ +export const clickAt = definePageTool({ name: 'click_at', description: `Clicks at the provided coordinates`, annotations: { category: ToolCategory.INPUT, readOnlyHint: false, conditions: ['computerVision'], - pageScoped: true, }, schema: { x: zod.number().describe('The x coordinate'), @@ -97,7 +96,7 @@ export const clickAt = defineTool({ includeSnapshot: includeSnapshotSchema, }, handler: async (request, response, context) => { - const page = request.page!; + const page = request.page; context.assertPageIsFocused(page); await context.waitForEventsAfterAction(async () => { await page.mouse.click(request.params.x, request.params.y, { @@ -115,7 +114,7 @@ export const clickAt = defineTool({ }, }); -export const hover = defineTool({ +export const hover = definePageTool({ name: 'hover', description: `Hover over the provided element`, annotations: { @@ -217,13 +216,12 @@ async function fillFormElement( } } -export const fill = defineTool({ +export const fill = definePageTool({ name: 'fill', description: `Type text into a input, text area or select an option from a '); await issuePromise; - await listConsoleMessages.handler({params: {}}, response, context); + await listConsoleMessages.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); const formattedResponse = await response.handle('test', context); const textContent = getTextContent(formattedResponse.content[0]); assert.ok( @@ -100,7 +120,11 @@ describe('console', () => { await page.setContent(''); await issuePromise; - await listConsoleMessages.handler({params: {}}, response, context); + await listConsoleMessages.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); { const formattedResponse = await response.handle('test', context); const textContent = getTextContent(formattedResponse.content[0]); @@ -143,9 +167,13 @@ describe('console', () => { '', ); // The list is needed to populate the console messages in the context. - await listConsoleMessages.handler({params: {}}, response, context); + await listConsoleMessages.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); await getConsoleMessage.handler( - {params: {msgid: 1}}, + {params: {msgid: 1}, page: context.getSelectedPage()}, response, context, ); @@ -170,10 +198,14 @@ describe('console', () => { await page.setContent(''); await context.createTextSnapshot(); await issuePromise; - await listConsoleMessages.handler({params: {}}, response, context); + await listConsoleMessages.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); const response2 = new McpResponse({} as ParsedArguments); await getConsoleMessage.handler( - {params: {msgid: 1}}, + {params: {msgid: 1}, page: context.getSelectedPage()}, response2, context, ); @@ -222,13 +254,13 @@ describe('console', () => { const id = context.getConsoleMessageStableId(issueMsg); assert.ok(id); await listConsoleMessages.handler( - {params: {types: ['issue']}}, + {params: {types: ['issue']}, page: context.getSelectedPage()}, response, context, ); const response2 = new McpResponse({} as ParsedArguments); await getConsoleMessage.handler( - {params: {msgid: id}}, + {params: {msgid: id}, page: context.getSelectedPage()}, response2, context, ); @@ -261,7 +293,7 @@ describe('console', () => { await page.goto(server.getRoute('/index.html')); await getConsoleMessage.handler( - {params: {msgid: 1}}, + {params: {msgid: 1}, page: context.getSelectedPage()}, response, context, ); @@ -290,7 +322,7 @@ describe('console', () => { await page.goto(server.getRoute('/index.html')); await getConsoleMessage.handler( - {params: {msgid: 1}}, + {params: {msgid: 1}, page: context.getSelectedPage()}, response, context, ); @@ -319,7 +351,7 @@ describe('console', () => { await page.goto(server.getRoute('/index.html')); await getConsoleMessage.handler( - {params: {msgid: 1}}, + {params: {msgid: 1}, page: context.getSelectedPage()}, response, context, ); @@ -348,7 +380,7 @@ describe('console', () => { await page.goto(server.getRoute('/index.html')); await getConsoleMessage.handler( - {params: {msgid: 1}}, + {params: {msgid: 1}, page: context.getSelectedPage()}, response, context, ); @@ -377,7 +409,7 @@ describe('console', () => { await page.goto(server.getRoute('/index.html')); await getConsoleMessage.handler( - {params: {msgid: 1}}, + {params: {msgid: 1}, page: context.getSelectedPage()}, response, context, ); @@ -420,7 +452,7 @@ describe('console', () => { await page.goto(server.getRoute('/index.html')); await getConsoleMessage.handler( - {params: {msgid: 1}}, + {params: {msgid: 1}, page: context.getSelectedPage()}, response, context, ); diff --git a/tests/tools/emulation.test.ts b/tests/tools/emulation.test.ts index 2aa31dad6..311eec34c 100644 --- a/tests/tools/emulation.test.ts +++ b/tests/tools/emulation.test.ts @@ -22,6 +22,7 @@ describe('emulation', () => { params: { networkConditions: 'Offline', }, + page: context.getSelectedPage(), }, response, context, @@ -37,6 +38,7 @@ describe('emulation', () => { params: { networkConditions: 'Slow 3G', }, + page: context.getSelectedPage(), }, response, context, @@ -53,6 +55,7 @@ describe('emulation', () => { params: { networkConditions: 'No emulation', }, + page: context.getSelectedPage(), }, response, context, @@ -69,6 +72,7 @@ describe('emulation', () => { params: { networkConditions: 'Slow 11G', }, + page: context.getSelectedPage(), }, response, context, @@ -85,6 +89,7 @@ describe('emulation', () => { params: { networkConditions: 'Slow 3G', }, + page: context.getSelectedPage(), }, response, context, @@ -108,6 +113,7 @@ describe('emulation', () => { params: { cpuThrottlingRate: 4, }, + page: context.getSelectedPage(), }, response, context, @@ -127,6 +133,7 @@ describe('emulation', () => { params: { cpuThrottlingRate: 1, }, + page: context.getSelectedPage(), }, response, context, @@ -143,6 +150,7 @@ describe('emulation', () => { params: { cpuThrottlingRate: 4, }, + page: context.getSelectedPage(), }, response, context, @@ -169,6 +177,7 @@ describe('emulation', () => { longitude: 11.576124, }, }, + page: context.getSelectedPage(), }, response, context, @@ -191,6 +200,7 @@ describe('emulation', () => { longitude: 11.576124, }, }, + page: context.getSelectedPage(), }, response, context, @@ -204,6 +214,7 @@ describe('emulation', () => { params: { geolocation: null, }, + page: context.getSelectedPage(), }, response, context, @@ -223,6 +234,7 @@ describe('emulation', () => { longitude: 11.576124, }, }, + page: context.getSelectedPage(), }, response, context, @@ -260,6 +272,7 @@ describe('emulation', () => { isLandscape: false, }, }, + page: context.getSelectedPage(), }, response, context, @@ -295,6 +308,7 @@ describe('emulation', () => { height: 400, }, }, + page: context.getSelectedPage(), }, response, context, @@ -318,6 +332,7 @@ describe('emulation', () => { params: { viewport: null, }, + page: context.getSelectedPage(), }, response, context, @@ -342,6 +357,7 @@ describe('emulation', () => { height: 400, }, }, + page: context.getSelectedPage(), }, response, context, @@ -370,6 +386,7 @@ describe('emulation', () => { params: { userAgent: 'MyUA', }, + page: context.getSelectedPage(), }, response, context, @@ -389,6 +406,7 @@ describe('emulation', () => { params: { userAgent: 'UA1', }, + page: context.getSelectedPage(), }, response, context, @@ -400,6 +418,7 @@ describe('emulation', () => { params: { userAgent: 'UA2', }, + page: context.getSelectedPage(), }, response, context, @@ -418,6 +437,7 @@ describe('emulation', () => { params: { userAgent: 'MyUA', }, + page: context.getSelectedPage(), }, response, context, @@ -430,6 +450,7 @@ describe('emulation', () => { params: { userAgent: null, }, + page: context.getSelectedPage(), }, response, context, @@ -450,6 +471,7 @@ describe('emulation', () => { params: { userAgent: 'MyUA', }, + page: context.getSelectedPage(), }, response, context, @@ -478,6 +500,7 @@ describe('emulation', () => { params: { colorScheme: 'dark', }, + page: context.getSelectedPage(), }, response, context, @@ -501,6 +524,7 @@ describe('emulation', () => { params: { colorScheme: 'dark', }, + page: context.getSelectedPage(), }, response, context, @@ -512,6 +536,7 @@ describe('emulation', () => { params: { colorScheme: 'light', }, + page: context.getSelectedPage(), }, response, context, @@ -540,6 +565,7 @@ describe('emulation', () => { params: { colorScheme: 'dark', }, + page: context.getSelectedPage(), }, response, context, @@ -559,6 +585,7 @@ describe('emulation', () => { params: { colorScheme: 'auto', }, + page: context.getSelectedPage(), }, response, context, diff --git a/tests/tools/input.test.ts b/tests/tools/input.test.ts index bc21df87e..caecdd377 100644 --- a/tests/tools/input.test.ts +++ b/tests/tools/input.test.ts @@ -42,6 +42,7 @@ describe('input', () => { params: { uid: '1_1', }, + page: context.getSelectedPage(), }, response, context, @@ -69,6 +70,7 @@ describe('input', () => { uid: '1_1', dblClick: true, }, + page: context.getSelectedPage(), }, response, context, @@ -102,6 +104,7 @@ describe('input', () => { params: { uid: '1_1', }, + page: context.getSelectedPage(), }, response, context, @@ -145,6 +148,7 @@ describe('input', () => { params: { uid: '1_1', }, + page: context.getSelectedPage(), }, response, context, @@ -171,6 +175,7 @@ describe('input', () => { params: { uid: '1_1', }, + page: context.getSelectedPage(), }, response, context, @@ -196,6 +201,7 @@ describe('input', () => { uid: '1_1', includeSnapshot: true, }, + page: context.getSelectedPage(), }, response, context, @@ -222,6 +228,7 @@ describe('input', () => { params: { uid: '1_1', }, + page: context.getSelectedPage(), }, response, context, @@ -253,6 +260,7 @@ describe('input', () => { x: 50, y: 50, }, + page: context.getSelectedPage(), }, response, context, @@ -283,6 +291,7 @@ describe('input', () => { y: 50, dblClick: true, }, + page: context.getSelectedPage(), }, response, context, @@ -309,6 +318,7 @@ describe('input', () => { uid: '1_1', value: 'test', }, + page: context.getSelectedPage(), }, response, context, @@ -338,6 +348,7 @@ describe('input', () => { uid: '1_1', value: 'two', }, + page: context.getSelectedPage(), }, response, context, @@ -365,6 +376,7 @@ describe('input', () => { uid: '1_1', value: '1', }, + page: context.getSelectedPage(), }, response, context, @@ -394,6 +406,7 @@ describe('input', () => { uid: '1_1', value: '1'.repeat(3000), }, + page: context.getSelectedPage(), }, response, context, @@ -424,6 +437,7 @@ describe('input', () => { params: { text: 'test', }, + page: context.getSelectedPage(), }, response, context, @@ -450,6 +464,7 @@ describe('input', () => { text: 'test', submitKey: 'Tab', }, + page: context.getSelectedPage(), }, response, context, @@ -487,6 +502,7 @@ describe('input', () => { text: 'test', submitKey: 'XXX', }, + page: context.getSelectedPage(), }, response, context, @@ -522,6 +538,7 @@ describe('input', () => { uid: '1_1', // email input value: 'new@test.com', }, + page: context.getSelectedPage(), }, response1, context, @@ -539,6 +556,7 @@ describe('input', () => { uid: '1_2', // password input value: 'secret', }, + page: context.getSelectedPage(), }, response2, context, @@ -611,6 +629,7 @@ describe('input', () => { from_uid: '1_1', to_uid: '1_2', }, + page: context.getSelectedPage(), }, response, context, @@ -662,6 +681,7 @@ describe('input', () => { }, ], }, + page: context.getSelectedPage(), }, response, context, @@ -708,6 +728,7 @@ describe('input', () => { uid: '1_1', filePath: testFilePath, }, + page: context.getSelectedPage(), }, response, context, @@ -750,6 +771,7 @@ describe('input', () => { uid: '1_1', filePath: testFilePath, }, + page: context.getSelectedPage(), }, response, context, @@ -785,6 +807,7 @@ describe('input', () => { uid: '1_1', filePath: testFilePath, }, + page: context.getSelectedPage(), }, response, context, @@ -848,6 +871,7 @@ describe('input', () => { params: { key: 'Control+Shift+C', }, + page: context.getSelectedPage(), }, response, context, diff --git a/tests/tools/lighthouse.test.ts b/tests/tools/lighthouse.test.ts index 15a1d19a1..8ad862164 100644 --- a/tests/tools/lighthouse.test.ts +++ b/tests/tools/lighthouse.test.ts @@ -30,6 +30,7 @@ describe('lighthouse', () => { mode: 'navigation', device: 'desktop', }, + page: context.getSelectedPage(), }, response, context, @@ -90,6 +91,7 @@ describe('lighthouse', () => { mode: 'snapshot', device: 'mobile', }, + page: context.getSelectedPage(), }, response, context, @@ -128,6 +130,7 @@ describe('lighthouse', () => { mode: 'snapshot', device: 'mobile', }, + page: context.getSelectedPage(), }, response, context, @@ -163,6 +166,7 @@ describe('lighthouse', () => { device: 'mobile', outputDirPath: folderPath, }, + page: context.getSelectedPage(), }, response, context, diff --git a/tests/tools/memory.test.ts b/tests/tools/memory.test.ts index 16fc0876e..f9a0ad0be 100644 --- a/tests/tools/memory.test.ts +++ b/tests/tools/memory.test.ts @@ -21,7 +21,7 @@ describe('memory', () => { const filePath = join(tmpdir(), 'test-screenshot.heapsnapshot'); try { await takeMemorySnapshot.handler( - {params: {filePath}}, + {params: {filePath}, page: context.getSelectedPage()}, response, context, ); diff --git a/tests/tools/network.test.ts b/tests/tools/network.test.ts index cbecc12f1..d3e56b537 100644 --- a/tests/tools/network.test.ts +++ b/tests/tools/network.test.ts @@ -24,7 +24,11 @@ describe('network', () => { describe('network_list_requests', () => { it('list requests', async () => { await withMcpContext(async (response, context) => { - await listNetworkRequests.handler({params: {}}, response, context); + await listNetworkRequests.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); assert.ok(response.includeNetworkRequests); assert.strictEqual(response.networkRequestsPageIdx, undefined); }); @@ -44,6 +48,8 @@ describe('network', () => { await listNetworkRequests.handler( { params: {}, + + page: context.getSelectedPage(), }, response, context, @@ -71,6 +77,7 @@ describe('network', () => { params: { includePreservedRequests: true, }, + page: context.getSelectedPage(), }, response, context, @@ -113,6 +120,7 @@ describe('network', () => { params: { includePreservedRequests: true, }, + page: context.getSelectedPage(), }, response, context, @@ -130,7 +138,7 @@ describe('network', () => { const page = context.getSelectedPage(); await page.goto('data:text/html,
Hello MCP
'); await getNetworkRequest.handler( - {params: {reqid: 1}}, + {params: {reqid: 1}, page: context.getSelectedPage()}, response, context, ); @@ -143,7 +151,7 @@ describe('network', () => { const page = context.getSelectedPage(); await page.goto('data:text/html,
Hello MCP
'); await getNetworkRequest.handler( - {params: {reqid: 1}}, + {params: {reqid: 1}, page: context.getSelectedPage()}, response, context, ); @@ -166,6 +174,7 @@ describe('network', () => { params: { reqid: 1, }, + page: context.getSelectedPage(), }, response, context, diff --git a/tests/tools/pages.test.ts b/tests/tools/pages.test.ts index ecc38e822..36a81aa37 100644 --- a/tests/tools/pages.test.ts +++ b/tests/tools/pages.test.ts @@ -38,7 +38,11 @@ describe('pages', () => { describe('list_pages', () => { it('list pages', async () => { await withMcpContext(async (response, context) => { - await listPages().handler({params: {}}, response, context); + await listPages().handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); assert.ok(response.includePages); }); }); @@ -64,7 +68,11 @@ describe('pages', () => { const listPageDef = listPages({ categoryExtensions, } as ParsedArguments); - await listPageDef.handler({params: {}}, response, context); + await listPageDef.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); const result = await response.handle(listPageDef.name, context); const textContent = result.content.find(c => c.type === 'text') as { @@ -548,7 +556,10 @@ describe('pages', () => { it('navigates to correct page', async () => { await withMcpContext(async (response, context) => { await navigatePage.handler( - {params: {url: 'data:text/html,
Hello MCP
'}}, + { + params: {url: 'data:text/html,
Hello MCP
'}, + page: context.getSelectedPage(), + }, response, context, ); @@ -571,7 +582,10 @@ describe('pages', () => { try { await navigatePage.handler( - {params: {url: 'data:text/html,
Hello MCP
'}}, + { + params: {url: 'data:text/html,
Hello MCP
'}, + page: context.getSelectedPage(), + }, response, context, ); @@ -597,6 +611,7 @@ describe('pages', () => { url: 'about:blank', timeout: 12345, }, + page: context.getSelectedPage(), }, response, context, @@ -616,7 +631,11 @@ describe('pages', () => { await withMcpContext(async (response, context) => { const page = context.getSelectedPage(); await page.goto('data:text/html,
Hello MCP
'); - await navigatePage.handler({params: {type: 'back'}}, response, context); + await navigatePage.handler( + {params: {type: 'back'}, page: context.getSelectedPage()}, + response, + context, + ); assert.equal( await page.evaluate(() => document.location.href), @@ -631,7 +650,7 @@ describe('pages', () => { await page.goto('data:text/html,
Hello MCP
'); await page.goBack(); await navigatePage.handler( - {params: {type: 'forward'}}, + {params: {type: 'forward'}, page: context.getSelectedPage()}, response, context, ); @@ -648,7 +667,7 @@ describe('pages', () => { const page = context.getSelectedPage(); await page.goto('data:text/html,
Hello MCP
'); await navigatePage.handler( - {params: {type: 'reload'}}, + {params: {type: 'reload'}, page: context.getSelectedPage()}, response, context, ); @@ -674,7 +693,7 @@ describe('pages', () => { ); await navigatePage.handler( - {params: {type: 'reload'}}, + {params: {type: 'reload'}, page: context.getSelectedPage()}, response, context, ); @@ -707,6 +726,7 @@ describe('pages', () => { handleBeforeUnload: 'decline', timeout: 500, }, + page: context.getSelectedPage(), }, response, context, @@ -724,7 +744,7 @@ describe('pages', () => { it('go forward with error', async () => { await withMcpContext(async (response, context) => { await navigatePage.handler( - {params: {type: 'forward'}}, + {params: {type: 'forward'}, page: context.getSelectedPage()}, response, context, ); @@ -739,7 +759,11 @@ describe('pages', () => { }); it('go back with error', async () => { await withMcpContext(async (response, context) => { - await navigatePage.handler({params: {type: 'back'}}, response, context); + await navigatePage.handler( + {params: {type: 'back'}, page: context.getSelectedPage()}, + response, + context, + ); assert.ok( response.responseLines @@ -757,6 +781,7 @@ describe('pages', () => { url: 'data:text/html,
Hello MCP
', initScript: 'window.initScript = "completed"', }, + page: context.getSelectedPage(), }, response, context, @@ -782,7 +807,7 @@ describe('pages', () => { }); }); await resizePage.handler( - {params: {width: 700, height: 500}}, + {params: {width: 700, height: 500}, page: context.getSelectedPage()}, response, context, ); @@ -813,7 +838,7 @@ describe('pages', () => { }); }); await resizePage.handler( - {params: {width: 650, height: 450}}, + {params: {width: 650, height: 450}, page: context.getSelectedPage()}, response, context, ); @@ -844,7 +869,7 @@ describe('pages', () => { }); }); await resizePage.handler( - {params: {width: 750, height: 550}}, + {params: {width: 750, height: 550}, page: context.getSelectedPage()}, response, context, ); @@ -875,7 +900,7 @@ describe('pages', () => { }); }); await resizePage.handler( - {params: {width: 725, height: 525}}, + {params: {width: 725, height: 525}, page: context.getSelectedPage()}, response, context, ); @@ -906,7 +931,7 @@ describe('pages', () => { }); }); await resizePage.handler( - {params: {width: 850, height: 650}}, + {params: {width: 850, height: 650}, page: context.getSelectedPage()}, response, context, ); @@ -940,6 +965,7 @@ describe('pages', () => { params: { action: 'accept', }, + page: context.getSelectedPage(), }, response, context, @@ -968,6 +994,7 @@ describe('pages', () => { params: { action: 'dismiss', }, + page: context.getSelectedPage(), }, response, context, @@ -997,6 +1024,7 @@ describe('pages', () => { params: { action: 'dismiss', }, + page: context.getSelectedPage(), }, response, context, @@ -1100,7 +1128,11 @@ describe('pages', () => { assert.ok(typeof page._tabId === 'string'); // @ts-expect-error _tabId is internal. page._tabId = 'test-tab-id'; - await getTabId.handler({params: {pageId: 1}}, response, context); + await getTabId.handler( + {params: {pageId: 1}, page: context.getSelectedPage()}, + response, + context, + ); const result = await response.handle('get_tab_id', context); // @ts-expect-error _tabId is internal. assert.strictEqual(result.structuredContent.tabId, 'test-tab-id'); diff --git a/tests/tools/performance.test.ts b/tests/tools/performance.test.ts index 605c20f10..a4e3d4250 100644 --- a/tests/tools/performance.test.ts +++ b/tests/tools/performance.test.ts @@ -49,7 +49,10 @@ describe('performance', () => { const selectedPage = context.getSelectedPage(); const startTracingStub = sinon.stub(selectedPage.tracing, 'start'); await startTrace.handler( - {params: {reload: true, autoStop: false}}, + { + params: {reload: true, autoStop: false}, + page: context.getSelectedPage(), + }, response, context, ); @@ -70,7 +73,10 @@ describe('performance', () => { const gotoStub = sinon.stub(selectedPage, 'goto'); const startTracingStub = sinon.stub(selectedPage.tracing, 'start'); await startTrace.handler( - {params: {reload: true, autoStop: false}}, + { + params: {reload: true, autoStop: false}, + page: context.getSelectedPage(), + }, response, context, ); @@ -106,7 +112,10 @@ describe('performance', () => { const clock = sinon.useFakeTimers(); const handlerPromise = startTrace.handler( - {params: {reload: true, autoStop: true}}, + { + params: {reload: true, autoStop: true}, + page: context.getSelectedPage(), + }, response, context, ); @@ -141,7 +150,10 @@ describe('performance', () => { const selectedPage = context.getSelectedPage(); const startTracingStub = sinon.stub(selectedPage.tracing, 'start'); await startTrace.handler( - {params: {reload: true, autoStop: false}}, + { + params: {reload: true, autoStop: false}, + page: context.getSelectedPage(), + }, response, context, ); @@ -172,7 +184,10 @@ describe('performance', () => { .resolves({filename: filePath}); const handlerPromise = startTrace.handler( - {params: {reload: true, autoStop: true, filePath}}, + { + params: {reload: true, autoStop: true, filePath}, + page: context.getSelectedPage(), + }, response, context, ); @@ -220,6 +235,7 @@ describe('performance', () => { insightSetId: 'NAVIGATION_0', insightName: 'LCPBreakdown', }, + page: context.getSelectedPage(), }, response, context, @@ -237,6 +253,7 @@ describe('performance', () => { insightSetId: '8463DF94CD61B265B664E7F768183DE3', insightName: 'LCPBreakdown', }, + page: context.getSelectedPage(), }, response, context, @@ -258,7 +275,11 @@ describe('performance', () => { context.setIsRunningPerformanceTrace(false); const selectedPage = context.getSelectedPage(); const stopTracingStub = sinon.stub(selectedPage.tracing, 'stop'); - await stopTrace.handler({params: {}}, response, context); + await stopTrace.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); sinon.assert.notCalled(stopTracingStub); assert.strictEqual(context.isRunningPerformanceTrace(), false); }); @@ -274,7 +295,11 @@ describe('performance', () => { .callsFake(async () => { return rawData; }); - await stopTrace.handler({params: {}}, response, context); + await stopTrace.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); assert.ok( response.responseLines.includes( 'The performance trace has been stopped.', @@ -294,7 +319,11 @@ describe('performance', () => { .returns(Promise.resolve(undefined)); await assert.rejects( - stopTrace.handler({params: {}}, response, context), + stopTrace.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ), /There was an unexpected error parsing the trace/, ); }); @@ -313,7 +342,11 @@ describe('performance', () => { .stub(context, 'saveFile') .resolves({filename: filePath}); - await stopTrace.handler({params: {filePath}}, response, context); + await stopTrace.handler( + {params: {filePath}, page: context.getSelectedPage()}, + response, + context, + ); sinon.assert.calledOnce(stopTracingStub); sinon.assert.calledOnce(saveFileStub); @@ -334,7 +367,11 @@ describe('performance', () => { const selectedPage = context.getSelectedPage(); sinon.stub(selectedPage.tracing, 'stop').resolves(rawData); - await stopTrace.handler({params: {}}, response, context); + await stopTrace.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); const cruxEndpoint = 'https://chromeuxreport.googleapis.com/v1/records:queryRecord'; diff --git a/tests/tools/screencast.test.ts b/tests/tools/screencast.test.ts index 71a053d3b..06bebea8a 100644 --- a/tests/tools/screencast.test.ts +++ b/tests/tools/screencast.test.ts @@ -33,7 +33,10 @@ describe('screencast', () => { .resolves(mockRecorder as never); await startScreencast.handler( - {params: {path: '/tmp/test-recording.mp4'}}, + { + params: {path: '/tmp/test-recording.mp4'}, + page: context.getSelectedPage(), + }, response, context, ); @@ -60,7 +63,11 @@ describe('screencast', () => { .stub(selectedPage, 'screencast') .resolves(mockRecorder as never); - await startScreencast.handler({params: {}}, response, context); + await startScreencast.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); sinon.assert.calledOnce(screencastStub); const callArgs = screencastStub.firstCall.args[0]; @@ -81,7 +88,11 @@ describe('screencast', () => { const selectedPage = context.getSelectedPage(); const screencastStub = sinon.stub(selectedPage, 'screencast'); - await startScreencast.handler({params: {}}, response, context); + await startScreencast.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); sinon.assert.notCalled(screencastStub); assert.ok( @@ -100,7 +111,7 @@ describe('screencast', () => { await assert.rejects( startScreencast.handler( - {params: {path: '/tmp/test.mp4'}}, + {params: {path: '/tmp/test.mp4'}, page: context.getSelectedPage()}, response, context, ), @@ -116,7 +127,11 @@ describe('screencast', () => { it('does nothing if no recording is active', async () => { await withMcpContext(async (response, context) => { assert.strictEqual(context.getScreenRecorder(), null); - await stopScreencast.handler({params: {}}, response, context); + await stopScreencast.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); assert.strictEqual(response.responseLines.length, 0); }); }); @@ -130,7 +145,11 @@ describe('screencast', () => { filePath, }); - await stopScreencast.handler({params: {}}, response, context); + await stopScreencast.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); sinon.assert.calledOnce(mockRecorder.stop); assert.strictEqual(context.getScreenRecorder(), null); @@ -152,7 +171,11 @@ describe('screencast', () => { }); await assert.rejects( - stopScreencast.handler({params: {}}, response, context), + stopScreencast.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ), /ffmpeg process error/, ); diff --git a/tests/tools/screenshot.test.ts b/tests/tools/screenshot.test.ts index dab541412..8f04e0358 100644 --- a/tests/tools/screenshot.test.ts +++ b/tests/tools/screenshot.test.ts @@ -21,7 +21,11 @@ describe('screenshot', () => { const fixture = screenshots.basic; const page = context.getSelectedPage(); await page.setContent(fixture.html); - await screenshot.handler({params: {format: 'png'}}, response, context); + await screenshot.handler( + {params: {format: 'png'}, page: context.getSelectedPage()}, + response, + context, + ); assert.equal(response.images.length, 1); assert.equal(response.images[0].mimeType, 'image/png'); @@ -37,7 +41,10 @@ describe('screenshot', () => { const page = context.getSelectedPage(); await page.setContent(fixture.html); await screenshot.handler( - {params: {format: 'png', quality: 0}}, + { + params: {format: 'png', quality: 0}, + page: context.getSelectedPage(), + }, response, context, ); @@ -52,7 +59,11 @@ describe('screenshot', () => { }); it('with jpeg', async () => { await withMcpContext(async (response, context) => { - await screenshot.handler({params: {format: 'jpeg'}}, response, context); + await screenshot.handler( + {params: {format: 'jpeg'}, page: context.getSelectedPage()}, + response, + context, + ); assert.equal(response.images.length, 1); assert.equal(response.images[0].mimeType, 'image/jpeg'); @@ -64,7 +75,11 @@ describe('screenshot', () => { }); it('with webp', async () => { await withMcpContext(async (response, context) => { - await screenshot.handler({params: {format: 'webp'}}, response, context); + await screenshot.handler( + {params: {format: 'webp'}, page: context.getSelectedPage()}, + response, + context, + ); assert.equal(response.images.length, 1); assert.equal(response.images[0].mimeType, 'image/webp'); @@ -80,7 +95,10 @@ describe('screenshot', () => { const page = context.getSelectedPage(); await page.setContent(fixture.html); await screenshot.handler( - {params: {format: 'png', fullPage: true}}, + { + params: {format: 'png', fullPage: true}, + page: context.getSelectedPage(), + }, response, context, ); @@ -112,7 +130,10 @@ describe('screenshot', () => { }); await screenshot.handler( - {params: {format: 'png', fullPage: true}}, + { + params: {format: 'png', fullPage: true}, + page: context.getSelectedPage(), + }, response, context, ); @@ -141,6 +162,7 @@ describe('screenshot', () => { format: 'png', uid: '1_1', }, + page: context.getSelectedPage(), }, response, context, @@ -163,7 +185,10 @@ describe('screenshot', () => { const page = context.getSelectedPage(); await page.setContent(fixture.html); await screenshot.handler( - {params: {format: 'png', filePath}}, + { + params: {format: 'png', filePath}, + page: context.getSelectedPage(), + }, response, context, ); @@ -204,7 +229,10 @@ describe('screenshot', () => { await page.setContent(fixture.html); await assert.rejects( screenshot.handler( - {params: {format: 'png', filePath}}, + { + params: {format: 'png', filePath}, + page: context.getSelectedPage(), + }, response, context, ), @@ -228,7 +256,10 @@ describe('screenshot', () => { await page.setContent(fixture.html); await assert.rejects( screenshot.handler( - {params: {format: 'png', filePath}}, + { + params: {format: 'png', filePath}, + page: context.getSelectedPage(), + }, response, context, ), @@ -253,7 +284,10 @@ describe('screenshot', () => { await page.setContent(fixture.html); await assert.rejects( screenshot.handler( - {params: {format: 'png', filePath}}, + { + params: {format: 'png', filePath}, + page: context.getSelectedPage(), + }, response, context, ), diff --git a/tests/tools/script.test.ts b/tests/tools/script.test.ts index 06c4875fe..c1c4d12b3 100644 --- a/tests/tools/script.test.ts +++ b/tests/tools/script.test.ts @@ -18,7 +18,10 @@ describe('script', () => { it('evaluates', async () => { await withMcpContext(async (response, context) => { await evaluateScript.handler( - {params: {function: String(() => 2 * 5)}}, + { + params: {function: String(() => 2 * 5)}, + page: context.getSelectedPage(), + }, response, context, ); @@ -29,7 +32,10 @@ describe('script', () => { it('runs in selected page', async () => { await withMcpContext(async (response, context) => { await evaluateScript.handler( - {params: {function: String(() => document.title)}}, + { + params: {function: String(() => document.title)}, + page: context.getSelectedPage(), + }, response, context, ); @@ -46,7 +52,10 @@ describe('script', () => { response.resetResponseLineForTesting(); await evaluateScript.handler( - {params: {function: String(() => document.title)}}, + { + params: {function: String(() => document.title)}, + page: context.getSelectedPage(), + }, response, context, ); @@ -73,6 +82,7 @@ describe('script', () => { return {scripts}; }), }, + page: context.getSelectedPage(), }, response, context, @@ -98,6 +108,7 @@ describe('script', () => { return 'Works'; }), }, + page: context.getSelectedPage(), }, response, context, @@ -123,6 +134,7 @@ describe('script', () => { }), args: [{uid: '1_1'}], }, + page: context.getSelectedPage(), }, response, context, @@ -148,6 +160,7 @@ describe('script', () => { }), args: [{uid: '1_0'}, {uid: '1_1'}], }, + page: context.getSelectedPage(), }, response, context, @@ -176,6 +189,7 @@ describe('script', () => { }), args: [{uid: '1_3'}], }, + page: context.getSelectedPage(), }, response, context, diff --git a/tests/tools/snapshot.test.ts b/tests/tools/snapshot.test.ts index 2aa40d8be..d1b068ada 100644 --- a/tests/tools/snapshot.test.ts +++ b/tests/tools/snapshot.test.ts @@ -14,7 +14,11 @@ describe('snapshot', () => { describe('browser_snapshot', () => { it('includes a snapshot', async () => { await withMcpContext(async (response, context) => { - await takeSnapshot.handler({params: {}}, response, context); + await takeSnapshot.handler( + {params: {}, page: context.getSelectedPage()}, + response, + context, + ); assert.ok(response.includeSnapshot); }); }); @@ -32,6 +36,7 @@ describe('snapshot', () => { params: { text: ['Hello'], }, + page, }, response, context, @@ -57,6 +62,7 @@ describe('snapshot', () => { params: { text: ['Complete', 'Error'], }, + page, }, response, context, @@ -79,6 +85,7 @@ describe('snapshot', () => { params: { text: ['Complete', 'Error'], }, + page, }, response, context, @@ -109,6 +116,7 @@ describe('snapshot', () => { params: { text: ['Hello World'], }, + page, }, response, context, @@ -140,6 +148,7 @@ describe('snapshot', () => { params: { text: ['Header'], }, + page, }, response, context, @@ -167,6 +176,7 @@ describe('snapshot', () => { params: { text: ['Hello iframe'], }, + page, }, response, context,