From 09f88f0bf8ba0cf558f32b2c61de3ef9a1450185 Mon Sep 17 00:00:00 2001 From: Barcode Betty Date: Wed, 15 Apr 2026 21:21:13 +0000 Subject: [PATCH] fix(e2e): await route mocks and add session mocking to all tests - Make mockAuthRoutes async and await all page.route() calls to prevent race conditions - Add auth route mocking to J8 unauth tests (required since VITE_MOCK_AUTH was removed) - Add auth route mocking to smoke test - Replace broken mockSessionPending with mockSessionDelayed for spinner test Co-Authored-By: Paperclip --- e2e/fixtures.ts | 13 +++++++------ e2e/journeys/j1-registration-login.spec.ts | 4 ++-- e2e/journeys/j8-unauth-access.spec.ts | 20 +++++++------------- e2e/smoke.spec.ts | 4 ++-- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/e2e/fixtures.ts b/e2e/fixtures.ts index 409411c..8f61575 100644 --- a/e2e/fixtures.ts +++ b/e2e/fixtures.ts @@ -14,8 +14,8 @@ export { expect } from "@playwright/test"; const MOCK_USER_ID = "mock_user_123"; const MOCK_SESSION_ID = "mock_session_456"; -function mockAuthRoutes(page: Page, authenticated = false) { - page.route(/.*\/auth\/sign-up\/email.*/, async (route) => { +async function mockAuthRoutes(page: Page, authenticated = false) { + await page.route(/.*\/auth\/sign-up\/email.*/, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -33,7 +33,7 @@ function mockAuthRoutes(page: Page, authenticated = false) { }); }); - page.route(/.*\/auth\/sign-in\/email.*/, async (route) => { + await page.route(/.*\/auth\/sign-in\/email.*/, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -52,7 +52,7 @@ function mockAuthRoutes(page: Page, authenticated = false) { }); }); - page.route(/.*\/auth\/get-session.*/, async (route) => { + await page.route(/.*\/auth\/get-session.*/, async (route) => { if (authenticated) { await route.fulfill({ status: 200, @@ -86,8 +86,9 @@ function mockAuthRoutes(page: Page, authenticated = false) { }); } -export function mockSessionPending(page: Page) { - page.route(/.*\/auth\/session.*/, async (route) => { +export async function mockSessionDelayed(page: Page, delayMs = 3000) { + await page.route(/.*\/auth\/get-session.*/, async (route) => { + await new Promise((r) => setTimeout(r, delayMs)); await route.fulfill({ status: 401, contentType: "application/json", diff --git a/e2e/journeys/j1-registration-login.spec.ts b/e2e/journeys/j1-registration-login.spec.ts index e860f4f..3444e4d 100644 --- a/e2e/journeys/j1-registration-login.spec.ts +++ b/e2e/journeys/j1-registration-login.spec.ts @@ -5,7 +5,7 @@ const uniqueEmail = () => `betty+e2e-${Date.now()}@cartsnitch.test`; test.describe('J1: Registration and Login', () => { test('can register a new account and lands on dashboard', async ({ page }) => { - mockAuthRoutes(page, true); + await mockAuthRoutes(page, true); await page.goto('/register'); await page.fill('[placeholder="Full Name"]', 'Betty Tester'); await page.fill('[placeholder="Email"]', uniqueEmail()); @@ -33,7 +33,7 @@ test.describe('J1: Registration and Login', () => { test('can sign in with credentials and land on dashboard', async ({ page }) => { const email = uniqueEmail(); - mockAuthRoutes(page, true); + await mockAuthRoutes(page, true); await page.goto('/register'); await page.fill('[placeholder="Full Name"]', 'Login Betty'); await page.fill('[placeholder="Email"]', email); diff --git a/e2e/journeys/j8-unauth-access.spec.ts b/e2e/journeys/j8-unauth-access.spec.ts index 9ed40da..7c9d17d 100644 --- a/e2e/journeys/j8-unauth-access.spec.ts +++ b/e2e/journeys/j8-unauth-access.spec.ts @@ -1,9 +1,9 @@ import { test, expect } from '@playwright/test'; +import { mockAuthRoutes, mockSessionDelayed } from '../fixtures'; test.describe('J8: Unauthenticated Access', () => { test('redirects /dashboard (/) to /login when not authenticated', async ({ page }) => { - // No session cookie — start fresh - await page.context().clearCookies(); + await mockAuthRoutes(page, false); await page.goto('/'); await expect(page).toHaveURL(/\/login/); @@ -11,7 +11,7 @@ test.describe('J8: Unauthenticated Access', () => { }); test('redirects /purchases to /login when not authenticated', async ({ page }) => { - await page.context().clearCookies(); + await mockAuthRoutes(page, false); await page.goto('/purchases'); await expect(page).toHaveURL(/\/login/); @@ -19,7 +19,7 @@ test.describe('J8: Unauthenticated Access', () => { }); test('redirects /products to /login when not authenticated', async ({ page }) => { - await page.context().clearCookies(); + await mockAuthRoutes(page, false); await page.goto('/products'); await expect(page).toHaveURL(/\/login/); @@ -27,7 +27,7 @@ test.describe('J8: Unauthenticated Access', () => { }); test('redirects /coupons to /login when not authenticated', async ({ page }) => { - await page.context().clearCookies(); + await mockAuthRoutes(page, false); await page.goto('/coupons'); await expect(page).toHaveURL(/\/login/); @@ -35,15 +35,9 @@ test.describe('J8: Unauthenticated Access', () => { }); test('shows loading spinner while auth session is pending', async ({ page }) => { - // Intercept but don't respond — session stays pending - await page.context().clearCookies(); - await page.request.fetch('/api/auth/session', { - method: 'GET', - }); - - // Just navigate to a protected route — ProtectedRoute will show spinner while session is pending + await mockSessionDelayed(page, 3000); await page.goto('/purchases'); - // Spinner is visible briefly; once resolved, should redirect to login + await expect(page.locator('.animate-spin')).toBeVisible({ timeout: 2000 }); await expect(page).toHaveURL(/\/login/, { timeout: 10_000 }); }); }); diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index 2819d15..7d7cf3c 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -1,8 +1,8 @@ -import { test, expect } from './fixtures'; +import { test, expect, mockAuthRoutes } from './fixtures'; test('app loads', async ({ page }) => { + await mockAuthRoutes(page, false); await page.goto('/'); - // Unauthenticated users are redirected to /login await expect(page).toHaveURL(/\/login/); await expect(page.getByRole('heading', { name: /CartSnitch/i })).toBeVisible(); });