Clean up browser test pipes and scope deliberate outage errors
This commit is contained in:
1 parent
0b0ce4fbd7
commit
cc15d003d4
4 files changed
+78
-6
No files matched your search
@@ -0,0 +1,51 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { once } from 'node:events';
|
||||
import { Client } from './client.mjs';
|
||||
|
||||
test('closing an exited browser releases stderr even when a helper inherited its pipe', { timeout: 5000 }, async () => {
|
||||
const child = spawn(process.execPath, ['-e', `
|
||||
const { spawn } = require('node:child_process');
|
||||
const helper = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 5000)'],
|
||||
{ stdio: ['ignore', 'ignore', 2], detached: true });
|
||||
helper.unref();
|
||||
process.send({ pid: helper.pid }, () => process.exit(0));
|
||||
`], { stdio: ['ignore', 'pipe', 'pipe', 'ipc'] });
|
||||
const exited = once(child, 'exit');
|
||||
let helperPid;
|
||||
try {
|
||||
[{ pid: helperPid }] = await once(child, 'message');
|
||||
child.stderr.resume();
|
||||
await exited;
|
||||
assert.equal(child.exitCode, 0);
|
||||
assert.equal(child.stderr.destroyed, false, 'the helper still holds the other end of stderr');
|
||||
const client = new Client(null, '', 'cleanup');
|
||||
client.browser = { process: () => child, close: async () => {} };
|
||||
await client.close();
|
||||
assert.equal(child.stderr.destroyed, true, 'the exited browser no longer keeps this test alive');
|
||||
} finally {
|
||||
if (helperPid) { try { process.kill(helperPid, 'SIGTERM'); } catch { /* already exited */ } }
|
||||
for (const stream of child.stdio) stream?.destroy?.();
|
||||
if (child.exitCode == null && child.signalCode == null) child.kill();
|
||||
}
|
||||
});
|
||||
|
||||
test('a failed shutdown leaves a live browser and its pipes alone', async () => {
|
||||
let destroyed = 0;
|
||||
const child = { exitCode: null, signalCode: null, stdio: [{ destroy: () => destroyed++ }] };
|
||||
const client = new Client(null, '', 'cleanup');
|
||||
client.browser = { process: () => child, close: async () => { throw new Error('not closed'); } };
|
||||
await client.close();
|
||||
assert.equal(destroyed, 0);
|
||||
});
|
||||
|
||||
test('a browser terminated by a signal releases only its owned pipes; an absent browser is harmless', async () => {
|
||||
let destroyed = 0;
|
||||
const client = new Client(null, '', 'cleanup');
|
||||
await client.close();
|
||||
client.browser = { process: () => ({ exitCode: null, signalCode: 'SIGTERM', stdio: [null, { destroy: () => destroyed++ }] }),
|
||||
close: async () => {} };
|
||||
await client.close();
|
||||
assert.equal(destroyed, 1);
|
||||
});
|
||||
+9
-1
@@ -189,7 +189,15 @@ export class Client {
|
||||
throw new Error(`${this.label}: timed out waiting for ${what} (last: ${JSON.stringify(last)})`);
|
||||
}
|
||||
|
||||
async close() { await this.browser?.close().catch(() => {}); }
|
||||
async close() {
|
||||
const child = this.browser?.process();
|
||||
await this.browser?.close().catch(() => {});
|
||||
// Chrome's crash/update helpers can inherit stderr after the launched browser exits. Release only our
|
||||
// exited child's pipes; otherwise readline keeps the test alive after all its assertions have finished.
|
||||
if (child && (child.exitCode != null || child.signalCode != null)) {
|
||||
for (const stream of child.stdio) stream?.destroy?.();
|
||||
}
|
||||
}
|
||||
|
||||
/** Real mouse click on the `nth` visible, uncovered element matching `sel` whose text includes `text`. */
|
||||
async click(sel, text = null, { timeout = 15000, optional = false, nth = 0, button = 'left', any = false } = {}) {
|
||||
|
||||
Reference in new issue
Block a user