chromedevtools--chrome-devtools-mcp
496ab1b45f
## Summary Enhances wait_for to support waiting on multiple possible texts and resolve when any one appears. This addresses long-running flows that can end in different UI outcomes (for example, "Complete" or "Error"), avoiding unnecessary 300s waits when only one expected string is provided. Closes #916. ## Tool Update ### wait_for Waits for text on the selected page, now with any-match support. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | text | string \| string[] | yes | A single text or a non-empty list of texts. Resolves when any value appears. | | timeout | integer | no | Maximum wait in ms (0 keeps default behavior). | --- ## Design / Implementation - Kept backward compatibility: existing single-string text calls continue to work unchanged. - Added schema support for string | string[] with non-empty array validation. - Updated context API to accept string | string[]. - Matching logic now normalizes to an array and races all candidates across all frames using both: - aria/<text> - text/<text> - Added clearer response output for array input: - Element matching one of ["Complete","Error"] found. - Updated generated tool docs for the new wait_for contract. - Improved docs generation to render ZodUnion types (so union params are documented correctly, not as unknown). --- ## Tests Added/updated coverage for: - Schema acceptance of: - single string - non-empty string array - rejection of empty array - Any-match array success case - Any-match array when matching text appears later (async/delayed content) - Existing wait_for behavior remains covered for single-text usage Executed relevant test suites: - tests/tools/snapshot.test.ts - tests/McpContext.test.ts - tests/index.test.ts Confirmation after the change applied: https://opncd.ai/share/8m6I4r4a
184 行
4.6 KiB
TypeScript
184 行
4.6 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import assert from 'node:assert';
|
|
import {describe, it} from 'node:test';
|
|
|
|
import {takeSnapshot, waitFor} from '../../src/tools/snapshot.js';
|
|
import {html, withMcpContext} from '../utils.js';
|
|
|
|
describe('snapshot', () => {
|
|
describe('browser_snapshot', () => {
|
|
it('includes a snapshot', async () => {
|
|
await withMcpContext(async (response, context) => {
|
|
await takeSnapshot.handler({params: {}}, response, context);
|
|
assert.ok(response.includeSnapshot);
|
|
});
|
|
});
|
|
});
|
|
describe('browser_wait_for', () => {
|
|
it('should work', async () => {
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedPage();
|
|
|
|
await page.setContent(
|
|
html`<main><span>Hello</span><span> </span><div>World</div></main>`,
|
|
);
|
|
await waitFor.handler(
|
|
{
|
|
params: {
|
|
text: ['Hello'],
|
|
},
|
|
},
|
|
response,
|
|
context,
|
|
);
|
|
|
|
assert.equal(
|
|
response.responseLines[0],
|
|
'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`<main><span>Status</span><div>Error</div></main>`,
|
|
);
|
|
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`<main
|
|
><span>Hello</span><span> </span><div>Complete</div></main
|
|
>`,
|
|
);
|
|
|
|
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();
|
|
|
|
const handlePromise = waitFor.handler(
|
|
{
|
|
params: {
|
|
text: ['Hello World'],
|
|
},
|
|
},
|
|
response,
|
|
context,
|
|
);
|
|
|
|
await page.setContent(
|
|
html`<main><span>Hello</span><span> </span><div>World</div></main>`,
|
|
);
|
|
|
|
await handlePromise;
|
|
|
|
assert.equal(
|
|
response.responseLines[0],
|
|
'Element matching one of ["Hello World"] found.',
|
|
);
|
|
assert.ok(response.includeSnapshot);
|
|
});
|
|
});
|
|
it('should work with aria elements', async () => {
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedPage();
|
|
|
|
await page.setContent(
|
|
html`<main><h1>Header</h1><div>Text</div></main>`,
|
|
);
|
|
|
|
await waitFor.handler(
|
|
{
|
|
params: {
|
|
text: ['Header'],
|
|
},
|
|
},
|
|
response,
|
|
context,
|
|
);
|
|
|
|
assert.equal(
|
|
response.responseLines[0],
|
|
'Element matching one of ["Header"] found.',
|
|
);
|
|
assert.ok(response.includeSnapshot);
|
|
});
|
|
});
|
|
|
|
it('should work with iframe content', async () => {
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedPage();
|
|
|
|
await page.setContent(
|
|
html`<h1>Top level</h1>
|
|
<iframe srcdoc="<p>Hello iframe</p>"></iframe>`,
|
|
);
|
|
|
|
await waitFor.handler(
|
|
{
|
|
params: {
|
|
text: ['Hello iframe'],
|
|
},
|
|
},
|
|
response,
|
|
context,
|
|
);
|
|
|
|
assert.equal(
|
|
response.responseLines[0],
|
|
'Element matching one of ["Hello iframe"] found.',
|
|
);
|
|
assert.ok(response.includeSnapshot);
|
|
});
|
|
});
|
|
});
|
|
});
|