Files
lpt-fe/e2e/study.spec.ts
T
cat-shark 3adbeb183c fix:Transition 白屏——组件多根节点 + Vite 模块请求拦截
- Welcome.vue:回忆卡片 el-dialog 和根 section 共存导致多根节点,<Transition>
  无法动画。包裹一层 <div>
- Study.vue:同上(权重配置对话框)
- MindMapViewer.vue:事件名 selectNode→selectNewNode(类型错误)
- e2e/study.spec.ts + navigation.spec.ts:地址栏 Vite 在模块 URL 后追加
  ?t=... 时间戳,.endsWith('.ts') 匹配失效,改用 url.includes('/src/')
2026-07-04 09:43:07 +08:00

121 lines
4.3 KiB
TypeScript

import { test, expect } from '@playwright/test';
const mockTasks = {
code: 200,
data: {
records: [
{
id: 1,
taskNum: 'T001',
taskName: '学习 Vue3 组合式 API',
taskDescription: '掌握 Composition API 的核心概念',
taskPriority: 5,
materialUrl: 'https://vuejs.org/guide',
lastLearningStatus: '进行中',
},
{
id: 2,
taskNum: 'T002',
taskName: 'TypeScript 类型体操',
taskDescription: '练习 TypeScript 高级类型',
taskPriority: 3,
materialUrl: '',
lastLearningStatus: '未开始',
},
],
},
};
const mockTasksEmpty = { code: 200, data: { records: [] } };
const mockFeed = { code: 200, data: [] };
const authAndNavigate = async (page: any) => {
await page.goto('/login');
await page.evaluate(() => localStorage.setItem('isLoggedIn', 'true'));
};
test.describe('Study page', () => {
test.beforeEach(async ({ page }) => {
await page.route('**/api/tasks**', (route) => {
const url = route.request().url();
// 排除 Vite 模块加载请求(.ts 文件),只拦截真正的 API 请求
// Vite 模块请求都走 /src/ 路径,跳过它们只拦截真正的 API 请求
if (url.includes('/src/')) return route.continue();
if (url.includes('/tasks') && !url.includes('/tasks/')) {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(mockTasks),
});
}
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ code: 200, data: {} }),
});
});
await page.route('**/api/review/feed**', (route) =>
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(mockFeed) }),
);
await authAndNavigate(page);
await page.goto('/study');
await page.waitForSelector('.study-page', { timeout: 10_000 });
});
test('displays task list with names and priorities', async ({ page }) => {
const taskItems = page.locator('.task-list-item');
await expect(taskItems).toHaveCount(2);
await expect(taskItems.first().locator('.task-name')).toContainText('学习 Vue3 组合式 API');
await expect(taskItems.nth(1).locator('.task-name')).toContainText('TypeScript 类型体操');
});
test('selecting a task shows its details', async ({ page }) => {
const taskItems = page.locator('.task-list-item');
await taskItems.nth(1).click();
await expect(page.locator('.detail-head h3')).toContainText('TypeScript 类型体操');
});
test('clicking add navigates to add-task form', async ({ page }) => {
await page.locator('.action-card').locator('button', { hasText: '添加任务' }).click();
await expect(page).toHaveURL(/\/add-task/);
});
test('clicking update navigates to update-task form', async ({ page }) => {
await page.locator('.action-card').locator('button', { hasText: '更新任务' }).click();
await expect(page).toHaveURL(/\/update-task/);
});
test('delete task removes it from the list', async ({ page }) => {
await page.route('**/api/tasks/1', (route) => {
const url = route.request().url();
// Vite 模块请求都走 /src/ 路径,跳过它们只拦截真正的 API 请求
if (url.includes('/src/')) return route.continue();
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ code: 200, message: '删除成功' }),
});
});
await page.locator('.action-card').locator('button', { hasText: '删除任务' }).click();
await expect(page.locator('.task-list-item')).toHaveCount(1, { timeout: 5000 });
});
test('shows empty state when no tasks', async ({ page }) => {
await page.route('**/api/tasks**', (route) => {
const url = route.request().url();
// Vite 模块请求都走 /src/ 路径,跳过它们只拦截真正的 API 请求
if (url.includes('/src/')) return route.continue();
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(mockTasksEmpty),
});
});
await page.goto('/study');
await page.waitForSelector('.study-page', { timeout: 10_000 });
await expect(page.locator('.el-empty')).toBeVisible({ timeout: 5000 });
});
});