import { mkdir, rm } from 'node:fs/promises'; import { chromium } from 'playwright'; export const CLOSE_TIMEOUT_MS = 3000; export const LAUNCH_TIMEOUT_MS = 10000; export const CONTEXT_TIMEOUT_MS = 8000; export const PAGE_TIMEOUT_MS = 15000; // Chromium puts profiles and internal files in the process temp directory. // Some CI/dev containers mount /tmp as a very small tmpfs, so the checks point // it at an already-ignored directory under .astro/ instead. export async function setupBrowserTmp(browserTmp) { await rm(browserTmp, { recursive: true, force: true }); await mkdir(browserTmp, { recursive: true }); process.env.TMPDIR = browserTmp; process.env.TMP = browserTmp; process.env.TEMP = browserTmp; } export async function cleanupBrowserTmp(browserTmp) { await rm(browserTmp, { recursive: true, force: true }).catch(() => {}); } export async function withTimeout(promise, timeoutMs, label) { let timeout; try { return await Promise.race([ promise, new Promise((_, reject) => { timeout = setTimeout(() => reject(new Error(label)), timeoutMs); }), ]); } finally { clearTimeout(timeout); } } export async function safeClosePage(page) { await withTimeout( page.close(), CLOSE_TIMEOUT_MS, 'Timed out while closing Playwright page' ).catch(() => {}); } export async function safeCloseContext(context) { await withTimeout( context.close(), CLOSE_TIMEOUT_MS, 'Timed out while closing Playwright context' ).catch(() => {}); } export async function safeCloseBrowser(browser) { const childProcess = browser.process?.(); try { await withTimeout( browser.close(), CLOSE_TIMEOUT_MS, 'Timed out while closing Chromium' ); } catch { childProcess?.kill('SIGKILL'); } } export function openBrowser(browserTmp) { return withTimeout( chromium.launch({ headless: true, env: { ...process.env, TMPDIR: browserTmp, TMP: browserTmp, TEMP: browserTmp, }, args: ['--disable-dev-shm-usage', '--disable-gpu', '--no-sandbox'], }), LAUNCH_TIMEOUT_MS, 'Timed out while launching Chromium' ); } export function shouldRetryNavigation(error) { const message = error instanceof Error ? error.message : String(error); return /ERR_INSUFFICIENT_RESOURCES|Execution context was destroyed|Target.*closed|has been closed|Timed out while|navigation/i.test( message ); }