// deploy/m2_probe5.js —— PipelineCard props 与渲染链探针(PM 使用,在远端运行) // 读组件实例 props(vnode.props.block)并比较与 pinia store 的引用/状态。 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(); 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 }); await page.fill('.composer textarea', 'SPL gene family in rice'); await page.click('.send-btn'); const t0 = Date.now(); let last = ''; while (Date.now() - t0 < 150000) { const snap = await page.evaluate(() => { const cardEl = document.querySelector('.pipeline-card'); if (!cardEl) return { noCard: true }; // 沿 vnode 链找组件实例 let vnode = cardEl.__vueParentComponent; const found = []; for (let i = 0; i < 6 && vnode; i++) { const p = vnode.props; if (p && p.block) { const b = p.block; found.push({ comp: vnode.type?.name || vnode.type?.__name || 'anon', blockStatus: b.status, steps: (b.steps || []).map((s) => `${s.phase}:${s.state}`).join(','), isReactiveProxy: !!b.__v_raw || typeof b.status !== 'string' || false, }); // 读原始 store 对象比较(raw) const raw = b.__v_raw || b; found[found.length - 1].rawStatus = raw.status; found[found.length - 1].rawSteps = (raw.steps || []).map((s) => s.state).join(','); break; } vnode = vnode.parent; } // pinia 直接读 let storeBlock = null; const rootEl = document.querySelector('#app') || document.querySelector('.app-shell'); let n = rootEl; let app = null; while (n && !app) { if (n.__vue_app__) app = n.__vue_app__; n = n.__vueParentComponent?.parent || null; } if (app) { const pinia = app.config.globalProperties.$pinia; const chat = pinia._s.get('chat'); const aMsg = [...chat.messages].reverse().find((m) => m.role === 'assistant'); const sb = aMsg?.blocks?.[0]; if (sb) storeBlock = { status: sb.status, steps: sb.steps.map((s) => s.state).join(',') }; } return { found, storeBlock, domText: cardEl.textContent.slice(0, 60) }; }); const key = JSON.stringify(snap); if (key !== last) { last = key; console.log(`T+${Math.round((Date.now() - t0) / 1000)}s`, JSON.stringify(snap, null, 1)); } // 流结束后看最终状态 const doneNow = await page.evaluate(() => { const rootEl = document.querySelector('#app'); let n = rootEl; let app = null; while (n && !app) { if (n.__vue_app__) app = n.__vue_app__; n = n.__vueParentComponent?.parent || null; } if (!app) return false; return !app.config.globalProperties.$pinia._s.get('chat').busy; }); if (doneNow && Date.now() - t0 > 30000) break; await new Promise((r) => setTimeout(r, 2500)); } await browser.close(); })();