项目从单体结构重构为 pnpm monorepo (shared/backend/frontend), 新增 YouTube、Instagram、Twitter/X、哔哩哔哩、微博 5 个平台适配器, 包含完整的单元测试和 E2E 测试覆盖。 - 完成 T-031~T-044: 5 个适配器实现、注册、配置和测试 - 重构前后端分离: Hono 后端 + Next.js 前端 - 151 个单元测试 + 21 个 Mock E2E + 25 个真实 E2E - 适配器基于真实 TikHub API 响应结构实现 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
135 lines
4.7 KiB
TypeScript
135 lines
4.7 KiB
TypeScript
import { test, expect, type Page } from "@playwright/test";
|
|
import { mockContentList, mockDetailItem } from "./fixtures";
|
|
|
|
async function mockTrendingAPI(page: Page, items = mockContentList) {
|
|
await page.route(/\/api\/tikhub\//, async (route) => {
|
|
const url = route.request().url();
|
|
if (url.includes("/detail")) {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ data: mockDetailItem }),
|
|
});
|
|
return;
|
|
}
|
|
const match = url.match(/\/api\/tikhub\/(\w+)/);
|
|
const platform = match?.[1];
|
|
const filtered = items.filter((i) => i.platform === platform);
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ data: filtered }),
|
|
});
|
|
});
|
|
}
|
|
|
|
test.describe("首页浏览流程", () => {
|
|
test("首页加载并显示内容卡片", async ({ page }) => {
|
|
await mockTrendingAPI(page);
|
|
await page.goto("/");
|
|
|
|
await expect(page.getByTestId("content-grid")).toBeVisible();
|
|
const cards = page.getByTestId("content-card");
|
|
await expect(cards).toHaveCount(9);
|
|
});
|
|
|
|
test("平台切换", async ({ page }) => {
|
|
await mockTrendingAPI(page);
|
|
await page.goto("/");
|
|
await expect(page.getByTestId("content-grid")).toBeVisible();
|
|
|
|
// Switch to douyin — 2 items (dy-001, dy-004)
|
|
await page.getByTestId("platform-tab-douyin").click();
|
|
await expect(page.getByTestId("content-card")).toHaveCount(2);
|
|
|
|
// Switch to tiktok — 1 item (tt-002)
|
|
await page.getByTestId("platform-tab-tiktok").click();
|
|
await expect(page.getByTestId("content-card")).toHaveCount(1);
|
|
|
|
// Switch to xiaohongshu — 1 item (xhs-003)
|
|
await page.getByTestId("platform-tab-xiaohongshu").click();
|
|
await expect(page.getByTestId("content-card")).toHaveCount(1);
|
|
|
|
// Switch to youtube — 1 item (yt-005)
|
|
await page.getByTestId("platform-tab-youtube").click();
|
|
await expect(page.getByTestId("content-card")).toHaveCount(1);
|
|
|
|
// Switch to instagram — 1 item (ig-006)
|
|
await page.getByTestId("platform-tab-instagram").click();
|
|
await expect(page.getByTestId("content-card")).toHaveCount(1);
|
|
|
|
// Switch to twitter — 1 item (tw-007)
|
|
await page.getByTestId("platform-tab-twitter").click();
|
|
await expect(page.getByTestId("content-card")).toHaveCount(1);
|
|
|
|
// Switch to bilibili — 1 item (bl-008)
|
|
await page.getByTestId("platform-tab-bilibili").click();
|
|
await expect(page.getByTestId("content-card")).toHaveCount(1);
|
|
|
|
// Switch to weibo — 1 item (wb-009)
|
|
await page.getByTestId("platform-tab-weibo").click();
|
|
await expect(page.getByTestId("content-card")).toHaveCount(1);
|
|
|
|
// Switch back to all — 9 items
|
|
await page.getByTestId("platform-tab-all").click();
|
|
await expect(page.getByTestId("content-card")).toHaveCount(9);
|
|
});
|
|
|
|
test("排序功能", async ({ page }) => {
|
|
await mockTrendingAPI(page);
|
|
await page.goto("/");
|
|
await expect(page.getByTestId("content-grid")).toBeVisible();
|
|
|
|
// Default: play_count desc — YouTube (10M) first
|
|
await expect(page.getByTestId("content-card").first()).toContainText(
|
|
"YouTube Top Music"
|
|
);
|
|
|
|
// Switch sort field to like_count
|
|
await page.getByTestId("sort-select").selectOption("like_count");
|
|
// like_count desc: yt-005(500K) first
|
|
await expect(page.getByTestId("content-card").first()).toContainText(
|
|
"YouTube Top Music"
|
|
);
|
|
|
|
// Toggle to ascending
|
|
await page.getByTestId("sort-order").click();
|
|
// like_count asc: dy-004(35K) first
|
|
await expect(page.getByTestId("content-card").first()).toContainText(
|
|
"生活小妙招"
|
|
);
|
|
});
|
|
|
|
test("卡片导航到详情页", async ({ page }) => {
|
|
await mockTrendingAPI(page);
|
|
await page.goto("/");
|
|
await expect(page.getByTestId("content-grid")).toBeVisible();
|
|
|
|
// Default sort: play_count desc → first card is YouTube (yt-005, 10M)
|
|
await page.getByTestId("content-card").first().click();
|
|
await expect(page).toHaveURL(/\/detail\/youtube\/yt-005/);
|
|
});
|
|
|
|
test("空状态", async ({ page }) => {
|
|
await mockTrendingAPI(page, []);
|
|
await page.goto("/");
|
|
|
|
await expect(page.getByTestId("empty-state")).toBeVisible();
|
|
await expect(page.getByText("暂无内容")).toBeVisible();
|
|
});
|
|
|
|
test("错误状态和重试", async ({ page }) => {
|
|
await page.route(/\/api\/tikhub\//, async (route) => {
|
|
await route.fulfill({
|
|
status: 500,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ error: "服务器错误" }),
|
|
});
|
|
});
|
|
|
|
await page.goto("/");
|
|
await expect(page.getByText("加载失败")).toBeVisible({ timeout: 30_000 });
|
|
await expect(page.getByTestId("error-retry")).toBeVisible();
|
|
});
|
|
});
|