@@ -128,22 +128,28 @@ interface DocumentNavigation {
128128
129129const documentNavigations = new WeakMap < Page , DocumentNavigation > ( )
130130
131- /** Marks the outgoing document before Playwright starts navigation and releases it on completion . */
131+ /** Tracks teardown until run resolves at document commit, then waits for the new page to load . */
132132async function withDocumentNavigation < T > (
133133 page : Page ,
134134 destination : string ,
135135 run : ( ) => Promise < T >
136136) : Promise < T > {
137137 const navigation = { from : page . url ( ) , to : new URL ( destination , baseUrl ) . href }
138138 documentNavigations . set ( page , navigation )
139+ let result : T
139140 try {
140- return await run ( )
141+ result = await run ( )
141142 } finally {
142143 if ( documentNavigations . get ( page ) === navigation ) documentNavigations . delete ( page )
143144 }
145+ await page . waitForLoadState ( 'load' , { timeout : 360_000 } )
146+ return result
144147}
145148
146- /** WebKit reports handled same-origin fetch failures during document teardown as page errors. */
149+ /**
150+ * WebKit reports handled same-origin requests during teardown as page errors.
151+ * Playwright splits stackless EventSource diagnostics at the URL colon and drops its first slash.
152+ */
147153function getNavigationCancellationPath (
148154 error : Error ,
149155 browserName : string ,
@@ -159,8 +165,13 @@ function getNavigationCancellationPath(
159165 const match = error . stack ?. match (
160166 / ^ F e t c h A P I c a n n o t l o a d ( h t t p s ? : \/ \/ \S + ) d u e t o a c c e s s c o n t r o l c h e c k s \. /
161167 )
162- if ( ! match ) return
163- const url = new URL ( match [ 1 ] )
168+ const eventSourceMatch =
169+ ! error . stack && error . name === 'EventSource cannot load http'
170+ ? error . message . match ( / ^ \/ ( [ ^ / \s ] \S * ) d u e t o a c c e s s c o n t r o l c h e c k s \. $ / )
171+ : null
172+ const target = match ?. [ 1 ] ?? ( eventSourceMatch ? `http://${ eventSourceMatch [ 1 ] } ` : undefined )
173+ if ( ! target ) return
174+ const url = new URL ( target )
164175 return url . origin === baseUrl . origin ? url . pathname : undefined
165176}
166177
@@ -251,7 +262,7 @@ async function visit(page: Page, route: string) {
251262 { timeout : 360_000 }
252263 ) ,
253264 withDocumentNavigation ( page , path , ( ) =>
254- page . goto ( path , { waitUntil : 'load ' , timeout : 360_000 } )
265+ page . goto ( path , { waitUntil : 'commit ' , timeout : 360_000 } )
255266 ) ,
256267 ] )
257268 assert ( response . status ( ) < 400 , `Could not load ${ route } : ${ response . status ( ) } ` )
@@ -478,9 +489,6 @@ async function exerciseViewport(
478489 await context . tracing . start ( { screenshots : true , snapshots : true } )
479490 const page = await context . newPage ( )
480491 const pageErrors : string [ ] = [ ]
481- page . on ( 'framenavigated' , ( frame ) => {
482- if ( frame === page . mainFrame ( ) ) documentNavigations . delete ( page )
483- } )
484492 page . on ( 'pageerror' , ( error ) => {
485493 const pendingNavigation = documentNavigations . get ( page ) ?? null
486494 const path = getNavigationCancellationPath ( error , browserName , pendingNavigation )
@@ -756,7 +764,7 @@ async function exerciseViewport(
756764 async ( ) => {
757765 const chatPath = `/chat/${ publicChatIdentifier } `
758766 const response = await withDocumentNavigation ( page , chatPath , ( ) =>
759- page . goto ( chatPath , { waitUntil : 'load ' , timeout : 360_000 } )
767+ page . goto ( chatPath , { waitUntil : 'commit ' , timeout : 360_000 } )
760768 )
761769 assert ( response && response . status ( ) < 400 )
762770 await expect ( page . getByText ( 'Public mobile welcome' , { exact : true } ) ) . toBeVisible ( {
@@ -855,7 +863,7 @@ async function exerciseViewport(
855863 } )
856864 . toBe ( value )
857865 await withDocumentNavigation ( page , page . url ( ) , ( ) =>
858- page . reload ( { waitUntil : 'load ' , timeout : 360_000 } )
866+ page . reload ( { waitUntil : 'commit ' , timeout : 360_000 } )
859867 )
860868 await expect ( cell ) . toHaveText ( value )
861869 await page . getByRole ( 'button' , { name : 'Column options' , exact : true } ) . first ( ) . tap ( )
@@ -907,7 +915,7 @@ async function exerciseViewport(
907915
908916 await check ( `${ prefix } /standalone settings sections remain accessible` , page , async ( ) => {
909917 const response = await withDocumentNavigation ( page , '/selfhost/settings/general' , ( ) =>
910- page . goto ( '/selfhost/settings/general' , { waitUntil : 'load ' , timeout : 360_000 } )
918+ page . goto ( '/selfhost/settings/general' , { waitUntil : 'commit ' , timeout : 360_000 } )
911919 )
912920 assert ( response && response . status ( ) < 400 )
913921 await expect ( page . getByRole ( 'button' , { name : 'Theme' , exact : true } ) ) . toBeVisible ( {
@@ -978,7 +986,7 @@ async function exerciseViewport(
978986 if ( viewport . name === 'phone' ) {
979987 await sql `update workflow set locked = true where id = ${ workflowId } `
980988 await withDocumentNavigation ( page , page . url ( ) , ( ) =>
981- page . reload ( { waitUntil : 'load ' , timeout : 360_000 } )
989+ page . reload ( { waitUntil : 'commit ' , timeout : 360_000 } )
982990 )
983991 const notifications = page . getByLabel ( 'Notifications' , { exact : true } )
984992 await expect ( notifications ) . toContainText ( 'This workflow is locked' , {
0 commit comments