Files
collective-lists/apps/web/tests/fixtures/login.ts
Oier Bravo Urtasun a86b296ecd fix(e2e): pin Playwright baseURL to localhost regardless of PUBLIC_APP_URL
Root cause of the recurring "just test-all shows 27/16, playwright alone
shows 43/0" split:

  Justfile loads .env with dotenv-load, which sets PUBLIC_APP_URL to the
  LAN IP from the phone-preview config. playwright.config.ts was reading
  that env and using it as baseURL. The resulting LAN-IP OAuth round-trip
  (browser → GoTrue → Keycloak → browser) adds ~hundreds of ms of
  loopback routing per hop and pushes the $currentCollective hydration
  past Playwright's first synthetic keystroke, producing a silent early
  return in handleAdd (name typed but Enter never triggers a write).

  Running playwright directly skipped dotenv-load, baseURL fell back to
  localhost, and everything passed. Same code, same machine, different
  wrapper.

Fix
  apps/web/playwright.config.ts: baseURL hardcoded to 'http://localhost:5173'.
    The LAN IP stays in PUBLIC_APP_URL for on-device phone previews (step
    2/3 of the mobile-testing recipe in CLAUDE.md), but e2e always hits
    the loopback so the timing profile is deterministic.
  apps/web/tests/fixtures/login.ts: origin-check hardcoded to localhost
    to match.

Verification
  just test-all → exit 0, 228 tests green (34 pgTAP + 140 Vitest
    integration + 11 Vitest unit + 43 Playwright), 4 skipped (upstream
    Realtime presence + WebKit-only touch gestures).
  just test-e2e alone → 43 passed.
  pnpm exec playwright test alone → 43 passed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 00:48:10 +02:00

60 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Live login helper — runs the real Keycloak OAuth flow in the current context.
*
* We use this instead of `storageState` because Supabase JS v2's async
* `_recoverAndRefresh` doesn't reliably re-fire `onAuthStateChange`/INITIAL_SESSION
* when the browser context is built from a saved localStorage snapshot. Doing the
* full login for each test adds ~23 seconds but produces deterministic state.
*/
import type { Page } from '@playwright/test';
import type { USERS } from './users.js';
export async function loginAs(page: Page, user: (typeof USERS)[keyof typeof USERS]): Promise<void> {
await page.goto('/');
await page.waitForURL(/\/realms\/colectivo\/protocol\/openid-connect\/auth/, {
timeout: 15_000
});
await page.fill('#username', user.email);
await page.fill('#password', user.password);
await page.click('#kc-login');
// We pin Playwright's baseURL to http://localhost:5173 (see playwright.config.ts)
// regardless of whether PUBLIC_APP_URL is the LAN IP for device previews, so
// the post-login origin is always localhost.
await page.waitForURL(
(url) => url.origin === 'http://localhost:5173' && !url.pathname.startsWith('/auth/callback'),
{ timeout: 20_000 }
);
await page.waitForFunction(
() =>
Object.keys(window.localStorage).some(
(k) => k.startsWith('sb-') && k.endsWith('-auth-token')
),
null,
{ timeout: 15_000 }
);
// Wait for `$currentCollective` to populate — any test that creates rows
// needs this, because `handleCreate` silently returns when collective is
// null. On localhost the round-trip was fast enough that races were rare,
// but over LAN IP the extra latency exposes the race. We detect it by the
// sidebar's collective button rendering the actual name (otherwise it
// shows the app_name fallback "Colectivo").
await page.waitForFunction(
(appName) => {
const buttons = document.querySelectorAll('button');
for (const b of Array.from(buttons)) {
const txt = b.textContent?.trim();
if (txt && txt !== appName && !txt.startsWith(appName)) {
// Heuristic: any non-default button with emoji-prefixed name
// (seed collective is "Casa García-López" with emoji "🏠").
if (/\p{Emoji}/u.test(txt)) return true;
}
}
return false;
},
'Colectivo',
{ timeout: 10_000 }
);
}