init: AI培训与智能巡检系统

This commit is contained in:
selfrelease
2026-06-16 00:55:20 +08:00
commit c55598494b
201 changed files with 53131 additions and 0 deletions
+374
View File
@@ -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);
});