@@ -524,14 +524,19 @@ async function launchChromium(options = {}) {
524524 // Write command script for debugging
525525 writeCmdScript ( path . join ( outputDir , 'cmd.sh' ) , binary , chromiumArgs ) ;
526526
527+ let chromiumProcess = null ;
528+ let chromePid = null ;
529+ let recentStderr = '' ;
530+ let recentStdout = '' ;
531+
527532 try {
528533 console . error ( `[*] Spawning Chromium (headless=${ headless } )...` ) ;
529- const chromiumProcess = spawn ( binary , chromiumArgs , {
534+ chromiumProcess = spawn ( binary , chromiumArgs , {
530535 stdio : [ 'ignore' , 'pipe' , 'pipe' ] ,
531536 detached : true ,
532537 } ) ;
533538
534- const chromePid = chromiumProcess . pid ;
539+ chromePid = chromiumProcess . pid ;
535540 const chromeStartTime = Date . now ( ) / 1000 ;
536541
537542 if ( chromePid ) {
@@ -541,16 +546,35 @@ async function launchChromium(options = {}) {
541546
542547 // Pipe Chrome output to stderr
543548 chromiumProcess . stdout . on ( 'data' , ( data ) => {
549+ recentStdout = `${ recentStdout } ${ String ( data ) } ` . slice ( - 4000 ) ;
544550 process . stderr . write ( `[chromium:stdout] ${ data } ` ) ;
545551 } ) ;
546552 chromiumProcess . stderr . on ( 'data' , ( data ) => {
553+ recentStderr = `${ recentStderr } ${ String ( data ) } ` . slice ( - 4000 ) ;
547554 process . stderr . write ( `[chromium:stderr] ${ data } ` ) ;
548555 } ) ;
549556
557+ const chromiumExit = new Promise ( ( _ , reject ) => {
558+ chromiumProcess . once ( 'error' , ( error ) => {
559+ reject ( new Error ( `Chromium process failed to start: ${ error . message } ` ) ) ;
560+ } ) ;
561+ chromiumProcess . once ( 'exit' , ( code , signal ) => {
562+ reject ( new Error (
563+ `Chromium exited before opening the debug port (code=${ code ?? 'null' } , signal=${ signal || 'none' } )`
564+ ) ) ;
565+ } ) ;
566+ } ) ;
567+ // Suppress unhandled rejection if chromiumExit loses the race but
568+ // fires later when the browser eventually shuts down.
569+ chromiumExit . catch ( ( ) => { } ) ;
570+
550571 // Wait for debug port
551572 console . error ( `[*] Waiting for debug port ${ debugPort } ...` ) ;
552573 const debugProbeTimeoutMs = getEnvInt ( 'CHROME_DEBUG_PORT_TIMEOUT_MS' , 30000 ) ;
553- const versionInfo = await waitForDebugPort ( debugPort , debugProbeTimeoutMs ) ;
574+ const versionInfo = await Promise . race ( [
575+ waitForDebugPort ( debugPort , debugProbeTimeoutMs ) ,
576+ chromiumExit ,
577+ ] ) ;
554578 const wsUrl = versionInfo . webSocketDebuggerUrl ;
555579
556580 console . error ( `[+] Chromium ready: ${ wsUrl } ` ) ;
@@ -584,7 +608,19 @@ async function launchChromium(options = {}) {
584608
585609 return result ;
586610 } catch ( e ) {
587- return { success : false , error : `${ e . name } : ${ e . message } ` } ;
611+ if ( chromePid ) {
612+ await cleanupLaunchArtifacts ( outputDir , chromePid ) ;
613+ }
614+ const extraOutput = [
615+ recentStdout ? `stdout=${ recentStdout . trim ( ) } ` : '' ,
616+ recentStderr ? `stderr=${ recentStderr . trim ( ) } ` : '' ,
617+ ] . filter ( Boolean ) . join ( ' ' ) ;
618+ return {
619+ success : false ,
620+ error : extraOutput
621+ ? `${ e . name } : ${ e . message } (${ extraOutput } )`
622+ : `${ e . name } : ${ e . message } ` ,
623+ } ;
588624 }
589625}
590626
@@ -1591,10 +1627,15 @@ function findChromium() {
15911627 if ( validateBinary ( c ) ) return c ;
15921628 }
15931629 // Also search puppeteer cache under LIB_DIR
1594- const libPuppeteerDir = path . join ( libDir , 'puppeteer' , 'chrome' ) ;
1595- const libPuppeteerBinary = findInPuppeteerDir ( libPuppeteerDir ) ;
1596- if ( libPuppeteerBinary && validateBinary ( libPuppeteerBinary ) ) {
1597- return libPuppeteerBinary ;
1630+ const libPuppeteerDirs = [
1631+ path . join ( libDir , 'puppeteer' , 'chromium' ) ,
1632+ path . join ( libDir , 'puppeteer' , 'chrome' ) ,
1633+ ] ;
1634+ for ( const libPuppeteerDir of libPuppeteerDirs ) {
1635+ const libPuppeteerBinary = findInPuppeteerDir ( libPuppeteerDir ) ;
1636+ if ( libPuppeteerBinary && validateBinary ( libPuppeteerBinary ) ) {
1637+ return libPuppeteerBinary ;
1638+ }
15981639 }
15991640 }
16001641
@@ -2212,14 +2253,24 @@ async function withConnectedBrowser(options, operation) {
22122253async function setBrowserDownloadBehavior ( options = { } ) {
22132254 const {
22142255 browser,
2256+ page,
22152257 downloadPath,
22162258 } = options ;
22172259
2218- if ( ! browser || ! downloadPath ) return false ;
2260+ if ( ! browser && ! page ) {
2261+ throw new Error ( 'setBrowserDownloadBehavior requires a browser or page' ) ;
2262+ }
2263+ if ( ! downloadPath ) {
2264+ throw new Error ( 'setBrowserDownloadBehavior requires downloadPath' ) ;
2265+ }
22192266
22202267 await fs . promises . mkdir ( downloadPath , { recursive : true } ) ;
2221- const session = await browser . target ( ) . createCDPSession ( ) ;
2268+ const sessionTarget = page ? page . target ( ) : browser . target ( ) ;
2269+ const session = await sessionTarget . createCDPSession ( ) ;
22222270
2271+ // Keep the CDP session alive for the lifetime of the caller's browser/page
2272+ // connection. Extension-driven downloads regress if we detach immediately
2273+ // after configuring download behavior.
22232274 try {
22242275 await session . send ( 'Browser.setDownloadBehavior' , {
22252276 behavior : 'allow' ,
@@ -2242,7 +2293,7 @@ async function setBrowserDownloadBehavior(options = {}) {
22422293 }
22432294}
22442295
2245- function fetchDevtoolsTargets ( cdpUrl ) {
2296+ function fetchDevtoolsTargets ( cdpUrl , timeoutMs = 5000 ) {
22462297 const port = getChromeDebugPortFromCdpUrl ( cdpUrl ) ;
22472298 if ( ! port ) {
22482299 return Promise . resolve ( [ ] ) ;
@@ -2264,11 +2315,14 @@ function fetchDevtoolsTargets(cdpUrl) {
22642315 } ) ;
22652316 }
22662317 ) ;
2318+ req . setTimeout ( timeoutMs , ( ) => {
2319+ req . destroy ( new Error ( `Timed out fetching DevTools targets after ${ timeoutMs } ms` ) ) ;
2320+ } ) ;
22672321 req . on ( 'error' , reject ) ;
22682322 } ) ;
22692323}
22702324
2271- function devtoolsHttpRequest ( cdpUrl , requestPath , method = 'GET' ) {
2325+ function devtoolsHttpRequest ( cdpUrl , requestPath , method = 'GET' , timeoutMs = 5000 ) {
22722326 const port = getChromeDebugPortFromCdpUrl ( cdpUrl ) ;
22732327 if ( ! port ) {
22742328 return Promise . reject ( new Error ( `Invalid CDP URL: ${ cdpUrl } ` ) ) ;
@@ -2289,14 +2343,18 @@ function devtoolsHttpRequest(cdpUrl, requestPath, method = 'GET') {
22892343 } ) ;
22902344 }
22912345 ) ;
2346+ req . setTimeout ( timeoutMs , ( ) => {
2347+ req . destroy ( new Error ( `Timed out waiting for DevTools ${ method } ${ requestPath } after ${ timeoutMs } ms` ) ) ;
2348+ } ) ;
22922349 req . on ( 'error' , reject ) ;
22932350 req . end ( ) ;
22942351 } ) ;
22952352}
22962353
2297- async function createDevtoolsPageTarget ( cdpUrl , initialUrl = 'about:blank' ) {
2354+ async function createDevtoolsPageTarget ( cdpUrl , initialUrl = 'about:blank' , options = { } ) {
2355+ const timeoutMs = options . timeoutMs || 5000 ;
22982356 const encodedUrl = encodeURIComponent ( initialUrl ) ;
2299- const response = await devtoolsHttpRequest ( cdpUrl , `/json/new?${ encodedUrl } ` , 'PUT' ) ;
2357+ const response = await devtoolsHttpRequest ( cdpUrl , `/json/new?${ encodedUrl } ` , 'PUT' , timeoutMs ) ;
23002358 const target = JSON . parse ( response || '{}' ) ;
23012359 if ( ! target ?. id ) {
23022360 throw new Error ( 'Failed to create DevTools page target' ) ;
@@ -2648,15 +2706,38 @@ async function getBrowserServerUrl(chromeSessionDir = '../chrome', options = {})
26482706 * @returns {Promise<{targetId: string}> }
26492707 */
26502708async function openTabInChromeSession ( options = { } ) {
2651- const { cdpUrl, puppeteer } = options ;
2709+ const {
2710+ cdpUrl,
2711+ puppeteer,
2712+ timeoutMs = 10000 ,
2713+ intervalMs = 250 ,
2714+ } = options ;
26522715 if ( ! cdpUrl ) {
26532716 throw new Error ( CHROME_SESSION_REQUIRED_ERROR ) ;
26542717 }
26552718 if ( puppeteer ) {
26562719 requirePuppeteerModule ( puppeteer , 'openTabInChromeSession' ) ;
26572720 }
2658- const target = await createDevtoolsPageTarget ( cdpUrl , 'about:blank' ) ;
2659- return { targetId : target . id } ;
2721+ const deadline = Date . now ( ) + Math . max ( timeoutMs , 0 ) ;
2722+ let lastError = null ;
2723+
2724+ while ( Date . now ( ) <= deadline ) {
2725+ try {
2726+ const requestTimeoutMs = Math . max ( 1000 , Math . min ( 5000 , deadline - Date . now ( ) ) ) ;
2727+ const target = await createDevtoolsPageTarget ( cdpUrl , 'about:blank' , {
2728+ timeoutMs : requestTimeoutMs ,
2729+ } ) ;
2730+ return { targetId : target . id } ;
2731+ } catch ( error ) {
2732+ lastError = error ;
2733+ if ( Date . now ( ) >= deadline ) {
2734+ break ;
2735+ }
2736+ await sleep ( intervalMs ) ;
2737+ }
2738+ }
2739+
2740+ throw lastError || new Error ( 'Failed to create DevTools page target' ) ;
26602741}
26612742
26622743/**
0 commit comments