接口开始5%

This commit is contained in:
rain
2026-07-22 17:31:33 +08:00
parent eced3d1e6e
commit 9b0ad62df4
426 changed files with 20111 additions and 28472 deletions
+107 -4
View File
@@ -1,4 +1,107 @@
const sleep=(ms)=>new Promise(r=>setTimeout(r,ms));const origin=process.argv[2]||'http://localhost:5173';
const connect=async()=>{const pages=await(await fetch('http://127.0.0.1:9222/json/list')).json();const page=pages.find(x=>x.type==='page'&&x.url.startsWith(`${origin}/`));if(!page)throw new Error('No application page');const socket=new WebSocket(page.webSocketDebuggerUrl);await new Promise((r,j)=>{socket.addEventListener('open',r,{once:true});socket.addEventListener('error',j,{once:true})});let id=0;const pending=new Map();socket.addEventListener('message',e=>{const m=JSON.parse(e.data),p=pending.get(m.id);if(!p)return;pending.delete(m.id);m.error?p.reject(new Error(m.error.message)):p.resolve(m.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(s,e)=>(await s('Runtime.evaluate',{expression:e,returnByValue:true})).result?.value;const waitFor=async(s,e,m)=>{for(let i=0;i<50;i+=1){if(await valueOf(s,e))return;await sleep(100)}throw new Error(m)};let auditId=0;const open=async(s,r,q,sel)=>{auditId+=1;const u=`${origin}/?audit=${auditId}#${r}${q}`;await s('Page.navigate',{url:u});await waitFor(s,`location.href === ${JSON.stringify(u)}`,'navigation failed');await waitFor(s,`Boolean(document.querySelector(${JSON.stringify(sel)}))`,`missing ${sel}`)};
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');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(e=>{process.stderr.write(`${e.stack||e.message}\n`);process.exit(1)})
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)
})