test(web+mp): E2E 测试全量实施 — Web 5 flow + MP 4 flow + 基础设施
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

Web 端 (Playwright):
- fixtures: test-data 工厂 + API Client (乐观锁 version) + 增强 auth fixture
- pages: LoginPage, PatientListPage, PatientDetailPage, HealthDataPage, AppointmentPage
- flows: 患者全流程, 体征数据链路, 预约排班链路, 随访管理链路, 告警处理链路
- smoke tests 迁移到 smoke/ 目录,import 路径更新
- playwright.config.ts 更新: globalSetup 环境检查, 60s timeout, video retain

小程序端 (Vitest + miniprogram-automator):
- helpers: AutomatorClient, MpApiClient, MpAuthHelper, MpNavigator
- flows: 患者健康数据查看, 体征数据录入, 积分签到兑换, 积分商城浏览
- vitest.config.ts + check-readiness.ts
- vitest 4.1.5 依赖安装

Playwright 发现 15 个测试 (5 flow + 10 smoke),全部就绪
This commit is contained in:
iven
2026-04-29 04:58:01 +08:00
parent 2f4be6dcd0
commit c6e8048bc5
32 changed files with 1798 additions and 5 deletions

View File

@@ -0,0 +1,70 @@
// apps/miniprogram/e2e/helpers/api-client.ts
// 简化版 API Client用于小程序 E2E 数据准备/清理
const API_BASE = process.env.E2E_API_URL || 'http://localhost:3000/api/v1';
export class MpApiClient {
private token = '';
async login(username?: string, password?: string) {
const res = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: username || process.env.E2E_ADMIN_USER || 'admin',
password: password || process.env.E2E_ADMIN_PASS || 'Admin@2026',
}),
});
const json = await res.json();
if (!json.success) throw new Error('Login failed');
this.token = json.data.access_token;
return json.data;
}
getToken() { return this.token; }
async createPatient(overrides?: Record<string, unknown>) {
return this.post('/health/patients', overrides ?? {});
}
async deletePatient(id: string, version: number) {
await this.del(`/health/patients/${id}`, { version });
}
async createVitalSigns(patientId: string, overrides?: Record<string, unknown>) {
return this.post(`/health/patients/${patientId}/vital-signs`, overrides ?? {});
}
async deleteVitalSigns(patientId: string, id: string, version: number) {
await this.del(`/health/patients/${patientId}/vital-signs/${id}`, { version });
}
async listPointsProducts() {
return this.get('/health/points/products');
}
private async headers() {
return { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` };
}
private async get(path: string) {
const res = await fetch(`${API_BASE}${path}`, { headers: await this.headers() });
const json = await res.json();
return json.data;
}
private async post(path: string, body: unknown) {
const res = await fetch(`${API_BASE}${path}`, {
method: 'POST', headers: await this.headers(), body: JSON.stringify(body),
});
const json = await res.json();
if (!json.success) throw new Error(`POST ${path} failed`);
return json.data;
}
private async del(path: string, body?: unknown) {
await fetch(`${API_BASE}${path}`, {
method: 'DELETE', headers: await this.headers(), body: body ? JSON.stringify(body) : undefined,
});
}
}