/** * 端到端验证「取消」链路:发起长回合 → 中途点停止 → 校验状态与内核行为。 * * 用法:node scripts/e2e-cancel.mjs */ const port = Number(process.argv[2] ?? 9335) async function main() { const list = await (await fetch(`http://127.0.0.1:${port}/json/list`)).json() const page = list.filter((t) => t.type === 'page')[0] if (!page) throw new Error('未找到页面') const ws = new WebSocket(page.webSocketDebuggerUrl) await new Promise((r, j) => { const t = setTimeout(() => j(new Error('连接超时')), 8000) ws.addEventListener('open', () => { clearTimeout(t) r() }) ws.addEventListener('error', () => { clearTimeout(t) j(new Error('连接失败')) }) }) const send = (method, params = {}) => new Promise((res, rej) => { const id = Math.floor(Math.random() * 1e9) const timer = setTimeout(() => rej(new Error(`超时 ${method}`)), 25000) const h = (ev) => { const m = JSON.parse(ev.data) if (m.id === id) { clearTimeout(timer) ws.removeEventListener('message', h) res(m.result) } } ws.addEventListener('message', h) ws.send(JSON.stringify({ id, method, params })) }) const evaluate = (expression) => send('Runtime.evaluate', { expression, returnByValue: true }) // 1) 发起一个长回合 const fill = `(() => { const el = document.querySelector('.composer__input'); const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; setter.call(el, '写一篇 4000 字的软件架构设计文章,分十个章节,每章都要有完整内容。'); el.dispatchEvent(new Event('input', { bubbles: true })); return 'OK'; })()` await evaluate(fill) const clicked = await evaluate(`(() => { const btn = document.querySelector('.btn-send'); if (!btn || btn.disabled) return 'FAIL'; btn.click(); return 'SENT'; })()`) console.log('发送:', clicked.result.value) if (clicked.result.value !== 'SENT') process.exit(1) // 2) 等出现停止按钮(说明回合在跑) let runningSeen = false for (let i = 0; i < 40; i++) { await new Promise((r) => setTimeout(r, 500)) const probe = await evaluate(`JSON.stringify({ stop: !!document.querySelector('.btn-stop') })`) if (JSON.parse(probe.result.value).stop) { runningSeen = true break } } console.log('回合进行中:', runningSeen) if (!runningSeen) { console.log('⚠️ 未进入运行态,无法验证取消') ws.close() process.exit(1) } // 3) 让它跑一小会儿再取消 await new Promise((r) => setTimeout(r, 3000)) const cancel = await evaluate(`(() => { const btn = document.querySelector('.btn-stop'); if (!btn) return 'NO_STOP'; btn.click(); return 'CANCELLED'; })()`) console.log('点击停止:', cancel.result.value) // 4) 等待终态 let final = null for (let i = 0; i < 40; i++) { await new Promise((r) => setTimeout(r, 700)) const probe = await evaluate(`JSON.stringify({ stopGone: !document.querySelector('.btn-stop'), sendBack: !!document.querySelector('.btn-send'), status: [...document.querySelectorAll('.msg--assistant .msg__status')].map((n) => n.textContent), contentLen: [...document.querySelectorAll('.msg--assistant .msg__content')].pop()?.textContent.length ?? 0 })`) final = JSON.parse(probe.result.value) if (final.stopGone && final.sendBack) break } console.log('终态:', JSON.stringify(final)) const ok = final.sendBack && final.stopGone console.log(ok ? '\n✅ 取消链路:停止按钮消失、发送按钮恢复' : '\n❌ 取消链路未恢复正常') ws.close() process.exit(ok ? 0 : 1) } main().catch((e) => { console.error('失败:', e.message) process.exit(1) })