@@ -350,13 +350,25 @@ export async function drainBrowserPreviewRouteTracker(tracker: BrowserPreviewRou
350350 }
351351}
352352
353- export async function closeBrowserAndDrainPreviewRoutes ( browser : Pick < import ( "playwright" ) . Browser , "close" > , tracker : BrowserPreviewRouteTracker ) : Promise < Error [ ] > {
353+ export async function closeBrowserAndDrainPreviewRoutes ( browser : Pick < import ( "playwright" ) . Browser , "close" > , tracker : BrowserPreviewRouteTracker , closeTimeoutMs = 1_000 ) : Promise < Error [ ] > {
354354 const errors : Error [ ] = [ ]
355+ let closeTimer : ReturnType < typeof setTimeout > | undefined
355356 try {
356- await browser . close ( )
357- } catch ( error ) {
358- errors . push ( browserPreviewLifecycleError ( "browser-close" , error ) )
357+ const closeResult = await Promise . race ( [
358+ browser . close ( ) . then ( ( ) => ( { status : "closed" as const } ) , ( error : unknown ) => ( { status : "failed" as const , error } ) ) ,
359+ new Promise < { status : "timeout" } > ( ( resolve ) => {
360+ closeTimer = setTimeout ( ( ) => resolve ( { status : "timeout" } ) , closeTimeoutMs )
361+ } ) ,
362+ ] )
363+ if ( closeResult . status === "failed" ) {
364+ errors . push ( browserPreviewLifecycleError ( "browser-close" , closeResult . error ) )
365+ } else if ( closeResult . status === "timeout" ) {
366+ errors . push ( browserPreviewLifecycleError ( "browser-close-timeout" , new Error ( `Browser close exceeded ${ closeTimeoutMs } ms` ) ) )
367+ }
359368 } finally {
369+ if ( closeTimer ) {
370+ clearTimeout ( closeTimer )
371+ }
360372 try {
361373 await drainBrowserPreviewRouteTracker ( tracker )
362374 } catch ( error ) {
@@ -568,7 +580,7 @@ async function fetchBrowserPreviewRoutedHost(route: Route, requestUrl: URL, poli
568580 }
569581
570582 if ( resourceType !== "document" || ! retryable ) {
571- await route . abort ( "failed" ) . catch ( ( ) => undefined )
583+ await abortBrowserPreviewRoute ( route , " abort-recoverable-fetch" )
572584 return undefined
573585 }
574586 throw browserPreviewRouteFetchExhaustedError ( route , currentUrl , attempt , error )
@@ -597,15 +609,15 @@ async function fetchBrowserPreviewRoutedHost(route: Route, requestUrl: URL, poli
597609 stat . external = true
598610 stat . blocked += 1
599611 policy . routedRedirectEscapes . push ( { rawOrigin : redirectedUrl . origin , effectiveOrigin : origin . origin , reason : "redirect-host-not-routed-to-preview" } )
600- await route . abort ( "blockedbyclient" )
612+ await abortBrowserPreviewRoute ( route , " abort-redirect-escape" , "blockedbyclient" )
601613 return undefined
602614 }
603615
604616 currentUrl = redirectedUrl
605617 }
606618
607619 if ( route . request ( ) . resourceType ( ) !== "document" ) {
608- await route . abort ( "failed" ) . catch ( ( ) => undefined )
620+ await abortBrowserPreviewRoute ( route , " abort-redirect-limit" )
609621 return undefined
610622 }
611623
@@ -664,6 +676,20 @@ function browserPreviewLifecycleError(operation: string, error: unknown): Error
664676 return diagnostic
665677}
666678
679+ async function abortBrowserPreviewRoute ( route : Route , operation : string , errorCode : Parameters < Route [ "abort" ] > [ 0 ] = "failed" ) : Promise < void > {
680+ try {
681+ await route . abort ( errorCode )
682+ } catch ( error ) {
683+ if ( isBrowserPreviewRouteClosedError ( error ) ) {
684+ return
685+ }
686+ const cause = sanitizeBrowserPreviewRouteError ( error ) . message . replace ( / [ \r \n ] + / g, " " )
687+ const diagnostic = new Error ( `wordpress.browser-probe route operation failed: operation=${ operation } cause=${ cause } ` )
688+ diagnostic . name = "BrowserPreviewRouteOperationError"
689+ throw diagnostic
690+ }
691+ }
692+
667693function browserPreviewRouteFetchExhaustedError ( route : Route , requestUrl : URL , attempts : number , error : unknown ) : Error {
668694 const method = route . request ( ) . method ( )
669695 const resourceType = route . request ( ) . resourceType ( )
0 commit comments