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() }) })