@web-auto/camo 0.1.26 → 0.2.1
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 +21 -21
- package/README.md +586 -586
- package/bin/browser-service.mjs +11 -11
- package/bin/camo.mjs +22 -22
- package/package.json +48 -48
- package/scripts/build.mjs +19 -19
- package/scripts/bump-version.mjs +34 -34
- package/scripts/check-file-size.mjs +80 -80
- package/scripts/file-size-policy.json +12 -2
- package/scripts/install.mjs +76 -76
- package/scripts/release.sh +54 -54
- package/src/autoscript/action-providers/index.mjs +6 -6
- package/src/autoscript/impact-engine.mjs +78 -78
- package/src/autoscript/runtime.mjs +1017 -1017
- package/src/autoscript/schema.mjs +376 -376
- package/src/cli.mjs +405 -405
- package/src/commands/attach.mjs +141 -141
- package/src/commands/autoscript.mjs +1011 -1011
- package/src/commands/browser.mjs +1255 -1257
- package/src/commands/container.mjs +401 -401
- package/src/commands/cookies.mjs +69 -69
- package/src/commands/create.mjs +98 -98
- package/src/commands/devtools.mjs +349 -349
- package/src/commands/events.mjs +152 -152
- package/src/commands/highlight-mode.mjs +24 -24
- package/src/commands/init.mjs +68 -68
- package/src/commands/lifecycle.mjs +275 -275
- package/src/commands/mouse.mjs +45 -45
- package/src/commands/profile.mjs +46 -46
- package/src/commands/record.mjs +115 -115
- package/src/commands/system.mjs +14 -14
- package/src/commands/window.mjs +123 -123
- package/src/container/change-notifier.mjs +362 -362
- package/src/container/element-filter.mjs +143 -143
- package/src/container/index.mjs +3 -3
- package/src/container/runtime-core/checkpoint.mjs +209 -209
- package/src/container/runtime-core/index.mjs +21 -21
- package/src/container/runtime-core/operations/index.mjs +774 -774
- package/src/container/runtime-core/operations/selector-scripts.mjs +277 -277
- package/src/container/runtime-core/operations/tab-pool.mjs +746 -746
- package/src/container/runtime-core/operations/viewport.mjs +189 -189
- package/src/container/runtime-core/search.mjs +190 -190
- package/src/container/runtime-core/subscription.mjs +224 -224
- package/src/container/runtime-core/utils.mjs +94 -94
- package/src/container/runtime-core/validation.mjs +127 -184
- package/src/container/runtime-core.mjs +1 -1
- package/src/container/subscription-registry.mjs +459 -459
- package/src/core/actions.mjs +561 -561
- package/src/core/browser.mjs +266 -266
- package/src/core/index.mjs +52 -52
- package/src/core/utils.mjs +91 -91
- package/src/events/daemon-entry.mjs +33 -33
- package/src/events/daemon.mjs +80 -80
- package/src/events/progress-log.mjs +109 -109
- package/src/events/ws-server.mjs +239 -239
- package/src/lib/client.mjs +200 -200
- package/src/lifecycle/cleanup.mjs +83 -83
- package/src/lifecycle/lock.mjs +126 -126
- package/src/lifecycle/session-registry.mjs +279 -279
- package/src/lifecycle/session-view.mjs +76 -76
- package/src/lifecycle/session-watchdog.mjs +281 -281
- package/src/services/browser-service/index.js +671 -674
- package/src/services/browser-service/internal/BrowserSession.input.test.js +389 -389
- package/src/services/browser-service/internal/BrowserSession.js +325 -336
- package/src/services/browser-service/internal/ElementRegistry.js +60 -60
- package/src/services/browser-service/internal/ProfileLock.js +84 -84
- package/src/services/browser-service/internal/SessionManager.js +184 -184
- package/src/services/browser-service/internal/SessionManager.test.js +39 -39
- package/src/services/browser-service/internal/browser-session/cookies.js +144 -144
- package/src/services/browser-service/internal/browser-session/input-ops.js +222 -219
- package/src/services/browser-service/internal/browser-session/input-pipeline.js +144 -144
- package/src/services/browser-service/internal/browser-session/logging.js +46 -46
- package/src/services/browser-service/internal/browser-session/navigation.js +38 -38
- package/src/services/browser-service/internal/browser-session/page-hooks.js +442 -442
- package/src/services/browser-service/internal/browser-session/page-management.js +302 -336
- package/src/services/browser-service/internal/browser-session/page-management.test.js +148 -148
- package/src/services/browser-service/internal/browser-session/recording.js +198 -198
- package/src/services/browser-service/internal/browser-session/runtime-events.js +61 -61
- package/src/services/browser-service/internal/browser-session/session-core.js +84 -84
- package/src/services/browser-service/internal/browser-session/session-state.js +38 -38
- package/src/services/browser-service/internal/browser-session/types.js +14 -14
- package/src/services/browser-service/internal/browser-session/utils.js +95 -95
- package/src/services/browser-service/internal/browser-session/viewport-manager.js +46 -46
- package/src/services/browser-service/internal/browser-session/viewport.js +215 -215
- package/src/services/browser-service/internal/container-matcher.js +851 -851
- package/src/services/browser-service/internal/container-registry.js +182 -182
- package/src/services/browser-service/internal/engine-manager.js +259 -259
- package/src/services/browser-service/internal/fingerprint.js +203 -203
- package/src/services/browser-service/internal/heartbeat.js +137 -137
- package/src/services/browser-service/internal/logging.js +46 -46
- package/src/services/browser-service/internal/page-runtime/runtime.js +1317 -1317
- package/src/services/browser-service/internal/pageRuntime.js +28 -28
- package/src/services/browser-service/internal/runtimeInjector.js +31 -31
- package/src/services/browser-service/internal/service-process-logger.js +140 -140
- package/src/services/browser-service/internal/state-bus.js +45 -45
- package/src/services/browser-service/internal/storage-paths.js +42 -42
- package/src/services/browser-service/internal/ws-server.js +1194 -1194
- package/src/services/browser-service/internal/ws-server.test.js +58 -58
- package/src/services/browser-service/server.mjs +6 -6
- package/src/services/controller/cli-bridge.js +93 -93
- package/src/services/controller/container-index.js +50 -50
- package/src/services/controller/container-storage.js +36 -36
- package/src/services/controller/controller-actions.js +207 -207
- package/src/services/controller/controller.js +1138 -1138
- package/src/services/controller/selectors.js +54 -54
- package/src/services/controller/transport.js +125 -125
- package/src/utils/args.mjs +26 -26
- package/src/utils/browser-service.mjs +544 -544
- package/src/utils/command-log.mjs +64 -64
- package/src/utils/config.mjs +214 -214
- package/src/utils/fingerprint.mjs +181 -181
- package/src/utils/help.mjs +216 -216
- package/src/utils/js-policy.mjs +13 -13
- package/src/utils/ws-client.mjs +30 -30
- package/src/container/runtime-core/operations/tab-pool.mjs.bak +0 -762
- package/src/container/runtime-core/operations/tab-pool.mjs.syntax-error +0 -762
- package/src/services/browser-service/index.js.bak +0 -671
|
@@ -1,389 +1,389 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { BrowserSession } from './BrowserSession.js';
|
|
4
|
-
function setEnv(key, value) {
|
|
5
|
-
const prev = process.env[key];
|
|
6
|
-
process.env[key] = value;
|
|
7
|
-
return () => {
|
|
8
|
-
if (prev === undefined)
|
|
9
|
-
delete process.env[key];
|
|
10
|
-
else
|
|
11
|
-
process.env[key] = prev;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
function createSessionWithPage(page) {
|
|
15
|
-
const session = new BrowserSession({ profileId: `test-input-${Date.now()}` });
|
|
16
|
-
session.ensurePrimaryPage = async () => page;
|
|
17
|
-
session.ensureInputReady = async () => { };
|
|
18
|
-
return session;
|
|
19
|
-
}
|
|
20
|
-
test('mouseMove is disabled to avoid unstable pointer movement path', async () => {
|
|
21
|
-
const page = {
|
|
22
|
-
mouse: {
|
|
23
|
-
move: async () => { },
|
|
24
|
-
},
|
|
25
|
-
bringToFront: async () => { },
|
|
26
|
-
waitForTimeout: async () => { },
|
|
27
|
-
};
|
|
28
|
-
const session = createSessionWithPage(page);
|
|
29
|
-
await assert.rejects(async () => session.mouseMove({ x: 10, y: 20 }), /mouse:move disabled/i);
|
|
30
|
-
});
|
|
31
|
-
test('input actions are serialized per session', async () => {
|
|
32
|
-
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '200');
|
|
33
|
-
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
34
|
-
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
35
|
-
try {
|
|
36
|
-
let active = 0;
|
|
37
|
-
let maxActive = 0;
|
|
38
|
-
const page = {
|
|
39
|
-
mouse: {
|
|
40
|
-
click: async () => {
|
|
41
|
-
active += 1;
|
|
42
|
-
maxActive = Math.max(maxActive, active);
|
|
43
|
-
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
44
|
-
active -= 1;
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
bringToFront: async () => { },
|
|
48
|
-
waitForTimeout: async () => { },
|
|
49
|
-
};
|
|
50
|
-
const session = createSessionWithPage(page);
|
|
51
|
-
await Promise.all([
|
|
52
|
-
session.mouseClick({ x: 1, y: 1, delay: 0 }),
|
|
53
|
-
session.mouseClick({ x: 2, y: 2, delay: 0 }),
|
|
54
|
-
]);
|
|
55
|
-
assert.equal(maxActive, 1);
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
restoreDelay();
|
|
59
|
-
restoreAttempts();
|
|
60
|
-
restoreTimeout();
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
test('mouseClick uses direct click pipeline', async () => {
|
|
64
|
-
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '200');
|
|
65
|
-
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
66
|
-
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
67
|
-
try {
|
|
68
|
-
const calls = [];
|
|
69
|
-
const page = {
|
|
70
|
-
mouse: {
|
|
71
|
-
click: async () => {
|
|
72
|
-
calls.push('click');
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
bringToFront: async () => { },
|
|
76
|
-
waitForTimeout: async () => { },
|
|
77
|
-
};
|
|
78
|
-
const session = createSessionWithPage(page);
|
|
79
|
-
await session.mouseClick({ x: 11, y: 22, delay: 0 });
|
|
80
|
-
assert.deepEqual(calls, ['click']);
|
|
81
|
-
}
|
|
82
|
-
finally {
|
|
83
|
-
restoreDelay();
|
|
84
|
-
restoreAttempts();
|
|
85
|
-
restoreTimeout();
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
test('mouseClick repeats direct click for multi-click', async () => {
|
|
89
|
-
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '200');
|
|
90
|
-
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
91
|
-
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
92
|
-
try {
|
|
93
|
-
const calls = [];
|
|
94
|
-
const page = {
|
|
95
|
-
mouse: {
|
|
96
|
-
click: async () => {
|
|
97
|
-
calls.push('click');
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
bringToFront: async () => { },
|
|
101
|
-
waitForTimeout: async () => { },
|
|
102
|
-
};
|
|
103
|
-
const session = createSessionWithPage(page);
|
|
104
|
-
await session.mouseClick({ x: 15, y: 25, delay: 0, clicks: 2 });
|
|
105
|
-
assert.deepEqual(calls, ['click', 'click']);
|
|
106
|
-
}
|
|
107
|
-
finally {
|
|
108
|
-
restoreDelay();
|
|
109
|
-
restoreAttempts();
|
|
110
|
-
restoreTimeout();
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
test('mouseClick nudges pointer before click when nudgeBefore is true', async () => {
|
|
114
|
-
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '200');
|
|
115
|
-
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
116
|
-
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
117
|
-
try {
|
|
118
|
-
const moves = [];
|
|
119
|
-
const page = {
|
|
120
|
-
isClosed: () => false,
|
|
121
|
-
viewportSize: () => ({ width: 1000, height: 700 }),
|
|
122
|
-
bringToFront: async () => { },
|
|
123
|
-
waitForTimeout: async () => { },
|
|
124
|
-
mouse: {
|
|
125
|
-
move: async (x, y) => {
|
|
126
|
-
moves.push([x, y]);
|
|
127
|
-
},
|
|
128
|
-
click: async () => { },
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
const session = createSessionWithPage(page);
|
|
132
|
-
await session.mouseClick({ x: 120, y: 200, delay: 0, nudgeBefore: true });
|
|
133
|
-
assert.ok(moves.length >= 2);
|
|
134
|
-
assert.deepEqual(moves[moves.length - 1], [120, 200]);
|
|
135
|
-
assert.notDeepEqual(moves[0], [120, 200]);
|
|
136
|
-
}
|
|
137
|
-
finally {
|
|
138
|
-
restoreDelay();
|
|
139
|
-
restoreAttempts();
|
|
140
|
-
restoreTimeout();
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
test('mouseClick falls back to down/up when direct click times out', async () => {
|
|
144
|
-
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '80');
|
|
145
|
-
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
146
|
-
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
147
|
-
const restoreBringToFrontTimeout = setEnv('CAMO_INPUT_RECOVERY_BRING_TO_FRONT_TIMEOUT_MS', '50');
|
|
148
|
-
const restoreReadySettle = setEnv('CAMO_INPUT_READY_SETTLE_MS', '0');
|
|
149
|
-
try {
|
|
150
|
-
const calls = [];
|
|
151
|
-
const page = {
|
|
152
|
-
isClosed: () => false,
|
|
153
|
-
viewportSize: () => ({ width: 1000, height: 700 }),
|
|
154
|
-
bringToFront: async () => { },
|
|
155
|
-
waitForTimeout: async () => { },
|
|
156
|
-
mouse: {
|
|
157
|
-
clickAttempts: 0,
|
|
158
|
-
click: async function click() {
|
|
159
|
-
this.clickAttempts += 1;
|
|
160
|
-
if (this.clickAttempts === 1)
|
|
161
|
-
return new Promise(() => { });
|
|
162
|
-
calls.push('click_retry');
|
|
163
|
-
},
|
|
164
|
-
move: async () => {
|
|
165
|
-
calls.push('move');
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
const session = createSessionWithPage(page);
|
|
170
|
-
await session.mouseClick({ x: 11, y: 22, delay: 0 });
|
|
171
|
-
assert.equal(calls.includes('click_retry'), true);
|
|
172
|
-
}
|
|
173
|
-
finally {
|
|
174
|
-
restoreReadySettle();
|
|
175
|
-
restoreBringToFrontTimeout();
|
|
176
|
-
restoreDelay();
|
|
177
|
-
restoreAttempts();
|
|
178
|
-
restoreTimeout();
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
test('ensureInputReady brings page to front even when document reports focus', async () => {
|
|
182
|
-
const page = {
|
|
183
|
-
bringToFrontCount: 0,
|
|
184
|
-
waitCount: 0,
|
|
185
|
-
bringToFront: async function bringToFront() {
|
|
186
|
-
this.bringToFrontCount += 1;
|
|
187
|
-
},
|
|
188
|
-
waitForTimeout: async function waitForTimeout() {
|
|
189
|
-
this.waitCount += 1;
|
|
190
|
-
},
|
|
191
|
-
evaluate: async () => ({
|
|
192
|
-
hasFocus: true,
|
|
193
|
-
hidden: false,
|
|
194
|
-
visibilityState: 'visible',
|
|
195
|
-
}),
|
|
196
|
-
isClosed: () => false,
|
|
197
|
-
};
|
|
198
|
-
const session = new BrowserSession({ profileId: `test-input-ready-${Date.now()}` });
|
|
199
|
-
await session.inputPipeline.ensureInputReady(page);
|
|
200
|
-
assert.equal(page.bringToFrontCount, 1);
|
|
201
|
-
assert.equal(page.waitCount, 1);
|
|
202
|
-
});
|
|
203
|
-
test('ensureInputReady skips bringToFront when CAMO_BRING_TO_FRONT_MODE=never', async () => {
|
|
204
|
-
const restoreSkip = setEnv('CAMO_BRING_TO_FRONT_MODE', 'never');
|
|
205
|
-
try {
|
|
206
|
-
const page = {
|
|
207
|
-
bringToFrontCount: 0,
|
|
208
|
-
waitCount: 0,
|
|
209
|
-
bringToFront: async function bringToFront() {
|
|
210
|
-
this.bringToFrontCount += 1;
|
|
211
|
-
},
|
|
212
|
-
waitForTimeout: async function waitForTimeout() {
|
|
213
|
-
this.waitCount += 1;
|
|
214
|
-
},
|
|
215
|
-
evaluate: async () => ({
|
|
216
|
-
hasFocus: false,
|
|
217
|
-
hidden: false,
|
|
218
|
-
visibilityState: 'visible',
|
|
219
|
-
}),
|
|
220
|
-
isClosed: () => false,
|
|
221
|
-
};
|
|
222
|
-
const session = new BrowserSession({ profileId: `test-input-ready-skip-${Date.now()}` });
|
|
223
|
-
await session.inputPipeline.ensureInputReady(page);
|
|
224
|
-
assert.equal(page.bringToFrontCount, 0);
|
|
225
|
-
assert.equal(page.waitCount, 1);
|
|
226
|
-
}
|
|
227
|
-
finally {
|
|
228
|
-
restoreSkip();
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
test('mouseWheel retries with refreshed active page after timeout', async () => {
|
|
232
|
-
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '80');
|
|
233
|
-
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '2');
|
|
234
|
-
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
235
|
-
const restoreBringToFrontTimeout = setEnv('CAMO_INPUT_RECOVERY_BRING_TO_FRONT_TIMEOUT_MS', '50');
|
|
236
|
-
const restoreReadySettle = setEnv('CAMO_INPUT_READY_SETTLE_MS', '0');
|
|
237
|
-
try {
|
|
238
|
-
const calls = [];
|
|
239
|
-
const page1 = {
|
|
240
|
-
isClosed: () => false,
|
|
241
|
-
viewportSize: () => ({ width: 1280, height: 720 }),
|
|
242
|
-
bringToFront: async () => { },
|
|
243
|
-
waitForTimeout: async () => { },
|
|
244
|
-
mouse: {
|
|
245
|
-
move: async () => { },
|
|
246
|
-
wheel: async () => new Promise(() => { }),
|
|
247
|
-
},
|
|
248
|
-
};
|
|
249
|
-
const page2 = {
|
|
250
|
-
isClosed: () => false,
|
|
251
|
-
viewportSize: () => ({ width: 1280, height: 720 }),
|
|
252
|
-
bringToFront: async () => { },
|
|
253
|
-
waitForTimeout: async () => { },
|
|
254
|
-
mouse: {
|
|
255
|
-
move: async () => { },
|
|
256
|
-
wheel: async () => {
|
|
257
|
-
calls.push('wheel_ok');
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
};
|
|
261
|
-
const session = new BrowserSession({ profileId: `test-input-refresh-${Date.now()}` });
|
|
262
|
-
let ensurePrimaryPageCalls = 0;
|
|
263
|
-
session.ensureInputReady = async () => { };
|
|
264
|
-
session.ensurePrimaryPage = async () => {
|
|
265
|
-
ensurePrimaryPageCalls += 1;
|
|
266
|
-
return ensurePrimaryPageCalls <= 3 ? page1 : page2;
|
|
267
|
-
};
|
|
268
|
-
await session.mouseWheel({ deltaY: 360 });
|
|
269
|
-
assert.equal(calls.length, 1);
|
|
270
|
-
assert.ok(ensurePrimaryPageCalls >= 4);
|
|
271
|
-
}
|
|
272
|
-
finally {
|
|
273
|
-
restoreReadySettle();
|
|
274
|
-
restoreBringToFrontTimeout();
|
|
275
|
-
restoreDelay();
|
|
276
|
-
restoreAttempts();
|
|
277
|
-
restoreTimeout();
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
test('mouseWheel prefers interactive viewport metrics for anchor clamping', async () => {
|
|
281
|
-
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '80');
|
|
282
|
-
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
283
|
-
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
284
|
-
const restoreBringToFrontTimeout = setEnv('CAMO_INPUT_RECOVERY_BRING_TO_FRONT_TIMEOUT_MS', '50');
|
|
285
|
-
const restoreReadySettle = setEnv('CAMO_INPUT_READY_SETTLE_MS', '0');
|
|
286
|
-
try {
|
|
287
|
-
const moves = [];
|
|
288
|
-
const page = {
|
|
289
|
-
isClosed: () => false,
|
|
290
|
-
viewportSize: () => ({ width: 1280, height: 720 }),
|
|
291
|
-
evaluate: async () => ({ innerWidth: 2560, innerHeight: 1440, visualWidth: 2560, visualHeight: 1440 }),
|
|
292
|
-
bringToFront: async () => { },
|
|
293
|
-
waitForTimeout: async () => { },
|
|
294
|
-
mouse: {
|
|
295
|
-
move: async (x, y) => {
|
|
296
|
-
moves.push([x, y]);
|
|
297
|
-
},
|
|
298
|
-
wheel: async () => { },
|
|
299
|
-
},
|
|
300
|
-
};
|
|
301
|
-
const session = createSessionWithPage(page);
|
|
302
|
-
await session.mouseWheel({ deltaY: 360, anchorX: 2564, anchorY: 228 });
|
|
303
|
-
assert.deepEqual(moves, [[2559, 228]]);
|
|
304
|
-
}
|
|
305
|
-
finally {
|
|
306
|
-
restoreReadySettle();
|
|
307
|
-
restoreBringToFrontTimeout();
|
|
308
|
-
restoreDelay();
|
|
309
|
-
restoreAttempts();
|
|
310
|
-
restoreTimeout();
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
test('mouseWheel falls back to keyboard paging when wheel keeps timing out', async () => {
|
|
314
|
-
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '80');
|
|
315
|
-
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
316
|
-
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
317
|
-
const restoreBringToFrontTimeout = setEnv('CAMO_INPUT_RECOVERY_BRING_TO_FRONT_TIMEOUT_MS', '50');
|
|
318
|
-
const restoreReadySettle = setEnv('CAMO_INPUT_READY_SETTLE_MS', '0');
|
|
319
|
-
try {
|
|
320
|
-
const calls = [];
|
|
321
|
-
const page = {
|
|
322
|
-
isClosed: () => false,
|
|
323
|
-
viewportSize: () => ({ width: 1280, height: 720 }),
|
|
324
|
-
bringToFront: async () => { },
|
|
325
|
-
waitForTimeout: async () => { },
|
|
326
|
-
mouse: {
|
|
327
|
-
move: async () => { },
|
|
328
|
-
wheel: async () => new Promise(() => { }),
|
|
329
|
-
},
|
|
330
|
-
keyboard: {
|
|
331
|
-
press: async (key) => {
|
|
332
|
-
calls.push(`press:${key}`);
|
|
333
|
-
},
|
|
334
|
-
},
|
|
335
|
-
};
|
|
336
|
-
const session = new BrowserSession({ profileId: `test-input-fallback-${Date.now()}` });
|
|
337
|
-
session.ensureInputReady = async () => { };
|
|
338
|
-
session.ensurePrimaryPage = async () => page;
|
|
339
|
-
await session.mouseWheel({ deltaY: 420 });
|
|
340
|
-
assert.deepEqual(calls, ['press:PageDown']);
|
|
341
|
-
}
|
|
342
|
-
finally {
|
|
343
|
-
restoreReadySettle();
|
|
344
|
-
restoreBringToFrontTimeout();
|
|
345
|
-
restoreDelay();
|
|
346
|
-
restoreAttempts();
|
|
347
|
-
restoreTimeout();
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
test('mouseWheel uses keyboard mode directly when CAMO_SCROLL_INPUT_MODE=keyboard', async () => {
|
|
351
|
-
const restoreMode = setEnv('CAMO_SCROLL_INPUT_MODE', 'keyboard');
|
|
352
|
-
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '80');
|
|
353
|
-
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
354
|
-
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
355
|
-
const restoreBringToFrontTimeout = setEnv('CAMO_INPUT_RECOVERY_BRING_TO_FRONT_TIMEOUT_MS', '50');
|
|
356
|
-
const restoreReadySettle = setEnv('CAMO_INPUT_READY_SETTLE_MS', '0');
|
|
357
|
-
try {
|
|
358
|
-
const calls = [];
|
|
359
|
-
const page = {
|
|
360
|
-
isClosed: () => false,
|
|
361
|
-
bringToFront: async () => { },
|
|
362
|
-
waitForTimeout: async () => { },
|
|
363
|
-
mouse: {
|
|
364
|
-
wheel: async () => {
|
|
365
|
-
calls.push('wheel');
|
|
366
|
-
},
|
|
367
|
-
},
|
|
368
|
-
keyboard: {
|
|
369
|
-
press: async (key) => {
|
|
370
|
-
calls.push(`press:${key}`);
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
};
|
|
374
|
-
const session = new BrowserSession({ profileId: `test-input-mode-${Date.now()}` });
|
|
375
|
-
session.ensureInputReady = async () => { };
|
|
376
|
-
session.ensurePrimaryPage = async () => page;
|
|
377
|
-
await session.mouseWheel({ deltaY: 760 });
|
|
378
|
-
assert.deepEqual(calls, ['press:PageDown', 'press:PageDown']);
|
|
379
|
-
}
|
|
380
|
-
finally {
|
|
381
|
-
restoreReadySettle();
|
|
382
|
-
restoreBringToFrontTimeout();
|
|
383
|
-
restoreDelay();
|
|
384
|
-
restoreAttempts();
|
|
385
|
-
restoreTimeout();
|
|
386
|
-
restoreMode();
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
//# sourceMappingURL=BrowserSession.input.test.js.map
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { BrowserSession } from './BrowserSession.js';
|
|
4
|
+
function setEnv(key, value) {
|
|
5
|
+
const prev = process.env[key];
|
|
6
|
+
process.env[key] = value;
|
|
7
|
+
return () => {
|
|
8
|
+
if (prev === undefined)
|
|
9
|
+
delete process.env[key];
|
|
10
|
+
else
|
|
11
|
+
process.env[key] = prev;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function createSessionWithPage(page) {
|
|
15
|
+
const session = new BrowserSession({ profileId: `test-input-${Date.now()}` });
|
|
16
|
+
session.ensurePrimaryPage = async () => page;
|
|
17
|
+
session.ensureInputReady = async () => { };
|
|
18
|
+
return session;
|
|
19
|
+
}
|
|
20
|
+
test('mouseMove is disabled to avoid unstable pointer movement path', async () => {
|
|
21
|
+
const page = {
|
|
22
|
+
mouse: {
|
|
23
|
+
move: async () => { },
|
|
24
|
+
},
|
|
25
|
+
bringToFront: async () => { },
|
|
26
|
+
waitForTimeout: async () => { },
|
|
27
|
+
};
|
|
28
|
+
const session = createSessionWithPage(page);
|
|
29
|
+
await assert.rejects(async () => session.mouseMove({ x: 10, y: 20 }), /mouse:move disabled/i);
|
|
30
|
+
});
|
|
31
|
+
test('input actions are serialized per session', async () => {
|
|
32
|
+
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '200');
|
|
33
|
+
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
34
|
+
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
35
|
+
try {
|
|
36
|
+
let active = 0;
|
|
37
|
+
let maxActive = 0;
|
|
38
|
+
const page = {
|
|
39
|
+
mouse: {
|
|
40
|
+
click: async () => {
|
|
41
|
+
active += 1;
|
|
42
|
+
maxActive = Math.max(maxActive, active);
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
44
|
+
active -= 1;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
bringToFront: async () => { },
|
|
48
|
+
waitForTimeout: async () => { },
|
|
49
|
+
};
|
|
50
|
+
const session = createSessionWithPage(page);
|
|
51
|
+
await Promise.all([
|
|
52
|
+
session.mouseClick({ x: 1, y: 1, delay: 0 }),
|
|
53
|
+
session.mouseClick({ x: 2, y: 2, delay: 0 }),
|
|
54
|
+
]);
|
|
55
|
+
assert.equal(maxActive, 1);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
restoreDelay();
|
|
59
|
+
restoreAttempts();
|
|
60
|
+
restoreTimeout();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
test('mouseClick uses direct click pipeline', async () => {
|
|
64
|
+
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '200');
|
|
65
|
+
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
66
|
+
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
67
|
+
try {
|
|
68
|
+
const calls = [];
|
|
69
|
+
const page = {
|
|
70
|
+
mouse: {
|
|
71
|
+
click: async () => {
|
|
72
|
+
calls.push('click');
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
bringToFront: async () => { },
|
|
76
|
+
waitForTimeout: async () => { },
|
|
77
|
+
};
|
|
78
|
+
const session = createSessionWithPage(page);
|
|
79
|
+
await session.mouseClick({ x: 11, y: 22, delay: 0 });
|
|
80
|
+
assert.deepEqual(calls, ['click']);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
restoreDelay();
|
|
84
|
+
restoreAttempts();
|
|
85
|
+
restoreTimeout();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
test('mouseClick repeats direct click for multi-click', async () => {
|
|
89
|
+
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '200');
|
|
90
|
+
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
91
|
+
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
92
|
+
try {
|
|
93
|
+
const calls = [];
|
|
94
|
+
const page = {
|
|
95
|
+
mouse: {
|
|
96
|
+
click: async () => {
|
|
97
|
+
calls.push('click');
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
bringToFront: async () => { },
|
|
101
|
+
waitForTimeout: async () => { },
|
|
102
|
+
};
|
|
103
|
+
const session = createSessionWithPage(page);
|
|
104
|
+
await session.mouseClick({ x: 15, y: 25, delay: 0, clicks: 2 });
|
|
105
|
+
assert.deepEqual(calls, ['click', 'click']);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
restoreDelay();
|
|
109
|
+
restoreAttempts();
|
|
110
|
+
restoreTimeout();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
test('mouseClick nudges pointer before click when nudgeBefore is true', async () => {
|
|
114
|
+
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '200');
|
|
115
|
+
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
116
|
+
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
117
|
+
try {
|
|
118
|
+
const moves = [];
|
|
119
|
+
const page = {
|
|
120
|
+
isClosed: () => false,
|
|
121
|
+
viewportSize: () => ({ width: 1000, height: 700 }),
|
|
122
|
+
bringToFront: async () => { },
|
|
123
|
+
waitForTimeout: async () => { },
|
|
124
|
+
mouse: {
|
|
125
|
+
move: async (x, y) => {
|
|
126
|
+
moves.push([x, y]);
|
|
127
|
+
},
|
|
128
|
+
click: async () => { },
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
const session = createSessionWithPage(page);
|
|
132
|
+
await session.mouseClick({ x: 120, y: 200, delay: 0, nudgeBefore: true });
|
|
133
|
+
assert.ok(moves.length >= 2);
|
|
134
|
+
assert.deepEqual(moves[moves.length - 1], [120, 200]);
|
|
135
|
+
assert.notDeepEqual(moves[0], [120, 200]);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
restoreDelay();
|
|
139
|
+
restoreAttempts();
|
|
140
|
+
restoreTimeout();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
test('mouseClick falls back to down/up when direct click times out', async () => {
|
|
144
|
+
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '80');
|
|
145
|
+
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
146
|
+
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
147
|
+
const restoreBringToFrontTimeout = setEnv('CAMO_INPUT_RECOVERY_BRING_TO_FRONT_TIMEOUT_MS', '50');
|
|
148
|
+
const restoreReadySettle = setEnv('CAMO_INPUT_READY_SETTLE_MS', '0');
|
|
149
|
+
try {
|
|
150
|
+
const calls = [];
|
|
151
|
+
const page = {
|
|
152
|
+
isClosed: () => false,
|
|
153
|
+
viewportSize: () => ({ width: 1000, height: 700 }),
|
|
154
|
+
bringToFront: async () => { },
|
|
155
|
+
waitForTimeout: async () => { },
|
|
156
|
+
mouse: {
|
|
157
|
+
clickAttempts: 0,
|
|
158
|
+
click: async function click() {
|
|
159
|
+
this.clickAttempts += 1;
|
|
160
|
+
if (this.clickAttempts === 1)
|
|
161
|
+
return new Promise(() => { });
|
|
162
|
+
calls.push('click_retry');
|
|
163
|
+
},
|
|
164
|
+
move: async () => {
|
|
165
|
+
calls.push('move');
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
const session = createSessionWithPage(page);
|
|
170
|
+
await session.mouseClick({ x: 11, y: 22, delay: 0 });
|
|
171
|
+
assert.equal(calls.includes('click_retry'), true);
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
restoreReadySettle();
|
|
175
|
+
restoreBringToFrontTimeout();
|
|
176
|
+
restoreDelay();
|
|
177
|
+
restoreAttempts();
|
|
178
|
+
restoreTimeout();
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
test('ensureInputReady brings page to front even when document reports focus', async () => {
|
|
182
|
+
const page = {
|
|
183
|
+
bringToFrontCount: 0,
|
|
184
|
+
waitCount: 0,
|
|
185
|
+
bringToFront: async function bringToFront() {
|
|
186
|
+
this.bringToFrontCount += 1;
|
|
187
|
+
},
|
|
188
|
+
waitForTimeout: async function waitForTimeout() {
|
|
189
|
+
this.waitCount += 1;
|
|
190
|
+
},
|
|
191
|
+
evaluate: async () => ({
|
|
192
|
+
hasFocus: true,
|
|
193
|
+
hidden: false,
|
|
194
|
+
visibilityState: 'visible',
|
|
195
|
+
}),
|
|
196
|
+
isClosed: () => false,
|
|
197
|
+
};
|
|
198
|
+
const session = new BrowserSession({ profileId: `test-input-ready-${Date.now()}` });
|
|
199
|
+
await session.inputPipeline.ensureInputReady(page);
|
|
200
|
+
assert.equal(page.bringToFrontCount, 1);
|
|
201
|
+
assert.equal(page.waitCount, 1);
|
|
202
|
+
});
|
|
203
|
+
test('ensureInputReady skips bringToFront when CAMO_BRING_TO_FRONT_MODE=never', async () => {
|
|
204
|
+
const restoreSkip = setEnv('CAMO_BRING_TO_FRONT_MODE', 'never');
|
|
205
|
+
try {
|
|
206
|
+
const page = {
|
|
207
|
+
bringToFrontCount: 0,
|
|
208
|
+
waitCount: 0,
|
|
209
|
+
bringToFront: async function bringToFront() {
|
|
210
|
+
this.bringToFrontCount += 1;
|
|
211
|
+
},
|
|
212
|
+
waitForTimeout: async function waitForTimeout() {
|
|
213
|
+
this.waitCount += 1;
|
|
214
|
+
},
|
|
215
|
+
evaluate: async () => ({
|
|
216
|
+
hasFocus: false,
|
|
217
|
+
hidden: false,
|
|
218
|
+
visibilityState: 'visible',
|
|
219
|
+
}),
|
|
220
|
+
isClosed: () => false,
|
|
221
|
+
};
|
|
222
|
+
const session = new BrowserSession({ profileId: `test-input-ready-skip-${Date.now()}` });
|
|
223
|
+
await session.inputPipeline.ensureInputReady(page);
|
|
224
|
+
assert.equal(page.bringToFrontCount, 0);
|
|
225
|
+
assert.equal(page.waitCount, 1);
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
restoreSkip();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
test('mouseWheel retries with refreshed active page after timeout', async () => {
|
|
232
|
+
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '80');
|
|
233
|
+
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '2');
|
|
234
|
+
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
235
|
+
const restoreBringToFrontTimeout = setEnv('CAMO_INPUT_RECOVERY_BRING_TO_FRONT_TIMEOUT_MS', '50');
|
|
236
|
+
const restoreReadySettle = setEnv('CAMO_INPUT_READY_SETTLE_MS', '0');
|
|
237
|
+
try {
|
|
238
|
+
const calls = [];
|
|
239
|
+
const page1 = {
|
|
240
|
+
isClosed: () => false,
|
|
241
|
+
viewportSize: () => ({ width: 1280, height: 720 }),
|
|
242
|
+
bringToFront: async () => { },
|
|
243
|
+
waitForTimeout: async () => { },
|
|
244
|
+
mouse: {
|
|
245
|
+
move: async () => { },
|
|
246
|
+
wheel: async () => new Promise(() => { }),
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
const page2 = {
|
|
250
|
+
isClosed: () => false,
|
|
251
|
+
viewportSize: () => ({ width: 1280, height: 720 }),
|
|
252
|
+
bringToFront: async () => { },
|
|
253
|
+
waitForTimeout: async () => { },
|
|
254
|
+
mouse: {
|
|
255
|
+
move: async () => { },
|
|
256
|
+
wheel: async () => {
|
|
257
|
+
calls.push('wheel_ok');
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
const session = new BrowserSession({ profileId: `test-input-refresh-${Date.now()}` });
|
|
262
|
+
let ensurePrimaryPageCalls = 0;
|
|
263
|
+
session.ensureInputReady = async () => { };
|
|
264
|
+
session.ensurePrimaryPage = async () => {
|
|
265
|
+
ensurePrimaryPageCalls += 1;
|
|
266
|
+
return ensurePrimaryPageCalls <= 3 ? page1 : page2;
|
|
267
|
+
};
|
|
268
|
+
await session.mouseWheel({ deltaY: 360 });
|
|
269
|
+
assert.equal(calls.length, 1);
|
|
270
|
+
assert.ok(ensurePrimaryPageCalls >= 4);
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
restoreReadySettle();
|
|
274
|
+
restoreBringToFrontTimeout();
|
|
275
|
+
restoreDelay();
|
|
276
|
+
restoreAttempts();
|
|
277
|
+
restoreTimeout();
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
test('mouseWheel prefers interactive viewport metrics for anchor clamping', async () => {
|
|
281
|
+
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '80');
|
|
282
|
+
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
283
|
+
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
284
|
+
const restoreBringToFrontTimeout = setEnv('CAMO_INPUT_RECOVERY_BRING_TO_FRONT_TIMEOUT_MS', '50');
|
|
285
|
+
const restoreReadySettle = setEnv('CAMO_INPUT_READY_SETTLE_MS', '0');
|
|
286
|
+
try {
|
|
287
|
+
const moves = [];
|
|
288
|
+
const page = {
|
|
289
|
+
isClosed: () => false,
|
|
290
|
+
viewportSize: () => ({ width: 1280, height: 720 }),
|
|
291
|
+
evaluate: async () => ({ innerWidth: 2560, innerHeight: 1440, visualWidth: 2560, visualHeight: 1440 }),
|
|
292
|
+
bringToFront: async () => { },
|
|
293
|
+
waitForTimeout: async () => { },
|
|
294
|
+
mouse: {
|
|
295
|
+
move: async (x, y) => {
|
|
296
|
+
moves.push([x, y]);
|
|
297
|
+
},
|
|
298
|
+
wheel: async () => { },
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
const session = createSessionWithPage(page);
|
|
302
|
+
await session.mouseWheel({ deltaY: 360, anchorX: 2564, anchorY: 228 });
|
|
303
|
+
assert.deepEqual(moves, [[2559, 228]]);
|
|
304
|
+
}
|
|
305
|
+
finally {
|
|
306
|
+
restoreReadySettle();
|
|
307
|
+
restoreBringToFrontTimeout();
|
|
308
|
+
restoreDelay();
|
|
309
|
+
restoreAttempts();
|
|
310
|
+
restoreTimeout();
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
test('mouseWheel falls back to keyboard paging when wheel keeps timing out', async () => {
|
|
314
|
+
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '80');
|
|
315
|
+
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
316
|
+
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
317
|
+
const restoreBringToFrontTimeout = setEnv('CAMO_INPUT_RECOVERY_BRING_TO_FRONT_TIMEOUT_MS', '50');
|
|
318
|
+
const restoreReadySettle = setEnv('CAMO_INPUT_READY_SETTLE_MS', '0');
|
|
319
|
+
try {
|
|
320
|
+
const calls = [];
|
|
321
|
+
const page = {
|
|
322
|
+
isClosed: () => false,
|
|
323
|
+
viewportSize: () => ({ width: 1280, height: 720 }),
|
|
324
|
+
bringToFront: async () => { },
|
|
325
|
+
waitForTimeout: async () => { },
|
|
326
|
+
mouse: {
|
|
327
|
+
move: async () => { },
|
|
328
|
+
wheel: async () => new Promise(() => { }),
|
|
329
|
+
},
|
|
330
|
+
keyboard: {
|
|
331
|
+
press: async (key) => {
|
|
332
|
+
calls.push(`press:${key}`);
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
const session = new BrowserSession({ profileId: `test-input-fallback-${Date.now()}` });
|
|
337
|
+
session.ensureInputReady = async () => { };
|
|
338
|
+
session.ensurePrimaryPage = async () => page;
|
|
339
|
+
await session.mouseWheel({ deltaY: 420 });
|
|
340
|
+
assert.deepEqual(calls, ['press:PageDown']);
|
|
341
|
+
}
|
|
342
|
+
finally {
|
|
343
|
+
restoreReadySettle();
|
|
344
|
+
restoreBringToFrontTimeout();
|
|
345
|
+
restoreDelay();
|
|
346
|
+
restoreAttempts();
|
|
347
|
+
restoreTimeout();
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
test('mouseWheel uses keyboard mode directly when CAMO_SCROLL_INPUT_MODE=keyboard', async () => {
|
|
351
|
+
const restoreMode = setEnv('CAMO_SCROLL_INPUT_MODE', 'keyboard');
|
|
352
|
+
const restoreTimeout = setEnv('CAMO_INPUT_ACTION_TIMEOUT_MS', '80');
|
|
353
|
+
const restoreAttempts = setEnv('CAMO_INPUT_ACTION_MAX_ATTEMPTS', '1');
|
|
354
|
+
const restoreDelay = setEnv('CAMO_INPUT_RECOVERY_DELAY_MS', '0');
|
|
355
|
+
const restoreBringToFrontTimeout = setEnv('CAMO_INPUT_RECOVERY_BRING_TO_FRONT_TIMEOUT_MS', '50');
|
|
356
|
+
const restoreReadySettle = setEnv('CAMO_INPUT_READY_SETTLE_MS', '0');
|
|
357
|
+
try {
|
|
358
|
+
const calls = [];
|
|
359
|
+
const page = {
|
|
360
|
+
isClosed: () => false,
|
|
361
|
+
bringToFront: async () => { },
|
|
362
|
+
waitForTimeout: async () => { },
|
|
363
|
+
mouse: {
|
|
364
|
+
wheel: async () => {
|
|
365
|
+
calls.push('wheel');
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
keyboard: {
|
|
369
|
+
press: async (key) => {
|
|
370
|
+
calls.push(`press:${key}`);
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
const session = new BrowserSession({ profileId: `test-input-mode-${Date.now()}` });
|
|
375
|
+
session.ensureInputReady = async () => { };
|
|
376
|
+
session.ensurePrimaryPage = async () => page;
|
|
377
|
+
await session.mouseWheel({ deltaY: 760 });
|
|
378
|
+
assert.deepEqual(calls, ['press:PageDown', 'press:PageDown']);
|
|
379
|
+
}
|
|
380
|
+
finally {
|
|
381
|
+
restoreReadySettle();
|
|
382
|
+
restoreBringToFrontTimeout();
|
|
383
|
+
restoreDelay();
|
|
384
|
+
restoreAttempts();
|
|
385
|
+
restoreTimeout();
|
|
386
|
+
restoreMode();
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
//# sourceMappingURL=BrowserSession.input.test.js.map
|