verify-elapsed-ticking.mjs 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. #!/usr/bin/env node
  2. /**
  3. * 计时读数实测(2026-11-10)
  4. *
  5. * 验证点:回合头部/段头的秒数**逐秒递增**(1s → 2s → 3s…),
  6. * 而不是只在容器发新事件时跳(5s → 18s → 30s)。
  7. *
  8. * 做法:
  9. * 1. 打开桌面端并进入一个会话
  10. * 2. 发一条极简指令(容器处理期间头像会有「运行了 N 步 · Ns」)
  11. * 3. 每 300ms 采样一次「Ns」文本,观察序列
  12. *
  13. * 用法:node scripts/verify-elapsed-ticking.mjs
  14. */
  15. import { chromium } from 'playwright';
  16. import { mkdir } from 'node:fs/promises';
  17. import { dirname, resolve } from 'node:path';
  18. import { fileURLToPath } from 'node:url';
  19. const here = dirname(fileURLToPath(import.meta.url));
  20. const root = resolve(here, '..');
  21. const outDir = resolve(root, 'test-results/elapsed-ticking');
  22. const BASE = process.env.BASE_URL || 'http://127.0.0.1:4300';
  23. await mkdir(outDir, { recursive: true });
  24. const browser = await chromium.launch({ channel: 'chrome' });
  25. const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
  26. await page.goto(`${BASE}/desktop/chat`, { waitUntil: 'domcontentloaded' });
  27. await page.waitForTimeout(8000);
  28. /*
  29. * 先进一个已有会话:直接打开 `/desktop/chat` 是「开始新对话」入口页,
  30. * 没有输入框(实测 textarea 数量 0)。
  31. */
  32. const firstSession = page.locator('text=使用HTML根据我这个').first();
  33. if (await firstSession.count()) {
  34. await firstSession.click();
  35. await page.waitForTimeout(6000);
  36. }
  37. const box = page.locator('textarea, [contenteditable="true"]').first();
  38. await box.click();
  39. await box.fill('用终端依次执行三条命令:ls /opt/data、pwd、date,然后一句话总结结果。');
  40. await page.keyboard.press('Enter');
  41. console.log('已发送,开始采样秒数…');
  42. /** 采样期间出现的所有「Ns」读数(按首次出现顺序去重) */
  43. const seen = [];
  44. const t0 = Date.now();
  45. let doneAt = 0;
  46. while (Date.now() - t0 < 150_000) {
  47. const texts = await page.locator('.head-time, .seg-time').allInnerTexts();
  48. for (const t of texts) {
  49. if (!seen.includes(t)) {
  50. seen.push(t);
  51. console.log(` +${((Date.now() - t0) / 1000).toFixed(1)}s ${t}`);
  52. }
  53. }
  54. await page.waitForTimeout(300);
  55. /*
  56. * 采样窗口固定 90 秒:容器处理期间逐秒读数会一直增长;
  57. * 结束后定格。按「出现多少个**不同**读数」判断是否在走表。
  58. */
  59. if (Date.now() - t0 >= 90_000) {
  60. doneAt = Date.now();
  61. break;
  62. }
  63. }
  64. await page.screenshot({ path: `${outDir}/after.png` });
  65. // 判断是否逐秒:去重后的数字序列应基本连续
  66. const nums = seen.map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n));
  67. let consecutive = 0;
  68. for (let i = 1; i < nums.length; i++) if (nums[i] - nums[i - 1] <= 2) consecutive++;
  69. const ratio = nums.length > 1 ? consecutive / (nums.length - 1) : 0;
  70. const pass = nums.length >= 4 && ratio >= 0.7;
  71. console.log(`\n读数序列:${nums.join(', ')}`);
  72. console.log(`${pass ? '✓ PASS' : '✗ FAIL'}:${nums.length} 个读数,` +
  73. `${Math.round(ratio * 100)}% 的相邻增量 ≤2s(逐秒跳变则应接近 0%)`);
  74. await browser.close();
  75. process.exit(pass ? 0 : 1);