init: AI培训与智能巡检系统
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
// 回归:贡献榜列表曾因 class 名 `.lb` 与灯箱冲突变成全屏遮罩,导致无法离开页面
|
||||
test('从贡献榜可正常导航离开', async ({ page }) => {
|
||||
await page.goto('/leaderboard');
|
||||
await expect(page.getByRole('heading', { name: '贡献榜' })).toBeVisible();
|
||||
await page.getByRole('button', { name: /图鉴/ }).click();
|
||||
await page.getByRole('menuitem', { name: '探索' }).click();
|
||||
await expect(page).toHaveURL(/\/explore/);
|
||||
});
|
||||
|
||||
// 回归:/api-docs 曾被 vite 的 /api 代理误吞(前缀匹配),导致路由 404
|
||||
test('开放API文档路由不被代理吞掉', async ({ page }) => {
|
||||
await page.goto('/api-docs');
|
||||
await expect(page.getByRole('heading', { name: /开放 API/ })).toBeVisible();
|
||||
});
|
||||
@@ -0,0 +1,374 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
/** MVP 全链路冒烟 — 对应 T-1.8(已适配叙事首页 + 探索页 IA)。*/
|
||||
|
||||
test('叙事首页:英雄区 + 时代章节 + 代表车', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.getByRole('heading', { name: /中国工业史/ })).toBeVisible();
|
||||
await expect(page.getByText('蒸汽时代')).toBeVisible();
|
||||
await expect(page.getByText('高铁时代')).toBeVisible();
|
||||
// 代表车 mini 卡存在
|
||||
await expect(page.locator('.mini').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('首页 → 探索(CTA)', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('link', { name: /进入图鉴探索/ }).click();
|
||||
await expect(page).toHaveURL(/\/explore/);
|
||||
await expect(page.getByTestId('gallery')).toBeVisible();
|
||||
});
|
||||
|
||||
test('探索页默认图鉴:分页(21/页) + 点击收集', async ({ page }) => {
|
||||
await page.goto('/explore');
|
||||
await expect(page.getByTestId('gallery-card').first()).toBeVisible();
|
||||
// 每页至多 21 个
|
||||
expect(await page.getByTestId('gallery-card').count()).toBeLessThanOrEqual(21);
|
||||
// 分页器存在(在图鉴上方)
|
||||
await expect(page.getByRole('button', { name: '下一页' })).toBeVisible();
|
||||
await expect(page.locator('.gcard-art .thumb').first()).toBeVisible();
|
||||
const firstCollect = page.locator('.gcard-collect').first();
|
||||
await expect(firstCollect).toHaveAttribute('aria-pressed', 'false');
|
||||
await firstCollect.click();
|
||||
await expect(firstCollect).toHaveAttribute('aria-pressed', 'true');
|
||||
});
|
||||
|
||||
test('筛选(折叠面板)→ 图鉴卡片 → 详情(全部详情, 全中文)', async ({ page }) => {
|
||||
await page.goto('/explore');
|
||||
await page.getByRole('button', { name: /筛选/ }).click();
|
||||
await page.locator('.filterbar select').first().selectOption('电力机车');
|
||||
const firstCard = page.locator('.gcard-art').first();
|
||||
await expect(firstCard).toBeVisible();
|
||||
await firstCard.click();
|
||||
await expect(page).toHaveURL(/\/models\/\d+/);
|
||||
await expect(page.getByRole('heading', { name: '基本信息' })).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: '动力与性能' })).toBeVisible();
|
||||
// 原始数据英文键已映射为中文
|
||||
await expect(page.getByText('model_code')).toHaveCount(0);
|
||||
await expect(page.getByText('max_speed_value')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('详情图册:管理员上传图片并灯箱缩放', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await page.getByRole('button', { name: /管理员/ }).click();
|
||||
await page.locator('.auth-submit').click();
|
||||
await expect(page.locator('.user-chip')).toBeVisible();
|
||||
|
||||
await page.goto('/models/1');
|
||||
await page.getByRole('heading', { name: '图册' }).scrollIntoViewIfNeeded();
|
||||
const png = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
||||
'base64',
|
||||
);
|
||||
await page.setInputFiles('input[type=file]', {
|
||||
name: 'demo.png',
|
||||
mimeType: 'image/png',
|
||||
buffer: png,
|
||||
});
|
||||
await expect(page.locator('.img-cell img').first()).toBeVisible();
|
||||
await page.locator('.img-cell img').first().click();
|
||||
await expect(page.getByTestId('lightbox')).toBeVisible();
|
||||
await page.getByRole('button', { name: '放大' }).click();
|
||||
await expect(page.getByTestId('lightbox')).toContainText('%');
|
||||
});
|
||||
|
||||
test('时间轴视图渲染并可点击节点', async ({ page }) => {
|
||||
await page.goto('/explore');
|
||||
await page.getByRole('button', { name: '时间轴' }).click();
|
||||
await expect(page.getByTestId('timeline')).toBeVisible();
|
||||
await expect(page.locator('.error')).toHaveCount(0);
|
||||
expect(await page.locator('.node').count()).toBeGreaterThan(30);
|
||||
await page.locator('.node').first().click();
|
||||
await expect(page).toHaveURL(/\/models\/\d+/);
|
||||
});
|
||||
|
||||
test('全局搜索命中并跳转详情', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByPlaceholder('搜索型号 / 生产商 / 系列…').fill('HXD');
|
||||
await expect(page.locator('.search-dropdown li').first()).toBeVisible();
|
||||
await page.locator('.search-dropdown li').first().click();
|
||||
await expect(page).toHaveURL(/\/models\/\d+/);
|
||||
});
|
||||
|
||||
test('首页章节链接 → 探索并带分类筛选', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('link', { name: /探索全部.*车型/ }).first().click();
|
||||
await expect(page).toHaveURL(/\/explore\?category=/);
|
||||
await expect(page.getByTestId('gallery')).toBeVisible();
|
||||
});
|
||||
|
||||
test('账户:注册 → 用户菜单 → 个人主页', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await expect(page.getByTestId('auth-modal')).toBeVisible();
|
||||
await page.getByRole('button', { name: '注册', exact: true }).click();
|
||||
await page.getByPlaceholder('昵称').fill('E2E用户');
|
||||
await page.getByPlaceholder('邮箱').fill(`e2e_${Date.now()}@test.com`);
|
||||
await page.getByPlaceholder('密码(至少 6 位)').fill('secret123');
|
||||
await page.getByRole('button', { name: '注册并登录' }).click();
|
||||
await expect(page.getByTestId('auth-modal')).toHaveCount(0);
|
||||
await expect(page.locator('.user-chip')).toBeVisible();
|
||||
await page.locator('.user-chip').click();
|
||||
await expect(page).toHaveURL(/\/me/);
|
||||
await expect(page.getByText('贡献积分', { exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test('管理员编辑词条 → 立即生效', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await page.getByRole('button', { name: /管理员/ }).click();
|
||||
await page.locator('.auth-submit').click();
|
||||
await expect(page.locator('.user-chip')).toBeVisible();
|
||||
|
||||
await page.goto('/models/1');
|
||||
await page.getByRole('button', { name: /编辑词条/ }).click();
|
||||
await expect(page.getByTestId('edit-modal')).toBeVisible();
|
||||
await page
|
||||
.locator('.edit-field', { hasText: '用途' })
|
||||
.locator('input')
|
||||
.fill(`用途修订 ${Date.now()}`);
|
||||
await page.getByRole('button', { name: '提交修改' }).click();
|
||||
await expect(page.locator('.notice')).toContainText('生效');
|
||||
});
|
||||
|
||||
test('普通用户看不到编辑/上传入口(图鉴只读)', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await page.getByRole('button', { name: /普通用户/ }).click();
|
||||
await page.locator('.auth-submit').click();
|
||||
await expect(page.locator('.user-chip')).toBeVisible();
|
||||
|
||||
await page.goto('/models/1');
|
||||
await expect(page.getByRole('button', { name: /编辑词条/ })).toHaveCount(0);
|
||||
await expect(page.getByRole('button', { name: '+ 添加图片' })).toHaveCount(0);
|
||||
});
|
||||
|
||||
async function registerUser(page: import('@playwright/test').Page, name: string) {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await page.getByRole('button', { name: '注册', exact: true }).click();
|
||||
await page.getByPlaceholder('昵称').fill(name);
|
||||
await page.getByPlaceholder('邮箱').fill(`u_${Date.now()}@test.com`);
|
||||
await page.getByPlaceholder('密码(至少 6 位)').fill('secret123');
|
||||
await page.getByRole('button', { name: '注册并登录' }).click();
|
||||
await expect(page.locator('.user-chip')).toBeVisible();
|
||||
}
|
||||
|
||||
test('认领词条维护 → 维护者署名出现', async ({ page }) => {
|
||||
const name = `维护${Date.now() % 100000}`;
|
||||
await registerUser(page, name);
|
||||
await page.goto('/models/1');
|
||||
await page.getByRole('button', { name: '认领维护' }).click();
|
||||
await expect(page.locator('.maintainers')).toContainText(name);
|
||||
});
|
||||
|
||||
test('个人主页含等级与徽章区', async ({ page }) => {
|
||||
await registerUser(page, `等级${Date.now() % 100000}`);
|
||||
await page.locator('.user-chip').click();
|
||||
await expect(page).toHaveURL(/\/me/);
|
||||
await expect(page.locator('.level-bar')).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: '徽章' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('贡献榜页可访问', async ({ page }) => {
|
||||
await page.goto('/leaderboard');
|
||||
await expect(page.getByRole('heading', { name: '贡献榜' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('测试账号一键填入并登录', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await page.getByRole('button', { name: /普通用户/ }).click();
|
||||
await page.locator('.auth-submit').click();
|
||||
await expect(page.locator('.user-chip')).toContainText('演示用户');
|
||||
});
|
||||
|
||||
test('管理员一键登录后可见审核入口', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await page.getByRole('button', { name: /管理员/ }).click();
|
||||
await page.locator('.auth-submit').click();
|
||||
await expect(page.locator('.user-chip')).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: '审核' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('社区:发帖 → 帖子页回复', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await page.getByRole('button', { name: /普通用户/ }).click();
|
||||
await page.locator('.auth-submit').click();
|
||||
await expect(page.locator('.user-chip')).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: /社区/ }).click();
|
||||
await page.getByRole('menuitem', { name: '论坛' }).click();
|
||||
await expect(page).toHaveURL(/\/community/);
|
||||
await page.getByRole('button', { name: '+ 发帖' }).click();
|
||||
const title = `测试帖 ${Date.now() % 100000}`;
|
||||
await page.getByPlaceholder('标题').fill(title);
|
||||
await page.getByPlaceholder('正文…').fill('这是一条 e2e 测试帖');
|
||||
await page.getByRole('button', { name: '发布' }).click();
|
||||
|
||||
await page.getByRole('link', { name: new RegExp(title) }).click();
|
||||
await expect(page).toHaveURL(/\/thread\/\d+/);
|
||||
await page.getByPlaceholder('写下你的回复…').fill('e2e 回复');
|
||||
await page.getByRole('button', { name: '回复' }).click();
|
||||
await expect(page.locator('.replies')).toContainText('e2e 回复');
|
||||
});
|
||||
|
||||
test('打卡:详情页登记目击 → 出现在列表', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await page.getByRole('button', { name: /普通用户/ }).click();
|
||||
await page.locator('.auth-submit').click();
|
||||
await expect(page.locator('.user-chip')).toBeVisible();
|
||||
|
||||
await page.goto('/models/1');
|
||||
await page.getByRole('heading', { name: '目击打卡' }).scrollIntoViewIfNeeded();
|
||||
await page.getByPlaceholder('纬度 lat').fill('39.90');
|
||||
await page.getByPlaceholder('经度 lng').fill('116.40');
|
||||
await page.getByPlaceholder('车站/地点').fill('北京南站');
|
||||
await page.getByRole('button', { name: '打卡', exact: true }).click();
|
||||
await expect(page.locator('.sighting-row').first()).toContainText('北京南站');
|
||||
});
|
||||
|
||||
test('打卡地图页加载', async ({ page }) => {
|
||||
await page.goto('/map');
|
||||
await expect(page.getByRole('heading', { name: '打卡地图' })).toBeVisible();
|
||||
await expect(page.getByTestId('map')).toBeVisible();
|
||||
});
|
||||
|
||||
test('参数对比:加入车型 → 雷达图 + 表格', async ({ page }) => {
|
||||
await page.goto('/compare');
|
||||
await expect(page.getByRole('heading', { name: '参数对比' })).toBeVisible();
|
||||
await page.getByPlaceholder('搜索型号加入对比…').fill('CR400');
|
||||
await page.locator('.cmp-search .search-dropdown li').first().click();
|
||||
await page.getByPlaceholder('搜索型号加入对比…').fill('HXD1');
|
||||
await page.locator('.cmp-search .search-dropdown li').first().click();
|
||||
await expect(page.getByTestId('radar')).toBeVisible();
|
||||
await expect(page.locator('.cmp-table')).toContainText('最高时速');
|
||||
await expect(page.locator('.cmp-chip')).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('技术族谱:按系列展示并可进详情', async ({ page }) => {
|
||||
await page.goto('/family');
|
||||
await expect(page.getByRole('heading', { name: '技术族谱' })).toBeVisible();
|
||||
await expect(page.locator('.family-series').first()).toBeVisible();
|
||||
await page.locator('.family-node').first().click();
|
||||
await expect(page).toHaveURL(/\/models\/\d+/);
|
||||
});
|
||||
|
||||
test('关注车型 → 个人主页"最新目击"区出现', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await page.getByRole('button', { name: /普通用户/ }).click();
|
||||
await page.locator('.auth-submit').click();
|
||||
await expect(page.locator('.user-chip')).toBeVisible();
|
||||
|
||||
await page.goto('/models/1');
|
||||
await page.getByRole('button', { name: /关注/ }).click();
|
||||
await expect(page.getByRole('button', { name: '★ 已关注' })).toBeVisible();
|
||||
|
||||
await page.locator('.user-chip').click();
|
||||
await expect(page.getByRole('heading', { name: /关注的车型/ })).toBeVisible();
|
||||
});
|
||||
|
||||
test('数据大屏渲染 KPI 与图表', async ({ page }) => {
|
||||
await page.goto('/stats');
|
||||
await expect(page.getByRole('heading', { name: /数据大屏/ })).toBeVisible();
|
||||
await expect(page.locator('.kpi').first()).toBeVisible();
|
||||
await expect(page.locator('.donut').first()).toBeVisible();
|
||||
await expect(page.locator('.vbars')).toBeVisible();
|
||||
await expect(page.getByTestId('evolution-curve')).toBeVisible();
|
||||
});
|
||||
|
||||
test('开放 API 文档页与导出链接', async ({ page }) => {
|
||||
await page.goto('/api-docs');
|
||||
await expect(page.getByRole('heading', { name: /开放 API/ })).toBeVisible();
|
||||
await expect(page.locator('a[href="/api/export/models.csv"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('AI 识车页:未登录提示 + 上传区', async ({ page }) => {
|
||||
await page.goto('/identify');
|
||||
await expect(page.getByRole('heading', { name: 'AI 识车' })).toBeVisible();
|
||||
await expect(page.locator('.identify-drop')).toBeVisible();
|
||||
await expect(page.locator('.notice')).toContainText('登录');
|
||||
});
|
||||
|
||||
test('管理员候选审图页可访问', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await page.getByRole('button', { name: /管理员/ }).click();
|
||||
await page.locator('.auth-submit').click();
|
||||
await expect(page.locator('.user-chip')).toBeVisible();
|
||||
await page.getByRole('link', { name: '候选审图' }).click();
|
||||
await expect(page).toHaveURL(/\/admin\/photos/);
|
||||
await expect(page.getByRole('heading', { name: '候选审图' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('普通用户无候选审图入口', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await page.getByRole('button', { name: /普通用户/ }).click();
|
||||
await page.locator('.auth-submit').click();
|
||||
await expect(page.locator('.user-chip')).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: '候选审图' })).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('图鉴:输入型号关键字直接筛选', async ({ page }) => {
|
||||
await page.goto('/explore');
|
||||
await expect(page.getByTestId('gallery-card').first()).toBeVisible();
|
||||
await page.locator('.kw-input').fill('HXD');
|
||||
// 防抖后结果收敛,首张卡片型号含 HXD
|
||||
await expect(page.locator('.gcard-code').first()).toContainText('HXD');
|
||||
// 计数随筛选下降(全量 500+ → 少量)
|
||||
await expect(page.locator('.result-count')).toBeVisible();
|
||||
});
|
||||
|
||||
test('管理员:上传后可将照片设为封面', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: '登录 / 注册' }).click();
|
||||
await page.getByRole('button', { name: /管理员/ }).click();
|
||||
await page.locator('.auth-submit').click();
|
||||
await expect(page.locator('.user-chip')).toBeVisible();
|
||||
|
||||
await page.goto('/models/2');
|
||||
await page.getByRole('heading', { name: '图册' }).scrollIntoViewIfNeeded();
|
||||
const png = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
||||
'base64',
|
||||
);
|
||||
await page.setInputFiles('input[type=file]', {
|
||||
name: 'cover.png',
|
||||
mimeType: 'image/png',
|
||||
buffer: png,
|
||||
});
|
||||
// 已确认的图库照片出现"设为封面"★ 按钮
|
||||
const featureBtn = page.getByRole('button', { name: '设为封面' }).first();
|
||||
await expect(featureBtn).toBeVisible();
|
||||
await featureBtn.click();
|
||||
// 设为封面后按钮进入选中态(金色 on)
|
||||
await expect(page.locator('.img-feature.on')).toBeVisible();
|
||||
});
|
||||
|
||||
test('图鉴:翻到第 2 页进详情,返回仍停在第 2 页', async ({ page }) => {
|
||||
await page.goto('/explore');
|
||||
await expect(page.getByTestId('gallery-card').first()).toBeVisible();
|
||||
await page.getByRole('button', { name: '下一页' }).click();
|
||||
await expect(page).toHaveURL(/[?&]gp=2/);
|
||||
await expect(page.locator('.pagination')).toContainText('第 2');
|
||||
const firstCode = await page.locator('.gcard-code').first().textContent();
|
||||
|
||||
await page.locator('.gcard-art').first().click();
|
||||
await expect(page).toHaveURL(/\/models\/\d+/);
|
||||
|
||||
await page.getByRole('button', { name: /返回图鉴/ }).click();
|
||||
await expect(page).toHaveURL(/[?&]gp=2/);
|
||||
await expect(page.locator('.pagination')).toContainText('第 2');
|
||||
await expect(page.locator('.gcard-code').first()).toHaveText(firstCode || '');
|
||||
});
|
||||
|
||||
test('图鉴页不再显示"收集示例"按钮', async ({ page }) => {
|
||||
await page.goto('/explore');
|
||||
await expect(page.getByTestId('gallery')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: '收集示例' })).toHaveCount(0);
|
||||
});
|
||||
Reference in New Issue
Block a user