@monoes/monobrowse 1.0.9 → 1.0.12
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.
- package/README.md +1 -1
- package/dist/src/__tests__/adapters.test.js +5 -2
- package/dist/src/__tests__/adapters.test.js.map +1 -1
- package/dist/src/__tests__/batch-eval-parsing.test.js +4 -2
- package/dist/src/__tests__/batch-eval-parsing.test.js.map +1 -1
- package/dist/src/__tests__/box-model.test.js +9 -5
- package/dist/src/__tests__/box-model.test.js.map +1 -1
- package/dist/src/__tests__/browser-launch.test.d.ts +10 -0
- package/dist/src/__tests__/browser-launch.test.d.ts.map +1 -1
- package/dist/src/__tests__/browser-launch.test.js +17 -10
- package/dist/src/__tests__/browser-launch.test.js.map +1 -1
- package/dist/src/__tests__/cdp-client.test.js +9 -3
- package/dist/src/__tests__/cdp-client.test.js.map +1 -1
- package/dist/src/__tests__/close-browser.test.d.ts +13 -0
- package/dist/src/__tests__/close-browser.test.d.ts.map +1 -1
- package/dist/src/__tests__/close-browser.test.js +40 -26
- package/dist/src/__tests__/close-browser.test.js.map +1 -1
- package/dist/src/__tests__/console-log.test.js +17 -6
- package/dist/src/__tests__/console-log.test.js.map +1 -1
- package/dist/src/__tests__/find.test.js +26 -6
- package/dist/src/__tests__/find.test.js.map +1 -1
- package/dist/src/__tests__/frame-command.test.js +9 -3
- package/dist/src/__tests__/frame-command.test.js.map +1 -1
- package/dist/src/__tests__/open-url-navigate-error.test.js +1 -3
- package/dist/src/__tests__/open-url-navigate-error.test.js.map +1 -1
- package/dist/src/__tests__/output.test.js +1 -1
- package/dist/src/__tests__/output.test.js.map +1 -1
- package/dist/src/__tests__/reap-idle-browser.test.d.ts +15 -0
- package/dist/src/__tests__/reap-idle-browser.test.d.ts.map +1 -0
- package/dist/src/__tests__/reap-idle-browser.test.js +238 -0
- package/dist/src/__tests__/reap-idle-browser.test.js.map +1 -0
- package/dist/src/__tests__/ref-cache.test.js +18 -10
- package/dist/src/__tests__/ref-cache.test.js.map +1 -1
- package/dist/src/__tests__/session-state.test.d.ts +8 -0
- package/dist/src/__tests__/session-state.test.d.ts.map +1 -1
- package/dist/src/__tests__/session-state.test.js +10 -10
- package/dist/src/__tests__/session-state.test.js.map +1 -1
- package/dist/src/__tests__/snapshot.test.js +45 -15
- package/dist/src/__tests__/snapshot.test.js.map +1 -1
- package/dist/src/__tests__/storage-eval.test.js +14 -6
- package/dist/src/__tests__/storage-eval.test.js.map +1 -1
- package/dist/src/__tests__/tabs.test.js +25 -9
- package/dist/src/__tests__/tabs.test.js.map +1 -1
- package/dist/src/browser/action-builder/analyzer.d.ts +1 -1
- package/dist/src/browser/action-builder/analyzer.d.ts.map +1 -1
- package/dist/src/browser/action-builder/analyzer.js +25 -9
- package/dist/src/browser/action-builder/analyzer.js.map +1 -1
- package/dist/src/browser/action-builder/types.d.ts.map +1 -1
- package/dist/src/browser/actions.d.ts +1 -1
- package/dist/src/browser/actions.d.ts.map +1 -1
- package/dist/src/browser/actions.js +16 -6
- package/dist/src/browser/actions.js.map +1 -1
- package/dist/src/browser/adapters/gemini.d.ts.map +1 -1
- package/dist/src/browser/adapters/gemini.js.map +1 -1
- package/dist/src/browser/adapters/google.d.ts.map +1 -1
- package/dist/src/browser/adapters/google.js.map +1 -1
- package/dist/src/browser/adapters/index.d.ts.map +1 -1
- package/dist/src/browser/adapters/index.js +3 -3
- package/dist/src/browser/adapters/index.js.map +1 -1
- package/dist/src/browser/adapters/instagram.d.ts.map +1 -1
- package/dist/src/browser/adapters/instagram.js.map +1 -1
- package/dist/src/browser/adapters/linkedin.d.ts.map +1 -1
- package/dist/src/browser/adapters/linkedin.js.map +1 -1
- package/dist/src/browser/adapters/microsoft.d.ts.map +1 -1
- package/dist/src/browser/adapters/microsoft.js.map +1 -1
- package/dist/src/browser/adapters/x.d.ts.map +1 -1
- package/dist/src/browser/adapters/x.js.map +1 -1
- package/dist/src/browser/browser.d.ts +30 -0
- package/dist/src/browser/browser.d.ts.map +1 -1
- package/dist/src/browser/browser.js +122 -15
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/src/browser/cdp.d.ts.map +1 -1
- package/dist/src/browser/cdp.js +21 -8
- package/dist/src/browser/cdp.js.map +1 -1
- package/dist/src/browser/console-log.d.ts.map +1 -1
- package/dist/src/browser/console-log.js +2 -2
- package/dist/src/browser/console-log.js.map +1 -1
- package/dist/src/browser/dashboard/server.d.ts.map +1 -1
- package/dist/src/browser/dashboard/server.js +29 -19
- package/dist/src/browser/dashboard/server.js.map +1 -1
- package/dist/src/browser/dashboard/ui.html +153 -165
- package/dist/src/browser/dialog.d.ts.map +1 -1
- package/dist/src/browser/dialog.js +3 -1
- package/dist/src/browser/dialog.js.map +1 -1
- package/dist/src/browser/emulation.d.ts.map +1 -1
- package/dist/src/browser/emulation.js +21 -6
- package/dist/src/browser/emulation.js.map +1 -1
- package/dist/src/browser/find.d.ts +4 -4
- package/dist/src/browser/find.d.ts.map +1 -1
- package/dist/src/browser/find.js +14 -6
- package/dist/src/browser/find.js.map +1 -1
- package/dist/src/browser/har.d.ts.map +1 -1
- package/dist/src/browser/har.js +14 -7
- package/dist/src/browser/har.js.map +1 -1
- package/dist/src/browser/index.d.ts +16 -16
- package/dist/src/browser/index.d.ts.map +1 -1
- package/dist/src/browser/index.js +16 -16
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/browser/network.d.ts.map +1 -1
- package/dist/src/browser/network.js +14 -4
- package/dist/src/browser/network.js.map +1 -1
- package/dist/src/browser/pdf.d.ts.map +1 -1
- package/dist/src/browser/pdf.js +3 -3
- package/dist/src/browser/pdf.js.map +1 -1
- package/dist/src/browser/profiler.d.ts.map +1 -1
- package/dist/src/browser/profiler.js +10 -5
- package/dist/src/browser/profiler.js.map +1 -1
- package/dist/src/browser/record.d.ts +1 -1
- package/dist/src/browser/record.d.ts.map +1 -1
- package/dist/src/browser/record.js +18 -7
- package/dist/src/browser/record.js.map +1 -1
- package/dist/src/browser/ref-cache.d.ts.map +1 -1
- package/dist/src/browser/ref-cache.js +9 -4
- package/dist/src/browser/ref-cache.js.map +1 -1
- package/dist/src/browser/screenshot.d.ts.map +1 -1
- package/dist/src/browser/screenshot.js +11 -7
- package/dist/src/browser/screenshot.js.map +1 -1
- package/dist/src/browser/session.d.ts +1 -1
- package/dist/src/browser/session.d.ts.map +1 -1
- package/dist/src/browser/session.js +27 -8
- package/dist/src/browser/session.js.map +1 -1
- package/dist/src/browser/snapshot.d.ts +1 -1
- package/dist/src/browser/snapshot.d.ts.map +1 -1
- package/dist/src/browser/snapshot.js +8 -4
- package/dist/src/browser/snapshot.js.map +1 -1
- package/dist/src/browser/storage.d.ts.map +1 -1
- package/dist/src/browser/storage.js.map +1 -1
- package/dist/src/browser/tabs.d.ts.map +1 -1
- package/dist/src/browser/tabs.js +9 -3
- package/dist/src/browser/tabs.js.map +1 -1
- package/dist/src/browser/trace.d.ts.map +1 -1
- package/dist/src/browser/trace.js +3 -3
- package/dist/src/browser/trace.js.map +1 -1
- package/dist/src/browser/types.d.ts.map +1 -1
- package/dist/src/browser/types.js +23 -4
- package/dist/src/browser/types.js.map +1 -1
- package/dist/src/browser/vitals.d.ts.map +1 -1
- package/dist/src/browser/vitals.js +1 -1
- package/dist/src/browser/vitals.js.map +1 -1
- package/dist/src/browser/wait.d.ts.map +1 -1
- package/dist/src/browser/wait.js +11 -5
- package/dist/src/browser/wait.js.map +1 -1
- package/dist/src/cli/action.d.ts.map +1 -1
- package/dist/src/cli/action.js +14 -11
- package/dist/src/cli/action.js.map +1 -1
- package/dist/src/cli/commands.d.ts.map +1 -1
- package/dist/src/cli/commands.js +453 -159
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/output.js +5 -5
- package/dist/src/cli/output.js.map +1 -1
- package/dist/src/cli/platform.d.ts.map +1 -1
- package/dist/src/cli/platform.js +15 -11
- package/dist/src/cli/platform.js.map +1 -1
- package/dist/src/cli/types.d.ts +1 -1
- package/dist/src/cli/types.d.ts.map +1 -1
- package/dist/src/cli.js +21 -9
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/__tests__/adapters.test.ts +15 -8
- package/src/__tests__/batch-eval-parsing.test.ts +4 -2
- package/src/__tests__/box-model.test.ts +9 -5
- package/src/__tests__/browser-launch.test.ts +26 -15
- package/src/__tests__/cdp-client.test.ts +25 -9
- package/src/__tests__/close-browser.test.ts +49 -26
- package/src/__tests__/console-log.test.ts +36 -13
- package/src/__tests__/find.test.ts +58 -20
- package/src/__tests__/frame-command.test.ts +12 -6
- package/src/__tests__/open-url-navigate-error.test.ts +2 -4
- package/src/__tests__/output.test.ts +1 -1
- package/src/__tests__/reap-idle-browser.test.ts +276 -0
- package/src/__tests__/ref-cache.test.ts +25 -12
- package/src/__tests__/session-state.test.ts +40 -28
- package/src/__tests__/snapshot.test.ts +61 -22
- package/src/__tests__/storage-eval.test.ts +30 -16
- package/src/__tests__/tabs.test.ts +26 -10
- package/src/browser/action-builder/analyzer.ts +39 -15
- package/src/browser/action-builder/types.ts +45 -8
- package/src/browser/actions.ts +288 -123
- package/src/browser/adapters/gemini.ts +7 -3
- package/src/browser/adapters/google.ts +5 -3
- package/src/browser/adapters/index.ts +5 -4
- package/src/browser/adapters/instagram.ts +4 -2
- package/src/browser/adapters/linkedin.ts +4 -2
- package/src/browser/adapters/microsoft.ts +9 -5
- package/src/browser/adapters/x.ts +7 -3
- package/src/browser/browser.ts +188 -42
- package/src/browser/cdp.ts +43 -11
- package/src/browser/console-log.ts +24 -7
- package/src/browser/dashboard/server.ts +44 -25
- package/src/browser/dashboard/ui.html +153 -165
- package/src/browser/dialog.ts +25 -8
- package/src/browser/emulation.ts +86 -36
- package/src/browser/find.ts +110 -47
- package/src/browser/har.ts +58 -26
- package/src/browser/index.ts +16 -16
- package/src/browser/network.ts +147 -46
- package/src/browser/pdf.ts +4 -4
- package/src/browser/profiler.ts +23 -10
- package/src/browser/record.ts +38 -19
- package/src/browser/ref-cache.ts +38 -17
- package/src/browser/screenshot.ts +58 -32
- package/src/browser/session.ts +51 -18
- package/src/browser/snapshot.ts +34 -20
- package/src/browser/storage.ts +73 -17
- package/src/browser/tabs.ts +29 -17
- package/src/browser/trace.ts +14 -10
- package/src/browser/types.ts +23 -4
- package/src/browser/vitals.ts +33 -16
- package/src/browser/wait.ts +47 -21
- package/src/cli/action.ts +52 -21
- package/src/cli/commands.ts +1093 -385
- package/src/cli/output.ts +5 -5
- package/src/cli/platform.ts +44 -23
- package/src/cli/types.ts +1 -1
- package/src/cli.ts +34 -18
- package/src/index.ts +2 -2
|
@@ -5,21 +5,21 @@
|
|
|
5
5
|
* browser at all: they filter the in-memory ref Map. findBySelector and
|
|
6
6
|
* findByTestId do issue CDP commands, which a recording stub answers.
|
|
7
7
|
*/
|
|
8
|
-
import { describe,
|
|
8
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
9
9
|
import type { CdpClient } from '../browser/cdp.js';
|
|
10
|
-
import type { ElementRef } from '../browser/types.js';
|
|
11
10
|
import {
|
|
12
|
-
findBySelector,
|
|
13
|
-
findByRole,
|
|
14
|
-
findByText,
|
|
15
11
|
findByLabel,
|
|
16
12
|
findByPlaceholder,
|
|
13
|
+
findByRole,
|
|
14
|
+
findBySelector,
|
|
17
15
|
findByTestId,
|
|
16
|
+
findByText,
|
|
17
|
+
isChecked,
|
|
18
18
|
isEnabled,
|
|
19
19
|
isVisible,
|
|
20
|
-
isChecked,
|
|
21
20
|
scrollIntoView,
|
|
22
21
|
} from '../browser/find.js';
|
|
22
|
+
import type { ElementRef } from '../browser/types.js';
|
|
23
23
|
|
|
24
24
|
function ref(partial: Partial<ElementRef> & { ref: string }): ElementRef {
|
|
25
25
|
return { role: 'generic', name: '', nodeId: 1, ...partial };
|
|
@@ -31,8 +31,21 @@ function sampleRefs(): Map<string, ElementRef> {
|
|
|
31
31
|
ref({ ref: 'e1', role: 'button', name: 'Save changes', backendDOMNodeId: 11 }),
|
|
32
32
|
ref({ ref: 'e2', role: 'BUTTON', name: 'Save', backendDOMNodeId: 12 }),
|
|
33
33
|
ref({ ref: 'e3', role: 'link', name: 'Save as draft', backendDOMNodeId: 13 }),
|
|
34
|
-
ref({
|
|
35
|
-
|
|
34
|
+
ref({
|
|
35
|
+
ref: 'e4',
|
|
36
|
+
role: 'textbox',
|
|
37
|
+
name: 'Email',
|
|
38
|
+
placeholder: 'you@example.com',
|
|
39
|
+
backendDOMNodeId: 14,
|
|
40
|
+
}),
|
|
41
|
+
ref({
|
|
42
|
+
ref: 'e5',
|
|
43
|
+
role: 'textbox',
|
|
44
|
+
name: 'Notes',
|
|
45
|
+
placeholder: 'Optional notes',
|
|
46
|
+
backendDOMNodeId: 15,
|
|
47
|
+
disabled: true,
|
|
48
|
+
}),
|
|
36
49
|
];
|
|
37
50
|
return new Map(entries.map((e) => [e.ref, e]));
|
|
38
51
|
}
|
|
@@ -84,8 +97,12 @@ describe('findByRole', () => {
|
|
|
84
97
|
|
|
85
98
|
it('requires a full name match under exact', async () => {
|
|
86
99
|
const { client } = stubClient();
|
|
87
|
-
expect(
|
|
88
|
-
|
|
100
|
+
expect(
|
|
101
|
+
(await findByRole(client, 'S1', sampleRefs(), 'button', { name: 'Save', exact: true }))?.ref,
|
|
102
|
+
).toBe('e2');
|
|
103
|
+
expect(
|
|
104
|
+
await findByRole(client, 'S1', sampleRefs(), 'button', { name: 'Sav', exact: true }),
|
|
105
|
+
).toBeNull();
|
|
89
106
|
});
|
|
90
107
|
|
|
91
108
|
it('nth is 1-based over the filtered matches', async () => {
|
|
@@ -98,7 +115,9 @@ describe('findByRole', () => {
|
|
|
98
115
|
|
|
99
116
|
it('last returns the final match', async () => {
|
|
100
117
|
const { client } = stubClient();
|
|
101
|
-
expect((await findByRole(client, 'S1', sampleRefs(), 'button', { last: true }))?.ref).toBe(
|
|
118
|
+
expect((await findByRole(client, 'S1', sampleRefs(), 'button', { last: true }))?.ref).toBe(
|
|
119
|
+
'e2',
|
|
120
|
+
);
|
|
102
121
|
});
|
|
103
122
|
|
|
104
123
|
it('returns null when no ref carries that role', async () => {
|
|
@@ -141,7 +160,9 @@ describe('findByText', () => {
|
|
|
141
160
|
it('findByLabel delegates to the same matching', async () => {
|
|
142
161
|
const { client } = stubClient();
|
|
143
162
|
const refs = sampleRefs();
|
|
144
|
-
expect(await findByLabel(client, 'S1', refs, 'Email')).toBe(
|
|
163
|
+
expect(await findByLabel(client, 'S1', refs, 'Email')).toBe(
|
|
164
|
+
await findByText(client, 'S1', refs, 'Email'),
|
|
165
|
+
);
|
|
145
166
|
});
|
|
146
167
|
});
|
|
147
168
|
|
|
@@ -154,14 +175,18 @@ describe('findByPlaceholder', () => {
|
|
|
154
175
|
it('treats a missing placeholder as empty rather than crashing', async () => {
|
|
155
176
|
const { client } = stubClient();
|
|
156
177
|
// e1..e3 have no placeholder; an empty needle must not match them under exact.
|
|
157
|
-
expect((await findByPlaceholder(client, 'S1', sampleRefs(), '', { exact: true }))?.ref).toBe(
|
|
178
|
+
expect((await findByPlaceholder(client, 'S1', sampleRefs(), '', { exact: true }))?.ref).toBe(
|
|
179
|
+
'e1',
|
|
180
|
+
);
|
|
158
181
|
expect(await findByPlaceholder(client, 'S1', sampleRefs(), 'nope')).toBeNull();
|
|
159
182
|
});
|
|
160
183
|
|
|
161
184
|
it('honours exact, nth and last', async () => {
|
|
162
185
|
const { client } = stubClient();
|
|
163
186
|
const refs = sampleRefs();
|
|
164
|
-
expect(
|
|
187
|
+
expect(
|
|
188
|
+
(await findByPlaceholder(client, 'S1', refs, 'Optional notes', { exact: true }))?.ref,
|
|
189
|
+
).toBe('e5');
|
|
165
190
|
expect((await findByPlaceholder(client, 'S1', refs, 'o', { nth: 1 }))?.ref).toBe('e4');
|
|
166
191
|
expect((await findByPlaceholder(client, 'S1', refs, 'o', { last: true }))?.ref).toBe('e5');
|
|
167
192
|
});
|
|
@@ -190,7 +215,12 @@ describe('findBySelector', () => {
|
|
|
190
215
|
});
|
|
191
216
|
const refs = sampleRefs();
|
|
192
217
|
const found = await findBySelector(client, 'S1', refs, 'div.hidden');
|
|
193
|
-
expect(found).toMatchObject({
|
|
218
|
+
expect(found).toMatchObject({
|
|
219
|
+
ref: 'sel-999',
|
|
220
|
+
role: 'generic',
|
|
221
|
+
name: 'div.hidden',
|
|
222
|
+
nodeId: 55,
|
|
223
|
+
});
|
|
194
224
|
expect(refs.get('sel-999')).toBe(found);
|
|
195
225
|
});
|
|
196
226
|
|
|
@@ -242,7 +272,9 @@ describe('findBySelector', () => {
|
|
|
242
272
|
'DOM.getDocument': doc,
|
|
243
273
|
'DOM.querySelector': new Error("SyntaxError: '((' is not a valid selector"),
|
|
244
274
|
});
|
|
245
|
-
await expect(findBySelector(client, 'S1', sampleRefs(), '((')).rejects.toThrow(
|
|
275
|
+
await expect(findBySelector(client, 'S1', sampleRefs(), '((')).rejects.toThrow(
|
|
276
|
+
/not a valid selector/,
|
|
277
|
+
);
|
|
246
278
|
});
|
|
247
279
|
|
|
248
280
|
it('swallows transient CDP errors and reports not-found', async () => {
|
|
@@ -282,7 +314,9 @@ describe('findByTestId', () => {
|
|
|
282
314
|
});
|
|
283
315
|
|
|
284
316
|
it('returns null when no attribute form matches', async () => {
|
|
285
|
-
const client = {
|
|
317
|
+
const client = {
|
|
318
|
+
send: vi.fn(async () => ({ result: { value: false } })),
|
|
319
|
+
} as unknown as CdpClient;
|
|
286
320
|
await expect(findByTestId(client, 'S1', 'nope')).resolves.toBeNull();
|
|
287
321
|
});
|
|
288
322
|
|
|
@@ -330,7 +364,9 @@ describe('element state helpers', () => {
|
|
|
330
364
|
'DOM.resolveNode': { object: { objectId: 'OBJ1' } },
|
|
331
365
|
'Runtime.callFunctionOn': { result: { value: true } },
|
|
332
366
|
});
|
|
333
|
-
await expect(isVisible(client, 'S1', ref({ ref: 'e1', backendDOMNodeId: 11 }))).resolves.toBe(
|
|
367
|
+
await expect(isVisible(client, 'S1', ref({ ref: 'e1', backendDOMNodeId: 11 }))).resolves.toBe(
|
|
368
|
+
true,
|
|
369
|
+
);
|
|
334
370
|
expect(calls.find((c) => c.method === 'Runtime.callFunctionOn')!.params).toMatchObject({
|
|
335
371
|
objectId: 'OBJ1',
|
|
336
372
|
returnByValue: true,
|
|
@@ -342,13 +378,15 @@ describe('element state helpers', () => {
|
|
|
342
378
|
'DOM.resolveNode': { object: { objectId: 'OBJ1' } },
|
|
343
379
|
'Runtime.callFunctionOn': { result: {} },
|
|
344
380
|
});
|
|
345
|
-
await expect(isChecked(client, 'S1', ref({ ref: 'e1', backendDOMNodeId: 11 }))).resolves.toBe(
|
|
381
|
+
await expect(isChecked(client, 'S1', ref({ ref: 'e1', backendDOMNodeId: 11 }))).resolves.toBe(
|
|
382
|
+
false,
|
|
383
|
+
);
|
|
346
384
|
});
|
|
347
385
|
|
|
348
386
|
it('scrollIntoView throws a ref-named error when the node is gone', async () => {
|
|
349
387
|
const { client } = stubClient({ 'DOM.resolveNode': { object: {} } });
|
|
350
388
|
await expect(
|
|
351
|
-
scrollIntoView(client, 'S1', ref({ ref: 'e1', backendDOMNodeId: 11 }))
|
|
389
|
+
scrollIntoView(client, 'S1', ref({ ref: 'e1', backendDOMNodeId: 11 })),
|
|
352
390
|
).rejects.toThrow('Cannot scroll: ref @e1 not found in DOM');
|
|
353
391
|
});
|
|
354
392
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* frame, and `frame main` must detach the frame session and restore the
|
|
5
5
|
* page session. The browser engine is mocked; no real Chrome.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
8
|
import type { Command, CommandContext } from '../cli/types.js';
|
|
9
9
|
|
|
10
10
|
const mocks = vi.hoisted(() => {
|
|
@@ -22,7 +22,12 @@ const mocks = vi.hoisted(() => {
|
|
|
22
22
|
})),
|
|
23
23
|
loadActivePortInfo: vi.fn(async () => null),
|
|
24
24
|
loadRefCache: vi.fn(async () => null),
|
|
25
|
-
switchToFrame: vi.fn(
|
|
25
|
+
switchToFrame: vi.fn(
|
|
26
|
+
async (): Promise<{ url: string | null; sessionId: string | null }> => ({
|
|
27
|
+
url: 'https://f.test/frame',
|
|
28
|
+
sessionId: 'S-FRAME',
|
|
29
|
+
}),
|
|
30
|
+
),
|
|
26
31
|
enableSessionDomains: vi.fn(async () => {}),
|
|
27
32
|
teardownRouteInterception: vi.fn(),
|
|
28
33
|
stopRequestCapture: vi.fn(),
|
|
@@ -52,7 +57,10 @@ beforeEach(() => {
|
|
|
52
57
|
vi.clearAllMocks();
|
|
53
58
|
mocks.browser.loadActivePortInfo.mockResolvedValue(null);
|
|
54
59
|
mocks.browser.loadRefCache.mockResolvedValue(null);
|
|
55
|
-
mocks.browser.switchToFrame.mockResolvedValue({
|
|
60
|
+
mocks.browser.switchToFrame.mockResolvedValue({
|
|
61
|
+
url: 'https://f.test/frame',
|
|
62
|
+
sessionId: 'S-FRAME',
|
|
63
|
+
});
|
|
56
64
|
mocks.browser.connectToTarget.mockResolvedValue({
|
|
57
65
|
client: mocks.client,
|
|
58
66
|
sessionId: 'S-PAGE',
|
|
@@ -72,9 +80,7 @@ describe('browse frame command (#97)', () => {
|
|
|
72
80
|
|
|
73
81
|
// Back to main — must detach the FRAME session (proves _sessionId was updated)
|
|
74
82
|
await frame.action!(ctx(['main']));
|
|
75
|
-
const detach = mocks.send.mock.calls.find(
|
|
76
|
-
([m]) => m === 'Target.detachFromTarget'
|
|
77
|
-
);
|
|
83
|
+
const detach = mocks.send.mock.calls.find(([m]) => m === 'Target.detachFromTarget');
|
|
78
84
|
expect(detach).toBeDefined();
|
|
79
85
|
expect(detach![1]).toEqual({ sessionId: 'S-FRAME' });
|
|
80
86
|
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* this checks that response before doing anything else.
|
|
8
8
|
*/
|
|
9
9
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
10
|
-
import type { CdpClient } from '../browser/cdp.js';
|
|
11
10
|
import { openUrl } from '../browser/browser.js';
|
|
11
|
+
import type { CdpClient } from '../browser/cdp.js';
|
|
12
12
|
|
|
13
13
|
afterEach(() => {
|
|
14
14
|
vi.restoreAllMocks();
|
|
@@ -43,9 +43,7 @@ describe('openUrl navigation-failure detection', () => {
|
|
|
43
43
|
await expect(openUrl(client, 'sess-1', 'http://127.0.0.1:1/')).rejects.toThrow(
|
|
44
44
|
/net::ERR_CONNECTION_REFUSED/,
|
|
45
45
|
);
|
|
46
|
-
expect(calls).toEqual([
|
|
47
|
-
{ method: 'Page.navigate', params: { url: 'http://127.0.0.1:1/' } },
|
|
48
|
-
]);
|
|
46
|
+
expect(calls).toEqual([{ method: 'Page.navigate', params: { url: 'http://127.0.0.1:1/' } }]);
|
|
49
47
|
});
|
|
50
48
|
|
|
51
49
|
it('rejects the 2 MB URL guard before ever calling Page.navigate', async () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
3
|
// The module exports a singleton `output` which reads process.stdout.isTTY at
|
|
4
4
|
// construction time. We need to control that, so we reset modules per test.
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #310: a launched Chrome outlives its session, holding its CDP port (9222 by
|
|
3
|
+
* convention, which other local tools want) with zero targets left to
|
|
4
|
+
* reattach to. reapIdleLaunchedBrowser() gives that port back.
|
|
5
|
+
*
|
|
6
|
+
* No real Chrome is spawned: the CDP HTTP endpoints (/json/version, /json/list)
|
|
7
|
+
* are served by a local http server that answers exactly like Chrome does
|
|
8
|
+
* (same approach as browser-launch.test.ts), the browser-level websocket is a
|
|
9
|
+
* fake `ws` socket (same approach as close-browser.test.ts), and process.kill
|
|
10
|
+
* is spied on rather than actually signaling anything.
|
|
11
|
+
*
|
|
12
|
+
* Fixed port range (23490-23499) chosen to avoid colliding with real services.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
16
|
+
import { createServer as createHttpServer, type Server as HttpServer } from 'node:http';
|
|
17
|
+
import { tmpdir } from 'node:os';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
20
|
+
import type { CdpTarget } from '../browser/types.js';
|
|
21
|
+
|
|
22
|
+
const BASE = 23490;
|
|
23
|
+
const MINUTE = 60_000;
|
|
24
|
+
|
|
25
|
+
let lastSocket: FakeWs | null = null;
|
|
26
|
+
|
|
27
|
+
/** Minimal `ws` stand-in: opens on its own and acknowledges every command,
|
|
28
|
+
* so `Browser.close` takes the graceful path without a real browser. */
|
|
29
|
+
class FakeWs {
|
|
30
|
+
handlers = new Map<string, Array<(...a: unknown[]) => void>>();
|
|
31
|
+
sent: string[] = [];
|
|
32
|
+
constructor(public url: string) {
|
|
33
|
+
lastSocket = this;
|
|
34
|
+
queueMicrotask(() => this.emit('open'));
|
|
35
|
+
}
|
|
36
|
+
on(event: string, fn: (...a: unknown[]) => void): void {
|
|
37
|
+
if (!this.handlers.has(event)) this.handlers.set(event, []);
|
|
38
|
+
this.handlers.get(event)!.push(fn);
|
|
39
|
+
}
|
|
40
|
+
emit(event: string, ...args: unknown[]): void {
|
|
41
|
+
for (const fn of [...(this.handlers.get(event) ?? [])]) fn(...args);
|
|
42
|
+
}
|
|
43
|
+
send(data: string, cb?: (err?: Error) => void): void {
|
|
44
|
+
void cb;
|
|
45
|
+
this.sent.push(data);
|
|
46
|
+
const { id } = JSON.parse(data) as { id: number };
|
|
47
|
+
queueMicrotask(() => this.emit('message', Buffer.from(JSON.stringify({ id, result: {} }))));
|
|
48
|
+
}
|
|
49
|
+
close(): void {}
|
|
50
|
+
/** Methods this socket was asked to run, e.g. ['Browser.close']. */
|
|
51
|
+
methods(): string[] {
|
|
52
|
+
return this.sent.map((s) => (JSON.parse(s) as { method: string }).method);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
vi.mock('ws', () => ({ WebSocket: FakeWs }));
|
|
57
|
+
|
|
58
|
+
let tempDir: string;
|
|
59
|
+
let servers: HttpServer[] = [];
|
|
60
|
+
let killSpy: ReturnType<typeof vi.spyOn>;
|
|
61
|
+
|
|
62
|
+
beforeEach(async () => {
|
|
63
|
+
lastSocket = null;
|
|
64
|
+
tempDir = await mkdtemp(join(tmpdir(), 'monobrowse-reap-test-'));
|
|
65
|
+
// ref-cache.ts resolves its cache dir from process.cwd() at module load, so
|
|
66
|
+
// this must be in place before the dynamic import in each test.
|
|
67
|
+
vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
|
|
68
|
+
// The browser is "already gone" by the time closeBrowser polls for its exit
|
|
69
|
+
// (ESRCH on the liveness probe) — the realistic outcome of Browser.close.
|
|
70
|
+
killSpy = vi.spyOn(process, 'kill').mockImplementation((_pid: unknown, signal?: unknown) => {
|
|
71
|
+
if (signal === 0) throw Object.assign(new Error('ESRCH'), { code: 'ESRCH' });
|
|
72
|
+
return true as never;
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(async () => {
|
|
77
|
+
await Promise.all(servers.map((s) => new Promise<void>((resolve) => s.close(() => resolve()))));
|
|
78
|
+
servers = [];
|
|
79
|
+
vi.restoreAllMocks();
|
|
80
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
/** An http server answering /json/version and /json/list exactly like Chrome. */
|
|
84
|
+
function fakeChrome(port: number, targets: Partial<CdpTarget>[]): Promise<void> {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const s = createHttpServer((req, res) => {
|
|
87
|
+
if (req.url === '/json/version') {
|
|
88
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
89
|
+
res.end(
|
|
90
|
+
JSON.stringify({
|
|
91
|
+
Browser: 'Chrome/999.0.0.0',
|
|
92
|
+
webSocketDebuggerUrl: `ws://127.0.0.1:${port}/devtools/browser/fake-browser-id`,
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
} else if (req.url === '/json/list') {
|
|
96
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
97
|
+
res.end(JSON.stringify(targets));
|
|
98
|
+
} else {
|
|
99
|
+
res.writeHead(404);
|
|
100
|
+
res.end();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
s.once('error', reject);
|
|
104
|
+
s.listen(port, '127.0.0.1', () => {
|
|
105
|
+
servers.push(s);
|
|
106
|
+
resolve();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const PORT_FILE = () => join(tempDir, '.monomind', 'monobrowse', 'active-port.json');
|
|
112
|
+
|
|
113
|
+
async function writePersistedPort(record: {
|
|
114
|
+
port: number;
|
|
115
|
+
pid?: number;
|
|
116
|
+
launched: boolean;
|
|
117
|
+
savedAt: number;
|
|
118
|
+
}): Promise<void> {
|
|
119
|
+
await mkdir(join(tempDir, '.monomind', 'monobrowse'), { recursive: true });
|
|
120
|
+
await writeFile(PORT_FILE(), JSON.stringify(record), 'utf-8');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function portFileExists(): Promise<boolean> {
|
|
124
|
+
return readFile(PORT_FILE(), 'utf-8').then(
|
|
125
|
+
() => true,
|
|
126
|
+
() => false,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function loadBrowser() {
|
|
131
|
+
vi.resetModules();
|
|
132
|
+
return import('../browser/browser.js');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A page target as Chrome reports it when NO debugger is attached to it. */
|
|
136
|
+
function idlePageTarget(id: string, port: number): Partial<CdpTarget> {
|
|
137
|
+
return {
|
|
138
|
+
id,
|
|
139
|
+
type: 'page',
|
|
140
|
+
title: 'Example',
|
|
141
|
+
url: 'https://example.com/',
|
|
142
|
+
webSocketDebuggerUrl: `ws://127.0.0.1:${port}/devtools/page/${id}`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
describe('#310 reapIdleLaunchedBrowser — give back a CDP port nothing is using', () => {
|
|
147
|
+
it('reaps a launched instance that has had zero targets past the idle threshold', async () => {
|
|
148
|
+
const port = BASE + 0;
|
|
149
|
+
await fakeChrome(port, []);
|
|
150
|
+
await writePersistedPort({
|
|
151
|
+
port,
|
|
152
|
+
pid: 54321,
|
|
153
|
+
launched: true,
|
|
154
|
+
savedAt: Date.now() - 45 * MINUTE,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
158
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBe(port);
|
|
159
|
+
|
|
160
|
+
expect(lastSocket?.url).toBe(`ws://127.0.0.1:${port}/devtools/browser/fake-browser-id`);
|
|
161
|
+
expect(lastSocket?.methods()).toContain('Browser.close');
|
|
162
|
+
// The port file is the session handle — a reaped session must not leave
|
|
163
|
+
// one behind for the next command to chase.
|
|
164
|
+
expect(await portFileExists()).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('does NOT reap a launched instance that still has open page targets', async () => {
|
|
168
|
+
const port = BASE + 1;
|
|
169
|
+
await fakeChrome(port, [idlePageTarget('AAA', port)]);
|
|
170
|
+
await writePersistedPort({
|
|
171
|
+
port,
|
|
172
|
+
pid: 54322,
|
|
173
|
+
launched: true,
|
|
174
|
+
savedAt: Date.now() - 45 * MINUTE,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
178
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
|
|
179
|
+
|
|
180
|
+
expect(lastSocket).toBeNull();
|
|
181
|
+
expect(killSpy).not.toHaveBeenCalled();
|
|
182
|
+
expect(await portFileExists()).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('does NOT reap a browser this tool never launched, however idle or old it is', async () => {
|
|
186
|
+
// `connect` records launched:false — that browser belongs to the user.
|
|
187
|
+
const port = BASE + 2;
|
|
188
|
+
await fakeChrome(port, []);
|
|
189
|
+
await writePersistedPort({
|
|
190
|
+
port,
|
|
191
|
+
pid: 54323,
|
|
192
|
+
launched: false,
|
|
193
|
+
savedAt: Date.now() - 48 * 60 * MINUTE,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
197
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
|
|
198
|
+
|
|
199
|
+
expect(lastSocket).toBeNull();
|
|
200
|
+
expect(killSpy).not.toHaveBeenCalled();
|
|
201
|
+
expect(await portFileExists()).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('does NOT reap while a client is still attached to a target', async () => {
|
|
205
|
+
// Chrome omits webSocketDebuggerUrl from a target that already has a
|
|
206
|
+
// debugger attached — an in-flight session, not an abandoned browser.
|
|
207
|
+
const port = BASE + 3;
|
|
208
|
+
await fakeChrome(port, [
|
|
209
|
+
{ id: 'BBB', type: 'page', title: 'Example', url: 'https://example.com/' },
|
|
210
|
+
]);
|
|
211
|
+
await writePersistedPort({
|
|
212
|
+
port,
|
|
213
|
+
pid: 54324,
|
|
214
|
+
launched: true,
|
|
215
|
+
savedAt: Date.now() - 45 * MINUTE,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
219
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
|
|
220
|
+
|
|
221
|
+
expect(lastSocket).toBeNull();
|
|
222
|
+
expect(await portFileExists()).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('does NOT reap an instance that is still inside the idle threshold', async () => {
|
|
226
|
+
// A live session can momentarily have no targets (last tab closed, next
|
|
227
|
+
// command about to open one) — reaping that would break reattach.
|
|
228
|
+
const port = BASE + 4;
|
|
229
|
+
await fakeChrome(port, []);
|
|
230
|
+
await writePersistedPort({ port, pid: 54325, launched: true, savedAt: Date.now() - MINUTE });
|
|
231
|
+
|
|
232
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
233
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
|
|
234
|
+
|
|
235
|
+
expect(lastSocket).toBeNull();
|
|
236
|
+
expect(await portFileExists()).toBe(true);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('does NOT reap when the persisted record has no savedAt (age unknowable)', async () => {
|
|
240
|
+
const port = BASE + 5;
|
|
241
|
+
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
|
+
|
|
245
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
246
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
|
|
247
|
+
expect(lastSocket).toBeNull();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('is a no-op with no persisted session at all', async () => {
|
|
251
|
+
const { reapIdleLaunchedBrowser } = await loadBrowser();
|
|
252
|
+
await expect(reapIdleLaunchedBrowser()).resolves.toBeNull();
|
|
253
|
+
expect(killSpy).not.toHaveBeenCalled();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('launchBrowser reaps the stale instance before deciding what to do with the port', async () => {
|
|
257
|
+
// The wiring that makes the reaper run at all: every launch/attach is a
|
|
258
|
+
// chance to give back a port a previous session abandoned.
|
|
259
|
+
const port = BASE + 6;
|
|
260
|
+
await fakeChrome(port, []);
|
|
261
|
+
await writePersistedPort({
|
|
262
|
+
port,
|
|
263
|
+
pid: 54327,
|
|
264
|
+
launched: true,
|
|
265
|
+
savedAt: Date.now() - 45 * MINUTE,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const { launchBrowser } = await loadBrowser();
|
|
269
|
+
// The fake Chrome stays listening, so launchBrowser attaches to it after
|
|
270
|
+
// the reap rather than spawning a real browser.
|
|
271
|
+
await expect(launchBrowser({ port })).resolves.toBe(port);
|
|
272
|
+
|
|
273
|
+
expect(lastSocket?.methods()).toContain('Browser.close');
|
|
274
|
+
expect(await portFileExists()).toBe(false);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
import {
|
|
1
|
+
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
|
|
6
6
|
// We need to mock process.cwd() before importing ref-cache so CACHE_DIR
|
|
7
7
|
// resolves to our temp directory.
|
|
@@ -26,7 +26,9 @@ async function importRefCache() {
|
|
|
26
26
|
return import('../browser/ref-cache.js');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function makeRefs(
|
|
29
|
+
function makeRefs(
|
|
30
|
+
count = 2,
|
|
31
|
+
): Map<string, { ref: string; role: string; name: string; nodeId: number }> {
|
|
30
32
|
const map = new Map();
|
|
31
33
|
for (let i = 1; i <= count; i++) {
|
|
32
34
|
map.set(`ref${i}`, { ref: `ref${i}`, role: 'button', name: `Button ${i}`, nodeId: i });
|
|
@@ -68,7 +70,7 @@ describe('ref-cache', () => {
|
|
|
68
70
|
|
|
69
71
|
// Now overwrite the file with invalid JSON
|
|
70
72
|
const cacheFile = join(tempDir, '.monomind', 'monobrowse', 'ax-snapshot.json');
|
|
71
|
-
const { writeFile: wf } = await import('fs/promises');
|
|
73
|
+
const { writeFile: wf } = await import('node:fs/promises');
|
|
72
74
|
await wf(cacheFile, '{{{not valid json!!!');
|
|
73
75
|
|
|
74
76
|
const loaded = await mod.loadRefCache('t');
|
|
@@ -117,7 +119,7 @@ describe('ref-cache', () => {
|
|
|
117
119
|
const cacheFile = join(tempDir, '.monomind', 'monobrowse', 'ax-snapshot.json');
|
|
118
120
|
const raw = JSON.parse(await readFile(cacheFile, 'utf8'));
|
|
119
121
|
raw.savedAt = Date.now() - mod.REF_CACHE_STALE_MS - 5000;
|
|
120
|
-
const { writeFile: wf } = await import('fs/promises');
|
|
122
|
+
const { writeFile: wf } = await import('node:fs/promises');
|
|
121
123
|
await wf(cacheFile, JSON.stringify(raw));
|
|
122
124
|
|
|
123
125
|
const loaded = await mod.loadRefCache('t1');
|
|
@@ -138,13 +140,21 @@ describe('active-port persistence', () => {
|
|
|
138
140
|
const mod = await importRefCache();
|
|
139
141
|
await mod.saveActivePort(9333);
|
|
140
142
|
expect(await mod.loadActivePort()).toBe(9333);
|
|
141
|
-
expect(await mod.loadActivePortInfo()).toEqual({
|
|
143
|
+
expect(await mod.loadActivePortInfo()).toEqual({
|
|
144
|
+
port: 9333,
|
|
145
|
+
launched: true,
|
|
146
|
+
savedAt: expect.any(Number),
|
|
147
|
+
});
|
|
142
148
|
});
|
|
143
149
|
|
|
144
150
|
it('connect provenance: launched:false survives the round-trip', async () => {
|
|
145
151
|
const mod = await importRefCache();
|
|
146
152
|
await mod.saveActivePort(9229, { launched: false });
|
|
147
|
-
expect(await mod.loadActivePortInfo()).toEqual({
|
|
153
|
+
expect(await mod.loadActivePortInfo()).toEqual({
|
|
154
|
+
port: 9229,
|
|
155
|
+
launched: false,
|
|
156
|
+
savedAt: expect.any(Number),
|
|
157
|
+
});
|
|
148
158
|
});
|
|
149
159
|
|
|
150
160
|
it('clear removes the file; load returns null afterwards', async () => {
|
|
@@ -196,11 +206,14 @@ describe('active-port persistence', () => {
|
|
|
196
206
|
|
|
197
207
|
it('#115: a non-numeric or non-positive persisted pid is dropped, not trusted', async () => {
|
|
198
208
|
const mod = await importRefCache();
|
|
199
|
-
const { writeFile, mkdir } = await import('fs/promises');
|
|
209
|
+
const { writeFile, mkdir } = await import('node:fs/promises');
|
|
200
210
|
const dir = join(process.cwd(), '.monomind', 'monobrowse');
|
|
201
211
|
await mkdir(dir, { recursive: true });
|
|
202
212
|
for (const badPid of ['54321', -1, 0, 1.5, null]) {
|
|
203
|
-
await writeFile(
|
|
213
|
+
await writeFile(
|
|
214
|
+
join(dir, 'active-port.json'),
|
|
215
|
+
JSON.stringify({ port: 9333, launched: true, pid: badPid }),
|
|
216
|
+
);
|
|
204
217
|
const info = await mod.loadActivePortInfo();
|
|
205
218
|
expect(info!.pid).toBeUndefined();
|
|
206
219
|
}
|
|
@@ -208,7 +221,7 @@ describe('active-port persistence', () => {
|
|
|
208
221
|
|
|
209
222
|
it('rejects out-of-range or non-integer persisted ports', async () => {
|
|
210
223
|
const mod = await importRefCache();
|
|
211
|
-
const { writeFile, mkdir } = await import('fs/promises');
|
|
224
|
+
const { writeFile, mkdir } = await import('node:fs/promises');
|
|
212
225
|
const dir = join(process.cwd(), '.monomind', 'monobrowse');
|
|
213
226
|
await mkdir(dir, { recursive: true });
|
|
214
227
|
for (const bad of [80, 70000, 1.5, '9222', null]) {
|