/** * Shared browser launch configuration and navigation helpers. * * Fixes: * - DNS resolution inside Docker (--dns-resolution-order=hostname-first) * - Slow sites: uses waitUntil: 'commit' + waitForLoadState instead of 'networkidle' * - UA fingerprinting: realistic Chrome user agent * * Usage: * const { launchBrowser, navigateTo } = require('./lib/browser-launcher'); * const browser = await launchBrowser(); * const page = ...; * await navigateTo(page, 'https://example.com'); */ const { chromium } = require('playwright'); const USE_DNS_FIX = process.env.DNS_RESOLUTION_ORDER === 'hostname-first'; const BASE_ARGS = [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu', '--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled', ...(USE_DNS_FIX ? ['--dns-resolution-order=hostname-first'] : []), ]; const DEFAULT_UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'; async function launchBrowser(options = {}) { const args = [...BASE_ARGS, ...(options.extraArgs || [])]; return chromium.launch({ headless: options.headless !== undefined ? options.headless : true, args, }); } async function newContext(browser, options = {}) { return browser.newContext({ viewport: { width: 1280, height: 720 }, deviceScaleFactor: 1, userAgent: DEFAULT_UA, ...options, }); } async function navigateTo(page, url, options = {}) { const waitUntil = options.waitUntil || 'commit'; const timeout = options.timeout || 60000; const response = await page.goto(url, { waitUntil, timeout }); if (options.waitForDom !== false) { await page.waitForLoadState('domcontentloaded', { timeout: options.domTimeout || 15000 }).catch(() => {}); } const delay = options.delay || 2000; if (delay > 0) await page.waitForTimeout(delay); return response; } module.exports = { launchBrowser, newContext, navigateTo, BASE_ARGS, DEFAULT_UA };