深入· 03
开发与测试
环境、命令、浏览器预览的调试参数、CDP 探针、生成器一致性校验,以及发布流程里那几道核对。
环境
| 需要 | 版本 | 说明 |
|---|---|---|
| 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核对版本与校验值。