import { mkdirSync } from "node:fs"; import { resolve } from "node:path"; import { expect, test } from "@playwright/test"; import { createServer, type ViteDevServer } from "vite"; let vite: ViteDevServer; let webUrl: string; // Raw traces for these mocked auth requests would retain the submitted email. test.use({ trace: "off" }); test.beforeAll(async () => { vite = await createServer({ configFile: resolve("apps/web/vite.config.ts"), root: resolve("apps/web"), server: { host: "127.0.0.1", port: 0 }, }); await vite.listen(); const address = vite.httpServer?.address(); if (!address || typeof address === "string") throw new Error("Vite did not expose a test port."); webUrl = `http://127.0.0.1:${address.port}`; }); test.afterAll(async () => vite.close()); test("TDD-WP1-AUTH-003 renders Z0pf8 login states without silently switching entry", async ({ page }) => { await page.route("**/api/v1/auth/login/send", async (route) => { await route.fulfill({ contentType: "application/json", status: 409, body: JSON.stringify({ error: { code: "AUTH_ENTRY_REJECTED", correlation_id: "00000000-0000-4000-8000-000000000001", details: { field_errors: [{ field: "email", message_key: "auth.account.suspended" }] }, message_key: "auth.entry.rejected", }, }), }); }); await page.goto(webUrl); await expect(page.getByRole("heading", { name: "邮箱验证码登录" })).toBeVisible(); await expect(page.getByText("测试数据仅保存在本机,不自动备份,也不会迁移到正式系统。")).toBeVisible(); await expect(page.getByRole("link", { name: "管理员登录" })).toHaveAttribute("href", "/admin"); await expect(page.getByLabel("邀请码")).toHaveCount(0); await expect(page.getByLabel("创作署名")).toHaveCount(0); await expect(page.getByLabel("社交 ID")).toHaveCount(0); await page.getByRole("tab", { name: "登录" }).focus(); await page.keyboard.press("Tab"); await expect(page.getByLabel("邮箱")).toBeFocused(); await page.getByLabel("邮箱").fill("suspended@example.invalid"); await page.getByRole("button", { name: "获取验证码" }).click(); await expect(page.getByRole("alert")).toContainText("账号已暂停"); await expect(page.getByRole("tab", { name: "登录" })).toHaveAttribute("aria-selected", "true"); await page.getByLabel("邮箱").fill(""); const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_AUTH_MATRIX; if (evidenceDirectory) { mkdirSync(resolve(evidenceDirectory, "screenshots"), { recursive: true }); await page.screenshot({ fullPage: true, path: resolve(evidenceDirectory, "screenshots", "entry-state.png") }); } }); test("TDD-WP1-AUTH-004 login controls remain stable on mobile and loading states", async ({ page }) => { await page.setViewportSize({ height: 844, width: 390 }); await page.route("**/api/v1/auth/login/send", async (route) => { await new Promise((resolveDelay) => setTimeout(resolveDelay, 150)); await route.fulfill({ contentType: "application/json", status: 200, body: JSON.stringify({ challenge_expires_at: "2026-07-28T08:10:00.000Z", registration_id: "00000000-0000-4000-8000-000000000002", resend_available_at: "2026-07-28T08:01:00.000Z", status: "verification_sent", }), }); }); await page.goto(webUrl); await page.getByLabel("邮箱").fill("mobile@example.invalid"); const send = page.getByRole("button", { name: "获取验证码" }); await send.click(); await expect(page.getByRole("button", { name: "发送中" })).toBeDisabled(); await expect(page.getByText(/验证码已发送至/)).toBeVisible(); await expect(page.locator("body")).not.toHaveCSS("overflow-x", "scroll"); });