import { chromium } from "playwright"; import fs from "node:fs"; const baseUrl = process.env.E2E_BASE_URL ?? "http://localhost:3000"; const memberApiKey = process.env.MEMBER_API_KEY; if (!memberApiKey) { throw new Error("MEMBER_API_KEY is required"); } fs.mkdirSync("artifacts", { recursive: true }); const browser = await chromium.launch({ headless: true }); try { // Flow A: free user signup const freeContext = await browser.newContext(); const freePage = await freeContext.newPage(); const freeSignupResp = await freeContext.request.post(`${baseUrl}/api/v1/signup/free`, { data: { display_name: "E2E Free Proof" }, }); if (freeSignupResp.status() !== 201) { throw new Error(`Free signup failed with status ${freeSignupResp.status()}`); } const freeData = await freeSignupResp.json(); const freeSetCookie = freeSignupResp.headers()["set-cookie"] ?? ""; const freeMatch = freeSetCookie.match(/tcg_session=([^;]+)/); if (!freeMatch) { throw new Error("Free signup did not return tcg_session cookie"); } await freeContext.addCookies([ { name: "tcg_session", value: decodeURIComponent(freeMatch[1]), domain: "localhost", path: "/", httpOnly: true, sameSite: "Lax", secure: false, }, ]); await freePage.goto(baseUrl, { waitUntil: "domcontentloaded" }); await freePage.waitForTimeout(1200); const freeTenantLabel = freePage.locator(`text=${freeData.tenant.id}`).first(); await freeTenantLabel.waitFor({ timeout: 15000 }); await freePage.screenshot({ path: "artifacts/e2e-free-user-success.png", fullPage: true }); await freeContext.close(); // Flow B: member login (API) -> UI session restore -> checkout redirect const memberContext = await browser.newContext(); const memberPage = await memberContext.newPage(); const loginResp = await memberContext.request.post(`${baseUrl}/api/v1/auth/session`, { data: { api_key: memberApiKey }, }); if (loginResp.status() !== 200) { throw new Error(`Member login failed with status ${loginResp.status()}`); } const setCookie = loginResp.headers()["set-cookie"] ?? ""; const match = setCookie.match(/tcg_session=([^;]+)/); if (!match) { throw new Error("Member login did not return tcg_session cookie"); } await memberContext.addCookies([ { name: "tcg_session", value: decodeURIComponent(match[1]), domain: "localhost", path: "/", httpOnly: true, sameSite: "Lax", secure: false, }, ]); await memberPage.goto(baseUrl, { waitUntil: "domcontentloaded" }); await memberPage.waitForTimeout(1200); const memberTenantLabel = memberPage.locator("text=/tenant_staging_admin/i").first(); await memberTenantLabel.waitFor({ timeout: 15000 }); await memberPage.waitForTimeout(500); await memberPage.getByRole("button", { name: /pricing \/ upgrade/i }).click(); await memberPage.waitForTimeout(800); await memberPage.screenshot({ path: "artifacts/e2e-member-signedin-pricing.png", fullPage: true }); const checkoutResp = await memberContext.request.post(`${baseUrl}/api/v1/billing/checkout/session`, { data: { plan: "pro_guardian", mode: "subscription", success_url: `${baseUrl}/?checkout=success`, cancel_url: `${baseUrl}/?checkout=cancel`, }, }); if (checkoutResp.status() !== 200) { throw new Error(`Checkout create failed with status ${checkoutResp.status()}`); } const checkoutData = await checkoutResp.json(); if (!checkoutData?.checkout_url) { throw new Error("Checkout URL missing from checkout session response"); } await memberPage.goto(checkoutData.checkout_url, { waitUntil: "domcontentloaded" }); await memberPage.waitForURL(/checkout\.stripe\.com/, { timeout: 30000 }); await memberPage.screenshot({ path: "artifacts/e2e-member-checkout-success.png", fullPage: true }); await memberContext.close(); } finally { await browser.close(); } console.log("Saved screenshots:"); console.log("- artifacts/e2e-free-user-success.png"); console.log("- artifacts/e2e-member-checkout-success.png");