@@ -92,96 +92,53 @@ test('screenshotAndroid waits for transient UI to settle before capture', async
9292} ) ;
9393
9494test ( 'screenshotAndroid writes a valid PNG when output is clean' , async ( ) => {
95- const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'screenshot-clean-' ) ) ;
96- try {
97- const outPath = path . join ( tmpDir , 'out.png' ) ;
95+ await withTempScreenshot ( 'screenshot-clean-' , async ( outPath ) => {
9896 await screenshotAndroid ( device , outPath ) ;
9997 const written = await fs . readFile ( outPath ) ;
10098 assert . deepEqual ( written , VALID_PNG ) ;
101- } finally {
102- await fs . rm ( tmpDir , { recursive : true , force : true } ) ;
103- }
99+ } ) ;
104100} ) ;
105101
106102test ( 'screenshotAndroid strips warning text before PNG signature' , async ( ) => {
107103 const warning =
108104 '[Warning] Multiple displays were found, but no display id was specified! Defaulting to the first display found.' ;
109- const payload = Buffer . concat ( [ Buffer . from ( warning ) , VALID_PNG ] ) ;
110- mockRunCmd . mockImplementation ( async ( _cmd , args ) => {
111- if ( args . includes ( 'exec-out' ) ) {
112- return { exitCode : 0 , stdout : '' , stderr : '' , stdoutBuffer : payload } ;
113- }
114- return { exitCode : 0 , stdout : '' , stderr : '' } ;
115- } ) ;
105+ mockScreenshotPayload ( Buffer . concat ( [ Buffer . from ( warning ) , VALID_PNG ] ) ) ;
116106
117- const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'screenshot-warning-' ) ) ;
118- try {
119- const outPath = path . join ( tmpDir , 'out.png' ) ;
107+ await withTempScreenshot ( 'screenshot-warning-' , async ( outPath ) => {
120108 await screenshotAndroid ( device , outPath ) ;
121109 const written = await fs . readFile ( outPath ) ;
122110 assert . deepEqual ( written , VALID_PNG ) ;
123- } finally {
124- await fs . rm ( tmpDir , { recursive : true , force : true } ) ;
125- }
111+ } ) ;
126112} ) ;
127113
128114test ( 'screenshotAndroid strips trailing garbage after PNG payload' , async ( ) => {
129- const payload = Buffer . concat ( [ VALID_PNG , Buffer . from ( '\ntrailing-warning\n' ) ] ) ;
130- mockRunCmd . mockImplementation ( async ( _cmd , args ) => {
131- if ( args . includes ( 'exec-out' ) ) {
132- return { exitCode : 0 , stdout : '' , stderr : '' , stdoutBuffer : payload } ;
133- }
134- return { exitCode : 0 , stdout : '' , stderr : '' } ;
135- } ) ;
115+ mockScreenshotPayload ( Buffer . concat ( [ VALID_PNG , Buffer . from ( '\ntrailing-warning\n' ) ] ) ) ;
136116
137- const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'screenshot-trailing-' ) ) ;
138- try {
139- const outPath = path . join ( tmpDir , 'out.png' ) ;
117+ await withTempScreenshot ( 'screenshot-trailing-' , async ( outPath ) => {
140118 await screenshotAndroid ( device , outPath ) ;
141119 const written = await fs . readFile ( outPath ) ;
142120 assert . deepEqual ( written , VALID_PNG ) ;
143- } finally {
144- await fs . rm ( tmpDir , { recursive : true , force : true } ) ;
145- }
121+ } ) ;
146122} ) ;
147123
148124test ( 'screenshotAndroid throws when output contains no PNG signature' , async ( ) => {
149- mockRunCmd . mockImplementation ( async ( _cmd , args ) => {
150- if ( args . includes ( 'exec-out' ) ) {
151- return { exitCode : 0 , stdout : '' , stderr : '' , stdoutBuffer : Buffer . from ( 'not a png' ) } ;
152- }
153- return { exitCode : 0 , stdout : '' , stderr : '' } ;
154- } ) ;
125+ mockScreenshotPayload ( Buffer . from ( 'not a png' ) ) ;
155126
156- const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'screenshot-nopng-' ) ) ;
157- try {
158- const outPath = path . join ( tmpDir , 'out.png' ) ;
127+ await withTempScreenshot ( 'screenshot-nopng-' , async ( outPath ) => {
159128 await assert . rejects ( ( ) => screenshotAndroid ( device , outPath ) , {
160129 message : 'Screenshot data does not contain a valid PNG header' ,
161130 } ) ;
162- } finally {
163- await fs . rm ( tmpDir , { recursive : true , force : true } ) ;
164- }
131+ } ) ;
165132} ) ;
166133
167134test ( 'screenshotAndroid throws when PNG payload is truncated' , async ( ) => {
168- const payload = VALID_PNG . subarray ( 0 , VALID_PNG . length - 3 ) ;
169- mockRunCmd . mockImplementation ( async ( _cmd , args ) => {
170- if ( args . includes ( 'exec-out' ) ) {
171- return { exitCode : 0 , stdout : '' , stderr : '' , stdoutBuffer : payload } ;
172- }
173- return { exitCode : 0 , stdout : '' , stderr : '' } ;
174- } ) ;
135+ mockScreenshotPayload ( VALID_PNG . subarray ( 0 , VALID_PNG . length - 3 ) ) ;
175136
176- const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'screenshot-truncated-' ) ) ;
177- try {
178- const outPath = path . join ( tmpDir , 'out.png' ) ;
137+ await withTempScreenshot ( 'screenshot-truncated-' , async ( outPath ) => {
179138 await assert . rejects ( ( ) => screenshotAndroid ( device , outPath ) , {
180139 message : 'Screenshot data does not contain a complete PNG payload' ,
181140 } ) ;
182- } finally {
183- await fs . rm ( tmpDir , { recursive : true , force : true } ) ;
184- }
141+ } ) ;
185142} ) ;
186143
187144function helperOutput ( xml : string ) : string {
@@ -209,6 +166,39 @@ function helperOutput(xml: string): string {
209166 ] . join ( '\n' ) ;
210167}
211168
169+ function mockScreenshotPayload ( payload : Buffer ) : void {
170+ mockRunCmd . mockImplementation ( async ( _cmd , args ) => {
171+ if ( args . includes ( 'exec-out' ) ) {
172+ return { exitCode : 0 , stdout : '' , stderr : '' , stdoutBuffer : payload } ;
173+ }
174+ return { exitCode : 0 , stdout : '' , stderr : '' } ;
175+ } ) ;
176+ }
177+
178+ async function withTempScreenshot (
179+ name : string ,
180+ callback : ( outPath : string ) => Promise < void > ,
181+ ) : Promise < void > {
182+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , name ) ) ;
183+ try {
184+ await callback ( path . join ( tmpDir , 'out.png' ) ) ;
185+ } finally {
186+ await fs . rm ( tmpDir , { recursive : true , force : true } ) ;
187+ }
188+ }
189+
190+ function mockAndroidSnapshotXml ( xml : string , activityDump = '' ) : void {
191+ mockRunCmd . mockImplementation ( async ( _cmd , args ) => {
192+ if ( args . includes ( 'exec-out' ) ) {
193+ return { exitCode : 0 , stdout : xml , stderr : '' } ;
194+ }
195+ if ( args . includes ( 'dumpsys' ) && args . includes ( 'activity' ) && args . includes ( 'top' ) ) {
196+ return { exitCode : 0 , stdout : activityDump , stderr : '' } ;
197+ }
198+ throw new Error ( `unexpected args: ${ args . join ( ' ' ) } ` ) ;
199+ } ) ;
200+ }
201+
212202test ( 'dumpUiHierarchy returns streamed XML even when exec-out exits non-zero' , async ( ) => {
213203 const xml =
214204 '<?xml version="1.0" encoding="UTF-8"?><hierarchy><node text="streamed"/></hierarchy>' ;
@@ -456,15 +446,7 @@ test('snapshotAndroid preserves hidden scroll content hints in interactive snaps
456446 ' com.facebook.react.views.view.ReactViewGroup{c V.E...... ........ 0,636-390,804 #3}' ,
457447 ] . join ( '\n' ) ;
458448
459- mockRunCmd . mockImplementation ( async ( _cmd , args ) => {
460- if ( args . includes ( 'exec-out' ) ) {
461- return { exitCode : 0 , stdout : xml , stderr : '' } ;
462- }
463- if ( args . includes ( 'dumpsys' ) && args . includes ( 'activity' ) && args . includes ( 'top' ) ) {
464- return { exitCode : 0 , stdout : dump , stderr : '' } ;
465- }
466- throw new Error ( `unexpected args: ${ args . join ( ' ' ) } ` ) ;
467- } ) ;
449+ mockAndroidSnapshotXml ( xml , dump ) ;
468450
469451 const result = await snapshotAndroid ( device , { interactiveOnly : true } ) ;
470452 const scrollArea = result . nodes . find ( ( node ) => node . type === 'android.widget.ScrollView' ) ;
@@ -487,15 +469,7 @@ test('snapshotAndroid keeps generic-id scroll containers in interactive snapshot
487469 </node>
488470</hierarchy>` ;
489471
490- mockRunCmd . mockImplementation ( async ( _cmd , args ) => {
491- if ( args . includes ( 'exec-out' ) ) {
492- return { exitCode : 0 , stdout : xml , stderr : '' } ;
493- }
494- if ( args . includes ( 'dumpsys' ) && args . includes ( 'activity' ) && args . includes ( 'top' ) ) {
495- return { exitCode : 0 , stdout : '' , stderr : '' } ;
496- }
497- throw new Error ( `unexpected args: ${ args . join ( ' ' ) } ` ) ;
498- } ) ;
472+ mockAndroidSnapshotXml ( xml ) ;
499473
500474 const result = await snapshotAndroid ( device , { interactiveOnly : true } ) ;
501475 const scrollArea = result . nodes . find (
@@ -544,15 +518,7 @@ test('snapshotAndroid derives hidden content hints for interactive snapshots fro
544518 </node>
545519</hierarchy>` ;
546520
547- mockRunCmd . mockImplementation ( async ( _cmd , args ) => {
548- if ( args . includes ( 'exec-out' ) ) {
549- return { exitCode : 0 , stdout : xml , stderr : '' } ;
550- }
551- if ( args . includes ( 'dumpsys' ) && args . includes ( 'activity' ) && args . includes ( 'top' ) ) {
552- return { exitCode : 0 , stdout : '' , stderr : '' } ;
553- }
554- throw new Error ( `unexpected args: ${ args . join ( ' ' ) } ` ) ;
555- } ) ;
521+ mockAndroidSnapshotXml ( xml ) ;
556522
557523 const result = await snapshotAndroid ( device , { interactiveOnly : true } ) ;
558524 const scrollArea = result . nodes . find ( ( node ) => node . type === 'android.widget.ScrollView' ) ;
@@ -587,15 +553,7 @@ test('snapshotAndroid preserves bottomed-out hidden-above hints in interactive s
587553 ' com.facebook.react.views.view.ReactViewGroup{c V.E...... ........ 0,636-390,804 #3}' ,
588554 ] . join ( '\n' ) ;
589555
590- mockRunCmd . mockImplementation ( async ( _cmd , args ) => {
591- if ( args . includes ( 'exec-out' ) ) {
592- return { exitCode : 0 , stdout : xml , stderr : '' } ;
593- }
594- if ( args . includes ( 'dumpsys' ) && args . includes ( 'activity' ) && args . includes ( 'top' ) ) {
595- return { exitCode : 0 , stdout : dump , stderr : '' } ;
596- }
597- throw new Error ( `unexpected args: ${ args . join ( ' ' ) } ` ) ;
598- } ) ;
556+ mockAndroidSnapshotXml ( xml , dump ) ;
599557
600558 const result = await snapshotAndroid ( device , { interactiveOnly : true } ) ;
601559 const scrollArea = result . nodes . find (
0 commit comments