library-picturebook-activity/lesingle-creation-frontend/e2e/leai/e2e-flow.spec.ts

227 lines
7.3 KiB
TypeScript
Raw Normal View History

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',
phone: '13800001111',
},
}),
})
})
await loggedInPage.route('http://192.168.1.120: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', 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.120: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', phone: '13800001111' },
}),
})
})
await loggedInPage.route('http://192.168.1.120: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)
})
})