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.
This commit is contained in:
Ryan Malloy 2026-02-03 00:23:15 -07:00
parent 8e0953abc4
commit a65f00667a

View File

@ -30,6 +30,33 @@ type PageEx = playwright.Page & {
_snapshotForAI: () => Promise<string>; _snapshotForAI: () => Promise<string>;
}; };
const SNAPSHOT_TIMEOUT_MS = 10000; // 10 seconds
async function snapshotWithTimeout(page: playwright.Page): Promise<string> {
let timeoutId: ReturnType<typeof setTimeout> | undefined;
const timeoutPromise = new Promise<string>((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 = { export const TabEvents = {
modalState: 'modalState' modalState: 'modalState'
}; };
@ -914,7 +941,7 @@ export class Tab extends EventEmitter<TabEventsInterface> {
result.push(...this._listDownloadsMarkdown()); result.push(...this._listDownloadsMarkdown());
await this._raceAgainstModalStates(async () => { await this._raceAgainstModalStates(async () => {
const snapshot = await (this.page as PageEx)._snapshotForAI(); const snapshot = await snapshotWithTimeout(this.page);
result.push( result.push(
`### Page state`, `### Page state`,
`- Page URL: ${this.page.url()}`, `- Page URL: ${this.page.url()}`,
@ -958,7 +985,7 @@ export class Tab extends EventEmitter<TabEventsInterface> {
} }
async refLocators(params: { element: string, ref: string }[]): Promise<playwright.Locator[]> { async refLocators(params: { element: string, ref: string }[]): Promise<playwright.Locator[]> {
const snapshot = await (this.page as PageEx)._snapshotForAI(); const snapshot = await snapshotWithTimeout(this.page);
return params.map(param => { return params.map(param => {
if (!snapshot.includes(`[ref=${param.ref}]`)) if (!snapshot.includes(`[ref=${param.ref}]`))
throw new Error(`Ref ${param.ref} not found in the current page snapshot. Try capturing new snapshot.`); throw new Error(`Ref ${param.ref} not found in the current page snapshot. Try capturing new snapshot.`);