import { test, expect } from '../fixtures/leai.fixture' import { randomWorkId } from '../fixtures/leai.fixture' import { fileURLToPath } from 'url' import path from 'path' /** * P2: 端到端完整流程测试 */ const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const MOCK_H5_PATH = path.resolve(__dirname, '../utils/mock-h5.html') test.describe('端到端:创作完整流程', () => { test('E2E-1: iframe 创作主流程', async ({ loggedInPage, sendWebhook }) => { // ── 步骤 1: 拦截 token API ── await loggedInPage.route('**/leai-auth/token', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 200, data: { token: 'e2e_test_token', orgId: 'gdlib', h5Url: 'http://localhost:3001', phone: '13800001111', }, }), }) }) await loggedInPage.route('http://192.168.1.72:3001/**', async (route) => { await route.fulfill({ status: 200, contentType: 'text/html', path: MOCK_H5_PATH }) }) // ── 步骤 2: 访问创作页 ── await loggedInPage.goto('/p/create') const iframe = loggedInPage.locator('iframe').first() await expect(iframe).toBeVisible({ timeout: 10_000 }) // ── 步骤 3: 模拟 Webhook status=1 (PENDING) ── const workId = randomWorkId() const result1 = await sendWebhook({ event: 'work.status_changed', data: { work_id: workId, status: 1, phone: '13800001111', title: 'E2E测试绘本' }, }) expect(result1.status).toBe(200) // ── 步骤 4: 模拟 Webhook status=2 (PROCESSING) ── const result2 = await sendWebhook({ event: 'work.progress', data: { work_id: workId, status: 2, progress: 50, progressMessage: '正在绘制插画...' }, }) expect(result2.status).toBe(200) // ── 步骤 5: 模拟 Webhook status=3 (COMPLETED) ── const result3 = await sendWebhook({ event: 'work.status_changed', data: { work_id: workId, status: 3, title: 'E2E测试绘本', pageList: [ { imageUrl: 'https://cdn.example.com/e2e/page1.png', text: '第一页' }, { imageUrl: 'https://cdn.example.com/e2e/page2.png', text: '第二页' }, ], }, }) expect(result3.status).toBe(200) // ── 步骤 6: 模拟 Webhook status=5 (DUBBED) ── const result5 = await sendWebhook({ event: 'work.status_changed', data: { work_id: workId, status: 5, title: 'E2E测试绘本', author: 'E2E测试作者', pageList: [ { imageUrl: 'https://cdn.example.com/e2e/page1.png', text: '第一页', audioUrl: 'https://cdn.example.com/e2e/audio1.mp3' }, { imageUrl: 'https://cdn.example.com/e2e/page2.png', text: '第二页', audioUrl: 'https://cdn.example.com/e2e/audio2.mp3' }, ], }, }) expect(result5.status).toBe(200) // 全流程无报错即通过 }) test('E2E-2: Token 过期自动刷新', async ({ loggedInPage }) => { let tokenCallCount = 0 let refreshCallCount = 0 await loggedInPage.route('**/leai-auth/token', async (route) => { tokenCallCount++ await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 200, data: { token: 'initial_token', orgId: 'gdlib', h5Url: 'http://localhost:3001', phone: '13800001111' }, }), }) }) await loggedInPage.route('**/leai-auth/refresh-token', async (route) => { refreshCallCount++ await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 200, data: { token: 'refreshed_token', orgId: 'gdlib', phone: '13800001111' }, }), }) }) await loggedInPage.route('http://192.168.1.72:3001/**', async (route) => { await route.fulfill({ status: 200, contentType: 'text/html', path: MOCK_H5_PATH }) }) await loggedInPage.goto('/p/create') const iframe = loggedInPage.locator('iframe').first() await expect(iframe).toBeVisible({ timeout: 10_000 }) expect(tokenCallCount).toBe(1) // 模拟 H5 发送 TOKEN_EXPIRED await loggedInPage.evaluate(() => { window.dispatchEvent(new MessageEvent('message', { data: { source: 'leai-creation', version: 1, type: 'TOKEN_EXPIRED', payload: { messageId: 'm1' } }, origin: '*', })) }) await loggedInPage.waitForTimeout(2000) expect(refreshCallCount).toBe(1) // iframe 应继续正常显示 await expect(iframe).toBeVisible() }) test('E2E-3: Webhook 幂等 + 状态不回退', async ({ sendWebhook }) => { const workId = randomWorkId() // 发送 status=1 (PENDING) const r1 = await sendWebhook({ event: 'work.status_changed', data: { work_id: workId, status: 1, phone: '13800001111' }, }) expect(r1.status).toBe(200) // 发送 status=3 (COMPLETED) const r3 = await sendWebhook({ event: 'work.status_changed', data: { work_id: workId, status: 3, title: '幂等测试' }, }) expect(r3.status).toBe(200) // 发送旧状态 status=2 (PROCESSING) — 应被忽略 const r2 = await sendWebhook({ event: 'work.status_changed', data: { work_id: workId, status: 2, progress: 80 }, }) expect(r2.status).toBe(200) // V4.0 规则:status=2 <= status=3,忽略 // 发送 status=-1 (FAILED) — 强制覆盖 const rf = await sendWebhook({ event: 'work.status_changed', data: { work_id: workId, status: -1, failReason: '测试强制失败' }, }) expect(rf.status).toBe(200) // V4.0 规则:FAILED 强制更新 }) test('E2E-4: 创作失败 → 重试流程', async ({ loggedInPage, sendWebhook }) => { const failedWorkId = randomWorkId() // 模拟 Webhook 推送失败 const r1 = await sendWebhook({ event: 'work.status_changed', data: { work_id: failedWorkId, status: 1, phone: '13800001111' }, }) expect(r1.status).toBe(200) const rf = await sendWebhook({ event: 'work.status_changed', data: { work_id: failedWorkId, status: -1, failReason: 'AI处理超时' }, }) expect(rf.status).toBe(200) // 用户回到创作页重新开始 await loggedInPage.route('**/leai-auth/token', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 200, data: { token: 'retry_token', orgId: 'gdlib', h5Url: 'http://localhost:3001', phone: '13800001111' }, }), }) }) await loggedInPage.route('http://192.168.1.72:3001/**', async (route) => { await route.fulfill({ status: 200, contentType: 'text/html', path: MOCK_H5_PATH }) }) await loggedInPage.goto('/p/create') const iframe = loggedInPage.locator('iframe').first() await expect(iframe).toBeVisible({ timeout: 10_000 }) // 新一轮创作成功 const newWorkId = randomWorkId() const r2 = await sendWebhook({ event: 'work.status_changed', data: { work_id: newWorkId, status: 1, phone: '13800001111' }, }) expect(r2.status).toBe(200) const r3 = await sendWebhook({ event: 'work.status_changed', data: { work_id: newWorkId, status: 3, title: '重试成功绘本' }, }) expect(r3.status).toBe(200) }) })