From 9d41e0ff1179c89a8ab7dc3df86faf972ad5c933 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 4 Mar 2026 08:22:17 -0800 Subject: [PATCH] chore(mcp): do not affect default timeouts by mcp (#39502) --- packages/playwright-core/src/mcp/browser/config.ts | 2 ++ packages/playwright-core/src/mcp/browser/tab.ts | 10 +++++++--- .../playwright-core/src/mcp/browser/tools/form.ts | 6 +++--- .../playwright-core/src/mcp/browser/tools/keyboard.ts | 6 +++--- .../playwright-core/src/mcp/browser/tools/navigate.ts | 6 +++--- .../src/mcp/browser/tools/screenshot.ts | 1 + .../playwright-core/src/mcp/browser/tools/snapshot.ts | 11 ++++++----- .../playwright-core/src/mcp/browser/tools/verify.ts | 6 +++--- packages/playwright-core/src/mcp/config.d.ts | 5 +++++ .../src/utils/isomorphic/stringUtils.ts | 2 +- 10 files changed, 34 insertions(+), 21 deletions(-) diff --git a/packages/playwright-core/src/mcp/browser/config.ts b/packages/playwright-core/src/mcp/browser/config.ts index 17a5803d8ea3a..88e5b1bec3a81 100644 --- a/packages/playwright-core/src/mcp/browser/config.ts +++ b/packages/playwright-core/src/mcp/browser/config.ts @@ -109,6 +109,7 @@ export const defaultConfig: FullConfig = { timeouts: { action: 5000, navigation: 60000, + expect: 5000, }, }; @@ -134,6 +135,7 @@ export type FullConfig = Config & { timeouts: { action: number; navigation: number; + expect: number; }, skillMode?: boolean; configFile?: string; diff --git a/packages/playwright-core/src/mcp/browser/tab.ts b/packages/playwright-core/src/mcp/browser/tab.ts index ae19819137ac8..6f474ceb78a56 100644 --- a/packages/playwright-core/src/mcp/browser/tab.ts +++ b/packages/playwright-core/src/mcp/browser/tab.ts @@ -103,6 +103,9 @@ export class Tab extends EventEmitter { private _recentEventEntries: EventEntry[] = []; private _consoleLog: LogFile; private _disposables: Disposable[]; + readonly actionTimeoutOptions: { timeout: number; }; + readonly navigationTimeoutOptions: { timeout: number; }; + readonly expectTimeoutOptions: { timeout: number; }; constructor(context: Context, page: playwright.Page, onPageClose: (tab: Tab) => void) { super(); @@ -130,12 +133,13 @@ export class Tab extends EventEmitter { void this._downloadStarted(download); }), ]; - page.setDefaultNavigationTimeout(this.context.config.timeouts.navigation); - page.setDefaultTimeout(this.context.config.timeouts.action); (page as any)[tabSymbol] = this; const wallTime = Date.now(); this._consoleLog = new LogFile(this.context, wallTime, 'console', 'Console'); this._initializedPromise = this._initialize(); + this.actionTimeoutOptions = { timeout: context.config.timeouts.action }; + this.navigationTimeoutOptions = { timeout: context.config.timeouts.navigation }; + this.expectTimeoutOptions = { timeout: context.config.timeouts.expect }; } dispose() { @@ -300,7 +304,7 @@ export class Tab extends EventEmitter { const { promise: downloadEvent, abort: abortDownloadEvent } = eventWaiter(this.page, 'download', 3000); try { - await this.page.goto(url, { waitUntil: 'domcontentloaded' }); + await this.page.goto(url, { waitUntil: 'domcontentloaded', ...this.navigationTimeoutOptions }); abortDownloadEvent(); } catch (_e: unknown) { const e = _e as Error; diff --git a/packages/playwright-core/src/mcp/browser/tools/form.ts b/packages/playwright-core/src/mcp/browser/tools/form.ts index ad6f69109eb2a..17b7ec136e8c5 100644 --- a/packages/playwright-core/src/mcp/browser/tools/form.ts +++ b/packages/playwright-core/src/mcp/browser/tools/form.ts @@ -43,13 +43,13 @@ const fillForm = defineTabTool({ const locatorSource = `await page.${resolved}`; if (field.type === 'textbox' || field.type === 'slider') { const secret = tab.context.lookupSecret(field.value); - await locator.fill(secret.value); + await locator.fill(secret.value, tab.actionTimeoutOptions); response.addCode(`${locatorSource}.fill(${secret.code});`); } else if (field.type === 'checkbox' || field.type === 'radio') { - await locator.setChecked(field.value === 'true'); + await locator.setChecked(field.value === 'true', tab.actionTimeoutOptions); response.addCode(`${locatorSource}.setChecked(${field.value});`); } else if (field.type === 'combobox') { - await locator.selectOption({ label: field.value }); + await locator.selectOption({ label: field.value }, tab.actionTimeoutOptions); response.addCode(`${locatorSource}.selectOption(${escapeWithQuotes(field.value)});`); } } diff --git a/packages/playwright-core/src/mcp/browser/tools/keyboard.ts b/packages/playwright-core/src/mcp/browser/tools/keyboard.ts index 71ab4ccd319f5..8b688e7d8ee6a 100644 --- a/packages/playwright-core/src/mcp/browser/tools/keyboard.ts +++ b/packages/playwright-core/src/mcp/browser/tools/keyboard.ts @@ -98,16 +98,16 @@ const type = defineTabTool({ if (params.slowly) { response.setIncludeSnapshot(); response.addCode(`await page.${resolved}.pressSequentially(${secret.code});`); - await locator.pressSequentially(secret.value); + await locator.pressSequentially(secret.value, tab.actionTimeoutOptions); } else { response.addCode(`await page.${resolved}.fill(${secret.code});`); - await locator.fill(secret.value); + await locator.fill(secret.value, tab.actionTimeoutOptions); } if (params.submit) { response.setIncludeSnapshot(); response.addCode(`await page.${resolved}.press('Enter');`); - await locator.press('Enter'); + await locator.press('Enter', tab.actionTimeoutOptions); } }); }, diff --git a/packages/playwright-core/src/mcp/browser/tools/navigate.ts b/packages/playwright-core/src/mcp/browser/tools/navigate.ts index 4f1ccf12bd5b2..8d947db504757 100644 --- a/packages/playwright-core/src/mcp/browser/tools/navigate.ts +++ b/packages/playwright-core/src/mcp/browser/tools/navigate.ts @@ -60,7 +60,7 @@ const goBack = defineTabTool({ }, handle: async (tab, params, response) => { - await tab.page.goBack(); + await tab.page.goBack(tab.navigationTimeoutOptions); response.setIncludeSnapshot(); response.addCode(`await page.goBack();`); }, @@ -78,7 +78,7 @@ const goForward = defineTabTool({ }, handle: async (tab, params, response) => { - await tab.page.goForward(); + await tab.page.goForward(tab.navigationTimeoutOptions); response.setIncludeSnapshot(); response.addCode(`await page.goForward();`); }, @@ -96,7 +96,7 @@ const reload = defineTabTool({ }, handle: async (tab, params, response) => { - await tab.page.reload(); + await tab.page.reload(tab.navigationTimeoutOptions); response.setIncludeSnapshot(); response.addCode(`await page.reload();`); }, diff --git a/packages/playwright-core/src/mcp/browser/tools/screenshot.ts b/packages/playwright-core/src/mcp/browser/tools/screenshot.ts index 0fefcd58903fe..f66130c5128e0 100644 --- a/packages/playwright-core/src/mcp/browser/tools/screenshot.ts +++ b/packages/playwright-core/src/mcp/browser/tools/screenshot.ts @@ -50,6 +50,7 @@ const screenshot = defineTabTool({ type: fileType, quality: fileType === 'png' ? undefined : 90, scale: 'css', + ...tab.actionTimeoutOptions, ...(params.fullPage !== undefined && { fullPage: params.fullPage }) }; diff --git a/packages/playwright-core/src/mcp/browser/tools/snapshot.ts b/packages/playwright-core/src/mcp/browser/tools/snapshot.ts index 94166832a6de7..53ad0cf8ce872 100644 --- a/packages/playwright-core/src/mcp/browser/tools/snapshot.ts +++ b/packages/playwright-core/src/mcp/browser/tools/snapshot.ts @@ -65,6 +65,7 @@ const click = defineTabTool({ const options = { button: params.button, modifiers: params.modifiers, + ...tab.actionTimeoutOptions, }; const optionsArg = formatObjectOrVoid(options); @@ -106,7 +107,7 @@ const drag = defineTabTool({ ]); await tab.waitForCompletion(async () => { - await start.locator.dragTo(end.locator); + await start.locator.dragTo(end.locator, tab.actionTimeoutOptions); }); response.addCode(`await page.${start.resolved}.dragTo(page.${end.resolved});`); @@ -130,7 +131,7 @@ const hover = defineTabTool({ response.addCode(`await page.${resolved}.hover();`); await tab.waitForCompletion(async () => { - await locator.hover(); + await locator.hover(tab.actionTimeoutOptions); }); }, }); @@ -156,7 +157,7 @@ const selectOption = defineTabTool({ response.addCode(`await page.${resolved}.selectOption(${formatObject(params.values)});`); await tab.waitForCompletion(async () => { - await locator.selectOption(params.values); + await locator.selectOption(params.values, tab.actionTimeoutOptions); }); }, }); @@ -192,7 +193,7 @@ const check = defineTabTool({ handle: async (tab, params, response) => { const { locator, resolved } = await tab.refLocator(params); response.addCode(`await page.${resolved}.check();`); - await locator.check(); + await locator.check(tab.actionTimeoutOptions); }, }); @@ -210,7 +211,7 @@ const uncheck = defineTabTool({ handle: async (tab, params, response) => { const { locator, resolved } = await tab.refLocator(params); response.addCode(`await page.${resolved}.uncheck();`); - await locator.uncheck(); + await locator.uncheck(tab.actionTimeoutOptions); }, }); diff --git a/packages/playwright-core/src/mcp/browser/tools/verify.ts b/packages/playwright-core/src/mcp/browser/tools/verify.ts index 788a2c4b46a26..f28fd0c3aa507 100644 --- a/packages/playwright-core/src/mcp/browser/tools/verify.ts +++ b/packages/playwright-core/src/mcp/browser/tools/verify.ts @@ -95,7 +95,7 @@ const verifyList = defineTabTool({ response.addError(`Item "${item}" not found`); return; } - itemTexts.push((await itemLocator.textContent())!); + itemTexts.push((await itemLocator.textContent(tab.expectTimeoutOptions))!); } const ariaSnapshot = `\` - list: @@ -125,14 +125,14 @@ const verifyValue = defineTabTool({ const { locator, resolved } = await tab.refLocator({ ref: params.ref, element: params.element }); const locatorSource = `page.${resolved}`; if (params.type === 'textbox' || params.type === 'slider' || params.type === 'combobox') { - const value = await locator.inputValue(); + const value = await locator.inputValue(tab.expectTimeoutOptions); if (value !== params.value) { response.addError(`Expected value "${params.value}", but got "${value}"`); return; } response.addCode(`await expect(${locatorSource}).toHaveValue(${escapeWithQuotes(params.value)});`); } else if (params.type === 'checkbox' || params.type === 'radio') { - const value = await locator.isChecked(); + const value = await locator.isChecked(tab.expectTimeoutOptions); if (value !== (params.value === 'true')) { response.addError(`Expected value "${params.value}", but got "${value}"`); return; diff --git a/packages/playwright-core/src/mcp/config.d.ts b/packages/playwright-core/src/mcp/config.d.ts index 685aff49a1c05..d270a6fdcebb4 100644 --- a/packages/playwright-core/src/mcp/config.d.ts +++ b/packages/playwright-core/src/mcp/config.d.ts @@ -214,6 +214,11 @@ export type Config = { * Configures default navigation timeout: https://playwright.dev/docs/api/class-page#page-set-default-navigation-timeout. Defaults to 60000ms. */ navigation?: number; + + /** + * Configures default expect timeout: https://playwright.dev/docs/test-timeouts#expect-timeout. Defaults to 5000ms. + */ + expect?: number; }; /** diff --git a/packages/playwright-core/src/utils/isomorphic/stringUtils.ts b/packages/playwright-core/src/utils/isomorphic/stringUtils.ts index ba1b4a6cbec27..6e8a9115071ba 100644 --- a/packages/playwright-core/src/utils/isomorphic/stringUtils.ts +++ b/packages/playwright-core/src/utils/isomorphic/stringUtils.ts @@ -53,7 +53,7 @@ export function formatObject(value: any, indent = ' ', mode: 'multiline' | 'one if (Array.isArray(value)) return `[${value.map(o => formatObject(o)).join(', ')}]`; if (typeof value === 'object') { - const keys = Object.keys(value).filter(key => value[key] !== undefined).sort(); + const keys = Object.keys(value).filter(key => key !== 'timeout' && value[key] !== undefined).sort(); if (!keys.length) return '{}'; const tokens: string[] = [];