Files
tyx_AI_xhs/tests/e2e/projects-workspace.spec.ts
T

124 lines
6.6 KiB
TypeScript

import { expect, test, type Page } from "@playwright/test";
import { createServer, type ViteDevServer } from "vite";
import { mkdirSync } from "node:fs";
import { resolve } from "node:path";
let vite: ViteDevServer;
let webUrl: string;
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());
const session = {
audience: "user", authenticated: true,
credits: { available_balance: 10, reserved_balance: 0 },
csrf_token: "csrf-projects-fixture-000000000000000000000000000000000000000",
expires_at: "2026-08-28T08:00:00.000Z",
user: { creator_name: "Project User", role: "user", social_id: "@project_user", status: "active", user_id: "00000000-0000-4000-8000-000000000201" },
};
function routeSession(page: Page) {
return page.route("**/api/v1/auth/session", (route) => route.fulfill({
body: JSON.stringify(session), contentType: "application/json", status: 200,
}));
}
async function captureEvidence(page: Page, caseId: string, name: string) {
const root = process.env.DADA_EVIDENCE_DIR_PROJECTS;
if (!root) return;
const directory = resolve(root, caseId, "screenshots");
mkdirSync(directory, { recursive: true });
await page.screenshot({ fullPage: true, path: resolve(directory, name) });
}
test("TDD-WP2-PROJ-001 renders the empty WbDYT workspace without inventing a model", async ({ page }) => {
await routeSession(page);
await page.route("**/api/v1/projects?status=active", (route) => route.fulfill({
body: JSON.stringify({ active_count: 0, active_limit: 20, projects: [] }), contentType: "application/json", status: 200,
}));
await page.goto(`${webUrl}/app`);
await expect(page.getByRole("heading", { name: "开始一张新作品" })).toBeVisible();
await expect(page.getByText("NEW PROJECT", { exact: true })).toBeVisible();
await expect(page.getByLabel("描述你想生成的画面")).toBeVisible();
await expect(page.getByRole("radio", { name: "3:4" })).toBeChecked();
for (const ratio of ["1:1", "4:3", "9:16"]) await expect(page.getByRole("radio", { name: ratio })).toBeVisible();
await expect(page.getByText("当前没有可用于新任务的模型")).toBeVisible();
await expect(page.getByRole("button", { name: "生成一张图片" })).toBeDisabled();
await expect(page.getByText("没有进行中的任务")).toBeVisible();
await expect(page.getByText("测试数据仅保存在本机,不自动备份,也不会迁移到正式系统。")).toBeVisible();
await expect(page.getByText("浏览示例模板")).toHaveCount(0);
await captureEvidence(page, "TDD-WP2-PROJ-001-new-versus-continue", "workspace-empty.png");
});
test("TDD-WP2-PROJ-005 limits failed-empty selection on the project list", async ({ page }) => {
await page.setViewportSize({ height: 760, width: 375 });
await routeSession(page);
const failedId = "00000000-0000-4000-8000-000000000211";
const successId = "00000000-0000-4000-8000-000000000212";
await page.route("**/api/v1/projects?status=active", (route) => route.fulfill({
body: JSON.stringify({
active_count: 2, active_limit: 20,
projects: [
{ current_image_id: null, name: "失败草稿", project_id: failedId, ratio: "3:4", state_version: 1, status: "failed_empty", successful_image_count: 0, updated_at: "2026-07-28T08:00:00.000Z" },
{ current_image_id: "00000000-0000-4000-8000-000000000213", name: "城市工作室", project_id: successId, ratio: "3:4", state_version: 2, status: "active", successful_image_count: 1, updated_at: "2026-07-28T08:02:00.000Z" },
],
}), contentType: "application/json", status: 200,
}));
let batchPayload: unknown;
await page.route("**/api/v1/projects/failed-empty/trash", async (route) => {
batchPayload = route.request().postDataJSON();
await route.fulfill({
body: JSON.stringify({ ignored_project_ids: [], trashed_project_ids: [failedId] }), contentType: "application/json", status: 200,
});
});
await page.goto(`${webUrl}/app/projects`);
await expect(page.getByRole("heading", { name: "项目" })).toBeVisible();
await expect(page.getByText("2 / 20 active")).toBeVisible();
await expect(page.getByLabel("选择失败草稿:失败草稿")).toBeVisible();
await expect(page.getByLabel("选择失败草稿:城市工作室")).toHaveCount(0);
await page.getByLabel("选择失败草稿:失败草稿").check();
await page.getByRole("button", { name: "批量移入回收站" }).click();
expect(batchPayload).toEqual({ project_ids: [failedId] });
await expect(page.getByText("失败草稿已移入回收站")).toBeVisible();
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBe(375);
await captureEvidence(page, "TDD-WP2-PROJ-005-failed-draft-retry", "projects-mobile.png");
});
test("TDD-WP2-PROJ-001 keeps ratio fixed and blocks an eleventh image in project detail", async ({ page }) => {
await routeSession(page);
const projectId = "00000000-0000-4000-8000-000000000221";
await page.route(`**/api/v1/projects/${projectId}`, (route) => route.fulfill({
body: JSON.stringify({
created_at: "2026-07-28T08:00:00.000Z", current_image_id: "00000000-0000-4000-8000-000000000222",
draft_prompt: "城市工作室", generations: [], images: Array.from({ length: 10 }, (_, index) => ({
created_at: `2026-07-28T08:${String(index).padStart(2, "0")}:00.000Z`,
generation_id: `00000000-0000-4000-8000-${String(223 + index).padStart(12, "0")}`,
image_id: `00000000-0000-4000-8000-${String(323 + index).padStart(12, "0")}`,
})),
name: "城市工作室", project_id: projectId, ratio: "3:4", state_version: 4,
status: "active", successful_image_count: 10, updated_at: "2026-07-28T08:10:00.000Z",
}), contentType: "application/json", status: 200,
}));
await page.goto(`${webUrl}/app/projects/${projectId}`);
await expect(page.getByRole("heading", { name: "城市工作室" })).toBeVisible();
await expect(page.getByText("固定比例 3:4")).toBeVisible();
await expect(page.getByText("10 / 10 张成功图")).toBeVisible();
await expect(page.getByRole("button", { name: "继续生成" })).toBeDisabled();
await expect(page.getByText("请先删除一张非当前底图的历史图")).toBeVisible();
await expect(page.getByRole("radio")).toHaveCount(0);
await captureEvidence(page, "TDD-WP2-PROJ-001-new-versus-continue", "project-detail-limit.png");
});