Files
digital-psychology/e2e-live/tests/fullstack-consistency.spec.ts
T
jackyu66gitandCursor 7e9023f0a8
ci / h5 (push) Canceled after 0s
ci / api (push) Canceled after 0s
ci / ess-docs (push) Canceled after 0s
test(e2e): 前后台联调 Playwright 与 admin 冒烟
新增 e2e-live 真 API 一致性全路径,并为 admin-h5 增加 mock 浏览器冒烟。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 22:41:43 +08:00

170 lines
7.2 KiB
TypeScript

import { test, expect, type Page, type BrowserContext } from '@playwright/test'
const USER = 'http://127.0.0.1:5173'
const ADMIN = 'http://127.0.0.1:5174'
const API = 'http://127.0.0.1:8080'
test.describe.configure({ mode: 'serial' })
test.describe('前后台联调 · 真 API 全路径', () => {
let userCtx: BrowserContext
let userPage: Page
let adminPage: Page
let userId = ''
let marker = ''
test.beforeAll(async ({ browser }) => {
const health = await fetch(`${API}/api/v1/healthz`)
if (!health.ok) {
throw new Error('API :8080 不可用。先 npm run deps:up && npm run dev:api')
}
marker = `L${Date.now().toString(36)}`
userCtx = await browser.newContext()
userPage = await userCtx.newPage()
adminPage = await browser.newPage()
})
test.afterAll(async () => {
await userCtx?.close()
await adminPage?.context().close()
})
test('0 · 运营后台可登录', async () => {
await adminPage.goto(`${ADMIN}/login`)
await adminPage.locator('input[type="password"]').fill('change-me')
await adminPage.getByRole('button', { name: '登录' }).click()
await expect(adminPage.getByRole('heading', { name: '用户' })).toBeVisible()
})
test('1 · 用户侧生成个人画像', async () => {
const portraitResp = userPage.waitForResponse(
(r) => r.url().includes('/reports/portrait') && r.request().method() === 'POST' && r.ok(),
)
await userPage.goto(`${USER}/psy/portrait`)
await expect(userPage.getByRole('heading', { name: '愈心解码' }).first()).toBeVisible()
const inputs = userPage.locator('.decode-inputs input')
await inputs.nth(0).fill('1992')
await inputs.nth(1).fill('7')
await inputs.nth(2).fill('18')
await userPage.getByRole('button', { name: '开始解码' }).click()
const resp = await portraitResp
const body = await resp.json()
userId = body?.data?.user_id || ''
expect(userId, 'portrait 响应应含 user_id').toMatch(/^[0-9a-f-]{36}$/i)
await expect(userPage.getByRole('link', { name: /在报告页打开/ })).toBeVisible({ timeout: 60_000 })
await expect(userPage.getByRole('button', { name: /解锁完整分析/ }).first()).toBeVisible()
})
test('2 · 后台能看到该用户,并与前台档案一致', async () => {
expect(userId).toBeTruthy()
await adminPage.goto(`${ADMIN}/users/${userId}`)
await expect(adminPage.getByRole('heading', { name: '用户详情' })).toBeVisible()
await expect(adminPage.getByText(userId).first()).toBeVisible()
await expect(adminPage.getByText('self').first()).toBeVisible()
})
test('3 · 前台开通会员 → 后台订单与会员状态一致', async () => {
await userPage.goto(`${USER}/psy/membership`)
await expect(userPage.getByRole('heading', { name: '成长会员' })).toBeVisible()
await userPage.getByRole('button', { name: /月卡/ }).click()
await expect(userPage.getByText('会员有效')).toBeVisible({ timeout: 30_000 })
await adminPage.goto(`${ADMIN}/users/${userId}`)
await expect(adminPage.getByText('有效').first()).toBeVisible({ timeout: 15_000 })
await expect(adminPage.getByText(/month|月/i).first()).toBeVisible()
await adminPage.goto(`${ADMIN}/orders`)
await expect(adminPage.getByRole('heading', { name: '订单' })).toBeVisible()
await expect(adminPage.getByText(userId).first()).toBeVisible()
await expect(adminPage.getByText('membership').first()).toBeVisible()
await expect(adminPage.getByText('paid').first()).toBeVisible()
})
test('4 · 后台授予年卡 → 前台会员与审计一致', async () => {
await adminPage.goto(`${ADMIN}/users/${userId}`)
await adminPage.locator('select').selectOption('year')
await adminPage.getByRole('button', { name: '授予 / 延长' }).click()
await expect(adminPage.getByText('已授予')).toBeVisible()
await adminPage.goto(`${ADMIN}/audit`)
await expect(adminPage.getByText('membership.grant').first()).toBeVisible()
await expect(adminPage.getByText(userId).first()).toBeVisible()
await userPage.goto(`${USER}/psy/membership`)
await expect(userPage.getByText('会员有效 · 年卡')).toBeVisible({ timeout: 15_000 })
})
test('5 · 成长报告列表可打开(会员后)', async () => {
await userPage.goto(`${USER}/psy/reports`)
await userPage.waitForLoadState('networkidle')
await expect(userPage.locator('.who-static')).toContainText('成长报告', { timeout: 20_000 })
await expect(userPage.getByText('愈心解码').first()).toBeVisible()
await userPage.getByText('查看报告').first().click()
await expect(userPage).toHaveURL(/\/reports\//)
})
test('6 · 关系理解', async () => {
await userPage.goto(`${USER}/psy/relation`)
await expect(userPage.getByRole('heading', { name: '看见彼此的相处模样' })).toBeVisible()
await userPage.getByPlaceholder('例如:伴侣').fill(marker.slice(0, 6))
const nums = userPage.locator('input[type="number"]')
await nums.nth(0).fill('1991')
await nums.nth(1).fill('3')
await nums.nth(2).fill('9')
await userPage.getByRole('button', { name: '去匹配' }).click()
await expect(userPage.getByRole('button', { name: '生成中…' })).toBeHidden({ timeout: 60_000 })
await expect(userPage.getByText(/默契|相处|沟通|匹配|性格/).first()).toBeVisible({ timeout: 60_000 })
})
test('7 · AI 问答', async () => {
await userPage.goto(`${USER}/psy/ask`)
const box = userPage.getByPlaceholder('让我来解答你的问题吧')
await expect(box).toBeVisible({ timeout: 30_000 })
const q = `联调提问${marker}`
await box.fill(q)
await userPage.getByRole('button', { name: '发送' }).click()
await expect(userPage.getByText(q)).toBeVisible()
await expect(userPage.locator('body')).toContainText(/你|建议|可以|了解|次数|会员/, { timeout: 60_000 })
})
test('8 · 探索测试完整提交', async () => {
await userPage.goto(`${USER}/psy/scales/communication-style`)
await expect(userPage.getByRole('button', { name: '开始' })).toBeVisible({ timeout: 20_000 })
await userPage.getByRole('button', { name: '开始' }).click()
for (let i = 0; i < 3; i++) {
await userPage.locator('label.opt').first().click()
const nextLabel = i === 2 ? '查看探索结果' : '下一题'
await userPage.getByRole('button', { name: nextLabel }).click()
}
await expect(userPage.getByText(/沟通|探索|结果|偏好/).first()).toBeVisible({ timeout: 30_000 })
})
test('9 · 其余核心页可打开(烟测)', async () => {
for (const path of [
'/psy/',
'/psy/explore',
'/psy/companion',
'/psy/mine',
'/psy/reports',
'/psy/profile',
'/psy/star',
'/psy/rhythm',
'/psy/cards',
'/psy/synastry',
]) {
await userPage.goto(`${USER}${path}`)
await expect(userPage.locator('#app')).toBeVisible()
await expect(userPage.getByText(/失败|unavailable|Internal Server/i)).toHaveCount(0)
}
})
test('10 · 后台用户详情仍可看到档案与近订单', async () => {
await adminPage.goto(`${ADMIN}/users/${userId}`)
await expect(adminPage.getByText('self').first()).toBeVisible()
await expect(adminPage.getByText('有效').first()).toBeVisible()
})
})