Files
jiapuapp/tests/root-pages-runtime-smoke.js
T
2026-07-22 17:31:38 +08:00

108 lines
5.5 KiB
JavaScript

const origin = process.argv[2] || 'http://localhost:5173'
const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))
const connect = async () => {
const pages = await (await fetch('http://127.0.0.1:9222/json/list')).json()
const page = pages.find((item) => item.type === 'page' && item.url.startsWith(`${origin}/`))
if (!page) throw new Error(`Chrome debugging has no ${origin} application page`)
const socket = new WebSocket(page.webSocketDebuggerUrl)
await new Promise((resolve, reject) => {
socket.addEventListener('open', resolve, { once: true })
socket.addEventListener('error', reject, { once: true })
})
let id = 0
const pending = new Map()
socket.addEventListener('message', (event) => {
const message = JSON.parse(event.data)
const request = pending.get(message.id)
if (!request) return
pending.delete(message.id)
message.error ? request.reject(new Error(message.error.message)) : request.resolve(message.result)
})
const send = (method, params = {}) => new Promise((resolve, reject) => {
id += 1
pending.set(id, { resolve, reject })
socket.send(JSON.stringify({ id, method, params }))
})
return { socket, send }
}
const valueOf = async (send, expression) => {
const result = await send('Runtime.evaluate', { expression, returnByValue: true })
if (result.exceptionDetails) throw new Error(result.exceptionDetails.exception?.description || result.exceptionDetails.text)
return result.result?.value
}
const waitFor = async (send, expression, message) => {
for (let attempt = 0; attempt < 60; attempt += 1) {
try {
if (await valueOf(send, expression)) return
} catch (error) {
if (!String(error.message).includes('Inspected target navigated or closed')) throw error
}
await sleep(100)
}
throw new Error(message)
}
let auditId = 0
const open = async (send, route, query, selector) => {
auditId += 1
const url = `${origin}/?rootAudit=${auditId}#${route}${query}`
await send('Page.navigate', { url })
await waitFor(send, `location.href === ${JSON.stringify(url)}`, `Navigation failed: ${route}${query}`)
await waitFor(send, `Boolean(document.querySelector(${JSON.stringify(selector)}))`, `Missing ${selector}: ${route}${query}`)
}
const run = async () => {
const { socket, send } = await connect()
try {
await send('Page.enable')
await send('Runtime.enable')
// 步骤一:四个根入口必须能进入各自默认状态,继续承担基础路由与渲染冒烟职责。
await open(send, '/pages/family/f01-family-feed', '?genealogyId=1001', '.feed-state--list')
await open(send, '/pages/family/f02-publish-feed', '?genealogyId=1001', '.publish-state--form')
await open(send, '/pages/notification/n01-message-center', '', '.notice-state--list')
await open(send, '/pages/profile/m01-profile-home', '', '.profile-state--ready')
// 步骤二:N01 单条消息要先标记已读再进入详情,返回后必须保留刚才的已读状态。
await open(send, '/pages/notification/n01-message-center', '?genealogyId=1001', '.notice-state--list')
if ((await valueOf(send, "document.querySelectorAll('.notice-card__status.is-unread').length")) !== 1) {
throw new Error('N01 initial unread count is invalid')
}
await valueOf(send, "document.querySelector('.notice-card').click()")
await waitFor(send, "location.hash.includes('/pages/notification/n02-message-detail?id=review-1&genealogyId=1001')", 'N01 first notice did not open its N02 detail')
await waitFor(send, "Boolean(document.querySelector('.notice-state--ready'))", 'N02 detail did not render after opening the notice')
await valueOf(send, "document.querySelector('.header-back').click()")
await waitFor(send, "Boolean(document.querySelector('.notice-state--list'))", 'N01 did not return from N02')
await waitFor(send, "document.querySelectorAll('.notice-card__status.is-unread').length === 0", 'N01 did not preserve the first notice read state')
// 步骤三:全标已读必须清除全部未读标记、隐藏页头操作,并显示项目内反馈。
await open(send, '/pages/notification/n01-message-center', '?genealogyId=1001', '.notice-state--list')
await valueOf(send, "document.querySelector('.header-action').click()")
await waitFor(send, "document.querySelectorAll('.notice-card__status.is-unread').length === 0", 'N01 mark-all did not clear unread notices')
await waitFor(send, "document.querySelector('.header-action')?.disabled === true && !document.querySelector('.header-action')?.textContent.trim()", 'N01 mark-all did not disable the completed header action')
await waitFor(send, "document.querySelector('.app-toast')?.textContent.includes('已全部标记为已读')", 'N01 mark-all did not show the project Toast')
// 步骤四:保留原有空态、成功态和错误态入口,防止新增交互覆盖挤掉页面状态冒烟。
await open(send, '/pages/family/f01-family-feed', '?state=empty&genealogyId=1001', '.feed-state--empty')
await open(send, '/pages/family/f02-publish-feed', '?state=success&genealogyId=1001', '.publish-state--success')
await open(send, '/pages/notification/n01-message-center', '?state=empty', '.notice-state--empty')
await open(send, '/pages/profile/m01-profile-home', '?state=error', '.profile-state--error')
process.stdout.write('ROOT-PAGES-RUNTIME-SMOKE PASS\n')
} finally {
socket.close()
}
}
run().catch((error) => {
process.stderr.write(`${error.stack || error.message}\n`)
process.exit(1)
})