@@ -48,82 +48,97 @@ export const test = base.extend<TestAppFixtures, WorkerAppFixtures>({
4848 ] ,
4949
5050 mainPage : async ( { electronApp } , use , testInfo : TestInfo ) => {
51- const page = await electronApp . firstWindow ( { timeout : PROCESS_READY_TIMEOUT } ) ;
5251 const rendererUrl = 'http://localhost:1212/index.html?logLevel=debug' ;
52+ const readyDeadline = Date . now ( ) + PROCESS_READY_TIMEOUT ;
5353
54- console . log ( `Window URL: ${ page . url ( ) } ` ) ;
5554 const onPageError = ( err : Error ) => console . error ( `Page error: ${ err . message } ` ) ;
5655 const onConsoleMsg = ( msg : ConsoleMessage ) => {
5756 if ( msg . type ( ) === 'error' ) console . error ( `Console error: ${ msg . text ( ) } ` ) ;
5857 } ;
59- page . on ( 'pageerror' , onPageError ) ;
60- page . on ( 'console' , onConsoleMsg ) ;
6158
62- const readyDeadline = Date . now ( ) + PROCESS_READY_TIMEOUT ;
63- let rootAttached = false ;
59+ const attachListeners = ( page : Page ) => {
60+ page . on ( 'pageerror' , onPageError ) ;
61+ page . on ( 'console' , onConsoleMsg ) ;
62+ } ;
63+
64+ const detachListeners = ( page : Page ) => {
65+ page . off ( 'pageerror' , onPageError ) ;
66+ page . off ( 'console' , onConsoleMsg ) ;
67+ } ;
6468
69+ let page : Page | undefined ;
6570 while ( Date . now ( ) < readyDeadline ) {
66- const currentUrl = page . url ( ) ;
67-
68- // CI can intermittently land on chrome-error://chromewebdata/ if Electron opens before the
69- // renderer navigation has fully settled. Drive the page back to the actual renderer URL and
70- // retry until React mounts or timeout.
71- if (
72- currentUrl . startsWith ( 'chrome-error://' ) ||
73- currentUrl === 'about:blank' ||
74- ! currentUrl . startsWith ( 'http://localhost:1212/' )
75- ) {
71+ const pages = electronApp . windows ( ) ;
72+ page =
73+ pages . find ( ( candidate ) => {
74+ const candidateUrl = candidate . url ( ) ;
75+ return (
76+ candidateUrl . startsWith ( 'http://localhost:1212/' ) &&
77+ ! candidateUrl . includes ( 'devtools://' )
78+ ) ;
79+ } ) ?? pages . find ( ( candidate ) => ! candidate . url ( ) . includes ( 'devtools://' ) ) ;
80+
81+ if ( page ) {
82+ attachListeners ( page ) ;
83+
7684 try {
77- await page . goto ( rendererUrl , {
78- waitUntil : 'domcontentloaded' ,
79- timeout : 15_000 ,
85+ const currentUrl = page . url ( ) ;
86+ console . log ( `Window URL: ${ currentUrl } ` ) ;
87+
88+ // CI can intermittently land on chrome-error://chromewebdata/ if Electron opens before the
89+ // renderer navigation has fully settled. Drive the page back to the actual renderer URL and
90+ // retry until React mounts or timeout.
91+ if (
92+ currentUrl . startsWith ( 'chrome-error://' ) ||
93+ currentUrl === 'about:blank' ||
94+ ! currentUrl . startsWith ( 'http://localhost:1212/' )
95+ ) {
96+ await page . goto ( rendererUrl , {
97+ waitUntil : 'domcontentloaded' ,
98+ timeout : 15_000 ,
99+ } ) ;
100+ }
101+
102+ const remaining = Math . max ( 0 , readyDeadline - Date . now ( ) ) ;
103+ if ( remaining <= 0 ) break ;
104+
105+ await page . waitForLoadState ( 'domcontentloaded' ) ;
106+ await page . waitForSelector ( '#root' , {
107+ state : 'attached' ,
108+ timeout : Math . min ( 5_000 , remaining ) ,
80109 } ) ;
110+
111+ console . log ( `Window URL: ${ page . url ( ) } ` ) ;
112+ await use ( page ) ;
113+ detachListeners ( page ) ;
114+
115+ if ( testInfo . status !== testInfo . expectedStatus ) {
116+ const screenshotPath = testInfo . outputPath ( 'failure.png' ) ;
117+ try {
118+ await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
119+ await testInfo . attach ( 'failure-screenshot' , {
120+ path : screenshotPath ,
121+ contentType : 'image/png' ,
122+ } ) ;
123+ console . log ( `Failure screenshot saved to ${ screenshotPath } ` ) ;
124+ } catch {
125+ console . warn ( 'Could not capture failure screenshot (window may already be closed)' ) ;
126+ }
127+ }
128+ return ;
81129 } catch {
82- // Retry loop handles transient dev-server unavailability.
130+ detachListeners ( page ) ;
131+ page = undefined ;
83132 }
84133 }
85134
86- const remaining = Math . max ( 0 , readyDeadline - Date . now ( ) ) ;
87- if ( remaining <= 0 ) break ;
88-
89- try {
90- await page . waitForLoadState ( 'domcontentloaded' ) ;
91- await page . waitForSelector ( '#root' , {
92- state : 'attached' ,
93- timeout : Math . min ( 5_000 , remaining ) ,
94- } ) ;
95- rootAttached = true ;
96- break ;
97- } catch {
98- // Keep retrying until deadline.
99- }
135+ await new Promise < void > ( ( resolve ) => {
136+ setTimeout ( resolve , 500 ) ;
137+ } ) ;
100138 }
101139
102- if ( ! rootAttached ) {
103- throw new Error (
104- `Main renderer did not mount #root within ${ PROCESS_READY_TIMEOUT } ms (current URL: ${ page . url ( ) } )` ,
105- ) ;
106- }
107-
108- console . log ( `Window URL: ${ page . url ( ) } ` ) ;
109-
110- await use ( page ) ;
111-
112- page . off ( 'pageerror' , onPageError ) ;
113- page . off ( 'console' , onConsoleMsg ) ;
114-
115- if ( testInfo . status !== testInfo . expectedStatus ) {
116- const screenshotPath = testInfo . outputPath ( 'failure.png' ) ;
117- try {
118- await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
119- await testInfo . attach ( 'failure-screenshot' , {
120- path : screenshotPath ,
121- contentType : 'image/png' ,
122- } ) ;
123- console . log ( `Failure screenshot saved to ${ screenshotPath } ` ) ;
124- } catch {
125- console . warn ( 'Could not capture failure screenshot (window may already be closed)' ) ;
126- }
127- }
140+ throw new Error (
141+ `Main renderer did not mount #root within ${ PROCESS_READY_TIMEOUT } ms (last URL: ${ page ?. url ( ) ?? 'no window' } )` ,
142+ ) ;
128143 } ,
129144} ) ;
0 commit comments