#!/usr/bin/env node /** * 复现「新建会话后立刻发消息,消息不显示」(2026-11-11) * * 不读代码推断,在真实浏览器里按用户操作顺序跑: * 1. 打开聊天页 → 点「新建对话」→ 弹窗里选容器 * 2. **立刻**在输入框里输入并回车 * 3. 每 200ms 采样消息区,看消息何时出现、输入框何时清空 * * 用法:node scripts/repro-new-session-send.mjs */ import { chromium } from 'playwright'; import { readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const login = JSON.parse(readFileSync(resolve(here, '../.claude/dev-login.json'), 'utf8')); const BASE = 'http://localhost:4200'; const AGENT = process.argv[2] ?? 'lj-test'; const b = await chromium.launch({ channel: 'chrome' }); const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } }); const page = await ctx.newPage(); await page.addInitScript( ([token, me]) => { localStorage.setItem('fmode_session_token', token); localStorage.setItem('fmode_me', me); }, [login.token, JSON.stringify(login.me)], ); const errors = []; page.on('pageerror', (e) => errors.push(String(e).slice(0, 200))); page.on('console', (m) => { if (m.type() === 'error') errors.push('console: ' + m.text().slice(0, 200)); }); console.log('=== 1. 打开聊天页 ==='); await page.goto(`${BASE}/desktop/chat`, { waitUntil: 'domcontentloaded', timeout: 60_000 }); await page.waitForTimeout(6000); console.log('=== 2. 点「新建对话」 ==='); await page.locator('text=新建对话').first().click(); await page.waitForTimeout(2000); console.log(`=== 3. 选容器 ${AGENT} ===`); const opt = page.locator(`text=${AGENT}`).last(); await opt.click(); await page.waitForTimeout(4000); console.log(' URL:', page.url()); /* 确认输入框就绪 */ const ta = page.locator('textarea').first(); await ta.waitFor({ state: 'visible', timeout: 20_000 }); console.log('\n=== 4. 立刻输入并回车(模拟用户"新建完就发")==='); const TEXT = '测试' + Date.now().toString().slice(-4); await ta.fill(TEXT); const t0 = Date.now(); await ta.press('Enter'); console.log('\n=== 5. 采样(每 200ms,最多 20 秒)==='); let firstSeenAt = null; let inputClearedAt = null; for (let i = 0; i < 100; i++) { const snap = await page.evaluate(() => { const ta = document.querySelector('textarea'); return { inputVal: ta ? ta.value : '(无)', bodyText: (document.body.innerText || '').slice(0, 6000), }; }); const ms = Date.now() - t0; if (snap.inputVal === '' && inputClearedAt === null) { inputClearedAt = ms; console.log(` ${ms}ms 输入框已清空`); } /* 消息区出现该文本(排除输入框里的那份) */ if (snap.bodyText.includes(TEXT) && firstSeenAt === null) { firstSeenAt = ms; console.log(` ${ms}ms ✅ 消息出现在页面上`); } if (i % 10 === 0 && firstSeenAt === null) { console.log(` ${ms}ms 还没出现 | 输入框="${snap.inputVal.slice(0, 20)}"`); } if (firstSeenAt !== null && inputClearedAt !== null) break; await page.waitForTimeout(200); } console.log('\n=== 结果 ==='); console.log(`文本: "${TEXT}"`); console.log(`输入框清空: ${inputClearedAt === null ? '❌ 从未清空' : inputClearedAt + 'ms'}`); console.log(`消息出现: ${firstSeenAt === null ? '❌ 20 秒内从未出现' : firstSeenAt + 'ms'}`); if (firstSeenAt !== null && firstSeenAt > 1500) { console.log('⚠️ 超过 1.5 秒 —— 用户能感知到卡顿'); } console.log('页面错误:', errors.length ? errors.slice(0, 5) : '无'); await page.screenshot({ path: 'test-results/repro-new-session.png' }); await b.close();