diff --git a/.changeset/jolly-tables-tan.md b/.changeset/jolly-tables-tan.md new file mode 100644 index 0000000..8d42262 --- /dev/null +++ b/.changeset/jolly-tables-tan.md @@ -0,0 +1,5 @@ +--- +"@yamatomo/playwright": patch +--- + +fix: Correct subprocess termination diff --git a/packages/playwright/src/getDescendantPids.ts b/packages/playwright/src/getDescendantPids.ts new file mode 100644 index 0000000..25332d9 --- /dev/null +++ b/packages/playwright/src/getDescendantPids.ts @@ -0,0 +1,23 @@ +import { execSync } from 'node:child_process' + +export const getDescendantPids = (pid: number) => { + if (process.platform.startsWith('win')) { + throw new Error('getDescendantPids unsupported on Windows') + } + + let children: number[] = [] + try { + children = execSync(`pgrep -P ${pid}`, { encoding: 'utf8' }).trim().split('\n').map(Number) + } catch {} + + const pids: number[] = [] + + if (children.length > 0) { + children.forEach((childPid) => { + pids.push(childPid) + pids.push(...getDescendantPids(childPid)) + }) + } + + return pids +} diff --git a/packages/playwright/src/playwrightTestHelpers.ts b/packages/playwright/src/playwrightTestHelpers.ts index a0892bd..8045422 100644 --- a/packages/playwright/src/playwrightTestHelpers.ts +++ b/packages/playwright/src/playwrightTestHelpers.ts @@ -1,9 +1,12 @@ +import type { ChildProcess } from 'node:child_process' + import { test as base } from '@playwright/test' import { waitPort } from '@yamatomo/internal-utils' import { createServer } from '@yamatomo/msw-server' import { setupServer } from 'msw/node' import { type AppEnv, type MswParams, store } from './fixtureConfig' +import { getDescendantPids } from './getDescendantPids.ts' const startApp = async (baseURL: string) => { if (!store.startApp) throw new Error('startApp is not defined.') @@ -15,6 +18,17 @@ const startApp = async (baseURL: string) => { return { process, env } } +const killAppProcess = (appProcess: ChildProcess) => { + if (!appProcess.pid) throw new Error('App process PID is unknown.') + + getDescendantPids(appProcess.pid) + .reverse() + .forEach((pid) => { + process.kill(pid) + }) + appProcess.kill('SIGTERM') +} + const startMswServer = async (appEnv: AppEnv) => { if (!store.resolveMswParams) throw new Error('resolveMswParams is not defined.') @@ -37,13 +51,9 @@ const test = base.extend({ const app = await startApp(baseURL) - process.on('exit', () => { - app.process.kill('SIGINT') - }) - await use(app) - app.process.kill('SIGINT') + killAppProcess(app.process) }, { scope: 'worker' }, ],