|
|
|
@@ -0,0 +1,169 @@
|
|
|
|
|
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()
|
|
|
|
|
})
|
|
|
|
|
})
|