From cad83149615c9b469f9fd99b9ec34a8cd50e09ae Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Fri, 12 Jun 2026 09:30:30 -0700 Subject: [PATCH] fix(mcp): pass action timeout to browser_wait_for waitFor calls The `browser_wait_for` tool calls `locator.waitFor()` without passing the configured action timeout. Since Playwright's default actionTimeout is 0 (infinite), if the text never appears or disappears, the tool hangs indefinitely, blocking the entire MCP session. Every other MCP tool in the same directory passes `tab.actionTimeoutOptions` to Playwright API calls. This commit aligns `browser_wait_for` with the established pattern. --- .../playwright-core/src/tools/backend/wait.ts | 4 +- tests/mcp/timeouts.spec.ts | 50 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/tools/backend/wait.ts b/packages/playwright-core/src/tools/backend/wait.ts index cdf9e90d13243..11aa2a8d5e29a 100644 --- a/packages/playwright-core/src/tools/backend/wait.ts +++ b/packages/playwright-core/src/tools/backend/wait.ts @@ -47,12 +47,12 @@ const wait = defineTool({ if (goneLocator) { response.addCode(`await page.getByText(${JSON.stringify(params.textGone)}).first().waitFor({ state: 'hidden' });`); - await goneLocator.waitFor({ state: 'hidden' }); + await goneLocator.waitFor({ state: 'hidden', ...tab.actionTimeoutOptions }); } if (locator) { response.addCode(`await page.getByText(${JSON.stringify(params.text)}).first().waitFor({ state: 'visible' });`); - await locator.waitFor({ state: 'visible' }); + await locator.waitFor({ state: 'visible', ...tab.actionTimeoutOptions }); } response.addTextResult(`Waited for ${params.text || params.textGone || params.time}`); diff --git a/tests/mcp/timeouts.spec.ts b/tests/mcp/timeouts.spec.ts index 6038db5b426d3..4ea3c18a18ab8 100644 --- a/tests/mcp/timeouts.spec.ts +++ b/tests/mcp/timeouts.spec.ts @@ -76,6 +76,56 @@ test('action timeout (custom)', async ({ startClient, server }) => { }); }); +test('wait_for text timeout', async ({ startClient, server }) => { + const { client } = await startClient({ args: [`--timeout-action=1234`] }); + server.setContent('/', ` + + +
Hello World
+ + `, 'text/html'); + + await client.callTool({ + name: 'browser_navigate', + arguments: { + url: server.PREFIX, + }, + }); + + expect(await client.callTool({ + name: 'browser_wait_for', + arguments: { text: 'This text will never appear' }, + })).toHaveResponse({ + error: expect.stringContaining(`Timeout 1234ms exceeded.`), + isError: true, + }); +}); + +test('wait_for textGone timeout', async ({ startClient, server }) => { + const { client } = await startClient({ args: [`--timeout-action=1234`] }); + server.setContent('/', ` + + +
Permanent text
+ + `, 'text/html'); + + await client.callTool({ + name: 'browser_navigate', + arguments: { + url: server.PREFIX, + }, + }); + + expect(await client.callTool({ + name: 'browser_wait_for', + arguments: { textGone: 'Permanent text' }, + })).toHaveResponse({ + error: expect.stringContaining(`Timeout 1234ms exceeded.`), + isError: true, + }); +}); + test('navigation timeout', async ({ startClient, server }) => { const { client } = await startClient({ args: [`--timeout-navigation=1234`] }); server.setRoute('/slow', async () => {