11import { test as base , Page , Browser , chromium } from '@playwright/test' ;
2- import { spawn , ChildProcess } from 'child_process' ;
2+ import { exec , spawn , ChildProcess } from 'child_process' ;
33import { join } from 'path' ;
44import { promisify } from 'util' ;
55
6- const execAsync = promisify ( require ( 'child_process' ) . exec ) ;
6+ const execAsync = promisify ( exec ) ;
77
88type GooseTestFixtures = {
99 goosePage : Page ;
@@ -28,7 +28,8 @@ type GooseTestFixtures = {
2828 */
2929export const test = base . extend < GooseTestFixtures > ( {
3030 // Test-scoped fixture: launches a fresh Electron app for each test
31- goosePage : async ( { } , use , testInfo ) => {
31+ goosePage : async ( { browserName } , providePage , testInfo ) => {
32+ void browserName ;
3233 console . log ( `Launching fresh Electron app for test: ${ testInfo . title } ` ) ;
3334
3435 let appProcess : ChildProcess | null = null ;
@@ -80,8 +81,9 @@ export const test = base.extend<GooseTestFixtures>({
8081 console . log ( `Connected to Electron app on attempt ${ attempt } (~${ ( attempt * retryDelay ) / 1000 } s)` ) ;
8182 break ;
8283 } catch ( error ) {
84+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
8385 if ( attempt === maxRetries ) {
84- throw new Error ( `Failed to connect to Electron app after ${ maxRetries } attempts (${ ( maxRetries * retryDelay ) / 1000 } s). Last error: ${ error . message } ` ) ;
86+ throw new Error ( `Failed to connect to Electron app after ${ maxRetries } attempts (${ ( maxRetries * retryDelay ) / 1000 } s). Last error: ${ errorMessage } ` ) ;
8587 }
8688 // Wait before next retry
8789 await new Promise ( resolve => setTimeout ( resolve , retryDelay ) ) ;
@@ -92,26 +94,28 @@ export const test = base.extend<GooseTestFixtures>({
9294 throw new Error ( 'Browser connection failed unexpectedly' ) ;
9395 }
9496
95- // Get the electron app context and first page
96- const contexts = browser . contexts ( ) ;
97- if ( contexts . length === 0 ) {
98- throw new Error ( 'No browser contexts found' ) ;
97+ // Wait for Electron to create its first window after the CDP endpoint is up.
98+ let page : Page | null = null ;
99+ for ( let attempt = 1 ; attempt <= 100 ; attempt ++ ) {
100+ const contexts = browser . contexts ( ) ;
101+ page = contexts . flatMap ( ( context ) => context . pages ( ) ) [ 0 ] ?? null ;
102+ if ( page ) {
103+ break ;
104+ }
105+ await new Promise ( ( resolve ) => setTimeout ( resolve , 100 ) ) ;
99106 }
100107
101- const pages = contexts [ 0 ] . pages ( ) ;
102- if ( pages . length === 0 ) {
108+ if ( ! page ) {
103109 throw new Error ( 'No windows/pages found' ) ;
104110 }
105111
106- const page = pages [ 0 ] ;
107-
108112 // Wait for page to be ready
109113 await page . waitForLoadState ( 'domcontentloaded' ) ;
110114
111115 // Try to wait for networkidle
112116 try {
113117 await page . waitForLoadState ( 'networkidle' , { timeout : 10000 } ) ;
114- } catch ( error ) {
118+ } catch {
115119 console . log ( 'NetworkIdle timeout (likely due to MCP activity), continuing...' ) ;
116120 }
117121
@@ -124,7 +128,7 @@ export const test = base.extend<GooseTestFixtures>({
124128 console . log ( 'App ready, starting test...' ) ;
125129
126130 // Provide the page to the test
127- await use ( page ) ;
131+ await providePage ( page ) ;
128132
129133 } finally {
130134 console . log ( 'Cleaning up Electron app for this test...' ) ;
@@ -146,19 +150,23 @@ export const test = base.extend<GooseTestFixtures>({
146150 // First try SIGTERM for graceful shutdown
147151 process . kill ( - appProcess . pid , 'SIGTERM' ) ;
148152 await new Promise ( resolve => setTimeout ( resolve , 2000 ) ) ;
149- } catch ( e ) {
153+ } catch {
150154 // Process might already be dead
151155 }
152156 // Then SIGKILL if still running
153157 try {
154158 process . kill ( - appProcess . pid , 'SIGKILL' ) ;
155- } catch ( e ) {
159+ } catch {
156160 // Process already exited
157161 }
158162 }
159163 console . log ( 'Cleaned up app process' ) ;
160164 } catch ( error ) {
161- if ( error . code !== 'ESRCH' && ! error . message ?. includes ( 'No such process' ) ) {
165+ if (
166+ error instanceof Error &&
167+ ! ( 'code' in error && error . code === 'ESRCH' ) &&
168+ ! error . message . includes ( 'No such process' )
169+ ) {
162170 console . error ( 'Error killing app process:' , error ) ;
163171 }
164172 }
0 commit comments