深入· 03

开发与测试

环境、命令、浏览器预览的调试参数、CDP 探针、生成器一致性校验,以及发布流程里那几道核对。

适用于 v0.2.0/约 8 分钟/深入

环境

需要 版本 说明
Windows 10 1809+ / 11 应用本身只支持 Windows;Rust 代码在其它平台上也能 cargo check
Node.js ≥ 22.6 一致性校验脚本用到 Node 的类型剥离
pnpm 12.x 包管理器,package.json 里钉了 packageManager
Rust ≥ 1.77 见 Cargo.toml 的 rust-version
.NET Framework 4.8+ 只有运行 ILMerge / ConfuserEx 时需要,开发界面不需要

本项目使用 pnpm,仓库里只有 pnpm-lock.yaml。不要用 npm 或 yarn 生成锁文件 —— 两个包管理器各自的锁文件会互相不一致,而 tauri.conf.json 的 beforeDevCommand / beforeBuildCommand 也假定 pnpm。

命令

pnpm install
pnpm run app:dev              # Tauri 开发模式(Vite dev server 固定 1420 端口)
pnpm run app:build            # 产出 NSIS 安装包
pnpm run build:preview        # 类型检查 + 构建浏览器预览 → dist-preview/
pnpm run check:render-parity  # 校验两侧模板生成器逐字一致

Rust 侧:

cd src-tauri
cargo test                                  # 单元测试
cargo test -- --ignored                     # 联网测试 + 重新生成 fixture
cargo fmt                                   # 提交前跑一下(CI 会检查)
cargo clippy --all-targets -- -D warnings
  • 前端没有 lint 或 formatter。 类型检查是 tsc --noEmit,只在 build / build:preview 里跑。 tsconfig.json 开了 noUnusedLocals / noUnusedParameters,未使用的变量会让构建失败。
  • Rust 有格式与 lint 门禁:cargo fmt --check 与 cargo clippy --all-targets -- -D warnings 都是干净的,CI 会卡住。
  • 单跑一条 Rust 测试:cargo test <过滤词>,例如 cargo test defaults::。

只调试界面(不启动 Tauri)

浏览器预览用一套假的 Tauri IPC 层(preview/mock.ts)把界面跑起来,数据是写死的样例工程。

pnpm run dev          # 然后浏览器打开 http://localhost:1420/preview.html
# 或:pnpm run build:preview 后直接打开 dist-preview/preview.html

预览页支持以下查询参数,便于单独调试某一块界面:

参数 作用
?skin=swiss / ?skin=terminal 指定设计
?theme=light / ?theme=dark 指定主题(同时退出自动模式)
?material=on 让 mock 回报「窗口材质可用」,走一遍半透明 chrome 的分支
?pick=none 让文件选择框一律返回「用户取消」,用于验证取消分支
?dialog=settings 自动打开设置面板
?dotnet=missing 模拟未安装 .NET Framework,用于验证环境告警横幅

覆盖结果会先写进 localStorage,再交给 src/skin.ts 的皮肤控制器读取, 所以预览参数与界面上的切换器不会互相打架。

界面回归探针

没有前端单元测试,安全网是 scripts/ui-probe.mjs —— 用 Chrome DevTools Protocol 驱动真实浏览器, 逐项断言计算样式与 DOM 状态(不是靠截图目测)。

pnpm run build:preview
node scripts/ui-probe.mjs "file:///D:/Codespace/SealPack/dist-preview" docs/shots

探针需要一个 Chromium 系浏览器:先看 CHROME_PATH,否则依次试常见安装位置。 Edge 也能跑 —— 两者是同一个引擎、同一套 CDP 与命令行开关,实测截图逐字节一致。

它按六组断言跑完,最后打印 FAIL 列表并以退出码反映结果:

组 盯的是
A. 设计切换 自动模式实时跟随系统;手动后系统变化不再影响;每套设计各记自己的明暗;刷新后持久化
B. 原生感 标题栏高度、caption 按钮、状态栏、正文 12px、user-select: none、动效令牌、焦点环、材质两条路径
C. 交互 右键菜单键盘语义、文件树 roving tabindex、对话框 Esc 让调用方 Promise 结束、主操作在左
D. 向导 五步结构、跳步、三个热区互不干扰、搜索收敛、折叠后底栏刷新、模板重建、全部执行
E. 选项面 分组渲染、默认值标注、条件显示、预设联动、参数真的落进模板、绿色版丢快捷方式
F. 修复回归 步骤条 tab 语义、重新扫描按钮接线、toast 是 live region、writes 被消费、口令字段类型

探针踩过的坑

  • 别把毫秒写死成 167ms —— 构建产物会被 Lightning CSS 压成 .167s,用 isMs() 换算再比。
  • color-mix() 的计算值不是 rgba() 而是 color(srgb r g b / a),判断半透明要解析 alpha。
  • 树形缩进这类几何问题必须实测 getBoundingClientRect(),不要靠缩放后的截图目测。
  • press() 返回的是「要 evaluate 的表达式字符串」,不是 Promise。
  • 面板是 display: none 时元素拿不到焦点 —— 测文件树之前必须先切到那一步。
  • 条件显示用 hidden + 显式 display: none —— .opt 本身是 display: flex,只写 [hidden] 压不住。

生成器一致性

「选项 → 模板」有两个实现:Rust 的 defaults.rs 与浏览器侧的 preview/render.ts。

cd src-tauri && cargo test --lib dump_render_fixtures -- --ignored   # 用 Rust 生成 fixture
pnpm run check:render-parity                                         # 与 TS 侧逐字比对

fixture 已随仓库提交(22 个用例,覆盖默认值、每条带参数的 protection、条件参数、强名称签名、 XML 转义、分卷、绿色版、加密三态、签名、LZMA 参数、未知取值回退与官方边界值), 所以第二条命令不装 Rust 也能跑。改任何一侧的生成器后都要跑一次。

Rust 单元测试

集中在几处最容易悄悄改坏、又最难在界面上发现的地方:

  • defaults::tests —— 「选项 → 模板」的映射:参数是否写对位置(snKey 在 <module> 上)、 取值是否规范、参数是否只在合法组合下出现、不认识的值是否退回默认、[Files] 里主程序是否只出现一次。
  • model::tests —— relative_to 的语义(大小写不敏感、分隔符归一、多字节路径不 panic)、 auto_ignores / ignore_list 的相对化与去重。
  • pipeline::tests —— 日志打码必须覆盖所有已知口令参数;产出识别只认「本次新产生 / 本次被改写」的文件。
  • store::tests —— \\?\ 前缀归一、工程 Id 的路径穿越防护、工作目录清理只动陈旧目录。
  • hash::tests —— 摘要算法用公开测试向量("abc" 与空输入)钉住,另测跨 64KB 块边界不丢字节。
  • download::tests —— 解压逻辑用真实形状的压缩包覆写,外加声明自洽性。
  • tools::tests —— 三级回退与 Inno Setup 的本机探测。

另有几条默认忽略的测试(cargo test -- --ignored):3 条真实去 NuGet / GitHub / 官方源下载并解压, 确认字节数与校验值都对得上;1 条生成 fixture。它们没有进 CI —— 依赖外网与第三方站点的可用性, 让它们卡住 PR 只会制造噪声。

构建与发布

pnpm run app:build    # NSIS 安装包 → src-tauri/target/release/bundle/nsis/

发版流程:

# 1. 三处版本号一起改(release.yml 会核对,不一致直接失败):
#    package.json / src-tauri/tauri.conf.json / src-tauri/Cargo.toml
# 2. 提交、推送,然后打标签:
git tag -a v0.2.0 -m "..." && git push origin v0.2.0

推 v* 标签会自动构建并创建 GitHub Release,安装包作为附件挂上去。

tauri.conf.json 里 targets: ["nsis"]、installMode: "currentUser"。 bundle.resources 包含 resources/tools(内置的 ILMerge 与 ConfuserEx)与 resources/isl (中文语言包)—— 模板本身不随包分发,它们由 Rust 生成。

CI

Job 运行环境 内容
frontend ubuntu-latest pnpm install --frozen-lockfile → build:preview(含 tsc --noEmit)→ check:render-parity
rust windows-latest cargo fmt --check → cargo clippy --all-targets -- -D warnings → cargo test
ui-probe windows-latest 构建预览 → 跑 CDP 探针;截图作为产物保留 7 天

两个 Windows job 而不是 Linux:Windows 是唯一的目标平台。放到 ubuntu 上要额外装 webkit2gtk 之类的系统依赖,而且测的不是真实环境。

推 main 不触发 CI —— 日常提交靠本地检查清单兜底,门禁只在合入(PR)与发版(v* 标签) 两个节点把关。门禁失败不会撤回已发布的安装包,发版前请先在本地跑完检查清单。

交付前的最小检查清单

pnpm run build:preview
pnpm run check:render-parity                      # 若动过模板生成器
node scripts/ui-probe.mjs "file:///D:/Codespace/SealPack/dist-preview" docs/shots
cd src-tauri && cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test

全绿再提交。改过界面就顺带看一眼 docs/shots/ 里对应的截图,确认观感没有意外变化。

另外两类改动必须额外验证:

  • 改了打包脚本的 [Setup] 指令或语言包引用 → 用真实 ISCC 编译一次。
  • 改了内置工具目录 → 用 cargo test -- --ignored 核对版本与校验值。
搜索Esc

↑ ↓ 选择 · Enter 打开 · 中英文都能搜