17720a2484
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 55s
CI/CD Pipeline / Unit Tests (push) Successful in 3m31s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 3m31s
CI/CD Pipeline / Integration Tests (push) Successful in 1m39s
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m50s
CI Build & Deploy Pipeline / Build Staging API Image (push) Successful in 21m44s
CI Build & Deploy Pipeline / Build Staging Web Image (push) Successful in 45s
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Successful in 7m35s
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been cancelled
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been cancelled
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
774 lines
22 KiB
TypeScript
774 lines
22 KiB
TypeScript
/**
|
|
* 素材库页面完整 E2E 测试
|
|
*
|
|
* 覆盖:页面加载、创建素材库、切换素材库、搜索/筛选、素材详情、
|
|
* 删除素材、批量删除、空状态
|
|
* 注意:test_asset.spec.ts 已覆盖 API 级别的素材库 CRUD,本文件聚焦 UI 交互
|
|
*/
|
|
import { expect, test, type APIRequestContext } from "@playwright/test";
|
|
|
|
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 });
|
|
});
|
|
};
|
|
|
|
function uniqueEmail(prefix: string): string {
|
|
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}@example.com`;
|
|
}
|
|
|
|
function uniqueUsername(prefix: string): string {
|
|
return `${prefix}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
}
|
|
|
|
/** 登录操作,遇到 429 限流自动等待重试 */
|
|
async function loginWithRetry(
|
|
request: APIRequestContext,
|
|
email: string,
|
|
password: string,
|
|
maxRetries = 2,
|
|
) {
|
|
for (let i = 0; i <= maxRetries; i++) {
|
|
const response = await request.post(`${apiBase}/auth/login`, {
|
|
data: { email, password },
|
|
});
|
|
if (response.status() !== 429) return response;
|
|
console.log(`[login] 触发限流,等待 65s 后重试 (${i + 1}/${maxRetries})`);
|
|
await new Promise((r) => setTimeout(r, 65000));
|
|
}
|
|
return request.post(`${apiBase}/auth/login`, {
|
|
data: { email, password },
|
|
});
|
|
}
|
|
|
|
/** 注册并登录,返回 { headers, email, username, userId } */
|
|
async function createAuthedUser(request: APIRequestContext, label: string) {
|
|
const email = uniqueEmail(label);
|
|
const username = uniqueUsername(label);
|
|
|
|
const reg = await request.post(`${apiBase}/auth/register`, {
|
|
data: { email, password: PASSWORD, username, display_name: `E2E ${label}` },
|
|
});
|
|
expect(reg.ok(), `注册应成功: ${await reg.text()}`).toBeTruthy();
|
|
const regData = await reg.json();
|
|
|
|
const login = await loginWithRetry(request, email, PASSWORD);
|
|
expect(login.ok(), `登录应成功: ${await login.text()}`).toBeTruthy();
|
|
const loginData = await login.json();
|
|
|
|
return {
|
|
headers: { Authorization: `Bearer ${loginData.access_token}` },
|
|
email,
|
|
username,
|
|
userId: regData.user_id,
|
|
accessToken: loginData.access_token,
|
|
};
|
|
}
|
|
|
|
/** 创建项目 */
|
|
async function createProject(
|
|
request: APIRequestContext,
|
|
headers: Record<string, string>,
|
|
suffix: string,
|
|
): Promise<string> {
|
|
const resp = await request.post(`${apiBase}/projects`, {
|
|
headers,
|
|
data: {
|
|
name: `Assets Test Proj ${suffix}`,
|
|
description: "E2E assets test",
|
|
},
|
|
});
|
|
expect(resp.ok(), `创建项目应成功: ${await resp.text()}`).toBeTruthy();
|
|
const data = await resp.json();
|
|
return data.id;
|
|
}
|
|
|
|
/** 创建素材库 */
|
|
async function createLibrary(
|
|
request: APIRequestContext,
|
|
headers: Record<string, string>,
|
|
projectId: string,
|
|
name: string,
|
|
kind: "video" | "image" = "video",
|
|
): Promise<string> {
|
|
const resp = await request.post(`${apiBase}/asset-libraries`, {
|
|
headers,
|
|
data: { project_id: projectId, name, kind },
|
|
});
|
|
expect(resp.ok(), `创建素材库应成功: ${await resp.text()}`).toBeTruthy();
|
|
const data = await resp.json();
|
|
return data.id;
|
|
}
|
|
|
|
/** 创建素材记录 */
|
|
async function createAsset(
|
|
request: APIRequestContext,
|
|
headers: Record<string, string>,
|
|
projectId: string,
|
|
libraryId: string,
|
|
userId: string,
|
|
name: string,
|
|
status: string = "ready",
|
|
): Promise<string> {
|
|
const resp = await request.post(`${apiBase}/assets`, {
|
|
headers,
|
|
data: {
|
|
project_id: projectId,
|
|
library_id: libraryId,
|
|
name,
|
|
storage_key: `uploads/e2e/${Date.now()}/${name}`,
|
|
mime_type: "video/mp4",
|
|
file_size: 1024000,
|
|
status,
|
|
uploaded_by_user_id: userId,
|
|
metadata: { duration: 15.5, resolution: "1080p" },
|
|
},
|
|
});
|
|
expect(resp.ok(), `创建素材应成功: ${await resp.text()}`).toBeTruthy();
|
|
const data = await resp.json();
|
|
return data.id;
|
|
}
|
|
|
|
/** 在浏览器中设置登录态 */
|
|
async function setupAuthInBrowser(
|
|
page: import("@playwright/test").Page,
|
|
token: string,
|
|
user: { id: string; email: string; username: string },
|
|
) {
|
|
await page.addInitScript(
|
|
({ token, user }) => {
|
|
localStorage.setItem("access_token", token);
|
|
localStorage.setItem(
|
|
"auth-storage",
|
|
JSON.stringify({
|
|
state: { user, isAuthenticated: true },
|
|
version: 0,
|
|
}),
|
|
);
|
|
},
|
|
{
|
|
token,
|
|
user: {
|
|
id: user.id,
|
|
user_id: user.id,
|
|
email: user.email,
|
|
username: user.username,
|
|
display_name: user.username,
|
|
is_email_verified: true,
|
|
email_verified: true,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
test.describe("素材库页面 - 完整交互测试", () => {
|
|
test.describe.configure({ timeout: 180_000 });
|
|
|
|
// ─── 页面加载 ──────────────────────────────────────
|
|
|
|
test("素材库列表页面加载", async ({ page, request }) => {
|
|
await routeBrowserApiToTestApi(page);
|
|
const { headers, userId, accessToken, email, username } =
|
|
await createAuthedUser(request, "assets-load");
|
|
const projectId = await createProject(
|
|
request,
|
|
headers,
|
|
Date.now().toString(),
|
|
);
|
|
await createLibrary(request, headers, projectId, "默认视频库", "video");
|
|
|
|
await setupAuthInBrowser(page, accessToken, {
|
|
id: userId,
|
|
email,
|
|
username,
|
|
});
|
|
|
|
await page.goto("/app/assets");
|
|
|
|
// 页面布局容器
|
|
await expect(page.locator(".xx-assets-page")).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
await expect(page.locator(".xx-assets-layout")).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
|
|
// 左侧素材库列表
|
|
await expect(page.locator(".xx-asset-library-list")).toBeVisible();
|
|
|
|
// 右侧内容区(上传区 + 筛选 + 素材网格)
|
|
await expect(page.locator(".xx-assets-content")).toBeVisible();
|
|
await expect(page.locator(".xx-asset-upload-zone")).toBeVisible();
|
|
await expect(page.locator(".xx-assets-filters")).toBeVisible();
|
|
|
|
// 无错误提示
|
|
await expect(page.getByText(/加载失败|素材库加载失败/)).toHaveCount(0, {
|
|
timeout: 5_000,
|
|
});
|
|
});
|
|
|
|
// ─── 创建素材库 ────────────────────────────────────
|
|
|
|
test("创建新素材库 - 通过 UI", async ({ page, request }) => {
|
|
await routeBrowserApiToTestApi(page);
|
|
const { headers, userId, accessToken, email, username } =
|
|
await createAuthedUser(request, "assets-create");
|
|
const projectId = await createProject(
|
|
request,
|
|
headers,
|
|
Date.now().toString(),
|
|
);
|
|
await createLibrary(request, headers, projectId, "初始库", "video");
|
|
|
|
await setupAuthInBrowser(page, accessToken, {
|
|
id: userId,
|
|
email,
|
|
username,
|
|
});
|
|
|
|
await page.goto("/app/assets");
|
|
await expect(page.locator(".xx-assets-layout")).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
|
|
// 点击新建素材库
|
|
await page.locator(".xx-asset-library-add").click();
|
|
|
|
// 弹窗出现
|
|
const modal = page
|
|
.locator(".ant-modal-content")
|
|
.filter({ hasText: "新建素材库" });
|
|
await expect(modal).toBeVisible();
|
|
|
|
// 填写表单
|
|
const newLibName = `E2E 新建库 ${Date.now()}`;
|
|
await modal.getByPlaceholder("请输入素材库名称").fill(newLibName);
|
|
// 类型选择默认是 video,保持即可
|
|
|
|
// 监听创建请求
|
|
const createPromise = page.waitForResponse(
|
|
(resp) =>
|
|
resp.url().includes("/asset-libraries") &&
|
|
resp.request().method() === "POST",
|
|
{ timeout: 10_000 },
|
|
);
|
|
|
|
// 点击创建
|
|
await modal.getByRole("button", { name: "创建" }).click();
|
|
|
|
const resp = await createPromise;
|
|
expect(resp.ok(), `创建素材库应成功: ${resp.status()}`).toBeTruthy();
|
|
|
|
// 新素材库应出现在列表中
|
|
await expect(
|
|
page.locator(".xx-asset-library-item").filter({ hasText: newLibName }),
|
|
).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
|
|
// ─── 切换素材库 ────────────────────────────────────
|
|
|
|
test("切换不同素材库", async ({ page, request }) => {
|
|
await routeBrowserApiToTestApi(page);
|
|
const { headers, userId, accessToken, email, username } =
|
|
await createAuthedUser(request, "assets-switch");
|
|
const projectId = await createProject(
|
|
request,
|
|
headers,
|
|
Date.now().toString(),
|
|
);
|
|
|
|
const videoLibName = "视频素材库 A";
|
|
const imageLibName = "图片素材库 B";
|
|
const videoLibId = await createLibrary(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
videoLibName,
|
|
"video",
|
|
);
|
|
|
|
// 在视频库里创建一个素材
|
|
await createAsset(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
videoLibId,
|
|
userId,
|
|
"demo_video.mp4",
|
|
);
|
|
|
|
await setupAuthInBrowser(page, accessToken, {
|
|
id: userId,
|
|
email,
|
|
username,
|
|
});
|
|
|
|
await page.goto("/app/assets");
|
|
await expect(page.locator(".xx-assets-layout")).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
|
|
// 点击视频库,应显示素材
|
|
const videoLibItem = page
|
|
.locator(".xx-asset-library-item")
|
|
.filter({ hasText: videoLibName });
|
|
await videoLibItem.click({ force: true });
|
|
await expect(videoLibItem).toHaveClass(/active/);
|
|
|
|
// 验证视频素材出现
|
|
await expect(page.getByText("demo_video.mp4")).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
|
|
// 点击图片库,应切换且不显示视频
|
|
const imageLibItem = page
|
|
.locator(".xx-asset-library-item")
|
|
.filter({ hasText: imageLibName });
|
|
await imageLibItem.click({ force: true });
|
|
await expect(imageLibItem).toHaveClass(/active/);
|
|
|
|
// 空状态或图片库内容
|
|
await expect(page.getByText("demo_video.mp4")).toHaveCount(0, {
|
|
timeout: 5_000,
|
|
});
|
|
});
|
|
|
|
// ─── 素材搜索 ──────────────────────────────────────
|
|
|
|
test("素材搜索功能", async ({ page, request }) => {
|
|
await routeBrowserApiToTestApi(page);
|
|
const { headers, userId, accessToken, email, username } =
|
|
await createAuthedUser(request, "assets-search");
|
|
const projectId = await createProject(
|
|
request,
|
|
headers,
|
|
Date.now().toString(),
|
|
);
|
|
const libraryId = await createLibrary(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
"搜索测试库",
|
|
"video",
|
|
);
|
|
|
|
// 创建两个不同名称的素材
|
|
await createAsset(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
libraryId,
|
|
userId,
|
|
"apple_clip.mp4",
|
|
);
|
|
await createAsset(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
libraryId,
|
|
userId,
|
|
"banana_clip.mp4",
|
|
);
|
|
|
|
await setupAuthInBrowser(page, accessToken, {
|
|
id: userId,
|
|
email,
|
|
username,
|
|
});
|
|
|
|
await page.goto("/app/assets");
|
|
await expect(page.locator(".xx-assets-layout")).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
|
|
// 确保在测试库中
|
|
const libItem = page
|
|
.locator(".xx-asset-library-item")
|
|
.filter({ hasText: "搜索测试库" });
|
|
await libItem.click({ force: true });
|
|
|
|
// 两个素材都应可见
|
|
await expect(page.getByText("apple_clip.mp4")).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
await expect(page.getByText("banana_clip.mp4")).toBeVisible();
|
|
|
|
// 搜索 apple,只显示 apple
|
|
await page.getByPlaceholder("搜索素材名称...").fill("apple");
|
|
await expect(page.getByText("apple_clip.mp4")).toBeVisible();
|
|
await expect(page.getByText("banana_clip.mp4")).toHaveCount(0);
|
|
|
|
// 清空搜索,两个都显示
|
|
await page.getByPlaceholder("搜索素材名称...").fill("");
|
|
await expect(page.getByText("apple_clip.mp4")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
await expect(page.getByText("banana_clip.mp4")).toBeVisible();
|
|
});
|
|
|
|
// ─── 筛选类型 ──────────────────────────────────────
|
|
|
|
test("素材类型筛选", async ({ page, request }) => {
|
|
await routeBrowserApiToTestApi(page);
|
|
const { headers, userId, accessToken, email, username } =
|
|
await createAuthedUser(request, "assets-filter");
|
|
const projectId = await createProject(
|
|
request,
|
|
headers,
|
|
Date.now().toString(),
|
|
);
|
|
const libraryId = await createLibrary(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
"筛选测试库",
|
|
"video",
|
|
);
|
|
|
|
// 创建视频素材
|
|
await createAsset(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
libraryId,
|
|
userId,
|
|
"video_clip.mp4",
|
|
);
|
|
|
|
await setupAuthInBrowser(page, accessToken, {
|
|
id: userId,
|
|
email,
|
|
username,
|
|
});
|
|
|
|
await page.goto("/app/assets");
|
|
await expect(page.locator(".xx-assets-layout")).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
|
|
const libItem = page
|
|
.locator(".xx-asset-library-item")
|
|
.filter({ hasText: "筛选测试库" });
|
|
await libItem.click({ force: true });
|
|
|
|
// 素材应可见
|
|
await expect(page.getByText("video_clip.mp4")).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
|
|
// 筛选类型下拉存在
|
|
const filterSelect = page.locator(".xx-assets-filters-left select").first();
|
|
await expect(filterSelect).toBeVisible();
|
|
});
|
|
|
|
// ─── 素材详情/播放 ────────────────────────────────
|
|
|
|
test("素材详情查看 - 播放弹窗", async ({ page, request }) => {
|
|
await routeBrowserApiToTestApi(page);
|
|
const { headers, userId, accessToken, email, username } =
|
|
await createAuthedUser(request, "assets-detail");
|
|
const projectId = await createProject(
|
|
request,
|
|
headers,
|
|
Date.now().toString(),
|
|
);
|
|
const libraryId = await createLibrary(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
"详情测试库",
|
|
"video",
|
|
);
|
|
await createAsset(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
libraryId,
|
|
userId,
|
|
"play_test.mp4",
|
|
);
|
|
|
|
await setupAuthInBrowser(page, accessToken, {
|
|
id: userId,
|
|
email,
|
|
username,
|
|
});
|
|
|
|
await page.goto("/app/assets");
|
|
await expect(page.locator(".xx-assets-layout")).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
|
|
const libItem = page
|
|
.locator(".xx-asset-library-item")
|
|
.filter({ hasText: "详情测试库" });
|
|
await libItem.click({ force: true });
|
|
|
|
// 找到素材卡片并点击播放按钮
|
|
const assetCard = page
|
|
.locator(".xx-asset-card")
|
|
.filter({ hasText: "play_test.mp4" });
|
|
await expect(assetCard).toBeVisible({ timeout: 10_000 });
|
|
|
|
// 点击播放按钮
|
|
await assetCard.locator(".xx-asset-play").click({ force: true });
|
|
|
|
// 播放弹窗出现
|
|
const modal = page
|
|
.locator(".ant-modal-content")
|
|
.filter({ hasText: "播放" });
|
|
await expect(modal).toBeVisible();
|
|
|
|
// 关闭弹窗
|
|
await modal.locator(".ant-modal-close").click();
|
|
await expect(modal).not.toBeVisible({ timeout: 5_000 });
|
|
});
|
|
|
|
// ─── 删除素材 ──────────────────────────────────────
|
|
|
|
test("删除素材 - 带确认对话框", async ({ page, request }) => {
|
|
await routeBrowserApiToTestApi(page);
|
|
const { headers, userId, accessToken, email, username } =
|
|
await createAuthedUser(request, "assets-delete");
|
|
const projectId = await createProject(
|
|
request,
|
|
headers,
|
|
Date.now().toString(),
|
|
);
|
|
const libraryId = await createLibrary(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
"删除测试库",
|
|
"video",
|
|
);
|
|
await createAsset(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
libraryId,
|
|
userId,
|
|
"to_delete.mp4",
|
|
);
|
|
|
|
await setupAuthInBrowser(page, accessToken, {
|
|
id: userId,
|
|
email,
|
|
username,
|
|
});
|
|
|
|
await page.goto("/app/assets");
|
|
await expect(page.locator(".xx-assets-layout")).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
|
|
const libItem = page
|
|
.locator(".xx-asset-library-item")
|
|
.filter({ hasText: "删除测试库" });
|
|
await libItem.click({ force: true });
|
|
|
|
const assetCard = page
|
|
.locator(".xx-asset-card")
|
|
.filter({ hasText: "to_delete.mp4" });
|
|
await expect(assetCard).toBeVisible({ timeout: 10_000 });
|
|
|
|
// 悬停显示删除按钮
|
|
await assetCard.hover();
|
|
|
|
// 点击删除
|
|
const deleteBtn = assetCard.locator(".xx-asset-delete");
|
|
await expect(deleteBtn).toBeVisible();
|
|
await deleteBtn.click({ force: true });
|
|
|
|
// 确认对话框出现
|
|
const confirmModal = page
|
|
.locator(".ant-popover")
|
|
.filter({ hasText: "确认删除" });
|
|
await expect(confirmModal).toBeVisible();
|
|
|
|
// 监听删除请求
|
|
const deletePromise = page.waitForResponse(
|
|
(resp) =>
|
|
resp.url().includes("/assets/") && resp.request().method() === "DELETE",
|
|
{ timeout: 10_000 },
|
|
);
|
|
|
|
// 点击确认删除
|
|
await confirmModal.getByRole("button", { name: "删除" }).click();
|
|
|
|
const resp = await deletePromise;
|
|
expect(resp.ok(), `删除素材应成功: ${resp.status()}`).toBeTruthy();
|
|
|
|
// 素材应从列表中消失
|
|
await expect(page.getByText("to_delete.mp4")).toHaveCount(0, {
|
|
timeout: 10_000,
|
|
});
|
|
});
|
|
|
|
// ─── 批量删除素材 ──────────────────────────────────
|
|
|
|
test("批量删除素材", async ({ page, request }) => {
|
|
await routeBrowserApiToTestApi(page);
|
|
const { headers, userId, accessToken, email, username } =
|
|
await createAuthedUser(request, "assets-batch");
|
|
const projectId = await createProject(
|
|
request,
|
|
headers,
|
|
Date.now().toString(),
|
|
);
|
|
const libraryId = await createLibrary(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
"批量删除库",
|
|
"video",
|
|
);
|
|
|
|
// 创建多个素材
|
|
await createAsset(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
libraryId,
|
|
userId,
|
|
"batch_1.mp4",
|
|
);
|
|
await createAsset(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
libraryId,
|
|
userId,
|
|
"batch_2.mp4",
|
|
);
|
|
await createAsset(
|
|
request,
|
|
headers,
|
|
projectId,
|
|
libraryId,
|
|
userId,
|
|
"batch_3.mp4",
|
|
);
|
|
|
|
await setupAuthInBrowser(page, accessToken, {
|
|
id: userId,
|
|
email,
|
|
username,
|
|
});
|
|
|
|
await page.goto("/app/assets");
|
|
await expect(page.locator(".xx-assets-layout")).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
|
|
const libItem = page
|
|
.locator(".xx-asset-library-item")
|
|
.filter({ hasText: "批量删除库" });
|
|
await libItem.click({ force: true });
|
|
|
|
// 所有素材应可见
|
|
await expect(page.getByText("batch_1.mp4")).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
await expect(page.getByText("batch_2.mp4")).toBeVisible();
|
|
await expect(page.getByText("batch_3.mp4")).toBeVisible();
|
|
|
|
// 点击全选
|
|
const selectAllBtn = page.getByRole("button", { name: "全选" });
|
|
await expect(selectAllBtn).toBeVisible();
|
|
await selectAllBtn.click();
|
|
|
|
// 批量操作栏出现
|
|
const batchBar = page.locator(".xx-assets-batch-bar");
|
|
await expect(batchBar).toBeVisible();
|
|
await expect(batchBar.getByText(/已选 3 项/)).toBeVisible();
|
|
|
|
// 点击批量删除
|
|
const batchDeleteBtn = batchBar.getByRole("button", { name: "批量删除" });
|
|
await expect(batchDeleteBtn).toBeVisible();
|
|
await batchDeleteBtn.click();
|
|
|
|
// 确认对话框
|
|
const confirmPop = page
|
|
.locator(".ant-popover")
|
|
.filter({ hasText: "确定删除" });
|
|
await expect(confirmPop).toBeVisible();
|
|
|
|
// 确认删除
|
|
await confirmPop.getByRole("button", { name: "删除" }).click();
|
|
|
|
// 验证素材已删除(通过 API 确认)
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const resp = await request.get(`${apiBase}/assets`, {
|
|
headers,
|
|
params: { library_id: libraryId },
|
|
});
|
|
if (!resp.ok()) return "error";
|
|
const data = await resp.json();
|
|
const items = data.items || [];
|
|
return items.length;
|
|
},
|
|
{ timeout: 15_000, intervals: [1_000, 2_000, 3_000] },
|
|
)
|
|
.toBe(0);
|
|
});
|
|
|
|
// ─── 空状态 ────────────────────────────────────────
|
|
|
|
test("空素材库展示空状态", async ({ page, request }) => {
|
|
await routeBrowserApiToTestApi(page);
|
|
const { headers, userId, accessToken, email, username } =
|
|
await createAuthedUser(request, "assets-empty");
|
|
const projectId = await createProject(
|
|
request,
|
|
headers,
|
|
Date.now().toString(),
|
|
);
|
|
await createLibrary(request, headers, projectId, "空素材库", "video");
|
|
|
|
await setupAuthInBrowser(page, accessToken, {
|
|
id: userId,
|
|
email,
|
|
username,
|
|
});
|
|
|
|
await page.goto("/app/assets");
|
|
await expect(page.locator(".xx-assets-layout")).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
|
|
const libItem = page
|
|
.locator(".xx-asset-library-item")
|
|
.filter({ hasText: "空素材库" });
|
|
await libItem.click({ force: true });
|
|
|
|
// 空状态应显示
|
|
await expect(page.locator(".xx-assets-empty")).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
await expect(page.getByText("暂无素材,请上传或切换素材库")).toBeVisible();
|
|
});
|
|
|
|
// ─── 未登录访问 ────────────────────────────────────
|
|
|
|
test("未登录访问素材库 - 重定向到登录页", async ({ page }) => {
|
|
await page.goto("/app/assets");
|
|
await expect(page).toHaveURL(/\/login/);
|
|
});
|
|
});
|