From edf0170821883b177302c7c0e397adac41c6a0f9 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Tue, 19 May 2026 11:47:12 +0200 Subject: [PATCH] fix(screenshotter): race cleanup against progress to honor timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `finally` blocks in `screenshotPage` and `screenshotElement` awaited `_restorePageAfterScreenshot()` without racing against `progress`. If the renderer's event loop was blocked (e.g. busy JS loop), `nonStallingEvaluateInExistingContext` would hang indefinitely because it only races against navigation / open dialogs, not against an arbitrary timeout — swallowing the timeout error and hanging the call. Wrap both cleanups in `progress.race(...).catch(() => {})`. The `.catch` is required to avoid masking an in-flight error from the `try` block with the abort error from the race. Fixes: https://github.com/microsoft/playwright/issues/36702 --- packages/playwright-core/src/server/screenshotter.ts | 4 ++-- tests/library/screenshot.spec.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/server/screenshotter.ts b/packages/playwright-core/src/server/screenshotter.ts index fe854a1fb6183..c29e9e0b7c5e7 100644 --- a/packages/playwright-core/src/server/screenshotter.ts +++ b/packages/playwright-core/src/server/screenshotter.ts @@ -218,7 +218,7 @@ export class Screenshotter { const viewportRect = options.clip ? trimClipToSize(options.clip, viewportSize) : { x: 0, y: 0, ...viewportSize }; return await this._screenshot(progress, format, undefined, viewportRect, true, options); } finally { - await this._restorePageAfterScreenshot(); + await progress.race(this._restorePageAfterScreenshot()).catch(() => {}); } }); } @@ -245,7 +245,7 @@ export class Screenshotter { documentRect.y += scrollOffset.y; return await this._screenshot(progress, format, helper.enclosingIntRect(documentRect), undefined, fitsViewport, options); } finally { - await this._restorePageAfterScreenshot(); + await progress.race(this._restorePageAfterScreenshot()).catch(() => {}); } }); } diff --git a/tests/library/screenshot.spec.ts b/tests/library/screenshot.spec.ts index 21e4678d5d293..f54f1034e3cdd 100644 --- a/tests/library/screenshot.spec.ts +++ b/tests/library/screenshot.spec.ts @@ -211,6 +211,16 @@ browserTest.describe('page screenshot', () => { expect(pixel(0, 999).r).toBeLessThan(128); expect(pixel(0, 999).b).toBeGreaterThan(128); }); + + browserTest('should not hang when event loop is blocked', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/36702' } }, async ({ page }) => { + browserTest.setTimeout(5000); + await page.evaluate(() => { + setTimeout(() => { + while (true) {} + }, 10); + }); + await expect(page.screenshot({ fullPage: true, timeout: 200 })).rejects.toThrow(/page.screenshot: Timeout 200ms exceeded/); + }); }); browserTest.describe('element screenshot', () => {