#!/usr/bin/env node /** * 图文同发实测(2026-11-10) * * 验证点:选图 + 打字一起发送后,用户侧**一个回合**(图与文字不分裂成两次发言)。 * * ⚠️ 容器侧合并窗口(见 docs/03-interface/容器侧改造-图文合并处理-20261110.md) * 尚未上线,所以容器仍可能对图片抢先回话。本脚本只断言 **App 侧渲染**: * - 用户消息里 image.png 与文字属于同一个回合 * - 不再出现「一个图气泡 + 一个空气泡 + 中间夹着处理提示」的分裂 * * 用法:node scripts/verify-image-text-send.mjs <图片路径> */ import { chromium } from 'playwright'; import { mkdir } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const root = resolve(here, '..'); const outDir = resolve(root, 'test-results/image-text-send'); const BASE = process.env.BASE_URL || 'http://127.0.0.1:4300'; const imgPath = process.argv[2]; if (!imgPath) { console.error('用法:node scripts/verify-image-text-send.mjs <图片路径>'); process.exit(2); } await mkdir(outDir, { recursive: true }); const browser = await chromium.launch({ channel: 'chrome' }); const page = await browser.newPage({ viewport: { width: 1440, height: 900 } }); await page.goto(`${BASE}/desktop/chat`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(8000); // 进一个已有会话(入口页没有输入框) const s = page.locator('text=使用HTML根据我这个').first(); if (await s.count()) { await s.click(); await page.waitForTimeout(5000); } // 选图(隐藏的 file input) const input = page.locator('input[type="file"]').first(); await input.setInputFiles(imgPath); await page.waitForTimeout(1200); await page.screenshot({ path: `${outDir}/1-staged.png` }); // 打字 + 发送 const box = page.locator('textarea').first(); await box.click(); await box.fill('这张图是什么形状?只回一句。'); await page.keyboard.press('Enter'); // 采集中途状态 for (const [i, wait] of [[2, 1500], [3, 5000], [4, 20_000]]) { await page.waitForTimeout(wait); await page.screenshot({ path: `${outDir}/${i}-after-${wait}ms.png` }); } // 统计「用户回合」数量:自己的气泡里同时含图和文 const stats = await page.evaluate(() => { const bubbles = [...document.querySelectorAll('app-message-bubble')]; const own = bubbles.filter((b) => b.className.includes('own') || b.innerHTML.includes('image.png')); return { bubbles: bubbles.length, withImage: bubbles.filter((b) => /image\.png/.test(b.innerText)).length, withText: bubbles.filter((b) => /这张图是什么形状/.test(b.innerText)).length, sample: own.slice(-3).map((b) => b.innerText.slice(0, 60)), }; }); console.log(JSON.stringify(stats, null, 2)); console.log(`\n截图:${outDir}`); await browser.close();