Files
PiCloud/dashboard/tests/e2e/global-setup.ts
MechaCat02 74f7b3b631 test(dashboard): add Playwright e2e scaffolding with smoke spec
Milestone A of the frontend test plan. Sets up the test rig — config,
globalSetup that probes the backend and seeds an admin session into
storageState, lightweight fixtures, and a 3-test smoke spec — without
yet covering any user journeys (those land in Milestone B).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:03:44 +02:00

95 lines
3.2 KiB
TypeScript

import { chromium, request } from '@playwright/test';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const API_BASE = process.env.E2E_API_BASE ?? 'http://127.0.0.1:18080';
const DASHBOARD_PORT = Number(process.env.PICLOUD_DASHBOARD_PORT ?? 5173);
const DASHBOARD_ORIGIN = process.env.E2E_DASHBOARD_ORIGIN ?? `http://localhost:${DASHBOARD_PORT}`;
const ADMIN_USERNAME = process.env.E2E_ADMIN_USERNAME ?? 'admin';
const ADMIN_PASSWORD = process.env.E2E_ADMIN_PASSWORD ?? 'admin';
const AUTH_DIR = path.join(__dirname, '.auth');
const ADMIN_STATE_PATH = path.join(AUTH_DIR, 'admin.json');
export default async function globalSetup(): Promise<void> {
await assertBackendUp();
await fs.mkdir(AUTH_DIR, { recursive: true });
const token = await loginAsAdmin();
await persistAdminStorageState(token);
}
async function assertBackendUp(): Promise<void> {
const probe = await request.newContext();
try {
const res = await probe.get(`${API_BASE}/healthz`, { timeout: 5_000 });
if (!res.ok()) {
throw new Error(
`backend /healthz returned ${res.status()} — is \`cargo run -p picloud\` listening on ${API_BASE}?`
);
}
} catch (err) {
throw new Error(
`Could not reach backend at ${API_BASE}/healthz. ` +
`Bring it up before running E2E tests:\n\n` +
` docker compose up -d postgres\n` +
` PICLOUD_BIND=127.0.0.1:18080 \\\n` +
` PICLOUD_ADMIN_USERNAME=${ADMIN_USERNAME} \\\n` +
` PICLOUD_ADMIN_PASSWORD=${ADMIN_PASSWORD} \\\n` +
` DATABASE_URL=postgres://picloud:picloud@127.0.0.1:15432/picloud \\\n` +
` cargo run -p picloud\n\n` +
`Underlying error: ${(err as Error).message}`
);
} finally {
await probe.dispose();
}
}
async function loginAsAdmin(): Promise<string> {
const ctx = await request.newContext();
try {
const res = await ctx.post(`${API_BASE}/api/v1/admin/auth/login`, {
data: { username: ADMIN_USERNAME, password: ADMIN_PASSWORD },
headers: { 'content-type': 'application/json' }
});
if (!res.ok()) {
const body = await res.text();
throw new Error(
`Admin login failed (${res.status()}): ${body}. ` +
`Verify PICLOUD_ADMIN_USERNAME / PICLOUD_ADMIN_PASSWORD match the seeded bootstrap admin.`
);
}
const payload = (await res.json()) as { token?: string };
if (!payload.token) {
throw new Error('Admin login response missing token field');
}
return payload.token;
} finally {
await ctx.dispose();
}
}
// The dashboard reads its session from localStorage under the key
// `picloud.admin.token` (see src/lib/auth.ts). We can't write to
// localStorage without a browser context, so launch a throwaway one,
// seed the value, then save storageState for every test to reuse.
async function persistAdminStorageState(token: string): Promise<void> {
const browser = await chromium.launch();
try {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(`${DASHBOARD_ORIGIN}/admin/login`);
await page.evaluate(
([key, value]) => {
localStorage.setItem(key, value);
},
['picloud.admin.token', token]
);
await context.storageState({ path: ADMIN_STATE_PATH });
} finally {
await browser.close();
}
}