Files
xiaoxia-saas/apps/web/e2e/core-generation.spec.ts
T
灵应 8935196fcd
Deploy / Staging E2E Tests (push) Has been skipped
Deploy / Build Production Runtime Images (push) Has been skipped
Deploy / Deploy Production (push) Has been skipped
Deploy / Production Browser E2E (push) Has been skipped
Deploy / Deploy Staging (push) Failing after 138h4m33s
CI/CD Pipeline / Frontend Lint (push) Failing after 138h4m39s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 138h4m39s
style: 后端代码black格式化
2026-07-03 18:49:54 +08:00

348 lines
12 KiB
TypeScript
Executable File

import { fileURLToPath } from "node:url";
import { expect, test } from "@playwright/test";
import fs from "node:fs";
import path from "node:path";
const currentDir = path.dirname(fileURLToPath(import.meta.url));
const PASSWORD = "SmokePass123!";
const apiBase = process.env.E2E_API_BASE || "/api/v1";
const apiOrigin = apiBase.endsWith("/api/v1")
? apiBase.slice(0, -"/api/v1".length)
: "";
const routeBrowserApiToTestApi = async (
page: import("@playwright/test").Page,
) => {
if (!apiOrigin) return;
await page.route("**/api/v1/**", async (route) => {
const sourceUrl = new URL(route.request().url());
const response = await route.fetch({
url: `${apiOrigin}${sourceUrl.pathname}${sourceUrl.search}`,
});
await route.fulfill({ response });
});
};
type ProjectResponse = { id: string };
type LibraryResponse = { id: string };
type AssetListResponse = {
items: Array<{
name: string;
status: string;
mime_type?: string;
file_type?: string;
}>;
};
type GenerationTaskResponse = {
id: string;
status: string;
progress: number;
result_count: number;
error_message?: string | null;
strategy_id?: string | null;
edit_plan_id?: string | null;
};
type ProjectTitleResponse = { id: string; text: string; usage_count: number };
type GeneratedVideoResponse = {
id: string;
name: string;
file_url: string;
file_size: number;
};
test.describe("Core generation and download flow", () => {
test("generates an MP4 from the browser and exposes a playable download", async ({
page,
request,
}) => {
test.setTimeout(180_000);
await routeBrowserApiToTestApi(page);
const suffix = Date.now().toString(36);
const email = `e2e-generation-${suffix}@example.com`;
const username = `e2e_generation_${suffix}`;
const libraryName = `E2E Generation Library ${suffix}`;
const register = await request.post(`${apiBase}/auth/register`, {
data: { email, username, password: PASSWORD, display_name: username },
});
expect(register.status(), await register.text()).toBe(201);
const registerData = (await register.json()) as { user_id: string };
const login = await request.post(`${apiBase}/auth/login`, {
data: { email, password: PASSWORD },
});
expect(login.status(), await login.text()).toBe(200);
const loginData = (await login.json()) as { access_token: string };
const headers = { Authorization: `Bearer ${loginData.access_token}` };
const project = await request.post(`${apiBase}/projects`, {
headers,
data: { name: `E2E Generation Project ${suffix}` },
});
expect(project.status(), await project.text()).toBe(200);
const projectData = (await project.json()) as ProjectResponse;
const library = await request.post(`${apiBase}/asset-libraries`, {
headers,
data: { project_id: projectData.id, name: libraryName, kind: "video" },
});
expect(library.status(), await library.text()).toBe(200);
const libraryData = (await library.json()) as LibraryResponse;
const projectTitleText = `E2E 生成标题 ${suffix}`;
const title = await request.post(
`${apiBase}/projects/${projectData.id}/titles`,
{
headers,
data: { text: projectTitleText, category: "marketing", favorite: true },
},
);
expect(title.status(), await title.text()).toBe(200);
const titleData = (await title.json()) as ProjectTitleResponse;
const fixture = fs.readFileSync(
path.join(currentDir, "fixtures", "sample.mp4"),
);
const upload = await request.post(`${apiBase}/upload`, {
headers,
multipart: {
project_id: projectData.id,
library_id: libraryData.id,
file: {
name: "e2e-generation-source.mp4",
mimeType: "video/mp4",
buffer: fixture,
},
},
});
expect(upload.status(), await upload.text()).toBe(200);
await expect
.poll(
async () => {
const assets = await request.get(`${apiBase}/assets`, {
headers,
params: { library_id: libraryData.id },
});
if (!assets.ok()) {
return `http_${assets.status()}`;
}
const data = (await assets.json()) as AssetListResponse;
const asset = data.items.find(
(item) => item.name === "e2e-generation-source.mp4",
);
return asset
? `${asset.mime_type || asset.file_type || ""}:${asset.status}`
: "missing";
},
{ timeout: 90_000, intervals: [1_000, 2_000, 3_000, 5_000] },
)
.toMatch(/^(video\/quicktime|video\/mp4|video)?:ready$/);
await page.addInitScript(
({ token, user, projectId }) => {
localStorage.setItem("access_token", token);
localStorage.setItem(
"auth-storage",
JSON.stringify({
state: { user, isAuthenticated: true },
version: 0,
}),
);
},
{
token: loginData.access_token,
projectId: projectData.id,
user: {
id: registerData.user_id,
user_id: registerData.user_id,
email,
username,
display_name: username,
is_email_verified: true,
email_verified: true,
},
},
);
await page.goto(`/projects/${projectData.id}/generation`);
await expect(page.getByText("剪辑参数")).toBeVisible({ timeout: 20_000 });
await page.locator(".ant-select-selector").first().click();
await page.getByText(`${libraryName} (video)`).click();
await page.locator(".ant-select-selector").nth(1).click();
await page.getByText(projectTitleText).click();
await expect(page.getByText(/素材就绪度:/)).toBeVisible({
timeout: 20_000,
});
await expect(
page.getByRole("button", { name: "重新生成计划" }),
).toBeEnabled({ timeout: 20_000 });
await page.getByRole("button", { name: "重新生成计划" }).click();
await expect(page.getByText("剪辑计划预览")).toBeVisible({
timeout: 20_000,
});
await expect(page.getByText(/自动选择/)).toBeVisible({ timeout: 20_000 });
await expect(page.getByText(/e2e-generation-source\.mp4/)).toBeVisible({
timeout: 20_000,
});
const confirmGenerationButton = page
.getByRole("complementary")
.getByRole("button", { name: "确认计划并生成" });
await expect(confirmGenerationButton).toBeEnabled({ timeout: 20_000 });
const createTaskResponsePromise = page.waitForResponse(
(response) =>
response.url().includes("/api/v1/generation/tasks") &&
response.request().method() === "POST",
{ timeout: 30_000 },
);
await confirmGenerationButton.click();
const createTaskResponse = await createTaskResponsePromise;
expect(createTaskResponse.status(), await createTaskResponse.text()).toBe(
200,
);
const createdTask =
(await createTaskResponse.json()) as GenerationTaskResponse;
expect(createdTask.edit_plan_id || "").not.toBe("");
await expect(page.getByText(/生成状态:生成完成/)).toBeVisible({
timeout: 90_000,
});
await expect(
page.getByText(/生成失败|生成任务加载失败|生成结果加载失败/),
).toHaveCount(0);
await expect
.poll(
async () => {
const task = await request.get(
`${apiBase}/generation/tasks/${createdTask.id}`,
{ headers },
);
if (!task.ok()) {
return `http_${task.status()}`;
}
const data = (await task.json()) as GenerationTaskResponse;
return `${data.status}:${data.result_count}:${data.strategy_id || ""}:${data.error_message || ""}`;
},
{ timeout: 90_000, intervals: [1_000, 2_000, 5_000] },
)
.toMatch(new RegExp(`^completed:[1-9]\\d*:${titleData.id}:`));
const results = await request.get(
`${apiBase}/generation/tasks/${createdTask.id}/results`,
{ headers },
);
expect(results.status(), await results.text()).toBe(200);
const resultsData = (await results.json()) as {
items: GeneratedVideoResponse[];
};
expect(resultsData.items.length).toBeGreaterThan(0);
const generatedVideo = resultsData.items[0];
expect(generatedVideo.name).toMatch(/\.mp4$/);
expect(generatedVideo.file_size).toBeGreaterThan(0);
await page.goto(`/projects/${projectData.id}/results`);
const resultCard = page
.locator(".xx-vertical-card")
.filter({ hasText: generatedVideo.name });
await expect(resultCard).toBeVisible({ timeout: 20_000 });
await expect(resultCard.getByText("待复核")).toBeVisible({
timeout: 20_000,
});
await expect(
resultCard.getByRole("button", { name: /下载/ }),
).toBeVisible();
await expect(
page.getByRole("button", { name: "批量获取下载地址" }),
).toBeEnabled();
await resultCard.getByRole("button", { name: "可发布" }).click();
await expect(page.getByText("成片复核状态已更新")).toBeVisible({
timeout: 10_000,
});
await expect(
resultCard.locator(".xx-pill.ok", { hasText: "可发布" }),
).toBeVisible({ timeout: 20_000 });
const reviewedVideo = await request.get(
`${apiBase}/generated-videos/${generatedVideo.id}`,
{ headers },
);
expect(reviewedVideo.status(), await reviewedVideo.text()).toBe(200);
const reviewedVideoData = (await reviewedVideo.json()) as {
review_status: string;
generation_params: Record<string, unknown>;
};
expect(reviewedVideoData.review_status).toBe("approved");
expect(reviewedVideoData.generation_params.title_id).toBe(titleData.id);
expect(reviewedVideoData.generation_params.edit_plan_id).toBe(
createdTask.edit_plan_id,
);
const downloadUrlResponse = await request.get(
`${apiBase}/generated-videos/${generatedVideo.id}/download-url`,
{ headers },
);
expect(downloadUrlResponse.status(), await downloadUrlResponse.text()).toBe(
200,
);
const downloadData = (await downloadUrlResponse.json()) as {
download_url: string;
};
const videoResponse = await request.get(downloadData.download_url, {
timeout: 30_000,
});
expect(videoResponse.status(), await videoResponse.text()).toBe(200);
expect(videoResponse.headers()["content-type"] || "").toContain(
"video/mp4",
);
const videoBody = await videoResponse.body();
expect(videoBody.length).toBeGreaterThan(1024);
const assetsAfterGeneration = await request.get(`${apiBase}/assets`, {
headers,
params: { library_id: libraryData.id },
});
expect(
assetsAfterGeneration.status(),
await assetsAfterGeneration.text(),
).toBe(200);
const assetsAfterGenerationData = (await assetsAfterGeneration.json()) as {
items: Array<{ name: string; metadata: Record<string, unknown> }>;
};
const sourceAsset = assetsAfterGenerationData.items.find(
(item) => item.name === "e2e-generation-source.mp4",
);
expect(sourceAsset?.metadata.generation_use_count).toBe(1);
expect(sourceAsset?.metadata.review_status).toBe("pending_review");
const titleAfterGeneration = await request.get(
`${apiBase}/projects/${projectData.id}/titles`,
{ headers },
);
expect(
titleAfterGeneration.status(),
await titleAfterGeneration.text(),
).toBe(200);
const titlesData = (await titleAfterGeneration.json()) as {
items: ProjectTitleResponse[];
};
expect(
titlesData.items.find((item) => item.id === titleData.id)?.usage_count,
).toBe(1);
await page.goto(`/projects/${projectData.id}/tasks`);
await expect(page.getByText("项目任务中心")).toBeVisible({
timeout: 20_000,
});
await expect(page.getByText("视频生成")).toBeVisible({ timeout: 20_000 });
await expect(page.getByText("已完成").first()).toBeVisible({
timeout: 20_000,
});
await expect(page.getByText(createdTask.id)).toBeVisible({
timeout: 20_000,
});
});
});