Skip to content

Commit c98d9e4

Browse files
committed
fix: 브라우저 재시작 process 경계를 통일한다
재시작 전용 runner도 POSIX 독립 process group으로 실행하고 비 detached 호출에는 종료 fallback을 둔다. 같은 profile을 쓰는 다음 phase가 이전 Chrome의 lock에 막히지 않도록 구조 계약을 고정한다. 검증: npm test, npm run test:web-computer
1 parent 33ec5cb commit c98d9e4

3 files changed

Lines changed: 13 additions & 3 deletions

File tree

scripts/browserControl/browserLauncher.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export function killBrowserProcess(proc, profileDir = null) {
8181
try { process.kill(-proc.pid, "SIGKILL"); }
8282
catch (error) {
8383
if (error?.code !== "ESRCH") throw error;
84+
if (proc.exitCode === null) proc.kill("SIGKILL");
8485
}
8586
}
8687
if (process.platform === "win32" && profileDir) {

tests/browser/run.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,10 @@ function launch(url, phase) {
132132
console.log(`${phase === 1 ? "pyproc 브라우저 게이트" : `\n브라우저 재시작 phase ${phase}`}\n browser: ${browser}\n url: ${url}\n`);
133133
launcherExit = null;
134134
const spawnedAt = Date.now();
135-
const child = spawn(browser, [...headlessArgs(currentProfile), url], { stdio: "ignore" });
135+
const child = spawn(browser, [...headlessArgs(currentProfile), url], {
136+
stdio: "ignore",
137+
detached: process.platform !== "win32",
138+
});
136139
child.on("exit", (code, signal) => { launcherExit = { code, signal, afterMs: Date.now() - spawnedAt }; });
137140
return child;
138141
}

tests/contracts/moduleBoundaries.mjs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ function verifyReleaseVersionSurfaces(packageJson) {
112112
}
113113
}
114114

115-
function verifyBrowserLauncherCleanup(source) {
115+
function verifyBrowserLauncherCleanup(source, browserRunner) {
116116
for (const required of [
117117
"PROFILE_ABSENCE_STABILITY_MS",
118118
'process.kill(-proc.pid, "SIGKILL")',
@@ -123,6 +123,9 @@ function verifyBrowserLauncherCleanup(source) {
123123
if (!/killBrowserProcess\(proc, profile\);\s*removeBrowserProfile\(profile\);/u.test(source)) {
124124
throw new Error("browser profile을 process tree 종료보다 먼저 지운다");
125125
}
126+
if (!browserRunner.includes('detached: process.platform !== "win32"')) {
127+
throw new Error("browser restart runner가 POSIX process group 경계를 만들지 않는다");
128+
}
126129
}
127130

128131
export function assertModuleBoundaries() {
@@ -144,7 +147,10 @@ export function assertModuleBoundaries() {
144147

145148
const goldenPage = readFileSync(join(ROOT, "tests", "browser", "goldenWorkflow.html"), "utf8");
146149
const packageJson = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));
147-
verifyBrowserLauncherCleanup(readFileSync(join(ROOT, "scripts", "browserControl", "browserLauncher.mjs"), "utf8"));
150+
verifyBrowserLauncherCleanup(
151+
readFileSync(join(ROOT, "scripts", "browserControl", "browserLauncher.mjs"), "utf8"),
152+
readFileSync(join(ROOT, "tests", "browser", "run.mjs"), "utf8"),
153+
);
148154
verifyReleaseVersionSurfaces(packageJson);
149155
verifyGoldenWorkflowImports(goldenPage, packageJson);
150156
// 음성 fixture: 공개 specifier는 그대로여도 import map이 package export target에서 표류하면

0 commit comments

Comments
 (0)