// deploy/m2_probe.js —— 诊断探针(PM 使用,在远端运行) // 目的:定位真实浏览器中 SSE progress 未推进流水线相位的原因 const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch(); const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } }); const page = await ctx.newPage(); const logs = []; page.on('console', (m) => { if (m.type() === 'error' || m.type() === 'warning') logs.push(`[${m.type()}] ${m.text().slice(0, 200)}`); }); page.on('pageerror', (e) => logs.push('[pageerror] ' + e.message.slice(0, 200))); await page.goto('http://127.0.0.1:5180', { waitUntil: 'domcontentloaded' }); await page.waitForSelector('.app-shell', { timeout: 15000 }); await page.click('.new-task-btn'); await page.waitForSelector('.composer textarea', { timeout: 15000 }); // 用「检索」模式(use_llm:false,走 /api/qa/search,更快的真实链路)排除揭示动画干扰 // 但 search 无 SSE——这里仍用默认问答走 SSE,只是探针只观察 60s await page.fill('.composer textarea', 'SPL gene family in rice'); await page.click('.send-btn'); const t0 = Date.now(); const timeline = []; let pipelineSeen = false; while (Date.now() - t0 < 90000) { const snap = await page.evaluate(() => { const card = document.querySelector('.pipeline-card'); const steps = Array.from(document.querySelectorAll('.pipeline-card .p-step')); const msgs = Array.from(document.querySelectorAll('.msg.assistant')); const stream = document.querySelector('.stream-text')?.textContent?.length || 0; const body = document.querySelector('.msg.assistant .msg-body')?.textContent?.length || 0; // 检查正在进行的 fetch 到 stream 端点 const perf = performance.getEntriesByType('resource').filter((r) => r.name.includes('chat/stream')); return { card: !!card, stepCount: steps.length, states: steps.map((s) => (s.className.includes('done') ? 'D' : s.className.includes('running') ? 'R' : 'W')).join(''), descs: steps.map((s) => s.querySelector('.p-desc')?.textContent?.slice(0, 20)).join('|'), msgCount: msgs.length, streamLen: stream, bodyLen: body, streamReqs: perf.length, }; }); const key = JSON.stringify(snap); if (!timeline.length || timeline[timeline.length - 1].key !== key) { timeline.push({ t: Math.round((Date.now() - t0) / 1000), key }); console.log(`T+${Math.round((Date.now() - t0) / 1000)}s`, JSON.stringify(snap)); } if (snap.card) pipelineSeen = true; if (snap.bodyLen > 30 && snap.streamLen === 0 && snap.states.includes('D')) break; if (snap.bodyLen > 50) break; // 答案已出现,看相位是否 done await new Promise((r) => setTimeout(r, 2000)); } console.log('\n--- 浏览器日志(error/warning)---'); logs.slice(0, 10).forEach((l) => console.log(l)); console.log('\n--- 最终 DOM 摘要 ---'); const fin = await page.evaluate(() => { const card = document.querySelector('.pipeline-card'); return card ? card.outerHTML.slice(0, 1200) : '无 pipeline-card'; }); console.log(fin); await browser.close(); })();