@@ -114,6 +114,7 @@ function makeSession(
114114 if ( method === "Page.getNavigationHistory" ) return { currentIndex : 0 , entries : [ { } ] } ;
115115 if ( method === "Page.getLayoutMetrics" ) return { cssVisualViewport : { pageX : 0 , pageY : 0 } } ;
116116 if ( method === "Page.captureScreenshot" ) {
117+ if ( ( input as { format : string } ) . format === "jpeg" && heldStill ) await heldStill ( ) ;
117118 // A snapshot sizes its screenshot from the PNG header; viewers get JPEG frames as-is.
118119 return { data : ( input as { format : string } ) . format === "png" ? PNG_HEADER : "ZnJhbWU=" } ;
119120 }
@@ -384,6 +385,7 @@ let encoderPageOverride: Page | undefined;
384385const encoderStreams : Array < { frameRate : number | undefined ; bitsPerSecond : number } > = [ ] ;
385386const contextRequests : Array < { profileId : string ; isolated : boolean } > = [ ] ;
386387let contextGate : PromiseWithResolvers < void > | null = null ;
388+ let heldStill : ( ( ) => Promise < void > ) | null = null ;
387389let pageCopy = "" ;
388390let keyCopy = "" ;
389391let focusedFrame : ReturnType < typeof makeFrame > | null = null ;
@@ -568,6 +570,7 @@ beforeEach(() => {
568570 encoderStreams . length = 0 ;
569571 contextRequests . length = 0 ;
570572 contextGate = null ;
573+ heldStill = null ;
571574 contextFailure = null ;
572575 pageSetupFailure = null ;
573576 initialNavigation = null ;
@@ -1130,6 +1133,38 @@ it.live("enforces provider ownership and explicit targets when a session has mul
11301133 ) . pipe ( Effect . provide ( layer ) ) ,
11311134) ;
11321135
1136+ it . effect ( "a first still that never returns does not stall the tab's later captures" , ( ) =>
1137+ Effect . scoped (
1138+ Effect . gen ( function * ( ) {
1139+ const { browser, broker, tabId } = yield * ready ;
1140+ vi . useFakeTimers ( { toFake : [ "setTimeout" , "clearTimeout" ] } ) ;
1141+ yield * Effect . addFinalizer ( ( ) => Effect . sync ( ( ) => vi . useRealTimers ( ) ) ) ;
1142+ const capturing = Promise . withResolvers < void > ( ) ;
1143+ const stuck = Promise . withResolvers < void > ( ) ;
1144+ heldStill = ( ) => {
1145+ heldStill = null ;
1146+ capturing . resolve ( ) ;
1147+ return stuck . promise ;
1148+ } ;
1149+ yield * Effect . addFinalizer ( ( ) => Effect . sync ( ( ) => stuck . resolve ( ) ) ) ;
1150+ yield * browser . attachViewer ( viewerInput ( tabId , true ) ) ;
1151+ yield * Effect . promise ( ( ) => capturing . promise ) ;
1152+ const snapshotting = yield * broker
1153+ . invoke < PreviewAutomationSnapshot > ( { scope, tabId, operation : "snapshot" , input : { } } )
1154+ . pipe ( Effect . forkScoped ) ;
1155+ yield * Effect . promise ( ( ) => vi . advanceTimersByTimeAsync ( 2_000 ) ) ;
1156+ const snapshot = yield * Fiber . join ( snapshotting ) ;
1157+ expect ( snapshot . accessibilityTree ) . toContain ( "delete" ) ;
1158+ const second = yield * browser . attachViewer ( viewerInput ( tabId , false ) ) ;
1159+ const frame = yield * Stream . fromQueue ( second . output ) . pipe (
1160+ Stream . filter ( ( output ) => output . _tag === "frame" ) ,
1161+ Stream . runHead ,
1162+ ) ;
1163+ expect ( Option . isSome ( frame ) ) . toBe ( true ) ;
1164+ } ) ,
1165+ ) . pipe ( Effect . provide ( layer ) ) ,
1166+ ) ;
1167+
11331168it . live ( "streams to a read-only viewer without allowing takeover, input, or viewport changes" , ( ) =>
11341169 Effect . scoped (
11351170 Effect . gen ( function * ( ) {
@@ -1148,7 +1183,10 @@ it.live("streams to a read-only viewer without allowing takeover, input, or view
11481183 yield * viewer . input ( message ) ;
11491184 expect ( page . setViewportSize ) . not . toHaveBeenCalled ( ) ;
11501185 expect ( session . send . mock . calls . some ( ( [ method ] ) => method . startsWith ( "Input." ) ) ) . toBe ( false ) ;
1151- const outputs = yield * Queue . takeAll ( viewer . output ) ;
1186+ const outputs = yield * Stream . fromQueue ( viewer . output ) . pipe (
1187+ Stream . takeUntil ( ( output ) => output . _tag === "frame" ) ,
1188+ Stream . runCollect ,
1189+ ) ;
11521190 expect ( outputs ) . toContainEqual (
11531191 expect . objectContaining ( { _tag : "frame" , data : Buffer . from ( "frame" ) } ) ,
11541192 ) ;
0 commit comments