test(desktop): 个性化全参数 + 退出无残留的自动化验证(verify:desktop 40 → 47 项)
新增 7 项真实断言(全部读真实窗口属性,而非界面显示): - 透明度 → win.getOpacity 实际 0.62 - 主题色 → 渲染进程 CSS 变量 --island-accent = #ff7043 - 停靠位置 → 左上 8,8 / 右上 1164,8 / 底部居中 586,800 / 顶部居中 586,8 - 置顶开关 → isAlwaysOnTop 关/开 - 动画开关 → 关闭后 60ms 内即到位(404x316),无逐帧过渡 - 动画速度 → speed=0.5 收回 435ms vs speed=2 收回 128ms - 持久化 → saveConfig/getConfig 全字段往返一致 需求 5 的'退出无残留'改为进程退出后实测: - 删除 userData 目录(SingletonLock/IndexedDB 文件锁)成功 → 无句柄残留 - 用同一配置目录二次启动成功并再次全绿 → 单实例锁与进程均已释放 验收: verify:desktop 47/47(二次启动 41/41), verify:e2e 125/125, verify:web 18/18 文档: README 客户端冒烟验证表刷新为最新实测值 + 新增退出无残留说明
This commit is contained in:
1 parent
d11f8e43a1
commit
b07ee4f8d7
3 files changed
+258
-48
No files matched your search
@@ -119,19 +119,74 @@ if (online === '1') {
|
||||
);
|
||||
}
|
||||
|
||||
const child = spawn(electronPath, ['.'], {
|
||||
cwd: root,
|
||||
stdio: 'inherit',
|
||||
env: resolveElectronEnv({
|
||||
ELECTRON_SMOKE_TEST: '1',
|
||||
ELECTRON_SMOKE_ONLINE: online,
|
||||
ELECTRON_SMOKE_PROFILE: profileDir,
|
||||
ELECTRON_SMOKE_CLASS_CODE: classCredentials?.code ?? '',
|
||||
ELECTRON_SMOKE_CLASS_PASSWORD: classCredentials?.password ?? '',
|
||||
}),
|
||||
});
|
||||
/** 启动一次冒烟(返回退出码) */
|
||||
function launchSmoke(extraEnv) {
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn(electronPath, ['.'], {
|
||||
cwd: root,
|
||||
stdio: 'inherit',
|
||||
env: resolveElectronEnv({
|
||||
ELECTRON_SMOKE_TEST: '1',
|
||||
ELECTRON_SMOKE_ONLINE: online,
|
||||
ELECTRON_SMOKE_PROFILE: profileDir,
|
||||
ELECTRON_SMOKE_CLASS_CODE: classCredentials?.code ?? '',
|
||||
ELECTRON_SMOKE_CLASS_PASSWORD: classCredentials?.password ?? '',
|
||||
...extraEnv,
|
||||
}),
|
||||
});
|
||||
child.on('exit', (code) => resolve(code ?? 1));
|
||||
});
|
||||
}
|
||||
|
||||
child.on('exit', (code) => {
|
||||
console.log(`\n[smoke] Electron 退出,code=${code ?? 1}`);
|
||||
process.exit(code ?? 1);
|
||||
});
|
||||
const code = await launchSmoke();
|
||||
console.log(`\n[smoke] Electron 退出,code=${code}`);
|
||||
if (code !== 0) process.exit(code);
|
||||
|
||||
/**
|
||||
* 需求 5 的"退出不留残留"实测(在进程真正退出之后做,才有意义):
|
||||
* 1) 配置目录(含 Chromium 的 SingletonLock / IndexedDB 文件锁)必须能被删除
|
||||
* —— 删不掉说明还有句柄被占用,即存在残留进程;
|
||||
* 2) 用同一个配置目录再启动一次:单实例锁与端口/句柄都已释放才会真正跑起来
|
||||
* (若还有残留进程占着锁,第二次启动会静默退出且不产出结果文件)。
|
||||
*/
|
||||
let lockReleased = true;
|
||||
try {
|
||||
fs.rmSync(profileDir, { recursive: true, force: true, maxRetries: 2, retryDelay: 200 });
|
||||
} catch (error) {
|
||||
lockReleased = false;
|
||||
console.error(`[smoke] [FAIL] 退出后配置文件锁未释放:${error?.message ?? error}`);
|
||||
}
|
||||
|
||||
const relaunchResult = path.join(repoRoot, '.cache', `desktop-smoke-relaunch-${process.pid}.json`);
|
||||
try {
|
||||
fs.rmSync(relaunchResult, { force: true });
|
||||
} catch {
|
||||
// 忽略
|
||||
}
|
||||
|
||||
console.log('\n[smoke] 二次启动校验(单实例锁 / 句柄是否已释放)...');
|
||||
const relaunchCode = await launchSmoke({ ELECTRON_SMOKE_ONLINE: '0', ELECTRON_SMOKE_RESULT: relaunchResult });
|
||||
|
||||
let relaunchPassed = 0;
|
||||
let relaunchTotal = 0;
|
||||
try {
|
||||
const payload = JSON.parse(fs.readFileSync(relaunchResult, 'utf8'));
|
||||
relaunchPassed = payload.passed ?? 0;
|
||||
relaunchTotal = payload.total ?? 0;
|
||||
} catch {
|
||||
relaunchPassed = -1;
|
||||
}
|
||||
|
||||
const resourcesOk =
|
||||
lockReleased && relaunchCode === 0 && relaunchTotal > 0 && relaunchPassed === relaunchTotal;
|
||||
console.log(
|
||||
resourcesOk
|
||||
? `[smoke] [PASS] 退出后无残留:配置文件锁已释放,二次启动成功(${relaunchPassed}/${relaunchTotal})`
|
||||
: `[smoke] [FAIL] 退出后可能仍有残留:文件锁释放=${lockReleased} 二次启动 code=${relaunchCode} 结果=${relaunchPassed}/${relaunchTotal}`,
|
||||
);
|
||||
try {
|
||||
fs.rmSync(profileDir, { recursive: true, force: true, maxRetries: 2, retryDelay: 200 });
|
||||
} catch {
|
||||
// 二次启动可能又建了目录,删除失败不影响结论(首次删除已证明锁已释放)
|
||||
}
|
||||
process.exit(resourcesOk ? 0 : 1);
|
||||
Reference in new issue
Block a user