agent-workspace-ui-smoke-test.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. 'use strict';
  2. const assert = require('assert/strict');
  3. const fs = require('fs');
  4. const path = require('path');
  5. const packageRoot = path.resolve(__dirname, '..');
  6. const dashboardRoot = path.join(packageRoot, 'mcp', 'src', 'dashboard');
  7. const html = fs.readFileSync(path.join(dashboardRoot, 'index.html'), 'utf8');
  8. const app = fs.readFileSync(path.join(dashboardRoot, 'app.js'), 'utf8');
  9. const css = fs.readFileSync(path.join(dashboardRoot, 'styles.css'), 'utf8');
  10. const server = fs.readFileSync(path.join(dashboardRoot, 'server.js'), 'utf8');
  11. const agentService = fs.readFileSync(path.join(dashboardRoot, 'agent-service.js'), 'utf8');
  12. const checks = [];
  13. function check(name, fn) {
  14. fn();
  15. checks.push(name);
  16. }
  17. check('工作台资源带缓存版本', () => {
  18. assert.match(html, /styles\.css\?v=/);
  19. assert.match(html, /app\.js\?v=/);
  20. });
  21. check('客服工作台可直接进入,不先被登录页挡住', () => {
  22. assert.match(app, /if \(!acc && !\[\'overview\', \'agent\', \'skills\', \'knowledge\', \'customer-ops\'\]\.includes\(page\)\)/);
  23. });
  24. check('监听按钮跟 listener.running,不再跟 AI 模式绑死', () => {
  25. assert.match(app, /const listenerRunning = Boolean\(listener\.running\);/);
  26. assert.match(app, /data-agent-action="\$\{allowlistReady \? \(listenerRunning \? 'stop-listener' : 'start-listener'\) : 'manage-allowlist'\}"/);
  27. assert.doesNotMatch(app, /aiListenerActive/);
  28. });
  29. check('离线账号和监听失败会直接显示在工作台', () => {
  30. assert.match(app, /账号离线,请先扫码登录/);
  31. assert.match(app, /登录后可启动监听/);
  32. assert.match(app, /listener\.lastError && !listenerRunning/);
  33. assert.match(app, /<strong>监听未运行<\/strong>/);
  34. });
  35. check('启动监听前在点击处理里检查白名单,避免 allowlistReady 作用域错误', () => {
  36. assert.match(app, /if \(action === 'start-listener'\) \{\s*const allowlistReady = Number\(state\.agent\.status\?\.config\?\.allowedSenderCount \|\| 0\) > 0;/);
  37. });
  38. check('删除账号会请求服务端清理本地凭据', () => {
  39. assert.match(app, /async function removeAccount\(id\)/);
  40. assert.match(app, /\/api\/accounts\/remove/);
  41. assert.match(server, /pathname === '\/api\/accounts\/remove'/);
  42. assert.match(agentService, /function removeActiveAccount/);
  43. });
  44. check('席位占满时提供强制切换登录', () => {
  45. assert.match(app, /async function forceSwitchLogin/);
  46. assert.match(app, /已有登录流程进行中\|设备登录数量已达上限\|停用旧设备/);
  47. assert.match(app, /startLoginFlow\(\{ forceSwitch: true \}\)/);
  48. assert.match(app, /\/api\/logout/);
  49. assert.match(server, /pathname === '\/api\/logout'/);
  50. });
  51. check('免扫码失败不再自动拉起二维码,避免占死登录通道', () => {
  52. assert.match(app, /不再自动发起登录流程/);
  53. assert.match(app, /toast\('免扫码恢复失败,请在登录页手动开始登录'/);
  54. assert.doesNotMatch(app, /setTimeout\(\(\) => startLoginFlow\(\), 100\)/);
  55. });
  56. check('媒体不可用有独立气泡和重试按钮', () => {
  57. assert.match(app, /function agentMediaUnavailableHtml/);
  58. assert.match(app, /agent-message-media unavailable agent-media-unavailable/);
  59. assert.match(css, /\.agent-message-media \{/);
  60. assert.match(css, /\.agent-media-retry/);
  61. assert.match(css, /\.agent-drawer-backdrop \{/);
  62. assert.match(css, /\.content > \.agent-page > \* \{ animation: none; \}/);
  63. });
  64. check('全自动接管和工作台会话入口仍在', () => {
  65. assert.match(app, /全自动接管/);
  66. assert.match(app, /AGENT_SNAPSHOT_KEY/);
  67. assert.match(app, /\/api\/accounts\/switch/);
  68. assert.match(app, /voicePreview/);
  69. assert.match(app, /openSessionConversation/);
  70. });
  71. check('全局、会话和群聊模式切换不再弹二次确认', () => {
  72. assert.match(app, /const modeButton = event\.target\.closest\('\[data-agent-mode\]'\)/);
  73. assert.match(app, /const conversationModeButton = event\.target\.closest\('\[data-agent-conversation-mode\]'\)/);
  74. assert.match(app, /const groupModeButton = event\.target\.closest\('\[data-agent-group-mode\]'\)/);
  75. const modeHandler = app.slice(app.indexOf("const modeButton = event.target.closest('[data-agent-mode]')"), app.indexOf("const actionButton = event.target.closest('[data-agent-action]')"));
  76. assert.doesNotMatch(modeHandler, /confirmModal\(/);
  77. assert.match(app, /if \(action === 'group-approve'\)[\s\S]*confirmModal\(/);
  78. assert.match(app, /if \(action === 'approve'\)[\s\S]*confirmModal\(/);
  79. });
  80. check('工作台语音可合成试听并真实发送', () => {
  81. assert.match(app, /data-agent-action="voice-preview"/);
  82. assert.match(app, /data-agent-action="voice-send"/);
  83. assert.match(app, /\/api\/agent\/voice\/preview/);
  84. assert.match(app, /\/api\/agent\/conversations\/\$\{id\}\/voice-send/);
  85. assert.match(app, /\/api\/agent\/messages\/\$\{encodeURIComponent\(message\.id\)\}\/voice-audio/);
  86. assert.match(server, /pathname === '\/api\/agent\/voice\/preview'/);
  87. assert.match(server, /voice-send/);
  88. assert.match(server, /serveVoicePreviewAudio/);
  89. assert.match(server, /serveSentVoiceAudio/);
  90. assert.match(agentService, /async function previewVoice/);
  91. assert.match(agentService, /async function sendClonedVoice/);
  92. assert.match(agentService, /confirmed: input.confirmed/);
  93. assert.match(css, /\.agent-voice-audio/);
  94. });
  95. check('通用仓保留 token 表单,不迁入房产业务字段', () => {
  96. assert.match(app, /function ensureAuthTokenReady/);
  97. assert.match(app, /function renderAuthTokenForm/);
  98. assert.match(app, /match\.community \|\| match\.title/);
  99. assert.doesNotMatch(app, /match\.estateName/);
  100. assert.doesNotMatch(app, /match\.layoutName/);
  101. assert.doesNotMatch(app, /match\.buildingArea/);
  102. assert.doesNotMatch(app, /match\.featureTags/);
  103. });
  104. console.log(JSON.stringify({ status: 'ok', checks: checks.length, coverage: checks }, null, 2));