项目文件夹

文件
Ashutosh Kumar 496ab1b45f feat: support any-match text arrays in wait_for (#1011)
## 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
2026-02-24 07:16:47 +00:00

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);
});
});
});
});