Files
goodbuddy/docs/quality/electron-ui-automation.md
T
mesalogo 3b560c9a5e feat: add external knowledge retrieval and cross-project activity
Connect Dify, FastGPT and RAGFlow through read-only bindings, encrypted local credentials, shared retrieval and persistent citations. Configuration and removal affect GoodBuddy only.

Show active conversations across projects, retain older live work, and improve project dialog scrolling. Queue concurrent questions and route managed SSH OpenCode questions through the Agent. Preserve recoverable read/search errors and partial search results.

Document the Electron UI automation standard, real model and external knowledge validation, and remaining acceptance limits.

Validation: earlier full regression passed 4081 tests with 66 skipped; subsequent focused remote-question checks and real Linux evidence are recorded in feature progress. Precommit typecheck and lint passed; latest project dialog and shared UI suites passed 58 tests. The full suite was not rerun for the final dialog changes.

Release note: Use external knowledge in conversations, find active work across projects, and answer concurrent local or managed SSH OpenCode questions. Managed SSH questions require the updated Agent adapter.
2026-09-13 13:10:01 +08:00

23 KiB
Raw Permalink Blame History

Electron UI 自动化测试规范

本文规定开发期 Electron UI 自动化的执行和取证方法,适用于表单、弹窗、菜单、主题、响应式布局及其业务路径。视觉与交互预期以 UI 设计系统和功能设计为准;通用命令见开发与构建,功能实测结果写入各功能的进度文档。

1. 测试分层与选择

先写清本次改动影响什么、在哪一层验证,以及该层替换了哪些依赖。按影响选择场景,fixture 和真实服务测试提供互补证据。

层次 运行方式 能证明的范围 不能据此宣称
单元与组件回归 Vitest、Testing Library、契约及 CSS 回归 状态分支、语义和回归断言 Electron 实际排版、原生键盘默认行为或生产端到端成功
Electron 视觉 fixture 当前组件、入口样式、i18n、主题函数;真实 Chromium 与 body Portal;IPC 返回受控假数据 排版、主题、控件外观、焦点、原生交互,以及受控空状态和错误状态 持久化、真实检索、模型生成或完整 App 接线成功
生产 IPC 集成 当前 Main 服务、注册 IPC、真实 Preload、隔离数据;按需接真实服务 跨进程契约、生产存储、检索及生成路径 用隔离验证 DOM 展示事件时,不能称为完整 App 输入区或引用 UI 验收
完整 App 当前生产 Main、Preload、Renderer,隔离 profile,从真实 UI 入口完成操作 用户动作到生产实现及最终可见结果 未执行的平台、Provider、模式、重启或远程路径

纯视觉修复通常先做组件回归和 Electron 视觉矩阵,在完整 App 中复核受影响入口及 Portal 接线;无需为每种主题、尺寸或 CSS 修改重复调用模型。保存、持久化、检索、生成、引用或 IPC 接线变化,补对应生产路径。真实生成声明必须来自真实生成请求;健康检查、模型列表、HTTP 200 或 fixture 答案均不足以证明。

沿用 AGENTS.md 的验证授权:验证变更路径所必需的有界真实文本模型调用已有持续授权,不逐次询问。只运行必要的最小请求,尽量关闭无关工具与附件;按需工具检索场景保留所需工具。本文不新增产品策略或审批流程。

2. 方法来源与证据边界

2026-09-13 的外部知识库复验是本规范的方法来源。永久记录由知识库进度及脱敏复验摘要维护。下表用于说明测试方法,不表示本文编写时重新执行了测试。

原始临时证据 已观察事实 保留的限制
external-visual-0913/results.json、preview.tsx、electron.cjs、run.mjs 及 PNG 当前源码组件和 styles.css、真实 body Portal、fixture IPC;浅深主题,1280×800、960×720、720×640、640×420;56 张场景截图、138 项检查,记录中无失败断言和 errors;计算样式、溢出、焦点与底部操作区配合 PNG 审阅 额外拼图和残留 failure.png 不计入 56 张;各尺寸场景数不同,未覆盖的组合不能按笛卡尔积推定通过;未验证真实服务
kb-real-generation/report.json 真实 Electron、当前 Preload、已注册生产 IPC 和 ModelAgentRuntime;三个 Provider 的 always、Dify auto;文本和引用由生产存储保存 Renderer 是隔离验证 DOM;auto 经进程内 ModelToolProvider → KnowledgeMcpGateway,不是 HTTP MCP 会话;首轮两家比较器误判保留,解码请求和内嵌证据 JSON 后重跑通过
kb-full-app-live/report.json 当前完整生产 App,隔离 safeStorage profile;UI 创建实例与非空绑定,在实际输入区选择 Dify 并点击发送,真实短答案生成、保存并点击引用;片段及文档/分块匹配 仅 Dify、Ask、always;测试密钥在 Main 保存 IPC 边界替入;无伪造消息、引用或 Agent 事件;不证明广泛问答质量或其他 Provider 的完整 App

早期验收曾仅凭“没有横向溢出”漏掉 Portal 移到工作区外后字段样式失效的问题。修正后复验同时检查 .field 共享样式、字体、背景、密码显隐布局和实际 PNG。以后不得把 scrollWidth <= clientWidth 当作视觉通过的充分条件。

上述生产 IPC 报告实际发出模型请求 7 次、KB 请求 12 次,其中检索 9 次、目录 3 次;包含比较器修正后的两次生成重跑,Runtime 模型重试为 0。完整 App 另发出模型请求 1 次、KB 请求 5 次,其中检索 3 次、目录和详情各 1 次。本轮精确增量为模型 8 次、KB 17 次;不采用原报告中覆盖不完整的历史会话累计区间。后续报告应独立计数,不沿用这些数字作为预期或配额。

原始文件位于当时工作机批准的临时目录。当前可读取视觉脚本;完整 App 的隔离 profile 和构建已删除,其目录只保留报告和截图。它们不是仓库承诺维护的 runner,也不是新环境的依赖。没有原始文件时仍可按下节重建临时驱动,但必须记录“驱动重建”,不能声称复用了原脚本或复现了全部历史证据。

3. 当前源码与隔离启动

环境与命令

需要 BUILD.md要求的 Node.js、锁定 npm 依赖、能显示窗口的 Electron 桌面会话,以及本次专用临时目录。记录操作系统、架构、Electron 版本、语言、主题、缩放和内容区尺寸。真实凭据场景还需要当前 OS 用户可用的 safeStorage 及已授权配置。

在仓库根目录逐条执行以下准备命令;依赖已经满足时不重复安装:

node --version
npm ci
git rev-parse HEAD
git status --short
node -p "require('electron/package.json').version"

用 HEAD 和相关未提交变更摘要标识本次源码。读取当前 Electron Vite 配置和 Main 入口,确认 Renderer、Preload、动态加载资源及用户数据路径。

开发期需要完整生产产物时执行 npm run build,确认成功后再用隔离启动器加载 out/main/index.js,并记录 Main、Preload、Renderer 入口及相关资源的构建标识。不要把已安装的旧版或陈旧 out 当作当前源码。并行任务会修改 out 时,使用本次专属源码/构建副本,或在测试前后核对产物哈希;不要覆盖别人的构建。发布操作另有候选 CI 构建规则,本节不改变该流程。

临时驱动的复现步骤

仓库已有 Electron 启动脚本及对应测试,可复用其 esbuild 临时 bundle、external: ['electron']、解析仓库 Electron 可执行文件、清除 ELECTRON_RUN_AS_NODE 和退出码处理方式。node build/run-browser-tabs-electron-e2e.cjs 是现有浏览器服务测试命令,不是通用 UI 矩阵命令,也不覆盖完整 App。

  1. 在本次临时目录准备场景入口、Electron bootstrap 和独立 Node supervisor。依赖从当前仓库解析;临时目录外脚本可用 createRequire 指向仓库 package.json,不要假定临时目录能自动解析 react 或 electron。
  2. 视觉层用 Vite createServer 服务临时入口,引用当前组件、styles.css、i18n 和 applyAppearanceTheme;React 依赖指向同一仓库实例,服务仅监听 loopback 随机端口,文件允许范围限于本次目录及仓库。保留真实 Portal 容器和位置,仅替换约定的 IPC 返回值;不要另画一份“相似 UI”。
  3. Electron bootstrap 在 app.whenReady() 前设置本次 userData、sessionData 和日志目录。视觉层创建 BrowserWindow,采用 useContentSize: true,通过 setContentSize 切换矩阵;完整 App 则在设置隔离路径后动态导入当前生产 Main,由生产代码创建窗口、注册 IPC 和加载实际 Preload/Renderer。监听 browser-window-created 找到主窗口,确认最终数据路径仍在测试目录,避免便携路径规则覆盖隔离设置。
  4. supervisor 用 Node spawn 启动 Electron 子进程,在子进程环境副本中删除 ELECTRON_RUN_AS_NODE。否则 Electron 可能作为 Node 执行,窗口和 app API 不可用。完整 App 检查实际加载的入口;不要绕过主程序仅挂载 App 后称为完整生产路径。
  5. 等待页面就绪、目标可见、document.fonts.ready 和所需异步状态;所有等待都有阶段期限。通过 webContents.executeJavaScript 读取 DOM、测量布局和定位元素,通过 Electron 输入 API 驱动交互,通过 capturePage().toPNG() 保存截图。记录程序化定位焦点的步骤,完整 Tab 导航另行验证。脚本按当前控件的可访问角色操作,例如实例选择器改为菜单后,应同步更新旧版 select 驱动。
  6. 场景完成即写检查和证据索引;失败也写阶段、退出码/信号、脱敏诊断与截图。结束后由 supervisor 核对结果完整性并清理本次子进程、服务和 profile。

生产窗口保持 contextIsolation: true、sandbox: true、nodeIntegration: false 及既有 CSP、可信发送方校验和显式 Preload API。不要复制其他专用 smoke 中的 --no-sandbox,也不要为驱动增加 eval、修改生产安全标志或向 Renderer 暴露 Node。驱动本身无法启动应记录为 harness 故障。

原视觉目录仍存在时,可先在 PowerShell 校验文件可用性。以下只检查可用性,不启动测试:

$visualRun = Join-Path $env:TEMP 'opencode\external-visual-0913'
Test-Path -LiteralPath $visualRun
'run.mjs', 'electron.cjs', 'preview.tsx', 'index.html', 'results.json' | ForEach-Object {
  [pscustomobject]@{ File = $_; Exists = Test-Path -LiteralPath (Join-Path $visualRun $_) }
}

原脚本包含工作机绝对路径和固定输出文件名。先读脚本、复制到新的临时运行目录并适配源码路径、输出目录、依赖解析及当前选择器,避免覆盖历史失败诊断;随后才可 node "<本次目录>/run.mjs"。这是需要适配的命令模板,仓库没有 npm run test:ui 或一条命令重放上述全部报告的入口。

超时与进程监督

监督进程独立于 Electron 和测试执行进程,持有明确的子进程句柄、开始时间和总期限。一次工具调用的 60 秒等待窗口不等于整个测试允许时长;长任务后台执行并定期读取状态,工具等待结束后仍能取得进度和最终结果。

历史视觉脚本的 watchdog 为 115 秒,现有浏览器服务脚本为 120 秒,都只适用于各自范围。2026-09-13 的全量 npm test -- --maxWorkers=1 --reporter=verbose 实测 599,334 毫秒,约 10 分钟。新一轮应按机器和规模给出有余量的有限预算,例如 15 分钟起,并记录预算和实际耗时,不能照搬 60 秒总上限或无限等待。

区分单步等待、服务请求超时和整轮超时。定期保存进度,超时先保留最后阶段、输出尾部及可取得的截图,再终止本次进程树。普通 child.kill() 未必能回收孙进程;按 PID 和启动归属核实,只清理本次 Electron、Node、临时服务及监听,不按进程名批量结束正在使用的 GoodBuddy。信号退出、超时或报告缺失均不得记为通过。

4. 视觉与原生交互取证

受影响组件默认检查浅色和深色主题,以及 1280×800、960×720、720×640、640×420 内容区。它们对应设计系统的宽度区间及短窗口压力场景;产品另有断点、长文案或缩放要求时补充场景。记录实际 innerWidth/innerHeight 和设备缩放,不能用带窗口边框的尺寸替代。

每个场景同时保存动作结果、计算值和 PNG。检查页面、弹窗、菜单、字段及操作区的 getBoundingClientRect(),并对文档及局部滚动容器比较 scrollWidth/clientWidth、scrollHeight/clientHeight。正常纵向滚动不算失败,但裁切、无意横向溢出、按钮不可达或滚动内容被粘附区遮挡需要单独断言。

Portal 必须真的挂在生产位置。检查它是否在工作区外、主题变量是否仍可用、背景是否随主题切换;读取字段 fontFamily、fontSize、高度、边框、圆角、背景和文字色。共享 .field、字体令牌、PageTabs、SegmentedControl、Switch、Checkbox、EmptyState 和通知模式按 UI 设计系统核对,不在本规范重新定义视觉数值。

密码显隐前后比较尺寸、字体、背景及相邻按钮位置;敏感输入只能用明确标识的 fixture 文本截图。选中态在 hover 时保持可辨,禁用项不会被键盘选中。底部保存区除了位于窗口内,还要验证聚焦的字段没有落到页头或粘附底部下面、末项可滚到并操作、焦点轮廓可见。

键盘采用 webContents.sendInputEvent 的 keyDown/keyUp,文本可用 insertText。窗口和 webContents 先获得焦点;必要时为 Enter 发送 char。按控件实际语义覆盖 Tab、Shift+Tab、Space、Enter、Escape、方向键、Home/End,并验证 document.activeElement、选中值和弹窗状态。dispatchEvent(new KeyboardEvent(...)) 不触发同等 Chromium 默认行为,不能用来证明原生 Tab 顺序、Space 开关或 select 行为。

检查焦点进入弹窗、正反向循环、背景 inert、关闭后恢复触发按钮;菜单跳过禁用项、选择后回到触发器;页签的选中状态与面板一致。表单取消保留原值,错误关联正确,失败保留输入和可操作上下文;成功通知不重复显示。

PNG 必须实际打开审阅,并写明审阅方式、具体文件及发现。拼图用于找异常,小尺寸、焦点、字段和异常截图还要按原尺寸检查;确认字体、对比度、密度、层级、遮挡和控件是否像共享设计。截图文件存在或自动像素相同都不能替代审阅。只读了 JSON 时应写“PNG 未审阅”。

5. 保存、真实生成与故障

涉及保存时,从实际 UI 修改、提交、重新打开,核对生产读取值;涉及重启持久化时关闭本次 App,用同一隔离 profile 重启,再验证状态。fixture 返回保存成功只证明前端反馈;数据库有记录也不等于已经完成重启验证。空列表、无搜索结果、加载、失败、取消和重试按改动范围检查,明确故障是 fixture 注入、传输注入还是真实服务返回。

完整 App 的真实生成场景从实际输入区选择范围、提交最短必要问题,等待检索、生成及完成事件,核对持久化答案和引用;点击生成答案中的引用,检查弹窗片段、文档/分块定位与生产保存的引用匹配。需要证明依据进入模型时,在 Main 本地解码实际 Provider 请求和内嵌证据后比较,只归档布尔结果与计数。不要用原始序列化字符串匹配造成转义误判。

不得注入答案、引用、消息或 Agent 事件后宣称真实生成/引用验收通过。生产 IPC 测试可用最小验证 DOM 观察真实事件,但要如实标注 Renderer 范围。真实片段进入模型、短答案依据匹配和引用正确,只证明该次路径;不能据此推断复杂问答质量。

配置与传输

凭据只在 Main 通过既有存储读取和解密。需要复用默认模型时,将所需加密配置复制到隔离 profile,核对原配置未改变;safeStorage 依赖 OS 用户及加密元数据,解密失败应保留诊断,不改为明文或修改正式设置。历史完整 App 驱动仅复制必要的加密 os_crypt 元数据,并在 Main 保存 IPC 边界替入测试 KB 密钥,这一替换必须在方法说明中披露。

HTTPS 验证在隔离子进程显式使用严格证书校验。检查实际 fetch/undici dispatcher、证书忽略开关和监听器;仅设 NODE_TLS_REJECT_UNAUTHORIZED=1 不足以证明请求没有被其他配置绕过。历史完整 App 在隔离子进程设置 rejectUnauthorized: true 并移除测试环境的证书绕过,不修改生产源码或全局配置。

端点沿用已授权配置,不在验证中擅自升级或替换协议。历史 Dify/FastGPT 使用严格 HTTPS,RAGFlow 和默认模型使用已配置的 HTTP。报告应分别记录各服务的 http/https 及 HTTPS 校验情况;strictTls: true 不能写成“全部请求 HTTPS”。

调用计数与失败诊断

从应用实际 HTTP dispatch 边界计数,按运行、场景、阶段和 attempt 记录。区分模型请求、KB 目录、详情、绑定验证检索、聊天预检索、按需工具检索;另记输入区提交数、工具执行数、Runtime 自动重试及人工/驱动重跑。一次提交可产生多个模型请求,不能用提交数代替用量。

计数包含失败、取消前已发出的请求和重跑;启动前失败仍记 attempt,但网络数为 0。已发出但未收到响应的请求也计入,并标明结果未知。上游代理内部重试、服务商 Embedding/重排不可观察时直接写不可观察。旧轮次缺少日志时报告已知精确增量与缺口,不能把估算区间当精确总数。

失败记录保留场景、动作、预期/实际、阶段、HTTP 状态或脱敏错误码、退出信号、截图和重跑关联。区分产品故障、harness 错误、环境阻塞与超时。修正比较器或选择器后重新取证,保留首轮失败,不将其改写为通过或误称为产品修复。控制台错误、page 加载失败及进程崩溃也应纳入报告。

6. 可复用检查清单

未执行项注明 not-run、原因和影响;不适用项注明 not-applicable。重启、错误或平台场景也须有本次执行证据后再勾选。

  • 列出变更范围、所选测试层、fixture/真实边界、当前源码及构建标识。
  • 确认桌面会话、依赖、临时目录、隔离 profile、safeStorage 前提及原设置不变。
  • 从独立 supervisor 启动当前 Electron,清除 run-as-node 环境,设置阶段与整轮期限。
  • 保持生产 Main/Preload/Renderer 路径与安全标志,披露测试替换点。
  • 检查浅深主题、四组内容区尺寸,以及实际长文案和空/加载/失败状态。
  • 真实 Portal 的共享字体、字段、主题和控件计算值正确;密码显隐无布局跳变。
  • 检查文档和局部溢出、滚动、焦点轮廓、底部按钮及字段无遮挡。
  • 原生键盘完成适用流程,菜单、页签、开关、焦点循环与恢复都有动作后断言。
  • 打开 PNG 审阅,记录文件、方式、缺陷及修正后的复验。
  • 按影响验证真实保存、重新打开、重启、取消、错误恢复;fixture 不冒充持久化。
  • 需要真实生成时,从所选层的入口取得真实答案/引用证据,完整 App 场景实际点击引用。
  • 精确统计模型与 KB dispatch、失败和重跑,协议与 TLS 分开记录。
  • 源码变更完成仓库要求的 npm test、npm run typecheck、npm run lint,保留完整结束状态;仅修改文档时做文档检查即可。
  • 报告覆盖范围、未测项和失败诊断,核对测试前后源码/产物是否被并行修改。
  • 脱敏后归档摘要,清理本次进程、监听、配置和临时敏感资料,记录清理结果。

7. 脱敏报告格式

下例是未执行模板,所有测量值为 null,没有伪造通过证据。null 表示未知或未测,只有确认没有调用时才填 0。状态使用 not-run、passed、failed、blocked、not-applicable;场景及断言逐项记录,不能只有总数。

{
  "schemaVersion": 1,
  "kind": "template-not-executed",
  "runId": null,
  "source": { "commit": null, "dirtyScope": [], "artifactHashes": {} },
  "environment": { "os": null, "arch": null, "electron": null, "locale": null, "scale": null },
  "scope": { "layers": [], "fixtureIpc": null, "fullProductionApp": null, "substitutions": [], "limitations": [] },
  "execution": { "command": null, "status": "not-run", "timeoutMs": null, "elapsedMs": null, "exitCode": null, "signal": null },
  "scenarios": [
    {
      "id": "replace-with-scenario-id",
      "layer": null,
      "theme": null,
      "viewport": null,
      "attempt": null,
      "rerunOf": null,
      "actions": [],
      "checks": [{ "name": "replace-with-check", "status": "not-run", "expected": null, "actual": null }],
      "computed": {},
      "screenshots": [],
      "pngReview": { "status": "not-run", "method": null, "files": [], "findings": [] }
    }
  ],
  "requests": {
    "boundary": "application HTTP dispatches for this run only",
    "model": null,
    "kbTotal": null,
    "kbCatalog": null,
    "kbDetail": null,
    "kbRetrieval": null,
    "runtimeModelRetries": null,
    "composerSubmissions": null,
    "toolExecutions": null,
    "ledger": [],
    "unknowns": []
  },
  "transport": [{ "serviceAlias": "service-1", "protocol": null, "strictCertificateValidation": null }],
  "failures": [],
  "cleanup": { "testProcessesStopped": null, "listenersClosed": null, "isolatedProfileRemoved": null, "sensitiveArtifactsRemoved": null, "originalSettingsUnchanged": null },
  "redaction": { "status": "not-run", "omitted": ["credentials", "endpoints", "private identifiers", "business and answer prose", "content hashes"] }
}

ledger 每条记录使用匿名服务标识、场景 ID、attempt、阶段、请求种类、HTTP 方法、协议、状态/错误类别和是否重试;不含 URL、请求头或正文。failures 使用同一场景/attempt 关联诊断与重跑。计算值可保存字体、颜色、边界与溢出数,排除 textContent 等可能带出业务文本的字段。

截图、完整 DOM、请求体和本地数据库可能含敏感资料,只在本次临时目录用于核验。仓库归档不包含凭据、真实端点、私有配置/远端文档 ID、问题和答案正文、业务片段或这些内容的哈希。需要比较时在本地完成,归档布尔结果、长度或数量;源码/构建产物哈希可用于标识测试版本。可归档截图必须使用无敏感数据的 fixture 或完成脱敏审阅,路径使用相对证据名。

先保留可复查的脱敏失败诊断,再清理原始敏感资料;不要为清理而丢掉失败原因。结束时删除本次隔离配置、加密凭据副本、数据库及不需保留的临时构建,关闭测试进程和端口,核对正式设置未改。仍需本地排障的资料注明临时位置用途与待清理项,不把临时绝对路径当永久证据链接。功能进度链接最终脱敏摘要,并明确原始 harness/截图是否仍可取得。