@monoes/monobrowse 1.0.6 → 1.0.8
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/LICENSE +191 -0
- package/README.md +68 -0
- package/dist/src/__tests__/adapters.test.d.ts +2 -0
- package/dist/src/__tests__/adapters.test.d.ts.map +1 -0
- package/dist/src/__tests__/adapters.test.js +104 -0
- package/dist/src/__tests__/adapters.test.js.map +1 -0
- package/dist/src/__tests__/batch-eval-parsing.test.d.ts +0 -0
- package/dist/src/__tests__/batch-eval-parsing.test.d.ts.map +0 -0
- package/dist/src/__tests__/batch-eval-parsing.test.js +0 -0
- package/dist/src/__tests__/batch-eval-parsing.test.js.map +0 -0
- package/dist/src/__tests__/box-model.test.d.ts +0 -0
- package/dist/src/__tests__/box-model.test.d.ts.map +0 -0
- package/dist/src/__tests__/box-model.test.js +0 -0
- package/dist/src/__tests__/box-model.test.js.map +0 -0
- package/dist/src/__tests__/browser-launch.test.d.ts +0 -0
- package/dist/src/__tests__/browser-launch.test.d.ts.map +0 -0
- package/dist/src/__tests__/browser-launch.test.js +0 -0
- package/dist/src/__tests__/browser-launch.test.js.map +0 -0
- package/dist/src/__tests__/cdp-client.test.d.ts +2 -0
- package/dist/src/__tests__/cdp-client.test.d.ts.map +1 -0
- package/dist/src/__tests__/cdp-client.test.js +326 -0
- package/dist/src/__tests__/cdp-client.test.js.map +1 -0
- package/dist/src/__tests__/close-browser.test.d.ts +2 -0
- package/dist/src/__tests__/close-browser.test.d.ts.map +1 -0
- package/dist/src/__tests__/close-browser.test.js +188 -0
- package/dist/src/__tests__/close-browser.test.js.map +1 -0
- package/dist/src/__tests__/console-log.test.d.ts +2 -0
- package/dist/src/__tests__/console-log.test.d.ts.map +1 -0
- package/dist/src/__tests__/console-log.test.js +195 -0
- package/dist/src/__tests__/console-log.test.js.map +1 -0
- package/dist/src/__tests__/find.test.d.ts +2 -0
- package/dist/src/__tests__/find.test.d.ts.map +1 -0
- package/dist/src/__tests__/find.test.js +299 -0
- package/dist/src/__tests__/find.test.js.map +1 -0
- package/dist/src/__tests__/frame-command.test.d.ts +2 -0
- package/dist/src/__tests__/frame-command.test.d.ts.map +1 -0
- package/dist/src/__tests__/frame-command.test.js +85 -0
- package/dist/src/__tests__/frame-command.test.js.map +1 -0
- package/dist/src/__tests__/output.test.d.ts +0 -0
- package/dist/src/__tests__/output.test.d.ts.map +0 -0
- package/dist/src/__tests__/output.test.js +0 -0
- package/dist/src/__tests__/output.test.js.map +0 -0
- package/dist/src/__tests__/ref-cache.test.d.ts +0 -0
- package/dist/src/__tests__/ref-cache.test.d.ts.map +0 -0
- package/dist/src/__tests__/ref-cache.test.js +43 -2
- package/dist/src/__tests__/ref-cache.test.js.map +1 -1
- package/dist/src/__tests__/session-state.test.d.ts +2 -0
- package/dist/src/__tests__/session-state.test.d.ts.map +1 -0
- package/dist/src/__tests__/session-state.test.js +203 -0
- package/dist/src/__tests__/session-state.test.js.map +1 -0
- package/dist/src/__tests__/snapshot.test.d.ts +2 -0
- package/dist/src/__tests__/snapshot.test.d.ts.map +1 -0
- package/dist/src/__tests__/snapshot.test.js +262 -0
- package/dist/src/__tests__/snapshot.test.js.map +1 -0
- package/dist/src/__tests__/storage-eval.test.d.ts +2 -0
- package/dist/src/__tests__/storage-eval.test.d.ts.map +1 -0
- package/dist/src/__tests__/storage-eval.test.js +133 -0
- package/dist/src/__tests__/storage-eval.test.js.map +1 -0
- package/dist/src/__tests__/tabs.test.d.ts +2 -0
- package/dist/src/__tests__/tabs.test.d.ts.map +1 -0
- package/dist/src/__tests__/tabs.test.js +161 -0
- package/dist/src/__tests__/tabs.test.js.map +1 -0
- package/dist/src/browser/action-builder/analyzer.d.ts +0 -0
- package/dist/src/browser/action-builder/analyzer.d.ts.map +0 -0
- package/dist/src/browser/action-builder/analyzer.js +0 -0
- package/dist/src/browser/action-builder/analyzer.js.map +0 -0
- package/dist/src/browser/action-builder/types.d.ts +0 -0
- package/dist/src/browser/action-builder/types.d.ts.map +0 -0
- package/dist/src/browser/action-builder/types.js +0 -0
- package/dist/src/browser/action-builder/types.js.map +0 -0
- package/dist/src/browser/actions.d.ts +0 -0
- package/dist/src/browser/actions.d.ts.map +0 -0
- package/dist/src/browser/actions.js +0 -0
- package/dist/src/browser/actions.js.map +0 -0
- package/dist/src/browser/adapters/gemini.d.ts +0 -0
- package/dist/src/browser/adapters/gemini.d.ts.map +0 -0
- package/dist/src/browser/adapters/gemini.js +0 -0
- package/dist/src/browser/adapters/gemini.js.map +0 -0
- package/dist/src/browser/adapters/google.d.ts +0 -0
- package/dist/src/browser/adapters/google.d.ts.map +0 -0
- package/dist/src/browser/adapters/google.js +0 -0
- package/dist/src/browser/adapters/google.js.map +0 -0
- package/dist/src/browser/adapters/index.d.ts +0 -0
- package/dist/src/browser/adapters/index.d.ts.map +0 -0
- package/dist/src/browser/adapters/index.js +0 -0
- package/dist/src/browser/adapters/index.js.map +0 -0
- package/dist/src/browser/adapters/instagram.d.ts +0 -0
- package/dist/src/browser/adapters/instagram.d.ts.map +0 -0
- package/dist/src/browser/adapters/instagram.js +0 -0
- package/dist/src/browser/adapters/instagram.js.map +0 -0
- package/dist/src/browser/adapters/linkedin.d.ts +0 -0
- package/dist/src/browser/adapters/linkedin.d.ts.map +0 -0
- package/dist/src/browser/adapters/linkedin.js +0 -0
- package/dist/src/browser/adapters/linkedin.js.map +0 -0
- package/dist/src/browser/adapters/microsoft.d.ts +0 -0
- package/dist/src/browser/adapters/microsoft.d.ts.map +0 -0
- package/dist/src/browser/adapters/microsoft.js +0 -0
- package/dist/src/browser/adapters/microsoft.js.map +0 -0
- package/dist/src/browser/adapters/x.d.ts +0 -0
- package/dist/src/browser/adapters/x.d.ts.map +0 -0
- package/dist/src/browser/adapters/x.js +0 -0
- package/dist/src/browser/adapters/x.js.map +0 -0
- package/dist/src/browser/batch.d.ts +0 -0
- package/dist/src/browser/batch.d.ts.map +0 -0
- package/dist/src/browser/batch.js +0 -0
- package/dist/src/browser/batch.js.map +0 -0
- package/dist/src/browser/browser.d.ts +13 -0
- package/dist/src/browser/browser.d.ts.map +1 -1
- package/dist/src/browser/browser.js +99 -16
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/src/browser/cdp.d.ts +4 -1
- package/dist/src/browser/cdp.d.ts.map +1 -1
- package/dist/src/browser/cdp.js +45 -16
- package/dist/src/browser/cdp.js.map +1 -1
- package/dist/src/browser/console-log.d.ts +0 -0
- package/dist/src/browser/console-log.d.ts.map +0 -0
- package/dist/src/browser/console-log.js +0 -0
- package/dist/src/browser/console-log.js.map +0 -0
- package/dist/src/browser/dashboard/api-types.d.ts +0 -0
- package/dist/src/browser/dashboard/api-types.d.ts.map +0 -0
- package/dist/src/browser/dashboard/api-types.js +0 -0
- package/dist/src/browser/dashboard/api-types.js.map +0 -0
- package/dist/src/browser/dashboard/server.d.ts +0 -0
- package/dist/src/browser/dashboard/server.d.ts.map +0 -0
- package/dist/src/browser/dashboard/server.js +0 -0
- package/dist/src/browser/dashboard/server.js.map +0 -0
- package/dist/src/browser/dashboard/ui.html +0 -0
- package/dist/src/browser/dialog.d.ts +0 -0
- package/dist/src/browser/dialog.d.ts.map +0 -0
- package/dist/src/browser/dialog.js +0 -0
- package/dist/src/browser/dialog.js.map +0 -0
- package/dist/src/browser/emulation.d.ts +0 -0
- package/dist/src/browser/emulation.d.ts.map +0 -0
- package/dist/src/browser/emulation.js +0 -0
- package/dist/src/browser/emulation.js.map +0 -0
- package/dist/src/browser/find.d.ts +0 -0
- package/dist/src/browser/find.d.ts.map +0 -0
- package/dist/src/browser/find.js +0 -0
- package/dist/src/browser/find.js.map +0 -0
- package/dist/src/browser/har.d.ts +0 -0
- package/dist/src/browser/har.d.ts.map +0 -0
- package/dist/src/browser/har.js +0 -0
- package/dist/src/browser/har.js.map +0 -0
- package/dist/src/browser/index.d.ts +0 -0
- package/dist/src/browser/index.d.ts.map +0 -0
- package/dist/src/browser/index.js +0 -0
- package/dist/src/browser/index.js.map +0 -0
- package/dist/src/browser/network.d.ts +0 -0
- package/dist/src/browser/network.d.ts.map +0 -0
- package/dist/src/browser/network.js +0 -0
- package/dist/src/browser/network.js.map +0 -0
- package/dist/src/browser/pdf.d.ts +0 -0
- package/dist/src/browser/pdf.d.ts.map +0 -0
- package/dist/src/browser/pdf.js +0 -0
- package/dist/src/browser/pdf.js.map +0 -0
- package/dist/src/browser/profiler.d.ts +0 -0
- package/dist/src/browser/profiler.d.ts.map +0 -0
- package/dist/src/browser/profiler.js +0 -0
- package/dist/src/browser/profiler.js.map +0 -0
- package/dist/src/browser/record.d.ts +0 -0
- package/dist/src/browser/record.d.ts.map +0 -0
- package/dist/src/browser/record.js +0 -0
- package/dist/src/browser/record.js.map +0 -0
- package/dist/src/browser/ref-cache.d.ts +6 -1
- package/dist/src/browser/ref-cache.d.ts.map +1 -1
- package/dist/src/browser/ref-cache.js +21 -3
- package/dist/src/browser/ref-cache.js.map +1 -1
- package/dist/src/browser/screenshot.d.ts +0 -0
- package/dist/src/browser/screenshot.d.ts.map +0 -0
- package/dist/src/browser/screenshot.js +0 -0
- package/dist/src/browser/screenshot.js.map +0 -0
- package/dist/src/browser/session.d.ts +0 -0
- package/dist/src/browser/session.d.ts.map +0 -0
- package/dist/src/browser/session.js +0 -0
- package/dist/src/browser/session.js.map +0 -0
- package/dist/src/browser/snapshot.d.ts +0 -0
- package/dist/src/browser/snapshot.d.ts.map +0 -0
- package/dist/src/browser/snapshot.js +0 -0
- package/dist/src/browser/snapshot.js.map +0 -0
- package/dist/src/browser/storage.d.ts +0 -0
- package/dist/src/browser/storage.d.ts.map +0 -0
- package/dist/src/browser/storage.js +0 -0
- package/dist/src/browser/storage.js.map +0 -0
- package/dist/src/browser/tabs.d.ts +5 -1
- package/dist/src/browser/tabs.d.ts.map +1 -1
- package/dist/src/browser/tabs.js +4 -3
- package/dist/src/browser/tabs.js.map +1 -1
- package/dist/src/browser/trace.d.ts +0 -0
- package/dist/src/browser/trace.d.ts.map +0 -0
- package/dist/src/browser/trace.js +0 -0
- package/dist/src/browser/trace.js.map +0 -0
- package/dist/src/browser/types.d.ts +4 -0
- package/dist/src/browser/types.d.ts.map +1 -1
- package/dist/src/browser/types.js +0 -0
- package/dist/src/browser/types.js.map +1 -1
- package/dist/src/browser/vitals.d.ts +0 -0
- package/dist/src/browser/vitals.d.ts.map +0 -0
- package/dist/src/browser/vitals.js +0 -0
- package/dist/src/browser/vitals.js.map +0 -0
- package/dist/src/browser/wait.d.ts +0 -0
- package/dist/src/browser/wait.d.ts.map +0 -0
- package/dist/src/browser/wait.js +0 -0
- package/dist/src/browser/wait.js.map +0 -0
- package/dist/src/cli/action.d.ts +0 -0
- package/dist/src/cli/action.d.ts.map +0 -0
- package/dist/src/cli/action.js +0 -0
- package/dist/src/cli/action.js.map +0 -0
- package/dist/src/cli/commands.d.ts +0 -0
- package/dist/src/cli/commands.d.ts.map +1 -1
- package/dist/src/cli/commands.js +96 -4
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/output.d.ts +0 -0
- package/dist/src/cli/output.d.ts.map +0 -0
- package/dist/src/cli/output.js +0 -0
- package/dist/src/cli/output.js.map +0 -0
- package/dist/src/cli/platform.d.ts +0 -0
- package/dist/src/cli/platform.d.ts.map +0 -0
- package/dist/src/cli/platform.js +0 -0
- package/dist/src/cli/platform.js.map +0 -0
- package/dist/src/cli/types.d.ts +0 -0
- package/dist/src/cli/types.d.ts.map +0 -0
- package/dist/src/cli/types.js +0 -0
- package/dist/src/cli/types.js.map +0 -0
- package/dist/src/cli.d.ts +0 -0
- package/dist/src/cli.d.ts.map +0 -0
- package/dist/src/cli.js.map +0 -0
- package/dist/src/index.d.ts +0 -0
- package/dist/src/index.d.ts.map +0 -0
- package/dist/src/index.js +0 -0
- package/dist/src/index.js.map +0 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -8
- package/src/__tests__/adapters.test.ts +125 -0
- package/src/__tests__/batch-eval-parsing.test.ts +0 -0
- package/src/__tests__/box-model.test.ts +0 -0
- package/src/__tests__/browser-launch.test.ts +0 -0
- package/src/__tests__/cdp-client.test.ts +375 -0
- package/src/__tests__/close-browser.test.ts +205 -0
- package/src/__tests__/console-log.test.ts +250 -0
- package/src/__tests__/find.test.ts +354 -0
- package/src/__tests__/frame-command.test.ts +98 -0
- package/src/__tests__/output.test.ts +0 -0
- package/src/__tests__/ref-cache.test.ts +47 -2
- package/src/__tests__/session-state.test.ts +251 -0
- package/src/__tests__/snapshot.test.ts +331 -0
- package/src/__tests__/storage-eval.test.ts +171 -0
- package/src/__tests__/tabs.test.ts +183 -0
- package/src/browser/action-builder/analyzer.ts +0 -0
- package/src/browser/action-builder/types.ts +0 -0
- package/src/browser/actions.ts +0 -0
- package/src/browser/adapters/gemini.ts +0 -0
- package/src/browser/adapters/google.ts +0 -0
- package/src/browser/adapters/index.ts +0 -0
- package/src/browser/adapters/instagram.ts +0 -0
- package/src/browser/adapters/linkedin.ts +0 -0
- package/src/browser/adapters/microsoft.ts +0 -0
- package/src/browser/adapters/x.ts +0 -0
- package/src/browser/batch.ts +0 -0
- package/src/browser/browser.ts +101 -16
- package/src/browser/cdp.ts +52 -15
- package/src/browser/console-log.ts +0 -0
- package/src/browser/dashboard/api-types.ts +0 -0
- package/src/browser/dashboard/server.ts +0 -0
- package/src/browser/dashboard/ui.html +0 -0
- package/src/browser/dialog.ts +0 -0
- package/src/browser/emulation.ts +0 -0
- package/src/browser/find.ts +0 -0
- package/src/browser/har.ts +0 -0
- package/src/browser/index.ts +0 -0
- package/src/browser/network.ts +0 -0
- package/src/browser/pdf.ts +0 -0
- package/src/browser/profiler.ts +0 -0
- package/src/browser/record.ts +0 -0
- package/src/browser/ref-cache.ts +29 -6
- package/src/browser/screenshot.ts +0 -0
- package/src/browser/session.ts +0 -0
- package/src/browser/snapshot.ts +0 -0
- package/src/browser/storage.ts +0 -0
- package/src/browser/tabs.ts +12 -4
- package/src/browser/trace.ts +0 -0
- package/src/browser/types.ts +4 -0
- package/src/browser/vitals.ts +0 -0
- package/src/browser/wait.ts +0 -0
- package/src/cli/action.ts +0 -0
- package/src/cli/commands.ts +90 -4
- package/src/cli/output.ts +0 -0
- package/src/cli/platform.ts +0 -0
- package/src/cli/types.ts +0 -0
- package/src/cli.ts +0 -0
- package/src/index.ts +0 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* captureSnapshot turns a CDP accessibility tree into the indented ref-tagged
|
|
3
|
+
* text the agent actually reads, and into the ref Map every later command
|
|
4
|
+
* resolves against. That transform is pure — the only browser interaction is
|
|
5
|
+
* the AX-tree fetch, which a stub answers from a literal node array.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
8
|
+
import { captureSnapshot, resolveRef, getObjectIdForRef, getElementBox } from '../browser/snapshot.js';
|
|
9
|
+
/** Client that serves a fixed AX tree plus location.href / document.title. */
|
|
10
|
+
function axClient(nodes, extra = {}) {
|
|
11
|
+
const calls = [];
|
|
12
|
+
const client = {
|
|
13
|
+
send: vi.fn(async (method, params) => {
|
|
14
|
+
calls.push(method);
|
|
15
|
+
switch (method) {
|
|
16
|
+
case 'Accessibility.getFullAXTree':
|
|
17
|
+
return { nodes };
|
|
18
|
+
case 'Accessibility.getPartialAXTree':
|
|
19
|
+
return { nodes: extra.partial ?? nodes };
|
|
20
|
+
case 'DOM.getDocument':
|
|
21
|
+
return { root: { nodeId: 1 } };
|
|
22
|
+
case 'DOM.querySelector':
|
|
23
|
+
return { nodeId: extra.selectorNodeId ?? 0 };
|
|
24
|
+
case 'Runtime.evaluate':
|
|
25
|
+
return {
|
|
26
|
+
result: {
|
|
27
|
+
value: params.expression === 'location.href'
|
|
28
|
+
? extra.url ?? 'https://x.test/'
|
|
29
|
+
: extra.title ?? 'Test Page',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
default:
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
return { client, calls };
|
|
38
|
+
}
|
|
39
|
+
const button = (id, name, over = {}) => ({
|
|
40
|
+
nodeId: id,
|
|
41
|
+
role: { value: 'button' },
|
|
42
|
+
name: { value: name },
|
|
43
|
+
backendDOMNodeId: id * 10,
|
|
44
|
+
...over,
|
|
45
|
+
});
|
|
46
|
+
describe('captureSnapshot — tree shape', () => {
|
|
47
|
+
it('emits one indented line per rendered node and a matching ref map', async () => {
|
|
48
|
+
const { client } = axClient([
|
|
49
|
+
{ nodeId: 1, role: { value: 'RootWebArea' }, name: { value: 'Test Page' }, childIds: [2] },
|
|
50
|
+
{ nodeId: 2, role: { value: 'form' }, name: { value: 'Login' }, parentId: 1, childIds: [3] },
|
|
51
|
+
button(3, 'Submit', { parentId: 2 }),
|
|
52
|
+
]);
|
|
53
|
+
const snap = await captureSnapshot(client, 'S1');
|
|
54
|
+
expect(snap.text).toBe('RootWebArea "Test Page" [ref=e1]\n' +
|
|
55
|
+
' form "Login" [ref=e2]\n' +
|
|
56
|
+
' button "Submit" [ref=e3]');
|
|
57
|
+
expect([...snap.refs.keys()]).toEqual(['e1', 'e2', 'e3']);
|
|
58
|
+
expect(snap.refs.get('e3')).toMatchObject({ role: 'button', name: 'Submit', backendDOMNodeId: 30 });
|
|
59
|
+
expect(snap).toMatchObject({ url: 'https://x.test/', title: 'Test Page' });
|
|
60
|
+
});
|
|
61
|
+
it('numbers refs sequentially in document order', async () => {
|
|
62
|
+
const { client } = axClient([
|
|
63
|
+
{ nodeId: 1, role: { value: 'RootWebArea' }, name: { value: '' }, childIds: [2, 3] },
|
|
64
|
+
button(2, 'A', { parentId: 1 }),
|
|
65
|
+
button(3, 'B', { parentId: 1 }),
|
|
66
|
+
]);
|
|
67
|
+
const snap = await captureSnapshot(client, 'S1');
|
|
68
|
+
expect(snap.refs.get('e2').name).toBe('A');
|
|
69
|
+
expect(snap.refs.get('e3').name).toBe('B');
|
|
70
|
+
});
|
|
71
|
+
it('flattens generic/none/inlineTextBox wrappers without consuming an indent level', async () => {
|
|
72
|
+
const { client } = axClient([
|
|
73
|
+
{ nodeId: 1, role: { value: 'RootWebArea' }, name: { value: '' }, childIds: [2] },
|
|
74
|
+
{ nodeId: 2, role: { value: 'generic' }, parentId: 1, childIds: [3] },
|
|
75
|
+
{ nodeId: 3, role: { value: 'none' }, parentId: 2, childIds: [4] },
|
|
76
|
+
button(4, 'Deep', { parentId: 3 }),
|
|
77
|
+
]);
|
|
78
|
+
const snap = await captureSnapshot(client, 'S1');
|
|
79
|
+
// The button sits one level under the root despite two wrappers between.
|
|
80
|
+
expect(snap.text).toBe('RootWebArea [ref=e1]\n button "Deep" [ref=e2]');
|
|
81
|
+
});
|
|
82
|
+
it('descends through ignored nodes without emitting or numbering them', async () => {
|
|
83
|
+
const { client } = axClient([
|
|
84
|
+
{ nodeId: 1, role: { value: 'RootWebArea' }, name: { value: '' }, childIds: [2] },
|
|
85
|
+
{ nodeId: 2, ignored: true, role: { value: 'button' }, parentId: 1, childIds: [3] },
|
|
86
|
+
button(3, 'Visible', { parentId: 2 }),
|
|
87
|
+
]);
|
|
88
|
+
const snap = await captureSnapshot(client, 'S1');
|
|
89
|
+
expect([...snap.refs.values()].map((r) => r.name)).toEqual(['', 'Visible']);
|
|
90
|
+
});
|
|
91
|
+
it('handles a forest whose parentId points outside the returned node set', async () => {
|
|
92
|
+
const { client } = axClient([
|
|
93
|
+
button(5, 'Orphan A', { parentId: 999 }),
|
|
94
|
+
button(6, 'Orphan B', { parentId: 998 }),
|
|
95
|
+
]);
|
|
96
|
+
const snap = await captureSnapshot(client, 'S1');
|
|
97
|
+
expect(snap.text.split('\n')).toHaveLength(2);
|
|
98
|
+
expect(snap.text).not.toMatch(/^ /m);
|
|
99
|
+
});
|
|
100
|
+
it('returns empty text and no refs for an empty tree', async () => {
|
|
101
|
+
const { client } = axClient([]);
|
|
102
|
+
const snap = await captureSnapshot(client, 'S1');
|
|
103
|
+
expect(snap.text).toBe('');
|
|
104
|
+
expect(snap.refs.size).toBe(0);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
describe('captureSnapshot — options', () => {
|
|
108
|
+
const page = [
|
|
109
|
+
{ nodeId: 1, role: { value: 'RootWebArea' }, name: { value: 'P' }, childIds: [2] },
|
|
110
|
+
{ nodeId: 2, role: { value: 'main' }, name: { value: '' }, parentId: 1, childIds: [3, 4] },
|
|
111
|
+
button(3, 'Go', { parentId: 2 }),
|
|
112
|
+
{ nodeId: 4, role: { value: 'paragraph' }, name: { value: 'text' }, parentId: 2 },
|
|
113
|
+
];
|
|
114
|
+
it('interactiveOnly keeps interactive roles but still walks past containers', async () => {
|
|
115
|
+
const { client } = axClient(page);
|
|
116
|
+
const snap = await captureSnapshot(client, 'S1', { interactiveOnly: true });
|
|
117
|
+
expect([...snap.refs.values()].map((r) => r.role)).toEqual(['button']);
|
|
118
|
+
// Skipped containers recurse with the SAME depth, so the surviving
|
|
119
|
+
// interactive nodes come out flat rather than indented by the levels of
|
|
120
|
+
// markup that were filtered away.
|
|
121
|
+
expect(snap.text).toBe('button "Go" [ref=e1]');
|
|
122
|
+
});
|
|
123
|
+
it('compact drops indentation and descriptions', async () => {
|
|
124
|
+
const { client } = axClient([
|
|
125
|
+
button(1, 'Go', { description: { value: 'primary action' } }),
|
|
126
|
+
]);
|
|
127
|
+
const spaced = await captureSnapshot(client, 'S1');
|
|
128
|
+
expect(spaced.text).toBe('button "Go" (primary action) [ref=e1]');
|
|
129
|
+
const { client: c2 } = axClient([button(1, 'Go', { description: { value: 'primary action' } })]);
|
|
130
|
+
const compact = await captureSnapshot(c2, 'S1', { compact: true });
|
|
131
|
+
expect(compact.text).toBe('button "Go" [ref=e1]');
|
|
132
|
+
// The description is still available on the ref even when not printed.
|
|
133
|
+
expect(compact.refs.get('e1').description).toBe('primary action');
|
|
134
|
+
});
|
|
135
|
+
it('maxDepth prunes nodes below the budget', async () => {
|
|
136
|
+
const { client } = axClient(page);
|
|
137
|
+
const snap = await captureSnapshot(client, 'S1', { maxDepth: 1 });
|
|
138
|
+
expect([...snap.refs.values()].map((r) => r.role)).toEqual(['RootWebArea', 'main']);
|
|
139
|
+
});
|
|
140
|
+
it('a selector scope uses getPartialAXTree when the selector resolves', async () => {
|
|
141
|
+
const { client, calls } = axClient(page, {
|
|
142
|
+
selectorNodeId: 42,
|
|
143
|
+
partial: [button(7, 'Scoped')],
|
|
144
|
+
});
|
|
145
|
+
const snap = await captureSnapshot(client, 'S1', { selector: '#form' });
|
|
146
|
+
expect(calls).toContain('Accessibility.getPartialAXTree');
|
|
147
|
+
expect(calls).not.toContain('Accessibility.getFullAXTree');
|
|
148
|
+
expect(snap.text).toBe('button "Scoped" [ref=e1]');
|
|
149
|
+
});
|
|
150
|
+
it('a selector that matches nothing falls back to the full tree', async () => {
|
|
151
|
+
const { client, calls } = axClient(page, { selectorNodeId: 0 });
|
|
152
|
+
await captureSnapshot(client, 'S1', { selector: '#nope' });
|
|
153
|
+
expect(calls).toContain('Accessibility.getFullAXTree');
|
|
154
|
+
expect(calls).not.toContain('Accessibility.getPartialAXTree');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
describe('captureSnapshot — property extraction and rendering', () => {
|
|
158
|
+
async function snapOf(props, over = {}) {
|
|
159
|
+
const { client } = axClient([
|
|
160
|
+
{ nodeId: 1, role: { value: 'textbox' }, name: { value: 'Email' }, properties: props, ...over },
|
|
161
|
+
]);
|
|
162
|
+
return captureSnapshot(client, 'S1');
|
|
163
|
+
}
|
|
164
|
+
it('renders value, placeholder and required', async () => {
|
|
165
|
+
const snap = await snapOf([
|
|
166
|
+
{ name: 'value', value: { value: 'a@b.c' } },
|
|
167
|
+
{ name: 'placeholder', value: { value: 'you@example.com' } },
|
|
168
|
+
{ name: 'required', value: { value: true } },
|
|
169
|
+
]);
|
|
170
|
+
expect(snap.text).toBe('textbox "Email" [ref=e1, value="a@b.c", placeholder="you@example.com", required]');
|
|
171
|
+
expect(snap.refs.get('e1')).toMatchObject({ value: 'a@b.c', placeholder: 'you@example.com' });
|
|
172
|
+
});
|
|
173
|
+
it('renders disabled as a bare flag only when true', async () => {
|
|
174
|
+
expect((await snapOf([{ name: 'disabled', value: { value: true } }])).text).toContain(', disabled]');
|
|
175
|
+
expect((await snapOf([{ name: 'disabled', value: { value: false } }])).text).not.toContain('disabled');
|
|
176
|
+
});
|
|
177
|
+
it('accepts checked/expanded as either the boolean or the string "true"', async () => {
|
|
178
|
+
const asString = await snapOf([
|
|
179
|
+
{ name: 'checked', value: { value: 'true' } },
|
|
180
|
+
{ name: 'expanded', value: { value: 'true' } },
|
|
181
|
+
]);
|
|
182
|
+
expect(asString.refs.get('e1')).toMatchObject({ checked: true, expanded: true });
|
|
183
|
+
const asBool = await snapOf([
|
|
184
|
+
{ name: 'checked', value: { value: true } },
|
|
185
|
+
{ name: 'expanded', value: { value: false } },
|
|
186
|
+
]);
|
|
187
|
+
expect(asBool.refs.get('e1')).toMatchObject({ checked: true, expanded: false });
|
|
188
|
+
// false is still rendered — the distinction from "absent" matters.
|
|
189
|
+
expect(asBool.text).toContain('expanded=false');
|
|
190
|
+
});
|
|
191
|
+
it('treats CDP\'s "mixed" checkbox state as not-checked', async () => {
|
|
192
|
+
const snap = await snapOf([{ name: 'checked', value: { value: 'mixed' } }]);
|
|
193
|
+
expect(snap.refs.get('e1').checked).toBe(false);
|
|
194
|
+
});
|
|
195
|
+
it('ignores a null property value rather than stringifying it', async () => {
|
|
196
|
+
const snap = await snapOf([{ name: 'value', value: { value: null } }]);
|
|
197
|
+
expect(snap.refs.get('e1').value).toBeUndefined();
|
|
198
|
+
expect(snap.text).not.toContain('value=');
|
|
199
|
+
});
|
|
200
|
+
it('falls back to node.value when no value property is present', async () => {
|
|
201
|
+
const snap = await snapOf([], { value: { value: 'from-node' } });
|
|
202
|
+
expect(snap.refs.get('e1').value).toBe('from-node');
|
|
203
|
+
});
|
|
204
|
+
it('prefers the value property over node.value', async () => {
|
|
205
|
+
const snap = await snapOf([{ name: 'value', value: { value: 'from-prop' } }], {
|
|
206
|
+
value: { value: 'from-node' },
|
|
207
|
+
});
|
|
208
|
+
expect(snap.refs.get('e1').value).toBe('from-prop');
|
|
209
|
+
});
|
|
210
|
+
it('ignores unrecognized properties', async () => {
|
|
211
|
+
const snap = await snapOf([{ name: 'invalid', value: { value: true } }]);
|
|
212
|
+
expect(snap.text).toBe('textbox "Email" [ref=e1]');
|
|
213
|
+
});
|
|
214
|
+
it('omits the quoted name entirely when the node is unnamed', async () => {
|
|
215
|
+
const { client } = axClient([{ nodeId: 1, role: { value: 'button' }, name: { value: '' } }]);
|
|
216
|
+
expect((await captureSnapshot(client, 'S1')).text).toBe('button [ref=e1]');
|
|
217
|
+
});
|
|
218
|
+
it('defaults a role-less node to generic, which flattens it away', async () => {
|
|
219
|
+
const { client } = axClient([{ nodeId: 1, name: { value: 'x' } }]);
|
|
220
|
+
expect((await captureSnapshot(client, 'S1')).text).toBe('');
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
describe('resolveRef', () => {
|
|
224
|
+
it('returns the ref when present', () => {
|
|
225
|
+
const refs = new Map([
|
|
226
|
+
['e1', { ref: 'e1', role: 'button', name: 'Go', nodeId: 1 }],
|
|
227
|
+
]);
|
|
228
|
+
expect(resolveRef({}, 'S1', refs, 'e1')).resolves.toMatchObject({ ref: 'e1' });
|
|
229
|
+
});
|
|
230
|
+
it('throws a snapshot-first hint when the ref is unknown', async () => {
|
|
231
|
+
await expect(resolveRef({}, 'S1', new Map(), 'e9')).rejects.toThrow('Element ref @e9 not found. Run snapshot first.');
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
describe('getObjectIdForRef', () => {
|
|
235
|
+
it('short-circuits to null without a CDP call when there is no backend node id', async () => {
|
|
236
|
+
const send = vi.fn();
|
|
237
|
+
const client = { send };
|
|
238
|
+
await expect(getObjectIdForRef(client, 'S1', { ref: 'e1', role: 'button', name: '', nodeId: 1 })).resolves.toBeNull();
|
|
239
|
+
expect(send).not.toHaveBeenCalled();
|
|
240
|
+
});
|
|
241
|
+
it('resolves via DOM.resolveNode on the backend node id', async () => {
|
|
242
|
+
const send = vi.fn(async () => ({ object: { objectId: 'OBJ' } }));
|
|
243
|
+
const client = { send };
|
|
244
|
+
await expect(getObjectIdForRef(client, 'S1', { ref: 'e1', role: 'button', name: '', nodeId: 1, backendDOMNodeId: 77 })).resolves.toBe('OBJ');
|
|
245
|
+
expect(send.mock.calls[0]).toEqual(['DOM.resolveNode', { backendNodeId: 77 }, 'S1']);
|
|
246
|
+
});
|
|
247
|
+
it('returns null when the node no longer resolves to an object', async () => {
|
|
248
|
+
const client = { send: vi.fn(async () => ({ object: {} })) };
|
|
249
|
+
await expect(getObjectIdForRef(client, 'S1', { ref: 'e1', role: 'button', name: '', nodeId: 1, backendDOMNodeId: 77 })).resolves.toBeNull();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
describe('getElementBox', () => {
|
|
253
|
+
it('reports the CENTER of the content quad, per the documented contract', async () => {
|
|
254
|
+
const client = {
|
|
255
|
+
send: vi.fn(async () => ({
|
|
256
|
+
model: { content: [10, 20, 110, 20, 110, 60, 10, 60], width: 100, height: 40 },
|
|
257
|
+
})),
|
|
258
|
+
};
|
|
259
|
+
await expect(getElementBox(client, 'S1', { ref: 'e1', role: 'button', name: '', nodeId: 1, backendDOMNodeId: 5 })).resolves.toEqual({ x: 60, y: 40, width: 100, height: 40 });
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
//# sourceMappingURL=snapshot.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.test.js","sourceRoot":"","sources":["../../../src/__tests__/snapshot.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-eval.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/storage-eval.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* storage.ts builds localStorage/sessionStorage expressions by string
|
|
3
|
+
* concatenation, so the JSON.stringify escaping is the only thing standing
|
|
4
|
+
* between a user-supplied key and script injection into the page. That plus
|
|
5
|
+
* evaluateJs's error/timeout contract is what this file pins down.
|
|
6
|
+
* Fake client throughout — no browser.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
9
|
+
import { evaluateJs } from '../browser/actions.js';
|
|
10
|
+
import { getLocalStorageKey, setLocalStorageKey, removeLocalStorageKey, clearLocalStorage, getAllLocalStorage, getSessionStorageKey, setSessionStorageKey, removeSessionStorageKey, clearSessionStorage, getAllSessionStorage, } from '../browser/storage.js';
|
|
11
|
+
/** Client stub returning a fixed Runtime.evaluate value, recording expressions. */
|
|
12
|
+
function stubClient(value, exceptionDetails) {
|
|
13
|
+
const exprs = [];
|
|
14
|
+
const sids = [];
|
|
15
|
+
const client = {
|
|
16
|
+
send: vi.fn(async (_m, params, sid) => {
|
|
17
|
+
exprs.push(params.expression);
|
|
18
|
+
sids.push(sid);
|
|
19
|
+
return exceptionDetails ? { result: {}, exceptionDetails } : { result: { value, type: 'string' } };
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
return { client, exprs, sids };
|
|
23
|
+
}
|
|
24
|
+
describe('evaluateJs', () => {
|
|
25
|
+
it('sends Runtime.evaluate with returnByValue and awaitPromise, scoped to the session', async () => {
|
|
26
|
+
const client = {
|
|
27
|
+
send: vi.fn(async () => ({ result: { value: 42, type: 'number' } })),
|
|
28
|
+
};
|
|
29
|
+
await expect(evaluateJs(client, 'S1', '1 + 41')).resolves.toBe(42);
|
|
30
|
+
expect(client.send.mock.calls[0]).toEqual([
|
|
31
|
+
'Runtime.evaluate',
|
|
32
|
+
{ expression: '1 + 41', returnByValue: true, awaitPromise: true },
|
|
33
|
+
'S1',
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
it('surfaces the exception description when the page throws', async () => {
|
|
37
|
+
const { client } = stubClient(undefined, {
|
|
38
|
+
text: 'Uncaught',
|
|
39
|
+
exception: { description: 'ReferenceError: foo is not defined' },
|
|
40
|
+
});
|
|
41
|
+
await expect(evaluateJs(client, 'S1', 'foo')).rejects.toThrow('JS evaluation error: ReferenceError: foo is not defined');
|
|
42
|
+
});
|
|
43
|
+
it('falls back to exceptionDetails.text when there is no description', async () => {
|
|
44
|
+
const { client } = stubClient(undefined, { text: 'Uncaught SyntaxError' });
|
|
45
|
+
await expect(evaluateJs(client, 'S1', '{{')).rejects.toThrow('JS evaluation error: Uncaught SyntaxError');
|
|
46
|
+
});
|
|
47
|
+
it('returns undefined for an expression with no value', async () => {
|
|
48
|
+
const client = { send: vi.fn(async () => ({ result: { type: 'undefined' } })) };
|
|
49
|
+
await expect(evaluateJs(client, 'S1', 'void 0')).resolves.toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
it('rejects rather than hanging when the page never settles the promise', async () => {
|
|
52
|
+
// awaitPromise:true means a never-settling expression would hang forever
|
|
53
|
+
// without the race; a 20ms budget proves the timer is armed.
|
|
54
|
+
const client = { send: vi.fn(() => new Promise(() => { })) };
|
|
55
|
+
await expect(evaluateJs(client, 'S1', 'new Promise(() => {})', 20)).rejects.toThrow('JS evaluation timed out after 20ms');
|
|
56
|
+
});
|
|
57
|
+
it('waits indefinitely when the timeout is disabled with 0', async () => {
|
|
58
|
+
let resolveSend = () => { };
|
|
59
|
+
const client = {
|
|
60
|
+
send: vi.fn(() => new Promise((r) => { resolveSend = r; })),
|
|
61
|
+
};
|
|
62
|
+
const p = evaluateJs(client, 'S1', 'slow()', 0);
|
|
63
|
+
resolveSend({ result: { value: 'done', type: 'string' } });
|
|
64
|
+
await expect(p).resolves.toBe('done');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('localStorage helpers', () => {
|
|
68
|
+
it('reads a key through a JSON-escaped getItem call', async () => {
|
|
69
|
+
const { client, exprs, sids } = stubClient('v');
|
|
70
|
+
await expect(getLocalStorageKey(client, 'S1', 'token')).resolves.toBe('v');
|
|
71
|
+
expect(exprs[0]).toBe('localStorage.getItem("token")');
|
|
72
|
+
expect(sids[0]).toBe('S1');
|
|
73
|
+
});
|
|
74
|
+
it('escapes a key containing quotes and backslashes instead of breaking out', async () => {
|
|
75
|
+
const { client, exprs } = stubClient(null);
|
|
76
|
+
await getLocalStorageKey(client, 'S1', 'a"); alert(1); //');
|
|
77
|
+
expect(exprs[0]).toBe('localStorage.getItem("a\\"); alert(1); //")');
|
|
78
|
+
// The payload must survive as *one* string literal: round-tripping the
|
|
79
|
+
// argument back through JSON.parse recovers the key verbatim, which it
|
|
80
|
+
// could not if the quote had terminated the literal early.
|
|
81
|
+
const arg = exprs[0].slice('localStorage.getItem('.length, -1);
|
|
82
|
+
expect(JSON.parse(arg)).toBe('a"); alert(1); //');
|
|
83
|
+
});
|
|
84
|
+
it('escapes newlines in a key so the expression stays one statement', async () => {
|
|
85
|
+
const { client, exprs } = stubClient(null);
|
|
86
|
+
await removeLocalStorageKey(client, 'S1', 'a\nb');
|
|
87
|
+
expect(exprs[0]).toBe('localStorage.removeItem("a\\nb")');
|
|
88
|
+
expect(exprs[0]).not.toContain('\n');
|
|
89
|
+
});
|
|
90
|
+
it('escapes both key and value on write', async () => {
|
|
91
|
+
const { client, exprs } = stubClient();
|
|
92
|
+
await setLocalStorageKey(client, 'S1', 'k"1', 'v"2');
|
|
93
|
+
expect(exprs[0]).toBe('localStorage.setItem("k\\"1", "v\\"2")');
|
|
94
|
+
});
|
|
95
|
+
it('clears with a literal expression', async () => {
|
|
96
|
+
const { client, exprs } = stubClient();
|
|
97
|
+
await clearLocalStorage(client, 'S1');
|
|
98
|
+
expect(exprs[0]).toBe('localStorage.clear()');
|
|
99
|
+
});
|
|
100
|
+
it('getAll parses the JSON dump', async () => {
|
|
101
|
+
const { client } = stubClient('{"a":"1","b":"2"}');
|
|
102
|
+
await expect(getAllLocalStorage(client, 'S1')).resolves.toEqual({ a: '1', b: '2' });
|
|
103
|
+
});
|
|
104
|
+
it('getAll degrades to {} on unparseable output rather than throwing', async () => {
|
|
105
|
+
const { client } = stubClient('not json');
|
|
106
|
+
await expect(getAllLocalStorage(client, 'S1')).resolves.toEqual({});
|
|
107
|
+
});
|
|
108
|
+
it('getItem returning null (missing key) is passed through as null', async () => {
|
|
109
|
+
const { client } = stubClient(null);
|
|
110
|
+
await expect(getLocalStorageKey(client, 'S1', 'missing')).resolves.toBeNull();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('sessionStorage helpers mirror the localStorage ones', () => {
|
|
114
|
+
it('read / write / remove / clear all target sessionStorage', async () => {
|
|
115
|
+
const { client, exprs } = stubClient('v');
|
|
116
|
+
await getSessionStorageKey(client, 'S1', 'k');
|
|
117
|
+
await setSessionStorageKey(client, 'S1', 'k', 'v');
|
|
118
|
+
await removeSessionStorageKey(client, 'S1', 'k');
|
|
119
|
+
await clearSessionStorage(client, 'S1');
|
|
120
|
+
expect(exprs).toEqual([
|
|
121
|
+
'sessionStorage.getItem("k")',
|
|
122
|
+
'sessionStorage.setItem("k", "v")',
|
|
123
|
+
'sessionStorage.removeItem("k")',
|
|
124
|
+
'sessionStorage.clear()',
|
|
125
|
+
]);
|
|
126
|
+
expect(exprs.some((e) => e.includes('localStorage'))).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
it('getAll parses, and degrades to {} on garbage', async () => {
|
|
129
|
+
await expect(getAllSessionStorage(stubClient('{"s":"1"}').client, 'S1')).resolves.toEqual({ s: '1' });
|
|
130
|
+
await expect(getAllSessionStorage(stubClient('<html>').client, 'S1')).resolves.toEqual({});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
//# sourceMappingURL=storage-eval.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-eval.test.js","sourceRoot":"","sources":["../../../src/__tests__/storage-eval.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/tabs.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tabs.ts — target filtering and the CDP command sequences for
|
|
3
|
+
* close/activate/frame-switch. Fake fetch + fake client; no browser.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, afterEach, vi } from 'vitest';
|
|
6
|
+
import { listTabs, newTab, closeTab, activateTab, switchToFrame } from '../browser/tabs.js';
|
|
7
|
+
const realFetch = globalThis.fetch;
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
globalThis.fetch = realFetch;
|
|
10
|
+
vi.restoreAllMocks();
|
|
11
|
+
});
|
|
12
|
+
function stubFetch(body, init = { status: 200 }) {
|
|
13
|
+
const fn = vi.fn(() => Promise.resolve(new Response(JSON.stringify(body), init)));
|
|
14
|
+
globalThis.fetch = fn;
|
|
15
|
+
return fn;
|
|
16
|
+
}
|
|
17
|
+
/** Client stub that records calls and answers from a per-method table. */
|
|
18
|
+
function stubClient(table = {}) {
|
|
19
|
+
const calls = [];
|
|
20
|
+
const client = {
|
|
21
|
+
send: vi.fn(async (method, params, sid) => {
|
|
22
|
+
calls.push({ method, params, sid });
|
|
23
|
+
const entry = table[method];
|
|
24
|
+
if (entry instanceof Error)
|
|
25
|
+
throw entry;
|
|
26
|
+
return entry ?? {};
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
return { client, calls };
|
|
30
|
+
}
|
|
31
|
+
describe('listTabs', () => {
|
|
32
|
+
it('keeps only page targets, dropping service workers, iframes and extensions', async () => {
|
|
33
|
+
stubFetch([
|
|
34
|
+
{ id: 'A', type: 'page', url: 'https://x.test' },
|
|
35
|
+
{ id: 'B', type: 'service_worker', url: 'https://x.test/sw.js' },
|
|
36
|
+
{ id: 'C', type: 'iframe', url: 'https://ads.test' },
|
|
37
|
+
{ id: 'D', type: 'background_page', url: 'chrome-extension://abc' },
|
|
38
|
+
{ id: 'E', type: 'page', url: 'about:blank' },
|
|
39
|
+
]);
|
|
40
|
+
const tabs = await listTabs(9222);
|
|
41
|
+
expect(tabs.map((t) => t.id)).toEqual(['A', 'E']);
|
|
42
|
+
});
|
|
43
|
+
it('returns [] when Chrome reports no targets at all', async () => {
|
|
44
|
+
stubFetch([]);
|
|
45
|
+
await expect(listTabs(9222)).resolves.toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
it('propagates a failed /json/list', async () => {
|
|
48
|
+
globalThis.fetch = vi.fn(() => Promise.resolve(new Response('', { status: 502, statusText: 'Bad Gateway' })));
|
|
49
|
+
await expect(listTabs(9222)).rejects.toThrow('Failed to fetch targets: Bad Gateway');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe('newTab', () => {
|
|
53
|
+
it('defaults to about:blank', async () => {
|
|
54
|
+
const fn = stubFetch({ id: 'T1', type: 'page' });
|
|
55
|
+
await newTab(9222);
|
|
56
|
+
expect(fn.mock.calls[0][0]).toBe('http://127.0.0.1:9222/json/new?' + encodeURIComponent('about:blank'));
|
|
57
|
+
});
|
|
58
|
+
it('passes an explicit url through, encoded', async () => {
|
|
59
|
+
const fn = stubFetch({ id: 'T1', type: 'page' });
|
|
60
|
+
await newTab(9222, 'https://x.test/a b?q=1');
|
|
61
|
+
expect(fn.mock.calls[0][0]).toContain(encodeURIComponent('https://x.test/a b?q=1'));
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('closeTab', () => {
|
|
65
|
+
it('sends Target.closeTarget for the given targetId', async () => {
|
|
66
|
+
const { client, calls } = stubClient();
|
|
67
|
+
await closeTab(client, 'S1', 'T9');
|
|
68
|
+
expect(calls).toEqual([{ method: 'Target.closeTarget', params: { targetId: 'T9' }, sid: undefined }]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('activateTab', () => {
|
|
72
|
+
it('detaches the old session, activates, then re-attaches flat and returns the new sessionId', async () => {
|
|
73
|
+
const { client, calls } = stubClient({ 'Target.attachToTarget': { sessionId: 'S-NEW' } });
|
|
74
|
+
await expect(activateTab(client, 'S-OLD', 'T9')).resolves.toBe('S-NEW');
|
|
75
|
+
expect(calls.map((c) => c.method)).toEqual([
|
|
76
|
+
'Target.detachFromTarget',
|
|
77
|
+
'Target.activateTarget',
|
|
78
|
+
'Target.attachToTarget',
|
|
79
|
+
]);
|
|
80
|
+
expect(calls[0].params).toEqual({ sessionId: 'S-OLD' });
|
|
81
|
+
expect(calls[2].params).toEqual({ targetId: 'T9', flatten: true });
|
|
82
|
+
});
|
|
83
|
+
it('skips the detach when there is no previous session', async () => {
|
|
84
|
+
const { client, calls } = stubClient({ 'Target.attachToTarget': { sessionId: 'S-NEW' } });
|
|
85
|
+
await activateTab(client, '', 'T9');
|
|
86
|
+
expect(calls.map((c) => c.method)).toEqual(['Target.activateTarget', 'Target.attachToTarget']);
|
|
87
|
+
});
|
|
88
|
+
it('tolerates a detach failure on an already-dead session', async () => {
|
|
89
|
+
const calls = [];
|
|
90
|
+
const client = {
|
|
91
|
+
send: vi.fn(async (method) => {
|
|
92
|
+
calls.push(method);
|
|
93
|
+
if (method === 'Target.detachFromTarget')
|
|
94
|
+
throw new Error('No session with given id');
|
|
95
|
+
if (method === 'Target.attachToTarget')
|
|
96
|
+
return { sessionId: 'S-NEW' };
|
|
97
|
+
return {};
|
|
98
|
+
}),
|
|
99
|
+
};
|
|
100
|
+
await expect(activateTab(client, 'S-STALE', 'T9')).resolves.toBe('S-NEW');
|
|
101
|
+
expect(calls).toContain('Target.activateTarget');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('switchToFrame', () => {
|
|
105
|
+
const frameTreeWith = (url) => ({
|
|
106
|
+
frameTree: { childFrames: [{ frame: { id: 'F1', url, securityOrigin: 'https://f.test' } }] },
|
|
107
|
+
});
|
|
108
|
+
it('returns null url when the selector does not resolve to an IFRAME', async () => {
|
|
109
|
+
const { client, calls } = stubClient({
|
|
110
|
+
'Runtime.evaluate': { result: { result: { value: null } } },
|
|
111
|
+
'Page.getFrameTree': { frameTree: {} },
|
|
112
|
+
});
|
|
113
|
+
await expect(switchToFrame(client, 'S1', 'div.not-a-frame')).resolves.toEqual({ url: null, sessionId: null });
|
|
114
|
+
// The selector is embedded as a JSON string literal, never interpolated raw.
|
|
115
|
+
const expr = calls[0].params.expression;
|
|
116
|
+
expect(expr).toContain('document.querySelector("div.not-a-frame")');
|
|
117
|
+
});
|
|
118
|
+
it('JSON-escapes a selector containing quotes rather than breaking the expression', async () => {
|
|
119
|
+
const { client, calls } = stubClient({
|
|
120
|
+
'Runtime.evaluate': { result: { result: { value: null } } },
|
|
121
|
+
'Page.getFrameTree': { frameTree: {} },
|
|
122
|
+
});
|
|
123
|
+
await switchToFrame(client, 'S1', 'iframe[title="a\\"b"]');
|
|
124
|
+
const expr = calls[0].params.expression;
|
|
125
|
+
expect(expr).toContain(JSON.stringify('iframe[title="a\\"b"]'));
|
|
126
|
+
});
|
|
127
|
+
it('returns the frame src with null sessionId when no OOPIF target matches', async () => {
|
|
128
|
+
const { client, calls } = stubClient({
|
|
129
|
+
'Runtime.evaluate': { result: { result: { value: 'https://f.test/frame' } } },
|
|
130
|
+
'Page.getFrameTree': frameTreeWith('https://f.test/frame'),
|
|
131
|
+
'Target.getTargets': { targetInfos: [{ targetId: 'T1', type: 'page', url: 'https://x.test' }] },
|
|
132
|
+
});
|
|
133
|
+
await expect(switchToFrame(client, 'S1', 'iframe')).resolves.toEqual({ url: 'https://f.test/frame', sessionId: null });
|
|
134
|
+
expect(calls.map((c) => c.method)).not.toContain('Target.attachToTarget');
|
|
135
|
+
});
|
|
136
|
+
it('attaches flat to a matching OOPIF target and returns its sessionId', async () => {
|
|
137
|
+
const { client, calls } = stubClient({
|
|
138
|
+
'Runtime.evaluate': { result: { result: { value: 'https://f.test/frame' } } },
|
|
139
|
+
'Page.getFrameTree': frameTreeWith('https://f.test/frame'),
|
|
140
|
+
'Target.getTargets': {
|
|
141
|
+
targetInfos: [
|
|
142
|
+
{ targetId: 'T-PAGE', type: 'page', url: 'https://f.test/frame' },
|
|
143
|
+
{ targetId: 'T-OOPIF', type: 'iframe', url: 'https://f.test/frame' },
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
'Target.attachToTarget': { sessionId: 'S-FRAME' },
|
|
147
|
+
});
|
|
148
|
+
await expect(switchToFrame(client, 'S1', 'iframe')).resolves.toEqual({ url: 'https://f.test/frame', sessionId: 'S-FRAME' });
|
|
149
|
+
const attach = calls.find((c) => c.method === 'Target.attachToTarget');
|
|
150
|
+
expect(attach.params).toEqual({ targetId: 'T-OOPIF', flatten: true });
|
|
151
|
+
});
|
|
152
|
+
it('returns the src with null sessionId when the frame tree has no match', async () => {
|
|
153
|
+
const { client, calls } = stubClient({
|
|
154
|
+
'Runtime.evaluate': { result: { result: { value: 'https://f.test/frame' } } },
|
|
155
|
+
'Page.getFrameTree': frameTreeWith('https://other.test/elsewhere'),
|
|
156
|
+
});
|
|
157
|
+
await expect(switchToFrame(client, 'S1', 'iframe')).resolves.toEqual({ url: 'https://f.test/frame', sessionId: null });
|
|
158
|
+
expect(calls.map((c) => c.method)).not.toContain('Target.getTargets');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
//# sourceMappingURL=tabs.test.js.map
|