import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { applyMigrations, closeContextLogs, printFailureContext, removeE2eDevVars, seedInvite, startDevServer, stopDevServer, type E2eContext, } from "./harness"; export const E2E_CONTEXT_PATH_ENV = "TESSERA_E2E_CONTEXT_PATH"; export interface PersistedE2eContext { baseUrl: string; tempDir: string; invite: { email: string; token: string; password: string }; } export default async function globalSetup(): Promise<() => Promise> { const tempDir = await mkdtemp(join(tmpdir(), "tessera-e2e-")); console.log(`[e2e] temp state: ${tempDir}`); await applyMigrations(tempDir); const invite = await seedInvite(tempDir, "e2e-invitee@example.com"); const ctx = await startDevServer(tempDir); const persisted: PersistedE2eContext = { baseUrl: ctx.baseUrl, tempDir, invite, }; const contextPath = join(tempDir, ".e2e-context.json"); await writeFile(contextPath, JSON.stringify(persisted)); // Playwright reads `use.baseURL` once, before specs run. We set it here // so the spec doesn't have to import the context file just to navigate. process.env.TESSERA_E2E_BASE_URL = ctx.baseUrl; process.env[E2E_CONTEXT_PATH_ENV] = contextPath; return async () => { await teardown(ctx, tempDir); }; } async function teardown(ctx: E2eContext, tempDir: string): Promise { await stopDevServer(ctx.serverProcess); await closeContextLogs(ctx); if (process.env.TESSERA_E2E_PRESERVE === "1") { printFailureContext(ctx); console.log(`[e2e] temp state preserved: ${tempDir}`); } else { await removeE2eDevVars(ctx); await rm(tempDir, { recursive: true, force: true }); } }