diff --git a/docs/tool-reference.md b/docs/tool-reference.md index 776859acf..9111d6fd0 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -1,6 +1,6 @@ -# Chrome DevTools MCP Tool Reference (~6885 cl100k_base tokens) +# Chrome DevTools MCP Tool Reference (~6916 cl100k_base tokens) - **[Input automation](#input-automation)** (8 tools) - [`click`](#click) @@ -195,7 +195,7 @@ **Parameters:** -- **text** (string) **(required)**: Text to appear on the page +- **text** (array) **(required)**: Non-empty list of texts. Resolves when any value appears on the page. - **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used. --- diff --git a/src/McpContext.ts b/src/McpContext.ts index 83697b5fb..a36f0c197 100644 --- a/src/McpContext.ts +++ b/src/McpContext.ts @@ -881,15 +881,17 @@ export class McpContext implements Context { return this.#networkCollector.getIdForResource(request); } - waitForTextOnPage(text: string, timeout?: number): Promise { + waitForTextOnPage(text: string[], timeout?: number): Promise { const page = this.getSelectedPage(); const frames = page.frames(); let locator = this.#locatorClass.race( - frames.flatMap(frame => [ - frame.locator(`aria/${text}`), - frame.locator(`text/${text}`), - ]), + frames.flatMap(frame => + text.flatMap(value => [ + frame.locator(`aria/${value}`), + frame.locator(`text/${value}`), + ]), + ), ); if (timeout) { diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index aee562b71..30d2cd9ac 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -144,7 +144,7 @@ export type Context = Readonly<{ action: () => Promise, options?: {timeout?: number}, ): Promise; - waitForTextOnPage(text: string, timeout?: number): Promise; + waitForTextOnPage(text: string[], timeout?: number): Promise; getDevToolsData(): Promise; /** * Returns a reqid for a cdpRequestId. diff --git a/src/tools/snapshot.ts b/src/tools/snapshot.ts index 143d04093..a07bf5825 100644 --- a/src/tools/snapshot.ts +++ b/src/tools/snapshot.ts @@ -49,7 +49,12 @@ export const waitFor = defineTool({ readOnlyHint: true, }, schema: { - text: zod.string().describe('Text to appear on the page'), + text: zod + .array(zod.string()) + .min(1) + .describe( + 'Non-empty list of texts. Resolves when any value appears on the page.', + ), ...timeoutSchema, }, handler: async (request, response, context) => { @@ -59,7 +64,7 @@ export const waitFor = defineTool({ ); response.appendResponseLine( - `Element with text "${request.params.text}" found.`, + `Element matching one of ${JSON.stringify(request.params.text)} found.`, ); response.includeSnapshot(); diff --git a/tests/tools/snapshot.test.ts b/tests/tools/snapshot.test.ts index 795e3d416..2aa40d8be 100644 --- a/tests/tools/snapshot.test.ts +++ b/tests/tools/snapshot.test.ts @@ -30,7 +30,7 @@ describe('snapshot', () => { await waitFor.handler( { params: { - text: 'Hello', + text: ['Hello'], }, }, response, @@ -39,11 +39,67 @@ describe('snapshot', () => { assert.equal( response.responseLines[0], - 'Element with text "Hello" found.', + 'Element matching one of ["Hello"] found.', ); assert.ok(response.includeSnapshot); }); }); + + it('should work with any-match array', async () => { + await withMcpContext(async (response, context) => { + const page = context.getSelectedPage(); + + await page.setContent( + html`
Status
Error
`, + ); + await waitFor.handler( + { + params: { + text: ['Complete', 'Error'], + }, + }, + response, + context, + ); + + assert.equal( + response.responseLines[0], + 'Element matching one of ["Complete","Error"] found.', + ); + assert.ok(response.includeSnapshot); + }); + }); + + it('should work with any-match array when element shows up later', async () => { + await withMcpContext(async (response, context) => { + const page = context.getSelectedPage(); + + const handlePromise = waitFor.handler( + { + params: { + text: ['Complete', 'Error'], + }, + }, + response, + context, + ); + + await page.setContent( + html`
Hello
Complete
`, + ); + + await handlePromise; + + assert.equal( + response.responseLines[0], + 'Element matching one of ["Complete","Error"] found.', + ); + assert.ok(response.includeSnapshot); + }); + }); + it('should work with element that show up later', async () => { await withMcpContext(async (response, context) => { const page = context.getSelectedPage(); @@ -51,7 +107,7 @@ describe('snapshot', () => { const handlePromise = waitFor.handler( { params: { - text: 'Hello World', + text: ['Hello World'], }, }, response, @@ -66,7 +122,7 @@ describe('snapshot', () => { assert.equal( response.responseLines[0], - 'Element with text "Hello World" found.', + 'Element matching one of ["Hello World"] found.', ); assert.ok(response.includeSnapshot); }); @@ -82,7 +138,7 @@ describe('snapshot', () => { await waitFor.handler( { params: { - text: 'Header', + text: ['Header'], }, }, response, @@ -91,7 +147,7 @@ describe('snapshot', () => { assert.equal( response.responseLines[0], - 'Element with text "Header" found.', + 'Element matching one of ["Header"] found.', ); assert.ok(response.includeSnapshot); }); @@ -109,7 +165,7 @@ describe('snapshot', () => { await waitFor.handler( { params: { - text: 'Hello iframe', + text: ['Hello iframe'], }, }, response, @@ -118,7 +174,7 @@ describe('snapshot', () => { assert.equal( response.responseLines[0], - 'Element with text "Hello iframe" found.', + 'Element matching one of ["Hello iframe"] found.', ); assert.ok(response.includeSnapshot); });