@@ -458,36 +458,36 @@ test.describe("Error Recovery", () => {
458458 await createFileWithUniqueContent ( page , fileName , baseURL ! ) ;
459459 await waitForEditorReady ( page ) ;
460460
461- // Close cleanly with code 1000 — should NOT trigger reconnect
462- // Note: code 1000 onclose only sets connected=false, does NOT call updateStatus()
461+ // Disconnect cleanly via the collab API (sets intentionalClose flag + close(1000))
462+ // We use disconnect() instead of raw ws.close(1000) because the WebSocket close
463+ // handshake can fail/timeout, causing the browser to fire onclose with code 1006
464+ // instead of the requested 1000 — which would spuriously trigger reconnect.
463465 await page . evaluate ( ( ) => {
464- const app = ( window as unknown as { idApp : { collab : { ws : WebSocket } } } ) . idApp ;
465- if ( app ?. collab ?. ws ) {
466- app . collab . ws . close ( 1000 , "Clean close" ) ;
466+ const app = ( window as unknown as { idApp : { collab : { disconnect : ( ) => void } } } ) . idApp ;
467+ if ( app ?. collab ) {
468+ app . collab . disconnect ( ) ;
467469 }
468470 } ) ;
469471
470- // Wait for the WebSocket close handshake to complete (async in Firefox)
471- await page . waitForFunction (
472- ( ) => {
473- const app = ( window as unknown as { idApp : { collab : { ws : WebSocket } } } ) . idApp ;
474- const ws = app ?. collab ?. ws ;
475- return ! ws || ws . readyState === WebSocket . CLOSED ;
476- } ,
477- { timeout : 5_000 } ,
478- ) ;
472+ // After disconnect(), currentWs is immediately set to null (no async wait needed)
473+ await page . waitForTimeout ( 500 ) ;
479474
480- // Wait 3s (longer than initial reconnect backoff of 1s) to verify no reconnect attempt
475+ // collab.ws getter returns null after disconnect() sets currentWs = null
476+ const wsIsNull = await page . evaluate ( ( ) => {
477+ const app = ( window as unknown as { idApp : { collab : { ws : WebSocket | null } | null } } ) . idApp ;
478+ return ! app ?. collab ?. ws ;
479+ } ) ;
480+ expect ( wsIsNull ) . toBeTruthy ( ) ;
481+
482+ // Wait longer than initial reconnect backoff (1s) to verify no reconnect attempt
481483 await page . waitForTimeout ( 3_000 ) ;
482484
483- // Status should still show "connected" (code 1000 doesn't update status)
484- // The key test is that NO reconnect happened — the WS stays closed
485- const wsStillClosed = await page . evaluate ( ( ) => {
486- const app = ( window as unknown as { idApp : { collab : { ws : WebSocket } } } ) . idApp ;
487- const ws = app ?. collab ?. ws ;
488- return ! ws || ws . readyState === WebSocket . CLOSED ;
485+ // WS should still be null — no reconnect was scheduled
486+ const wsStillNull = await page . evaluate ( ( ) => {
487+ const app = ( window as unknown as { idApp : { collab : { ws : WebSocket | null } | null } } ) . idApp ;
488+ return ! app ?. collab ?. ws ;
489489 } ) ;
490- expect ( wsStillClosed ) . toBeTruthy ( ) ;
490+ expect ( wsStillNull ) . toBeTruthy ( ) ;
491491 } ) ;
492492} ) ;
493493
@@ -496,12 +496,8 @@ test.describe("Error Recovery", () => {
496496// ---------------------------------------------------------------------------
497497
498498test . describe ( "Multi-User Collab" , ( ) => {
499- // Helper to get the base URL for collab tests (can't use page fixture's baseURL)
500- const getBaseURL = ( ) => `http://localhost:${ process . env . TEST_PORT || 4173 } ` ;
501-
502499 /** Set up two pages with the same file open in both editors */
503- async function setupCollabPair ( browser : import ( "@playwright/test" ) . Browser ) {
504- const baseURL = getBaseURL ( ) ;
500+ async function setupCollabPair ( browser : import ( "@playwright/test" ) . Browser , baseURL : string ) {
505501 const fileName = `collab-${ Date . now ( ) } .txt` ;
506502 const context1 = await browser . newContext ( { baseURL } ) ;
507503 const context2 = await browser . newContext ( { baseURL } ) ;
@@ -526,8 +522,8 @@ test.describe("Multi-User Collab", () => {
526522 return { context1, context2, page1, page2, fileName } ;
527523 }
528524
529- test ( "two tabs can open the same file simultaneously" , async ( { browser } ) => {
530- const { context1, context2, page1, page2 } = await setupCollabPair ( browser ) ;
525+ test ( "two tabs can open the same file simultaneously" , async ( { browser, baseURL } ) => {
526+ const { context1, context2, page1, page2 } = await setupCollabPair ( browser , baseURL ! ) ;
531527
532528 try {
533529 // Both editors should be connected
@@ -539,8 +535,8 @@ test.describe("Multi-User Collab", () => {
539535 }
540536 } ) ;
541537
542- test ( "edits from one user appear in other user's editor" , async ( { browser } ) => {
543- const { context1, context2, page1, page2 } = await setupCollabPair ( browser ) ;
538+ test ( "edits from one user appear in other user's editor" , async ( { browser, baseURL } ) => {
539+ const { context1, context2, page1, page2 } = await setupCollabPair ( browser , baseURL ! ) ;
544540
545541 try {
546542 // User 1 types something (with delay to allow collab sync per character)
@@ -557,8 +553,8 @@ test.describe("Multi-User Collab", () => {
557553 }
558554 } ) ;
559555
560- test ( "bidirectional editing works" , async ( { browser } ) => {
561- const { context1, context2, page1, page2 } = await setupCollabPair ( browser ) ;
556+ test ( "bidirectional editing works" , async ( { browser, baseURL } ) => {
557+ const { context1, context2, page1, page2 } = await setupCollabPair ( browser , baseURL ! ) ;
562558
563559 try {
564560 // User 1 types first (slow enough for collab to sync each step)
0 commit comments