From a65f00667a56d431b3ed0c7d97aef2dcdcf9f45d Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Tue, 3 Feb 2026 00:23:15 -0700 Subject: [PATCH] fix: add 10-second timeout to accessibility snapshots Prevents infinite hangs when _snapshotForAI() blocks on complex pages, SVG files, or file:// URLs. Returns a helpful message suggesting browser_take_screenshot as an alternative. --- src/tab.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/tab.ts b/src/tab.ts index a93534e..3c9d084 100644 --- a/src/tab.ts +++ b/src/tab.ts @@ -30,6 +30,33 @@ type PageEx = playwright.Page & { _snapshotForAI: () => Promise; }; +const SNAPSHOT_TIMEOUT_MS = 10000; // 10 seconds + +async function snapshotWithTimeout(page: playwright.Page): Promise { + let timeoutId: ReturnType | undefined; + + const timeoutPromise = new Promise((resolve) => { + timeoutId = setTimeout(() => { + resolve( + `[Snapshot timed out after ${SNAPSHOT_TIMEOUT_MS / 1000} seconds]\n` + + `This can happen with complex pages, SVG files, or file:// URLs.\n` + + `Use browser_take_screenshot to view the page, or disable auto-snapshots with browser_configure_snapshots.` + ); + }, SNAPSHOT_TIMEOUT_MS); + }); + + try { + const result = await Promise.race([ + (page as PageEx)._snapshotForAI(), + timeoutPromise, + ]); + return result; + } finally { + if (timeoutId) + clearTimeout(timeoutId); + } +} + export const TabEvents = { modalState: 'modalState' }; @@ -914,7 +941,7 @@ export class Tab extends EventEmitter { result.push(...this._listDownloadsMarkdown()); await this._raceAgainstModalStates(async () => { - const snapshot = await (this.page as PageEx)._snapshotForAI(); + const snapshot = await snapshotWithTimeout(this.page); result.push( `### Page state`, `- Page URL: ${this.page.url()}`, @@ -958,7 +985,7 @@ export class Tab extends EventEmitter { } async refLocators(params: { element: string, ref: string }[]): Promise { - const snapshot = await (this.page as PageEx)._snapshotForAI(); + const snapshot = await snapshotWithTimeout(this.page); return params.map(param => { if (!snapshot.includes(`[ref=${param.ref}]`)) throw new Error(`Ref ${param.ref} not found in the current page snapshot. Try capturing new snapshot.`);