@monoes/monobrowse 1.0.19 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/src/__tests__/browse-concurrency.test.d.ts +15 -0
  2. package/dist/src/__tests__/browse-concurrency.test.d.ts.map +1 -0
  3. package/dist/src/__tests__/browse-concurrency.test.js +198 -0
  4. package/dist/src/__tests__/browse-concurrency.test.js.map +1 -0
  5. package/dist/src/__tests__/cli-version.test.d.ts +20 -0
  6. package/dist/src/__tests__/cli-version.test.d.ts.map +1 -0
  7. package/dist/src/__tests__/cli-version.test.js +45 -0
  8. package/dist/src/__tests__/cli-version.test.js.map +1 -0
  9. package/dist/src/__tests__/close-browser.test.js +2 -2
  10. package/dist/src/__tests__/close-browser.test.js.map +1 -1
  11. package/dist/src/__tests__/frame-command.test.js +12 -2
  12. package/dist/src/__tests__/frame-command.test.js.map +1 -1
  13. package/dist/src/__tests__/reap-idle-browser.test.js +13 -13
  14. package/dist/src/__tests__/reap-idle-browser.test.js.map +1 -1
  15. package/dist/src/__tests__/ref-cache.test.js +96 -58
  16. package/dist/src/__tests__/ref-cache.test.js.map +1 -1
  17. package/dist/src/__tests__/report-exit-code.test.js +1 -1
  18. package/dist/src/__tests__/session-discovery.test.d.ts +13 -0
  19. package/dist/src/__tests__/session-discovery.test.d.ts.map +1 -0
  20. package/dist/src/__tests__/session-discovery.test.js +218 -0
  21. package/dist/src/__tests__/session-discovery.test.js.map +1 -0
  22. package/dist/src/browser/browser.d.ts +2 -1
  23. package/dist/src/browser/browser.d.ts.map +1 -1
  24. package/dist/src/browser/browser.js +17 -9
  25. package/dist/src/browser/browser.js.map +1 -1
  26. package/dist/src/browser/ref-cache.d.ts +50 -22
  27. package/dist/src/browser/ref-cache.d.ts.map +1 -1
  28. package/dist/src/browser/ref-cache.js +122 -57
  29. package/dist/src/browser/ref-cache.js.map +1 -1
  30. package/dist/src/cli/commands-context.js +1 -1
  31. package/dist/src/cli/commands-context.js.map +1 -1
  32. package/dist/src/cli/commands-navigate.js +2 -2
  33. package/dist/src/cli/commands-navigate.js.map +1 -1
  34. package/dist/src/cli/commands-page.js +2 -2
  35. package/dist/src/cli/commands-page.js.map +1 -1
  36. package/dist/src/cli/commands-report.js +2 -2
  37. package/dist/src/cli/commands-report.js.map +1 -1
  38. package/dist/src/cli/commands-session.d.ts.map +1 -1
  39. package/dist/src/cli/commands-session.js +70 -56
  40. package/dist/src/cli/commands-session.js.map +1 -1
  41. package/dist/src/cli/commands.d.ts.map +1 -1
  42. package/dist/src/cli/commands.js +102 -66
  43. package/dist/src/cli/commands.js.map +1 -1
  44. package/dist/src/cli/session.d.ts +66 -2
  45. package/dist/src/cli/session.d.ts.map +1 -1
  46. package/dist/src/cli/session.js +190 -33
  47. package/dist/src/cli/session.js.map +1 -1
  48. package/dist/src/cli.js +3 -1
  49. package/dist/src/cli.js.map +1 -1
  50. package/dist/tsconfig.tsbuildinfo +1 -1
  51. package/package.json +1 -1
  52. package/src/__tests__/browse-concurrency.test.ts +233 -0
  53. package/src/__tests__/cli-version.test.ts +50 -0
  54. package/src/__tests__/close-browser.test.ts +2 -2
  55. package/src/__tests__/frame-command.test.ts +12 -2
  56. package/src/__tests__/reap-idle-browser.test.ts +15 -14
  57. package/src/__tests__/ref-cache.test.ts +112 -59
  58. package/src/__tests__/report-exit-code.test.ts +1 -1
  59. package/src/__tests__/session-discovery.test.ts +259 -0
  60. package/src/browser/browser.ts +23 -9
  61. package/src/browser/ref-cache.ts +144 -72
  62. package/src/cli/commands-context.ts +1 -1
  63. package/src/cli/commands-navigate.ts +2 -2
  64. package/src/cli/commands-page.ts +2 -2
  65. package/src/cli/commands-report.ts +2 -2
  66. package/src/cli/commands-session.ts +74 -55
  67. package/src/cli/commands.ts +106 -66
  68. package/src/cli/session.ts +208 -33
  69. package/src/cli.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monoes/monobrowse",
3
- "version": "1.0.19",
3
+ "version": "1.0.22",
4
4
  "description": "Native browser automation via Chrome DevTools Protocol — the engine powering monomind browse",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,233 @@
1
+ /**
2
+ * #318: two uncoordinated `browse open` invocations with no `--port` raced on
3
+ * the shared default CDP port (9222) and the userDataDir derived from it —
4
+ * one failed outright ("Chrome exited before the CDP endpoint opened on port
5
+ * 9222"), or both attached to ONE Chrome and a single `browse close` killed
6
+ * the other caller's browser too.
7
+ *
8
+ * This is a real-process test on purpose: the bug lives in the interaction
9
+ * between two separate CLI processes and the session state they share on
10
+ * disk, which no in-process unit test can reproduce. It spawns the built CLI
11
+ * (dist/src/cli.js) against a file:// fixture page, so it is skipped when the
12
+ * package has not been built or no Chrome/Chromium is installed.
13
+ */
14
+
15
+ import { execFile, execFileSync, spawn } from 'node:child_process';
16
+ import { existsSync } from 'node:fs';
17
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
18
+ import { tmpdir } from 'node:os';
19
+ import { dirname, join } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { afterEach, describe, expect, it } from 'vitest';
22
+ import { CHROME_EXECUTABLES } from '../browser/types.js';
23
+
24
+ const here = dirname(fileURLToPath(import.meta.url));
25
+ const CLI = join(here, '..', '..', 'dist', 'src', 'cli.js');
26
+
27
+ function chromeAvailable(): boolean {
28
+ if (CHROME_EXECUTABLES.some((p) => existsSync(p))) return true;
29
+ try {
30
+ return (
31
+ execFileSync('which', ['google-chrome', 'chromium-browser', 'chromium'], {
32
+ encoding: 'utf8',
33
+ stdio: ['ignore', 'pipe', 'ignore'],
34
+ }).trim().length > 0
35
+ );
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ const runnable = existsSync(CLI) && chromeAvailable();
42
+
43
+ interface Run {
44
+ code: number | null;
45
+ stdout: string;
46
+ stderr: string;
47
+ }
48
+
49
+ function runCli(args: string[], cwd: string, profileRoot: string): Promise<Run> {
50
+ return new Promise((resolve) => {
51
+ const child = spawn(process.execPath, [CLI, ...args], {
52
+ cwd,
53
+ // Per-trial TMPDIR: every browser profile this invocation creates lands
54
+ // under it, so the sweep in afterEach can identify (and only kill) the
55
+ // Chrome processes this test started.
56
+ env: { ...process.env, TMPDIR: profileRoot, CI: '1' },
57
+ stdio: ['ignore', 'pipe', 'pipe'],
58
+ });
59
+ let stdout = '';
60
+ let stderr = '';
61
+ child.stdout.on('data', (d) => {
62
+ stdout += d;
63
+ });
64
+ child.stderr.on('data', (d) => {
65
+ stderr += d;
66
+ });
67
+ child.on('close', (code) => resolve({ code, stdout, stderr }));
68
+ });
69
+ }
70
+
71
+ /** The CDP port `open` reports for the session it started. */
72
+ function reportedPort(run: Run): number | null {
73
+ const m = /\[port (\d+)\]/.exec(run.stdout);
74
+ return m ? Number(m[1]) : null;
75
+ }
76
+
77
+ async function cdpAlive(port: number): Promise<boolean> {
78
+ try {
79
+ const res = await fetch(`http://127.0.0.1:${port}/json/version`, {
80
+ signal: AbortSignal.timeout(1500),
81
+ });
82
+ return res.ok;
83
+ } catch {
84
+ return false;
85
+ }
86
+ }
87
+
88
+ const dirs: string[] = [];
89
+
90
+ afterEach(async () => {
91
+ for (const dir of dirs.splice(0)) {
92
+ // Kill anything still running out of this trial's profile root before the
93
+ // directory goes away — a leaked Chrome would otherwise outlive the run.
94
+ await new Promise<void>((resolve) => {
95
+ execFile('pkill', ['-f', dir], () => resolve());
96
+ });
97
+ // A killed Chrome keeps writing out its profile for a moment; retry the
98
+ // removal instead of failing the test on ENOTEMPTY.
99
+ await rm(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 });
100
+ }
101
+ });
102
+
103
+ describe.skipIf(!runnable)('#318 concurrent browse sessions (real processes)', () => {
104
+ for (const trial of [1, 2, 3, 4, 5]) {
105
+ it(`trial ${trial}: two bare \`open\` calls each get their own browser, and each close kills only its own`, async () => {
106
+ const root = await mkdtemp(join(process.env.TMPDIR ?? tmpdir(), 'monobrowse-318-'));
107
+ dirs.push(root);
108
+ const page = join(root, 'fixture.html');
109
+ await writeFile(page, '<!doctype html><title>Fixture</title><h1>hello</h1>');
110
+ const url = `file://${page}`;
111
+
112
+ // The reported race: two uncoordinated invocations, same cwd, no --port.
113
+ const [a, b] = await Promise.all([
114
+ runCli(['open', url], root, root),
115
+ runCli(['open', url], root, root),
116
+ ]);
117
+
118
+ expect(a.code, `open A failed: ${a.stdout}${a.stderr}`).toBe(0);
119
+ expect(b.code, `open B failed: ${b.stdout}${b.stderr}`).toBe(0);
120
+
121
+ const portA = reportedPort(a);
122
+ const portB = reportedPort(b);
123
+ expect(portA, `open A did not report a session port: ${a.stdout}`).not.toBeNull();
124
+ expect(portB, `open B did not report a session port: ${b.stdout}`).not.toBeNull();
125
+ expect(portA).not.toBe(portB);
126
+
127
+ expect(await cdpAlive(portA!)).toBe(true);
128
+ expect(await cdpAlive(portB!)).toBe(true);
129
+
130
+ // Closing A must leave B's browser running.
131
+ const closeA = await runCli(['close', '--port', String(portA)], root, root);
132
+ expect(closeA.code, `close A failed: ${closeA.stdout}${closeA.stderr}`).toBe(0);
133
+ expect(await cdpAlive(portA!)).toBe(false);
134
+ expect(await cdpAlive(portB!)).toBe(true);
135
+
136
+ const closeB = await runCli(['close', '--port', String(portB)], root, root);
137
+ expect(closeB.code, `close B failed: ${closeB.stdout}${closeB.stderr}`).toBe(0);
138
+ expect(await cdpAlive(portB!)).toBe(false);
139
+ }, 90_000);
140
+ }
141
+ });
142
+
143
+ /**
144
+ * Upgrading monobrowse while a session was open used to strand its Chrome:
145
+ * the handle lived in `active-port.json`, which the per-port store replaced.
146
+ * The new CLI adopts that record instead — the same real-process shape as
147
+ * above, since the whole point is what a *later* process can still do.
148
+ */
149
+ describe.skipIf(!runnable)('#318 adoption of a pre-#318 session (real processes)', () => {
150
+ /** Open a session, then rewrite its state as a pre-#318 install left it. */
151
+ async function openThenDowngradeState(root: string, url: string): Promise<number> {
152
+ const opened = await runCli(['open', url], root, root);
153
+ expect(opened.code, `open failed: ${opened.stdout}${opened.stderr}`).toBe(0);
154
+ const port = reportedPort(opened);
155
+ expect(port).not.toBeNull();
156
+
157
+ const stateDir = join(root, '.monomind', 'monobrowse');
158
+ const record = await readFile(join(stateDir, 'sessions', `${port}.json`), 'utf8');
159
+ await writeFile(join(stateDir, 'active-port.json'), record);
160
+ await rm(join(stateDir, 'sessions'), { recursive: true, force: true });
161
+ return port!;
162
+ }
163
+
164
+ it('a later command adopts the legacy session, and close then kills that browser', async () => {
165
+ const root = await mkdtemp(join(process.env.TMPDIR ?? tmpdir(), 'monobrowse-318-legacy-'));
166
+ dirs.push(root);
167
+ const page = join(root, 'fixture.html');
168
+ await writeFile(page, '<!doctype html><title>Fixture</title><h1>hello</h1>');
169
+ const port = await openThenDowngradeState(root, `file://${page}`);
170
+
171
+ // A later, separate process with no --port finds it.
172
+ const title = await runCli(['get', 'title'], root, root);
173
+ expect(title.code, `get title failed: ${title.stdout}${title.stderr}`).toBe(0);
174
+ expect(title.stdout).toContain('Fixture');
175
+
176
+ // Adopted: it is an ordinary per-port record now, and the old file is gone.
177
+ const stateDir = join(root, '.monomind', 'monobrowse');
178
+ expect(existsSync(join(stateDir, 'sessions', `${port}.json`))).toBe(true);
179
+ expect(existsSync(join(stateDir, 'active-port.json'))).toBe(false);
180
+
181
+ // And `--port` works on it exactly like a native session.
182
+ const closed = await runCli(['close', '--port', String(port)], root, root);
183
+ expect(closed.code, `close failed: ${closed.stdout}${closed.stderr}`).toBe(0);
184
+ expect(await cdpAlive(port)).toBe(false);
185
+ }, 90_000);
186
+
187
+ it('a legacy record whose browser is gone is cleaned up, and the command still works', async () => {
188
+ const root = await mkdtemp(join(process.env.TMPDIR ?? tmpdir(), 'monobrowse-318-legacy-'));
189
+ dirs.push(root);
190
+ const stateDir = join(root, '.monomind', 'monobrowse');
191
+ await mkdir(stateDir, { recursive: true });
192
+ // A port nothing is listening on — the state an upgrade finds after the
193
+ // old browser died (or was killed) with its record left behind.
194
+ await writeFile(
195
+ join(stateDir, 'active-port.json'),
196
+ JSON.stringify({ port: 23499, launched: true, pid: 999999, savedAt: Date.now() }),
197
+ );
198
+
199
+ const closed = await runCli(['close'], root, root);
200
+ expect(closed.code, `close failed: ${closed.stdout}${closed.stderr}`).toBe(0);
201
+ expect(closed.stdout).toContain('No active browser session');
202
+ expect(existsSync(join(stateDir, 'active-port.json'))).toBe(false);
203
+ }, 60_000);
204
+
205
+ it('adoption does not change bare `open`: it still starts a browser of its own', async () => {
206
+ const root = await mkdtemp(join(process.env.TMPDIR ?? tmpdir(), 'monobrowse-318-legacy-'));
207
+ dirs.push(root);
208
+ const page = join(root, 'fixture.html');
209
+ await writeFile(page, '<!doctype html><title>Fixture</title><h1>hello</h1>');
210
+ const url = `file://${page}`;
211
+ const legacyPort = await openThenDowngradeState(root, url);
212
+
213
+ const fresh = await runCli(['open', url], root, root);
214
+ expect(fresh.code, `open failed: ${fresh.stdout}${fresh.stderr}`).toBe(0);
215
+ const freshPort = reportedPort(fresh);
216
+ expect(freshPort).not.toBe(legacyPort);
217
+ // Neither joined nor disturbed: both browsers are up, and the legacy
218
+ // record is still waiting to be adopted by whoever needs it.
219
+ expect(await cdpAlive(legacyPort)).toBe(true);
220
+ expect(await cdpAlive(freshPort!)).toBe(true);
221
+ expect(existsSync(join(root, '.monomind', 'monobrowse', 'active-port.json'))).toBe(true);
222
+
223
+ // Closing the new session leaves the legacy browser alone; a second
224
+ // close then adopts and ends it.
225
+ const closeFresh = await runCli(['close', '--port', String(freshPort)], root, root);
226
+ expect(closeFresh.code).toBe(0);
227
+ expect(await cdpAlive(legacyPort)).toBe(true);
228
+
229
+ const closeLegacy = await runCli(['close'], root, root);
230
+ expect(closeLegacy.code, `close failed: ${closeLegacy.stdout}${closeLegacy.stderr}`).toBe(0);
231
+ expect(await cdpAlive(legacyPort)).toBe(false);
232
+ }, 90_000);
233
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * `monobrowse --version` crashed instead of printing a version. (#320)
3
+ *
4
+ * `src/cli.ts` read the manifest with `_require('../package.json')`. That path
5
+ * is right for the SOURCE file — `src/cli.ts` sits one level under the package
6
+ * root — and wrong for the only file that ever runs it. The package compiles
7
+ * with `rootDir: "."`, so `src/cli.ts` emits to `dist/src/cli.js`, and
8
+ * `createRequire(import.meta.url)` resolves relative to THAT file: `../` is
9
+ * `dist/`, which holds no package.json. Every invocation of the standalone
10
+ * binary died with `Cannot find module '../package.json'`, in the repo and
11
+ * from a clean install alike.
12
+ *
13
+ * WHY THIS IS A SUBPROCESS TEST. The bug lives entirely in the emitted file's
14
+ * position on disk. Importing anything from `src/` re-resolves the path from
15
+ * the source tree, where the broken version was already correct — an in-process
16
+ * test would have passed against the bug. Only spawning the built `dist/src/
17
+ * cli.js`, the exact file `bin.monobrowse` points at, can observe it.
18
+ */
19
+
20
+ import { execFileSync } from 'node:child_process';
21
+ import { existsSync, readFileSync } from 'node:fs';
22
+ import { dirname, join } from 'node:path';
23
+ import { fileURLToPath } from 'node:url';
24
+ import { describe, expect, it } from 'vitest';
25
+
26
+ const here = dirname(fileURLToPath(import.meta.url));
27
+ const pkgRoot = join(here, '..', '..');
28
+ const distCli = join(pkgRoot, 'dist', 'src', 'cli.js');
29
+ const hasBuild = existsSync(distCli);
30
+
31
+ describe.skipIf(!hasBuild)('the built monobrowse binary reports its version', () => {
32
+ const declared = JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8'))
33
+ .version as string;
34
+
35
+ for (const flag of ['--version', '-V']) {
36
+ it(`\`monobrowse ${flag}\` exits 0 and prints the package version`, () => {
37
+ // THE BUG: this threw — exit 1, `✗ Cannot find module '../package.json'`.
38
+ const out = execFileSync('node', [distCli, flag], { encoding: 'utf8', stdio: 'pipe' });
39
+
40
+ expect(out.trim()).toBe(declared);
41
+ expect(out.trim()).toMatch(/^\d+\.\d+\.\d+/);
42
+ }, 15_000);
43
+ }
44
+ });
45
+
46
+ describe.skipIf(hasBuild)('monobrowse --version subprocess test', () => {
47
+ it('is skipped because dist/ is not built (run `npm run build`)', () => {
48
+ expect(hasBuild).toBe(false);
49
+ });
50
+ });
@@ -72,10 +72,10 @@ afterEach(async () => {
72
72
  });
73
73
 
74
74
  async function writePersistedPort(port: number, pid: number, savedAt: number): Promise<void> {
75
- const dir = join(tempDir, '.monomind', 'monobrowse');
75
+ const dir = join(tempDir, '.monomind', 'monobrowse', 'sessions');
76
76
  await mkdir(dir, { recursive: true });
77
77
  await writeFile(
78
- join(dir, 'active-port.json'),
78
+ join(dir, `${port}.json`),
79
79
  JSON.stringify({ port, pid, launched: true, savedAt }),
80
80
  'utf-8',
81
81
  );
@@ -20,7 +20,15 @@ const mocks = vi.hoisted(() => {
20
20
  sessionId: 'S-PAGE',
21
21
  target: { id: 'T-PAGE', url: 'https://x.test' },
22
22
  })),
23
- loadActivePortInfo: vi.fn(async () => null),
23
+ listSessionRecords: vi.fn(async () => []),
24
+ loadSessionRecord: vi.fn(async () => null),
25
+ loadLegacySessionRecord: vi.fn(async () => null),
26
+ removeLegacySessionRecord: vi.fn(async () => {}),
27
+ saveSessionRecord: vi.fn(async () => {}),
28
+ removeSessionRecord: vi.fn(async () => {}),
29
+ clearRefCache: vi.fn(async () => {}),
30
+ getLaunchedPid: vi.fn(() => undefined),
31
+ getLaunchedUserDataDir: vi.fn(() => undefined),
24
32
  loadRefCache: vi.fn(async () => null),
25
33
  switchToFrame: vi.fn(
26
34
  async (): Promise<{ url: string | null; sessionId: string | null }> => ({
@@ -55,7 +63,9 @@ const ctx = (args: string[]): CommandContext => ({
55
63
 
56
64
  beforeEach(() => {
57
65
  vi.clearAllMocks();
58
- mocks.browser.loadActivePortInfo.mockResolvedValue(null);
66
+ mocks.browser.listSessionRecords.mockResolvedValue([]);
67
+ mocks.browser.loadSessionRecord.mockResolvedValue(null);
68
+ mocks.browser.loadLegacySessionRecord.mockResolvedValue(null);
59
69
  mocks.browser.loadRefCache.mockResolvedValue(null);
60
70
  mocks.browser.switchToFrame.mockResolvedValue({
61
71
  url: 'https://f.test/frame',
@@ -108,20 +108,22 @@ function fakeChrome(port: number, targets: Partial<CdpTarget>[]): Promise<void>
108
108
  });
109
109
  }
110
110
 
111
- const PORT_FILE = () => join(tempDir, '.monomind', 'monobrowse', 'active-port.json');
111
+ // Sessions are recorded one file per port (#318).
112
+ const PORT_FILE = (port: number) =>
113
+ join(tempDir, '.monomind', 'monobrowse', 'sessions', `${port}.json`);
112
114
 
113
115
  async function writePersistedPort(record: {
114
116
  port: number;
115
117
  pid?: number;
116
118
  launched: boolean;
117
- savedAt: number;
119
+ savedAt?: number;
118
120
  }): Promise<void> {
119
- await mkdir(join(tempDir, '.monomind', 'monobrowse'), { recursive: true });
120
- await writeFile(PORT_FILE(), JSON.stringify(record), 'utf-8');
121
+ await mkdir(join(tempDir, '.monomind', 'monobrowse', 'sessions'), { recursive: true });
122
+ await writeFile(PORT_FILE(record.port), JSON.stringify(record), 'utf-8');
121
123
  }
122
124
 
123
- async function portFileExists(): Promise<boolean> {
124
- return readFile(PORT_FILE(), 'utf-8').then(
125
+ async function portFileExists(port: number): Promise<boolean> {
126
+ return readFile(PORT_FILE(port), 'utf-8').then(
125
127
  () => true,
126
128
  () => false,
127
129
  );
@@ -161,7 +163,7 @@ describe('#310 reapIdleLaunchedBrowser — give back a CDP port nothing is using
161
163
  expect(lastSocket?.methods()).toContain('Browser.close');
162
164
  // The port file is the session handle — a reaped session must not leave
163
165
  // one behind for the next command to chase.
164
- expect(await portFileExists()).toBe(false);
166
+ expect(await portFileExists(port)).toBe(false);
165
167
  });
166
168
 
167
169
  it('does NOT reap a launched instance that still has open page targets', async () => {
@@ -179,7 +181,7 @@ describe('#310 reapIdleLaunchedBrowser — give back a CDP port nothing is using
179
181
 
180
182
  expect(lastSocket).toBeNull();
181
183
  expect(killSpy).not.toHaveBeenCalled();
182
- expect(await portFileExists()).toBe(true);
184
+ expect(await portFileExists(port)).toBe(true);
183
185
  });
184
186
 
185
187
  it('does NOT reap a browser this tool never launched, however idle or old it is', async () => {
@@ -198,7 +200,7 @@ describe('#310 reapIdleLaunchedBrowser — give back a CDP port nothing is using
198
200
 
199
201
  expect(lastSocket).toBeNull();
200
202
  expect(killSpy).not.toHaveBeenCalled();
201
- expect(await portFileExists()).toBe(true);
203
+ expect(await portFileExists(port)).toBe(true);
202
204
  });
203
205
 
204
206
  it('does NOT reap while a client is still attached to a target', async () => {
@@ -219,7 +221,7 @@ describe('#310 reapIdleLaunchedBrowser — give back a CDP port nothing is using
219
221
  await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
220
222
 
221
223
  expect(lastSocket).toBeNull();
222
- expect(await portFileExists()).toBe(true);
224
+ expect(await portFileExists(port)).toBe(true);
223
225
  });
224
226
 
225
227
  it('does NOT reap an instance that is still inside the idle threshold', async () => {
@@ -233,14 +235,13 @@ describe('#310 reapIdleLaunchedBrowser — give back a CDP port nothing is using
233
235
  await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
234
236
 
235
237
  expect(lastSocket).toBeNull();
236
- expect(await portFileExists()).toBe(true);
238
+ expect(await portFileExists(port)).toBe(true);
237
239
  });
238
240
 
239
241
  it('does NOT reap when the persisted record has no savedAt (age unknowable)', async () => {
240
242
  const port = BASE + 5;
241
243
  await fakeChrome(port, []);
242
- await mkdir(join(tempDir, '.monomind', 'monobrowse'), { recursive: true });
243
- await writeFile(PORT_FILE(), JSON.stringify({ port, pid: 54326, launched: true }), 'utf-8');
244
+ await writePersistedPort({ port, pid: 54326, launched: true });
244
245
 
245
246
  const { reapIdleLaunchedBrowser } = await loadBrowser();
246
247
  await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
@@ -271,6 +272,6 @@ describe('#310 reapIdleLaunchedBrowser — give back a CDP port nothing is using
271
272
  await expect(launchBrowser({ port })).resolves.toBe(port);
272
273
 
273
274
  expect(lastSocket?.methods()).toContain('Browser.close');
274
- expect(await portFileExists()).toBe(false);
275
+ expect(await portFileExists(port)).toBe(false);
275
276
  });
276
277
  });