Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/tool-reference.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- AUTO GENERATED DO NOT EDIT - run 'npm run docs' to update-->

# 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)
Expand Down Expand Up @@ -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.

---
Expand Down
12 changes: 7 additions & 5 deletions src/McpContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -881,15 +881,17 @@ export class McpContext implements Context {
return this.#networkCollector.getIdForResource(request);
}

waitForTextOnPage(text: string, timeout?: number): Promise<Element> {
waitForTextOnPage(text: string[], timeout?: number): Promise<Element> {
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) {
Expand Down
2 changes: 1 addition & 1 deletion src/tools/ToolDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export type Context = Readonly<{
action: () => Promise<unknown>,
options?: {timeout?: number},
): Promise<void>;
waitForTextOnPage(text: string, timeout?: number): Promise<Element>;
waitForTextOnPage(text: string[], timeout?: number): Promise<Element>;
getDevToolsData(): Promise<DevToolsData>;
/**
* Returns a reqid for a cdpRequestId.
Expand Down
9 changes: 7 additions & 2 deletions src/tools/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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.`,
Comment thread
Lightning00Blade marked this conversation as resolved.
);

response.includeSnapshot();
Expand Down
72 changes: 64 additions & 8 deletions tests/tools/snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('snapshot', () => {
await waitFor.handler(
{
params: {
text: 'Hello',
text: ['Hello'],
},
},
response,
Expand All @@ -39,19 +39,75 @@ 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`<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',
text: ['Hello World'],
},
},
response,
Expand All @@ -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);
});
Expand All @@ -82,7 +138,7 @@ describe('snapshot', () => {
await waitFor.handler(
{
params: {
text: 'Header',
text: ['Header'],
},
},
response,
Expand All @@ -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);
});
Expand All @@ -109,7 +165,7 @@ describe('snapshot', () => {
await waitFor.handler(
{
params: {
text: 'Hello iframe',
text: ['Hello iframe'],
},
},
response,
Expand All @@ -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);
});
Expand Down