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; const categories = [ ["upstream_timeout", "使用原输入重试"], ["upstream_failed", "稍后重试"], ["safety_rejected", "修改提示词或参考图"], ["model_disabled", "选择其他模型或等待"], ["gateway_balance_insufficient", "选择未受影响模型或联系管理员"], ["gateway_contract_invalid", "选择其他模型或联系管理员"], ["reference_invalid", "更换或移除参考图"], ["unknown_retryable", "稍后重试"], ["unknown_non_retryable", "联系管理员"], ] as const; 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("TASK-WP2-06 renders the single frozen action for every generation category", async ({ page }) => { let activeCategory: typeof categories[number][0] = "upstream_timeout"; await page.route("**/api/v1/auth/session", (route) => route.fulfill({ contentType: "application/json", json: { audience: "user", authenticated: true, credits: { available_balance: 10, reserved_balance: 0 }, csrf_token: "csrf-terminal-fixture-00000000000000000000000000000000", local_data: { capacity_status: "normal", hard_limit_bytes: 5_368_709_120, managed_content_bytes: 0 }, user: { creator_name: "Terminal User", role: "user", social_id: "@terminal", status: "active", user_id: "00000000-0000-4000-8000-000000000801" }, } })); await page.route("**/api/v1/projects?status=active", (route) => route.fulfill({ contentType: "application/json", json: { active_count: 1, active_limit: 20, projects: [] } })); await page.route("**/api/v1/models", (route) => route.fulfill({ contentType: "application/json", json: { config_set_version: 1, configured_default_model_id: "gemini-3.1-flash-image-preview", models: [], recommended_model_id: null, } })); await page.route("**/api/v1/generations/current", (route) => route.fulfill({ contentType: "application/json", json: { confirmed_credit_cost: 1, created_at: "2026-08-02T14:00:00.000Z", error_category: activeCategory, generation_id: "00000000-0000-4000-8000-000000000802", model_config_version: 1, model_id: "gemini-3.1-flash-image-preview", project_id: "00000000-0000-4000-8000-000000000803", prompt: "失败任务", ratio: "3:4", reference_count: 0, reserved_credits: 0, status: activeCategory === "safety_rejected" ? "rejected" : "failed", updated_at: "2026-08-02T14:01:00.000Z", } })); const evidenceRoot = process.env.DADA_EVIDENCE_DIR_GENERATION_RUNTIME; for (const [category, action] of categories) { activeCategory = category; await page.goto(`${webUrl}/app`); await expect(page.getByRole("button", { name: action })).toBeVisible(); await expect(page.locator(".current-task").getByRole("button")).toHaveCount(1); await expect(page.getByText(/UPSTREAM_SECRET|internal\.invalid|stack trace/i)).toHaveCount(0); if (evidenceRoot) { const suffix = category === "gateway_balance_insufficient" ? "gateway-balance" : category === "gateway_contract_invalid" ? "gateway-contract" : category.replaceAll("_", "-"); const directory = resolve(evidenceRoot, `TDD-WP2-ERR-001-${suffix}`, "screenshots"); mkdirSync(directory, { recursive: true }); await page.screenshot({ fullPage: true, path: resolve(directory, `${category}.png`) }); } } }); test("TASK-WP2-06 succeeded task exposes its result without an error action", async ({ page }) => { await page.route("**/api/v1/auth/session", (route) => route.fulfill({ contentType: "application/json", json: { audience: "user", authenticated: true, credits: { available_balance: 9, reserved_balance: 0 }, csrf_token: "csrf-success-fixture-00000000000000000000000000000000", local_data: { capacity_status: "normal", hard_limit_bytes: 5_368_709_120, managed_content_bytes: 104 }, user: { creator_name: "Success User", role: "user", social_id: "@success", status: "active", user_id: "00000000-0000-4000-8000-000000000811" }, } })); await page.route("**/api/v1/projects?status=active", (route) => route.fulfill({ contentType: "application/json", json: { active_count: 1, active_limit: 20, projects: [] } })); await page.route("**/api/v1/models", (route) => route.fulfill({ contentType: "application/json", json: { config_set_version: 1, configured_default_model_id: "gemini-3.1-flash-image-preview", models: [], recommended_model_id: null } })); await page.route("**/api/v1/generations/current", (route) => route.fulfill({ contentType: "application/json", json: { confirmed_credit_cost: 1, created_at: "2026-08-02T14:00:00.000Z", error_category: null, generation_id: "00000000-0000-4000-8000-000000000812", model_config_version: 1, model_id: "gemini-3.1-flash-image-preview", project_id: "00000000-0000-4000-8000-000000000813", prompt: "成功任务", ratio: "3:4", reference_count: 0, reserved_credits: 0, status: "succeeded", updated_at: "2026-08-02T14:01:00.000Z", } })); await page.goto(`${webUrl}/app`); await expect(page.getByText("生成成功", { exact: true })).toBeVisible(); await expect(page.getByRole("link", { name: "进入编辑" })).toBeVisible(); const evidenceRoot = process.env.DADA_EVIDENCE_DIR_GENERATION_RUNTIME; if (evidenceRoot) { const directory = resolve(evidenceRoot, "TDD-WP2-GEN-002-success", "screenshots"); mkdirSync(directory, { recursive: true }); await page.screenshot({ fullPage: true, path: resolve(directory, "succeeded.png") }); } });