From 506278f74bfc510af464ca647ea2be197ed1ab54 Mon Sep 17 00:00:00 2001 From: Shrinish Vhanbatte Date: Fri, 31 Jul 2026 16:06:49 +0530 Subject: [PATCH] fix(TE-23055): bound page/context teardown so a crashed target cannot hang the build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On rate-limited/WAF-protected sites the target can crash mid-capture (page.goto: Page crashed / setViewportSize: Target crashed). page.close()/context.close() then never resolve on the dead target, so the capture promise never settles, Promise.allSettled never returns and finalizeBuild is unreachable — the build stays 'running' until the job times out (seen as ~30 minute scans). Evidence from an instrumented run: 12 'closing page/context' vs 11 'page/context closed'. Race the teardown against BROWSER_CLOSE_TIMEOUT (15s) and continue if it does not return; closeBrowsers() force-kills the browser process afterwards. Scope: this fixes only the hang, so the build completes quickly. Screenshots may still show Access Denied after the first few captures — that is the customer's WAF rate limiting and is resolved only once the HYE VMs are whitelisted. Co-Authored-By: Claude Opus 4.8 (1M context) --- package.json | 2 +- src/lib/constants.ts | 3 +++ src/lib/screenshot.ts | 14 ++++++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index e5e6e48..2afcdfa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lambdatest/smartui-cli", - "version": "4.1.77", + "version": "4.1.78", "description": "A command line interface (CLI) to run SmartUI tests on LambdaTest", "files": [ "dist/**/*" diff --git a/src/lib/constants.ts b/src/lib/constants.ts index ab1f058..7ec0f44 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -165,6 +165,9 @@ export default { // Default page load time DEFAULT_PAGE_LOAD_TIMEOUT: 180000, + // Cap on page/context teardown; a crashed target never returns from close(). + BROWSER_CLOSE_TIMEOUT: 15000, + // Magic Numbers MAGIC_NUMBERS: [ { ext: 'jpg', magic: Buffer.from([0xFF, 0xD8, 0xFF]) }, diff --git a/src/lib/screenshot.ts b/src/lib/screenshot.ts index 102da46..906d446 100644 --- a/src/lib/screenshot.ts +++ b/src/lib/screenshot.ts @@ -371,8 +371,18 @@ async function captureScreenshotsForConfig( } catch (error) { throw new Error(`captureScreenshotsForConfig failed for browser ${browserName}; error: ${error}`); } finally { - await page?.close(); - await context?.close(); + // A crashed target never resolves close(), which would strand this capture and block + // finalizeBuild. Abandoning it is safe: closeBrowsers() kills the browser process after. + const closed = await Promise.race([ + (async () => { await page?.close(); await context?.close(); return true; })().catch(() => true), + new Promise(resolve => { + const timer = setTimeout(() => resolve(false), constants.BROWSER_CLOSE_TIMEOUT); + (timer as any).unref?.(); + }) + ]); + if (!closed) { + ctx.log.warn(`${browserName}: page/context close timed out after ${constants.BROWSER_CLOSE_TIMEOUT}ms; abandoning it`); + } } }