Files
MailUI4Agents/client/electron/test/manual/README.md
JianFeeeee 8b2206ed53 fix(electron): Phase 3 验收抓到的两个静默缺陷 —— 白屏与登录
Phase 3(写信 + 附件 + 权限面板)的验收脚本第一次跑就把这两件事翻出来了,
两个都**表现正常**:进程活着、窗口标题对、接口能通,只有结果不对。

## 1. 打包后的应用是白屏(vite 的 base 缺省值)

`vite.config.ts` 没设 `base`,Vite 按默认的 `/` 生成 `src="/assets/index-xxx.js"`。
同一份 dist 有两个宿主:网关在 `/` 下伺服它(Web 正常),Electron 用 `loadFile()`
从 **file:///…/dist/index.html** 加载它 —— 绝对路径在那儿解析成
`file:///assets/index-xxx.js`(不存在),**JS 根本没加载**。

现场:`#root` 里一个子节点都没有。没有报错对话框,控制台里只有一条不起眼的
资源加载失败。而当时所有既有检查都是绿的:`npm run build` 成功、deb 元数据检查、
asar 内容清点(**它们只看文件在不在,不看文件引用什么**)。

修法:`base: './'` —— 两边都对(Web 在 /index.html 里 `./assets/x.js` → `/assets/x.js`;
Electron 在 dist/index.html 里 → `dist/assets/x.js`)。

## 2. 桌面壳用账号密码登录是断的,而且静默失败

账号密码登录靠 `SameSite=Lax` 的会话 Cookie,而桌面壳的页面是 `file://`
(**不透明源**)—— Chromium 按第三方上下文处理它,Cookie **不予存储**。

实测现场:`POST /auth/login` 返 **200**、响应体能读出用户名,但 `document.cookie`
是空的,紧接着的 `/auth/me` 返 **401**;界面停在登录页,看起来像「密码错了」,
而同样的账号密码用 curl 登录是成功的。所以这不是凭据问题。

修法:桌面壳里**不再给账号密码表**(一个必然失败的按钮比没有更糟),改成粘贴
**用户密钥**(`Authorization: Bearer`,桌面端本来就该这么用):
- preload 显式声明 `__AGENTMAIL_SHELL__ = 'desktop'`(宿主契约,而不是让渲染层
  sniff 协议;顺带让 jsdom 里可测 —— 那里的 `location.protocol` 不可重写)
- 新增 `authStore.loginWithKey`:成功后才留下令牌,失败**还原**(否则之后每个请求
  都会带上这个坏 key 并 401,而人看到的是「重输一次也不行」)
- 顺手修了 label 与 input 没有关联(`htmlFor`/`id`)—— 无障碍缺陷,也让测试能按标签查

## 验收

- 结构性守卫进 `npm test`(`test/packaging.test.mjs`,不需要浏览器):base 必须是
  相对路径、产物里不能有绝对资源引用、**安装包里的 dist 与当前构建一致**
  (前端改了没重打包时,装上去的人看到的是旧界面,两边不一致却谁都不报错)。
  判据自检过:把 base 改回 `/` 或把产物改回绝对路径,各自都能让对应那条变红。
- 组件测试 6 条(两种壳的形态、密钥成功/失败、空密钥不可提交)。
- `test/manual/desktop-phase3-verify.mjs`:真起打包好的应用(xvfb + CDP),
  一条贯穿的链 —— 用桌面 UI 写信带附件 → 外部核验信与附件真到了网关 →
  这封信触发 zcode 的真实授权请求 → 在桌面**授权面板**里点同意 →
  外部核验 **Agent 真的执行了**(标记文件出现)。第二次跑 14/14 全绿。
- 客户端全量 222/222;网关换新产物后 Web 依旧正常(相对路径在 `/` 下同样成立,
  实测渲染出收件箱、无控制台错误),并真发一封邮件确认回信到达。

## 判据自己的错(记一笔)

第一次跑时「附件真的挂在信上」报红,而库里那 41 字节的附件**明明挂在信上** ——
我把端点写成了 `/me/mail/{id}`(不存在,404),正确是 `/mail/{id}`。
判据用错端点时以「附件是空的」现形,看起来像功能 bug。

另:`pkill -f 'agentmail-web'` 会把**自己这条命令**也杀掉(命令行里含同样的字符串),
表现是「脚本没有任何输出、退出码 143」。改用端口定位(`ss -tlnp | grep :9223`)。
2026-09-12 20:25:00 +08:00

100 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 手工浏览器实测脚本
不进 `npm test` —— 它们需要一个跑着的 Chromium 与一个活的 Gateway。
日常回归靠 `../narrow-layout.test.mjs`(读源码验形态,无外部依赖)。
## 为什么两套都要
结构性断言守住「代码写成了什么形态」,量不出「按钮实际多大、点下去命中谁」。
窄屏那轮修复里最严重的一个 bug 是抽屉式侧栏(`fixed ... z-50` 铺满视口高度)
把底部导航最左那一项盖住 —— 按钮在那里、尺寸也够、`md:hidden` 之类的规则也
没写错,**只有 `elementFromPoint` 才能发现它命中的是抽屉里的 SVG**。
## 用法
```bash
# 窄屏390pxiPhone 14 Pro+ 320pxiPhone SE
ADMIN_PW=<密码> npm run test:narrow
# 宽屏回归:窄屏修复不能把桌面改坏
ADMIN_PW=<密码> npm run test:wide
```
环境变量:
| 变量 | 默认 | 说明 |
|---|---|---|
| `ADMIN_PW` | 无(必填) | 管理员密码 |
| `ADMIN_USER` | `admin` | 登录用户名 |
| `AGENTMAIL_URL` | `https://mail.jianfgit.xyz` | 目标地址 |
| `CDP_URL` | `http://127.0.0.1:9222` | 浏览器 CDP 端点 |
| `PLAYWRIGHT` | `/usr/lib/node_modules/playwright/index.mjs` | playwright 入口 |
浏览器用的是本机 systemd 托管的共享 Chromium`homeagent-browser.service`
通过 CDP 连上去开自己的标签页,用完关掉。没有它时先
`systemctl start homeagent-browser`
## 文件
| 文件 | 作用 |
|---|---|
| `narrow-probe-helper.mjs` | 连浏览器、登录、量盒子/溢出/命中区/命中测试 |
| `narrow-verify.mjs` | 窄屏 13 项验收 |
| `wide-regression.mjs` | 宽屏 5 项回归 |
| `inbox-group-verify.mjs` | 收件箱按会话分组 |
| `theme-verify.mjs` | 深浅两色的 WCAG 对比度 |
| `accent-verify.mjs` | 强调色(红/绿/橙/黄/蓝17 组配色,两模式各一遍 |
| `desktop-phase3-verify.mjs` | **桌面客户端**:写信 + 附件 + 权限面板(见下) |
`narrow-probe-helper.mjs` 里两个函数值得单独知道:
- `tapTargets(page, labels)` —— 量 `.tap` 按钮的**真实**命中区(`::after`
伪元素的尺寸)。`.tap` 刻意不改变视觉尺寸,所以只看 `boundingBox` 会误判成偏小
- `hitTest(page, selector)` —— 每个元素点下去是否命中自己。遮挡类 bug 只能这样查
`accent-verify.mjs` 存在的理由是一次真实事故:`tailwind.config.js``colors`
里同时写了固定 hex 与 `accent()` 两份 red/green/amber/orange/yellowJS 对象
字面量重复键**后者胜出**(不报错),而 `index.css` 当时没有对应的 `--c-red-*`
变量。`rgb(var(--c-red-600) / 1)` 里变量未定义 → 整条 `background-color` 声明
失效 → `bg-red-600` 退回透明、`text-white` 的白字落在白卡片上:
**按钮看不见但点得动**。所有静态检查都过,只有肉眼能发现。
因此这个脚本量的是**实际计算值**:它把类名注入真页面、读 `getComputedStyle`
把「背景透明」单独判为失败(那正是上述 bug 的指纹),再算 WCAG 对比度。
只以 `hover:` 变体出现的档(`bg-red-700` / `bg-blue-700`**不能**放进探针:
Tailwind 不生成未被使用的基础类,探它必然得到透明背景 —— 那是假阳性。
它们由 `../theme.test.mjs` 的档位断言覆盖。
## 桌面客户端那一个(`desktop-phase3-verify.mjs`
它不连共享浏览器,而是**自己起打包好的 Electron 应用**xvfb + `--remote-debugging-port`
然后走一条贯穿全流程的链:
```bash
ADMIN_PW=<密码> DESKTOP_BIN=release/linux-unpacked/agentmail-web \
node test/manual/desktop-phase3-verify.mjs
```
1. 用桌面 UI 写一封信、**带一个附件**,发给 `zcode`
2. 外部(直接打网关 API核验信真的在、附件真的挂着 —— 界面说「已发送」不算证据
3. 这封信让 `zcode` 触发一次**真实的授权请求**(执行门禁)
4. 在桌面的**授权面板**里点「同意」
5. 外部核验:决策被记录 **且** Agent 真把命令执行了(标记文件出现)
第 5 步是这条链的重点:它证明界面上的那一下点击真的走到了 Agent 那侧。
只验界面变成「已同意」的话,一个只在本地改状态、根本没提交给网关的实现也能全绿。
第一条判据是「**应用渲染出内容了吗**」(`#root` 有子节点)—— 白屏时后面每一条都会
以奇怪的方式失败,而真正的原因只以一条资源错误出现。这个脚本第一次跑就靠它抓到了
`base` 那个白屏缺陷(见 `../packaging.test.mjs`)。
## 已知限制
headless Chromium 报告 `hover: none`,因此 `.reveal`(只在支持悬停的设备上隐藏)
在这里永远是可见的 —— 脚本只能验「触摸设备上可见」这一半,
「鼠标设备上隐藏」那一半靠 `../narrow-layout.test.mjs` 检查 CSS 规则存在。
没有像素级视觉比对:字体差异下极脆,维护成本高于收益。