// Shared helpers for the browser-driven validation checks. // // These exist because a class of admin-panel bug is structurally invisible to // Go tests: a typo in an hx-target, a fragment that renders as a blank panel, a // 500 dressed up as an empty state. The Go tests cover the job framework // (pkg/hold/admin/jobs_test.go); these cover the wiring. // // Session model (pkg/hold/admin/auth.go), which dictates the whole design: // * Sessions live in an in-memory map, so ANY hold rebuild invalidates them. // Air rebuilds on checkout, so expect to re-run login.mjs after a switch. // * Sessions are pinned to User-Agent and client IP prefix. A cookie replayed // from curl or another browser is not merely rejected — it DELETES the // session server-side. Always drive through ctx.request, which inherits the // browser's cookie jar and UA. import { chromium } from '@playwright/test'; export const BASE = process.env.ATCR_HOLD_URL ?? 'http://127.0.0.1:8080'; export const APPVIEW = process.env.ATCR_APPVIEW_URL ?? 'http://127.0.0.1:5000'; export const PROFILE = process.env.ATCR_E2E_PROFILE ?? '/tmp/atcr-e2e-profile'; export async function open({ headless = false } = {}) { return chromium.launchPersistentContext(PROFILE, { headless, viewport: null, args: ['--window-size=1400,1000'], }); } // Returns a page on an authenticated /admin, or exits 2 if the session is gone. export async function adminPage(ctx) { const page = ctx.pages()[0] ?? (await ctx.newPage()); await page.goto(`${BASE}/admin`, { waitUntil: 'networkidle' }); if (page.url().includes('/auth/login')) { console.error('SESSION DEAD — the hold rebuilt (sessions are in-memory).'); console.error('Re-run: node test/e2e/login.mjs'); await ctx.close(); process.exit(2); } return page; } export const csrfOf = (page) => page.locator('input[name="csrf_token"]').first().getAttribute('value'); // Crew rows hydrate individually via hx-trigger="load", so the tab needs a real // settle window before anything is scrapeable. export async function openCrew(page, settleMs = 6000) { await page.goto(`${BASE}/admin`, { waitUntil: 'networkidle' }); await page.click('a[href="/admin#crew"]'); await page.waitForTimeout(settleMs); } // Crew delete is a