@@ -359,50 +359,43 @@ async function signInWithMagicLinkAndRetry(page: Page, testingUser: TestingUser,
359359 // Use magic link for login
360360 await page . goto ( magicLink ) ;
361361 await page . getByRole ( "button" , { name : "Sign in with magic link" } ) . click ( ) ;
362- await page . waitForLoadState ( "networkidle" ) ;
363362
364- const currentUrl = page . url ( ) ;
365- const isSuccessful = currentUrl . includes ( "/course" ) ;
366- // Check to see if we got the magic link expired notice
367- if ( ! isSuccessful ) {
368- // Magic link expired, retry if we have retries remaining
369- if ( retriesRemaining > 0 ) {
370- return await signInWithMagicLinkAndRetry ( page , testingUser , retriesRemaining - 1 ) ;
371- } else {
372- throw new Error ( "Magic link expired and no retries remaining" ) ;
363+ // On slower machines (especially in dev mode), redirect can lag after submit.
364+ // Wait for either successful course navigation or explicit expired-link message.
365+ let outcome : "success" | "expired" | "unknown" = "unknown" ;
366+ try {
367+ await page . waitForURL ( / \/ c o u r s e ( \/ | $ ) / , { timeout : 30_000 } ) ;
368+ outcome = "success" ;
369+ } catch {
370+ const expiredMessage = page . getByText ( / E m a i l l i n k i s i n v a l i d o r h a s e x p i r e d / i) ;
371+ try {
372+ await expiredMessage . waitFor ( { state : "visible" , timeout : 2_000 } ) ;
373+ outcome = "expired" ;
374+ } catch {
375+ outcome = "unknown" ;
373376 }
374377 }
375378
376- if ( ! isSuccessful ) {
377- throw new Error ( "Failed to sign in - neither success nor expired state detected" ) ;
379+ if ( outcome === "success" ) {
380+ return ;
381+ }
382+ if ( retriesRemaining > 0 ) {
383+ return await signInWithMagicLinkAndRetry ( page , testingUser , retriesRemaining - 1 ) ;
378384 }
385+ if ( outcome === "expired" ) {
386+ throw new Error ( "Magic link expired and no retries remaining" ) ;
387+ }
388+ throw new Error ( `Magic link sign-in did not complete (final URL: ${ page . url ( ) } )` ) ;
379389 } catch ( error ) {
380- if ( retriesRemaining > 0 && ( error as Error ) . message . includes ( "Failed to sign in" ) ) {
381- console . log ( `Sign in failed, retrying... (${ retriesRemaining } retries remaining)` ) ;
390+ if ( retriesRemaining > 0 && ( error as Error ) . message . includes ( "did not complete" ) ) {
382391 return await signInWithMagicLinkAndRetry ( page , testingUser , retriesRemaining - 1 ) ;
383392 }
384393 throw new Error ( `Failed to sign in with magic link: ${ ( error as Error ) . message } ` ) ;
385394 }
386395}
387396export async function loginAsUser ( page : Page , testingUser : TestingUser , course ?: Course ) {
388397 await page . goto ( "/" ) ;
389- try {
390- await signInWithMagicLinkAndRetry ( page , testingUser ) ;
391- } catch {
392- // Fallback for local environments where magic-link verification is flaky.
393- await page . goto ( `/sign-in?email=${ encodeURIComponent ( testingUser . email ) } ` ) ;
394- await page . getByLabel ( "Sign in email" ) . fill ( testingUser . email ) ;
395- await page . getByLabel ( "Sign in password" ) . fill ( testingUser . password ) ;
396- await page . getByRole ( "button" , { name : "Sign in with email" } ) . click ( ) ;
397- try {
398- await page . waitForURL ( / \/ c o u r s e ( \/ | $ ) / , { timeout : 30_000 } ) ;
399- } catch {
400- // Fall through to explicit URL check below for a clear error.
401- }
402- if ( ! page . url ( ) . includes ( "/course" ) ) {
403- throw new Error ( "Failed to sign in with both magic link and email/password fallback" ) ;
404- }
405- }
398+ await signInWithMagicLinkAndRetry ( page , testingUser ) ;
406399
407400 if ( course ) {
408401 await page . waitForLoadState ( "networkidle" ) ;
0 commit comments