diff --git a/packages/playwright-core/src/utils/utils.ts b/packages/playwright-core/src/utils/utils.ts index 85f52ac8cb..ab06bf399d 100644 --- a/packages/playwright-core/src/utils/utils.ts +++ b/packages/playwright-core/src/utils/utils.ts @@ -577,3 +577,45 @@ export async function transformCommandsForRoot(commands: string[]): Promise<{ co return { command: 'sudo', args: ['--', 'sh', '-c', `${commands.join('&& ')}`], elevatedPermissions: true }; return { command: 'su', args: ['root', '-c', `${commands.join('&& ')}`], elevatedPermissions: true }; } + +export class SigIntWatcher { + private _hadSignal: boolean = false; + private _sigintPromise: Promise; + private _sigintHandler: () => void; + constructor() { + let sigintCallback: () => void; + this._sigintPromise = new Promise(f => sigintCallback = f); + this._sigintHandler = () => { + // We remove the handler so that second Ctrl+C immediately kills the runner + // via the default sigint handler. This is handy in the case where our shutdown + // takes a lot of time or is buggy. + // + // When running through NPM we might get multiple SIGINT signals + // for a single Ctrl+C - this is an NPM bug present since at least NPM v6. + // https://github.com/npm/cli/issues/1591 + // https://github.com/npm/cli/issues/2124 + // + // Therefore, removing the handler too soon will just kill the process + // with default handler without printing the results. + // We work around this by giving NPM 1000ms to send us duplicate signals. + // The side effect is that slow shutdown or bug in our runner will force + // the user to hit Ctrl+C again after at least a second. + setTimeout(() => process.off('SIGINT', this._sigintHandler), 1000); + this._hadSignal = true; + sigintCallback(); + }; + process.on('SIGINT', this._sigintHandler); + } + + promise(): Promise { + return this._sigintPromise; + } + + hadSignal(): boolean { + return this._hadSignal; + } + + disarm() { + process.off('SIGINT', this._sigintHandler); + } +} diff --git a/packages/playwright-test/src/runner.ts b/packages/playwright-test/src/runner.ts index 335778265b..32c04ad5b8 100644 --- a/packages/playwright-test/src/runner.ts +++ b/packages/playwright-test/src/runner.ts @@ -38,6 +38,7 @@ import { Minimatch } from 'minimatch'; import { Config, FullConfig } from './types'; import { WebServer } from './webServer'; import { raceAgainstTimeout } from 'playwright-core/lib/utils/async'; +import { SigIntWatcher } from 'playwright-core/lib/utils/utils'; const removeFolderAsync = promisify(rimraf); const readDirAsync = promisify(fs.readdir); @@ -345,46 +346,24 @@ export class Runner { } (config as any).__testGroupsCount = testGroups.length; - let sigint = false; - let sigintCallback: () => void; - const sigIntPromise = new Promise(f => sigintCallback = f); - const sigintHandler = () => { - // We remove the handler so that second Ctrl+C immediately kills the runner - // via the default sigint handler. This is handy in the case where our shutdown - // takes a lot of time or is buggy. - // - // When running through NPM we might get multiple SIGINT signals - // for a single Ctrl+C - this is an NPM bug present since at least NPM v6. - // https://github.com/npm/cli/issues/1591 - // https://github.com/npm/cli/issues/2124 - // - // Therefore, removing the handler too soon will just kill the process - // with default handler without printing the results. - // We work around this by giving NPM 1000ms to send us duplicate signals. - // The side effect is that slow shutdown or bug in our runner will force - // the user to hit Ctrl+C again after at least a second. - setTimeout(() => process.off('SIGINT', sigintHandler), 1000); - sigint = true; - sigintCallback(); - }; - process.on('SIGINT', sigintHandler); + const sigintWatcher = new SigIntWatcher(); this._reporter.onBegin?.(config, rootSuite); this._didBegin = true; let hasWorkerErrors = false; if (!list) { const dispatcher = new Dispatcher(this._loader, testGroups, this._reporter); - await Promise.race([dispatcher.run(), sigIntPromise]); - if (!sigint) { + await Promise.race([dispatcher.run(), sigintWatcher.promise()]); + if (!sigintWatcher.hadSignal()) { // We know for sure there was no Ctrl+C, so we remove custom SIGINT handler // as soon as we can. - process.off('SIGINT', sigintHandler); + sigintWatcher.disarm(); } await dispatcher.stop(); hasWorkerErrors = dispatcher.hasWorkerErrors(); } - if (sigint) { + if (sigintWatcher.hadSignal()) { const result: FullResult = { status: 'interrupted' }; return result; }