playwright/tests/library/signals.spec.ts
Dmitry Gozman 9b657b54fb
fix(launcher): unregister global process handlers when all browser are closed (#29011)
Otherwise, we forever block SIGTERM and SIGHUP by registering a handler
that does not do anything (due to no browsers to close) and prevents
default handler that exits from running.

Fixes #28091.
2024-01-16 14:41:26 -08:00

145 lines
6.6 KiB
TypeScript

/**
* Copyright 2019 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { playwrightTest as test, expect } from '../config/browserTest';
import { execSync } from 'child_process';
import fs from 'fs';
import os from 'os';
test.slow();
test('should close the browser when the node process closes', async ({ startRemoteServer, isWindows, server }) => {
const remoteServer = await startRemoteServer('launchServer', { url: server.EMPTY_PAGE });
try {
if (isWindows)
execSync(`taskkill /pid ${remoteServer.child().pid} /T /F`, { stdio: 'ignore' });
else
process.kill(remoteServer.child().pid);
} catch (error) {
console.log(error);
if (error.stdout)
console.log('--- stdout ---\n', error.stdout);
if (error.stderr)
console.log('--- stderr ---\n', error.stderr);
throw error;
}
// We might not get browser exitCode in time when killing the parent node process,
// so we don't check it here.
expect(await remoteServer.childExitCode()).toBe(isWindows ? 1 : 0);
});
test('should remove temp dir on process.exit', async ({ startRemoteServer, server, platform }, testInfo) => {
test.skip(platform === 'win32', 'Removing user data dir synchronously is blocked on Windows');
const file = testInfo.outputPath('exit.file');
const remoteServer = await startRemoteServer('launchServer', { url: server.EMPTY_PAGE, exitOnFile: file });
const tempDir = await remoteServer.out('tempDir');
const before = fs.existsSync(tempDir);
fs.writeFileSync(file, 'data', 'utf-8');
expect(await remoteServer.childExitCode()).toBe(42);
const after = fs.existsSync(tempDir);
expect(before).toBe(true);
expect(after).toBe(false);
});
test.describe('signals', () => {
test.skip(({ platform }) => platform === 'win32');
test('should report browser close signal 2', async ({ startRemoteServer, server, isMac, browserName }) => {
const remoteServer = await startRemoteServer('launchServer', { url: server.EMPTY_PAGE });
const pid = await remoteServer.out('pid');
process.kill(-pid, 'SIGKILL');
if (isMac && browserName === 'webkit' && parseInt(os.release(), 10) > 22 && os.arch() === 'arm64') {
// WebKit on newer macOS exits differently.
expect(await remoteServer.out('exitCode')).toBe('137');
expect(await remoteServer.out('signal')).toBe('null');
} else {
expect(await remoteServer.out('exitCode')).toBe('null');
expect(await remoteServer.out('signal')).toBe('SIGKILL');
}
process.kill(remoteServer.child().pid);
await remoteServer.childExitCode();
});
test('should close the browser on SIGINT', async ({ startRemoteServer, server }) => {
const remoteServer = await startRemoteServer('launchServer', { url: server.EMPTY_PAGE });
process.kill(remoteServer.child().pid, 'SIGINT');
expect(await remoteServer.out('exitCode')).toBe('0');
expect(await remoteServer.out('signal')).toBe('null');
expect(await remoteServer.childExitCode()).toBe(130);
});
test('should close the browser on SIGTERM', async ({ startRemoteServer, server }) => {
const remoteServer = await startRemoteServer('launchServer', { url: server.EMPTY_PAGE });
process.kill(remoteServer.child().pid, 'SIGTERM');
expect(await remoteServer.out('exitCode')).toBe('0');
expect(await remoteServer.out('signal')).toBe('null');
expect(await remoteServer.childExitCode()).toBe(0);
});
test('should close the browser on SIGHUP', async ({ startRemoteServer, server }) => {
const remoteServer = await startRemoteServer('launchServer', { url: server.EMPTY_PAGE });
process.kill(remoteServer.child().pid, 'SIGHUP');
expect(await remoteServer.out('exitCode')).toBe('0');
expect(await remoteServer.out('signal')).toBe('null');
expect(await remoteServer.childExitCode()).toBe(0);
});
test('should kill the browser on double SIGINT and remove temp dir', async ({ startRemoteServer, server }) => {
const remoteServer = await startRemoteServer('launchServer', { stallOnClose: true, url: server.EMPTY_PAGE });
const tempDir = await remoteServer.out('tempDir');
const before = fs.existsSync(tempDir);
process.kill(remoteServer.child().pid, 'SIGINT');
await remoteServer.out('stalled');
process.kill(remoteServer.child().pid, 'SIGINT');
expect(await remoteServer.out('exitCode')).toBe('null');
expect(await remoteServer.out('signal')).toBe('SIGKILL');
expect(await remoteServer.childExitCode()).toBe(130);
const after = fs.existsSync(tempDir);
expect(before).toBe(true);
expect(after).toBe(false);
});
test('should kill the browser on SIGINT + SIGTERM', async ({ startRemoteServer, server }) => {
const remoteServer = await startRemoteServer('launchServer', { stallOnClose: true, url: server.EMPTY_PAGE });
process.kill(remoteServer.child().pid, 'SIGINT');
await remoteServer.out('stalled');
process.kill(remoteServer.child().pid, 'SIGTERM');
expect(await remoteServer.out('exitCode')).toBe('null');
expect(await remoteServer.out('signal')).toBe('SIGKILL');
expect(await remoteServer.childExitCode()).toBe(0);
});
test('should kill the browser on SIGTERM + SIGINT', async ({ startRemoteServer, server }) => {
const remoteServer = await startRemoteServer('launchServer', { stallOnClose: true, url: server.EMPTY_PAGE });
process.kill(remoteServer.child().pid, 'SIGTERM');
await remoteServer.out('stalled');
process.kill(remoteServer.child().pid, 'SIGINT');
expect(await remoteServer.out('exitCode')).toBe('null');
expect(await remoteServer.out('signal')).toBe('SIGKILL');
expect(await remoteServer.childExitCode()).toBe(130);
});
test('should not prevent default SIGTERM handling after browser close', async ({ startRemoteServer, server, platform }, testInfo) => {
const remoteServer = await startRemoteServer('launchServer', { startStopAndRunHttp: true });
expect(await remoteServer.out('closed')).toBe('success');
process.kill(remoteServer.child().pid, 'SIGTERM');
expect(await remoteServer.childExitCode()).toBe(null);
expect(await remoteServer.childSignal()).toBe('SIGTERM');
});
});