@alumnium/selenium 0.0.0-stage → 0.24.0-alpha.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.md +21 -0
- package/README.md +2 -2
- package/package.json +37 -4
- package/src/Alumni.d.ts +16 -0
- package/src/drivers/SeleniumCdpConnection.d.ts +11 -0
- package/src/drivers/SeleniumDriver.d.ts +60 -0
- package/src/index.cjs +1229 -0
- package/src/index.cjs.map +12 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +1139 -0
- package/src/index.js.map +12 -0
package/src/index.cjs
ADDED
|
@@ -0,0 +1,1229 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode
|
|
16
|
+
? (__toESMCache_node ??= new WeakMap())
|
|
17
|
+
: (__toESMCache_esm ??= new WeakMap());
|
|
18
|
+
var cached = cache.get(mod);
|
|
19
|
+
if (cached) return cached;
|
|
20
|
+
}
|
|
21
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
|
+
const to =
|
|
23
|
+
isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default")
|
|
24
|
+
? __defProp(target, "default", { value: mod, enumerable: true })
|
|
25
|
+
: target;
|
|
26
|
+
if ((mod && typeof mod === "object") || typeof mod === "function") {
|
|
27
|
+
for (let key of __getOwnPropNames(mod))
|
|
28
|
+
if (!__hasOwnProp.call(to, key))
|
|
29
|
+
__defProp(to, key, {
|
|
30
|
+
get: __accessProp.bind(mod, key),
|
|
31
|
+
enumerable: true,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (canCache) cache.set(mod, to);
|
|
35
|
+
return to;
|
|
36
|
+
};
|
|
37
|
+
var __toCommonJS = (from) => {
|
|
38
|
+
var entry = (__moduleCache ??= new WeakMap()).get(from),
|
|
39
|
+
desc;
|
|
40
|
+
if (entry) return entry;
|
|
41
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
42
|
+
if ((from && typeof from === "object") || typeof from === "function") {
|
|
43
|
+
for (var key of __getOwnPropNames(from))
|
|
44
|
+
if (!__hasOwnProp.call(entry, key))
|
|
45
|
+
__defProp(entry, key, {
|
|
46
|
+
get: __accessProp.bind(from, key),
|
|
47
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
__moduleCache.set(from, entry);
|
|
51
|
+
return entry;
|
|
52
|
+
};
|
|
53
|
+
var __moduleCache;
|
|
54
|
+
var __returnValue = (v) => v;
|
|
55
|
+
function __exportSetter(name, newValue) {
|
|
56
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
57
|
+
}
|
|
58
|
+
var __export = (target, all) => {
|
|
59
|
+
for (var name in all)
|
|
60
|
+
__defProp(target, name, {
|
|
61
|
+
get: all[name],
|
|
62
|
+
enumerable: true,
|
|
63
|
+
configurable: true,
|
|
64
|
+
set: __exportSetter.bind(all, name),
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
var __legacyDecorateClassTS = function (decorators, target, key, desc) {
|
|
68
|
+
var c = arguments.length,
|
|
69
|
+
r =
|
|
70
|
+
c < 3
|
|
71
|
+
? target
|
|
72
|
+
: desc === null
|
|
73
|
+
? (desc = Object.getOwnPropertyDescriptor(target, key))
|
|
74
|
+
: desc,
|
|
75
|
+
d;
|
|
76
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
77
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
78
|
+
else
|
|
79
|
+
for (var i = decorators.length - 1; i >= 0; i--)
|
|
80
|
+
if ((d = decorators[i]))
|
|
81
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
82
|
+
return (c > 3 && r && Object.defineProperty(target, key, r), r);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// selenium/src/index.ts
|
|
86
|
+
var exports_src = {};
|
|
87
|
+
__export(exports_src, {
|
|
88
|
+
Alumni: () => Alumni,
|
|
89
|
+
SeleniumDriver: () => SeleniumDriver,
|
|
90
|
+
});
|
|
91
|
+
module.exports = __toCommonJS(exports_src);
|
|
92
|
+
|
|
93
|
+
// selenium/src/Alumni.ts
|
|
94
|
+
var import_selenium_webdriver2 = require("selenium-webdriver");
|
|
95
|
+
var import_core = require("@alumnium/core");
|
|
96
|
+
|
|
97
|
+
// selenium/src/drivers/SeleniumDriver.ts
|
|
98
|
+
var fs = __toESM(require("fs/promises"), 1);
|
|
99
|
+
var import_alwaysly = require("alwaysly");
|
|
100
|
+
var import_selenium_webdriver = require("selenium-webdriver");
|
|
101
|
+
var import_error = require("selenium-webdriver/lib/error.js");
|
|
102
|
+
var import_internal = require("@alumnium/core/internal");
|
|
103
|
+
|
|
104
|
+
// selenium/src/drivers/SeleniumCdpConnection.ts
|
|
105
|
+
var AUTO_ATTACH_PARAMS = {
|
|
106
|
+
autoAttach: true,
|
|
107
|
+
waitForDebuggerOnStart: true,
|
|
108
|
+
flatten: true,
|
|
109
|
+
};
|
|
110
|
+
var TIMEOUT_MS = 5000;
|
|
111
|
+
|
|
112
|
+
class SeleniumCdpConnection {
|
|
113
|
+
#socket;
|
|
114
|
+
#waiterScript;
|
|
115
|
+
#nextId = 1;
|
|
116
|
+
#pending = new Map();
|
|
117
|
+
#targetSessions = new Map();
|
|
118
|
+
#sessionConfigurations = new Map();
|
|
119
|
+
#closed = false;
|
|
120
|
+
constructor(socket, waiterScript) {
|
|
121
|
+
this.#socket = socket;
|
|
122
|
+
this.#waiterScript = waiterScript;
|
|
123
|
+
socket.addEventListener("message", (event) => this.#onMessage(event.data));
|
|
124
|
+
socket.addEventListener("close", () => this.#failConnection());
|
|
125
|
+
socket.addEventListener("error", () => this.#failConnection());
|
|
126
|
+
}
|
|
127
|
+
static async connect(capabilities, waiterScript) {
|
|
128
|
+
const url = await websocketUrl(capabilities);
|
|
129
|
+
const socket = new WebSocket(url);
|
|
130
|
+
try {
|
|
131
|
+
await new Promise((resolve, reject) => {
|
|
132
|
+
const timeout = setTimeout(
|
|
133
|
+
() => reject(new Error("Timed out connecting to Chrome CDP")),
|
|
134
|
+
TIMEOUT_MS,
|
|
135
|
+
);
|
|
136
|
+
socket.addEventListener(
|
|
137
|
+
"open",
|
|
138
|
+
() => {
|
|
139
|
+
clearTimeout(timeout);
|
|
140
|
+
resolve();
|
|
141
|
+
},
|
|
142
|
+
{ once: true },
|
|
143
|
+
);
|
|
144
|
+
socket.addEventListener(
|
|
145
|
+
"error",
|
|
146
|
+
() => {
|
|
147
|
+
clearTimeout(timeout);
|
|
148
|
+
reject(new Error("Could not connect to Chrome CDP"));
|
|
149
|
+
},
|
|
150
|
+
{ once: true },
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
} catch (error) {
|
|
154
|
+
socket.close();
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
const connection = new SeleniumCdpConnection(socket, waiterScript);
|
|
158
|
+
try {
|
|
159
|
+
await connection.#send("Target.setAutoAttach", AUTO_ATTACH_PARAMS);
|
|
160
|
+
await connection.#send("Target.setDiscoverTargets", { discover: true });
|
|
161
|
+
const targets = await connection.#send("Target.getTargets");
|
|
162
|
+
for (const target of targets.targetInfos ?? []) {
|
|
163
|
+
if (target.type === "page")
|
|
164
|
+
await connection.#awaitSession(target.targetId);
|
|
165
|
+
}
|
|
166
|
+
return connection;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
connection.close();
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
close() {
|
|
173
|
+
if (this.#closed) return;
|
|
174
|
+
this.#closed = true;
|
|
175
|
+
this.#failPending(new Error("CDP connection closed"));
|
|
176
|
+
this.#socket.close();
|
|
177
|
+
this.#targetSessions.clear();
|
|
178
|
+
this.#sessionConfigurations.clear();
|
|
179
|
+
}
|
|
180
|
+
#send(method, params = {}, sessionId = "", wait = true) {
|
|
181
|
+
if (this.#closed) return Promise.reject(new Error("CDP connection closed"));
|
|
182
|
+
const id = this.#nextId++;
|
|
183
|
+
const message = { id, method, params, ...(sessionId ? { sessionId } : {}) };
|
|
184
|
+
if (!wait) {
|
|
185
|
+
this.#socket.send(JSON.stringify(message));
|
|
186
|
+
return Promise.resolve({});
|
|
187
|
+
}
|
|
188
|
+
return new Promise((resolve, reject) => {
|
|
189
|
+
const timeout = setTimeout(() => {
|
|
190
|
+
this.#pending.delete(id);
|
|
191
|
+
reject(new Error(`Timed out sending CDP command ${method}`));
|
|
192
|
+
}, TIMEOUT_MS);
|
|
193
|
+
this.#pending.set(id, { resolve, reject, timeout });
|
|
194
|
+
try {
|
|
195
|
+
this.#socket.send(JSON.stringify(message));
|
|
196
|
+
} catch (error) {
|
|
197
|
+
clearTimeout(timeout);
|
|
198
|
+
this.#pending.delete(id);
|
|
199
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
#onMessage(data) {
|
|
204
|
+
if (data instanceof Blob) {
|
|
205
|
+
data.text().then((text) => this.#onMessage(text));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const text =
|
|
209
|
+
typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
210
|
+
const message = JSON.parse(text);
|
|
211
|
+
if (message.id) {
|
|
212
|
+
const command = this.#pending.get(message.id);
|
|
213
|
+
if (!command) return;
|
|
214
|
+
this.#pending.delete(message.id);
|
|
215
|
+
clearTimeout(command.timeout);
|
|
216
|
+
if (message.error) command.reject(new Error(message.error.message));
|
|
217
|
+
else command.resolve(message.result ?? {});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const method = message.method ?? "";
|
|
221
|
+
const params = message.params ?? {};
|
|
222
|
+
if (method === "Target.attachedToTarget") {
|
|
223
|
+
const targetInfo = params.targetInfo;
|
|
224
|
+
const sessionId = String(params.sessionId);
|
|
225
|
+
let configuration = Promise.resolve();
|
|
226
|
+
if (targetInfo?.type === "page" || targetInfo?.type === "iframe") {
|
|
227
|
+
if (targetInfo.targetId)
|
|
228
|
+
this.#targetSessions.set(targetInfo.targetId, sessionId);
|
|
229
|
+
configuration = this.#configureSession(sessionId);
|
|
230
|
+
}
|
|
231
|
+
configuration
|
|
232
|
+
.finally(() =>
|
|
233
|
+
this.#send("Runtime.runIfWaitingForDebugger", {}, sessionId),
|
|
234
|
+
)
|
|
235
|
+
.catch(() => {
|
|
236
|
+
return;
|
|
237
|
+
});
|
|
238
|
+
} else if (method === "Target.detachedFromTarget") {
|
|
239
|
+
this.#detachSession(String(params.sessionId ?? ""));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
#configureSession(sessionId) {
|
|
243
|
+
const existing = this.#sessionConfigurations.get(sessionId);
|
|
244
|
+
if (existing) return existing;
|
|
245
|
+
const configuration = this.#configureNewSession(sessionId);
|
|
246
|
+
this.#sessionConfigurations.set(sessionId, configuration);
|
|
247
|
+
return configuration;
|
|
248
|
+
}
|
|
249
|
+
async #configureNewSession(sessionId) {
|
|
250
|
+
await this.#send("Target.setAutoAttach", AUTO_ATTACH_PARAMS, sessionId);
|
|
251
|
+
await this.#send("Page.enable", {}, sessionId);
|
|
252
|
+
await this.#send(
|
|
253
|
+
"Page.addScriptToEvaluateOnNewDocument",
|
|
254
|
+
{ source: this.#waiterScript, runImmediately: true },
|
|
255
|
+
sessionId,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
async #awaitSession(targetId) {
|
|
259
|
+
const deadline = Date.now() + TIMEOUT_MS;
|
|
260
|
+
while (Date.now() < deadline) {
|
|
261
|
+
const sessionId = this.#targetSessions.get(targetId);
|
|
262
|
+
const configuration = sessionId
|
|
263
|
+
? this.#sessionConfigurations.get(sessionId)
|
|
264
|
+
: undefined;
|
|
265
|
+
if (sessionId && configuration) {
|
|
266
|
+
await configuration;
|
|
267
|
+
return sessionId;
|
|
268
|
+
}
|
|
269
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
270
|
+
}
|
|
271
|
+
return "";
|
|
272
|
+
}
|
|
273
|
+
#detachSession(sessionId) {
|
|
274
|
+
if (!sessionId) return;
|
|
275
|
+
this.#sessionConfigurations.delete(sessionId);
|
|
276
|
+
for (const [targetId, attachedSession] of this.#targetSessions) {
|
|
277
|
+
if (attachedSession === sessionId) this.#targetSessions.delete(targetId);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
#failConnection() {
|
|
281
|
+
this.#closed = true;
|
|
282
|
+
this.#failPending(new Error("CDP connection closed"));
|
|
283
|
+
}
|
|
284
|
+
#failPending(error) {
|
|
285
|
+
for (const command of this.#pending.values()) {
|
|
286
|
+
clearTimeout(command.timeout);
|
|
287
|
+
command.reject(error);
|
|
288
|
+
}
|
|
289
|
+
this.#pending.clear();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function websocketUrl(capabilities) {
|
|
293
|
+
const cdpUrl = capabilities.get("se:cdp");
|
|
294
|
+
if (typeof cdpUrl === "string") return cdpUrl;
|
|
295
|
+
const options =
|
|
296
|
+
capabilities.get("goog:chromeOptions") ??
|
|
297
|
+
capabilities.get("ms:edgeOptions");
|
|
298
|
+
if (!options?.debuggerAddress) {
|
|
299
|
+
throw new Error("Chromium did not expose a CDP debugger address");
|
|
300
|
+
}
|
|
301
|
+
const response = await fetch(
|
|
302
|
+
`http://${options.debuggerAddress}/json/version`,
|
|
303
|
+
{
|
|
304
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
305
|
+
},
|
|
306
|
+
);
|
|
307
|
+
if (!response.ok) throw new Error(`CDP discovery failed: ${response.status}`);
|
|
308
|
+
const version = await response.json();
|
|
309
|
+
return version.webSocketDebuggerUrl;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// selenium/src/drivers/SeleniumDriver.ts
|
|
313
|
+
var { tracer, logger } = import_internal.Telemetry.get(
|
|
314
|
+
"packages/typescript/selenium/src/drivers/SeleniumDriver.ts",
|
|
315
|
+
);
|
|
316
|
+
var { span } = tracer.dec();
|
|
317
|
+
var stateful = import_internal.BaseDriver.stateful;
|
|
318
|
+
var WAITER_SCRIPT = import_internal.waiterScriptSource;
|
|
319
|
+
|
|
320
|
+
class SeleniumDriver extends import_internal.BaseDriver {
|
|
321
|
+
driver;
|
|
322
|
+
kind = "selenium";
|
|
323
|
+
platform = "chromium";
|
|
324
|
+
autoswitchToNewTab = true;
|
|
325
|
+
#shadowChildToHostMap = {};
|
|
326
|
+
#cdpConnection = null;
|
|
327
|
+
#cdpReady;
|
|
328
|
+
fullPageScreenshot = import_internal.Env.ALUMNIUM_FULL_PAGE_SCREENSHOT;
|
|
329
|
+
supportedTools = new Set([
|
|
330
|
+
import_internal.ClickTool,
|
|
331
|
+
import_internal.DragAndDropTool,
|
|
332
|
+
import_internal.HoverTool,
|
|
333
|
+
import_internal.PressKeyTool,
|
|
334
|
+
import_internal.TypeTool,
|
|
335
|
+
import_internal.UploadTool,
|
|
336
|
+
]);
|
|
337
|
+
constructor(driver) {
|
|
338
|
+
super();
|
|
339
|
+
this.driver = driver;
|
|
340
|
+
this.#cdpReady = this.initCdpConnection();
|
|
341
|
+
}
|
|
342
|
+
async fetchAccessibilityTree() {
|
|
343
|
+
await this.driver.switchTo().defaultContent();
|
|
344
|
+
await this.driver.switchTo().window(await this.driver.getWindowHandle());
|
|
345
|
+
logger.debug("Waiting for page to load before getting accessibility tree");
|
|
346
|
+
await this.waitForPageToLoad();
|
|
347
|
+
logger.debug("Page loaded, retrieving accessibility tree");
|
|
348
|
+
const frameTree = await this.executeCdpCommand("Page.getFrameTree", {});
|
|
349
|
+
const frameIds = this.getAllFrameIds(frameTree.frameTree);
|
|
350
|
+
const mainFrameId = frameTree.frameTree.frame.id;
|
|
351
|
+
logger.debug(`Found ${frameIds.length} frames`);
|
|
352
|
+
await this.executeCdpCommand("DOM.enable", {});
|
|
353
|
+
const domResponse = await this.executeCdpCommand(
|
|
354
|
+
"DOM.getFlattenedDocument",
|
|
355
|
+
{ depth: -1, pierce: true },
|
|
356
|
+
);
|
|
357
|
+
const domNodes = domResponse.nodes || [];
|
|
358
|
+
const frameToIframeMap = new Map();
|
|
359
|
+
const frameParentMap = new Map();
|
|
360
|
+
await this.buildFrameHierarchy(
|
|
361
|
+
frameTree.frameTree,
|
|
362
|
+
mainFrameId,
|
|
363
|
+
frameToIframeMap,
|
|
364
|
+
frameParentMap,
|
|
365
|
+
);
|
|
366
|
+
const allNodes = [];
|
|
367
|
+
for (const frameId of frameIds) {
|
|
368
|
+
try {
|
|
369
|
+
const response = await this.executeCdpCommand(
|
|
370
|
+
"Accessibility.getFullAXTree",
|
|
371
|
+
{ frameId },
|
|
372
|
+
);
|
|
373
|
+
const nodes = response.nodes || [];
|
|
374
|
+
logger.debug(
|
|
375
|
+
` -> Frame ${frameId.slice(0, 20)}...: ${nodes.length} nodes`,
|
|
376
|
+
);
|
|
377
|
+
const frameChain = this.getFrameChain(
|
|
378
|
+
frameId,
|
|
379
|
+
frameToIframeMap,
|
|
380
|
+
frameParentMap,
|
|
381
|
+
);
|
|
382
|
+
for (const node of nodes) {
|
|
383
|
+
if (frameChain.length > 0) {
|
|
384
|
+
node._frame_chain = frameChain;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
allNodes.push(...nodes);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
logger.debug(
|
|
390
|
+
` -> Frame ${frameId.slice(0, 20)}...: failed (${error instanceof Error ? error.message : String(error)})`,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
logger.debug(`Total accessibility nodes collected: ${allNodes.length}`);
|
|
395
|
+
try {
|
|
396
|
+
const frameChainsByBackendId = {};
|
|
397
|
+
for (const node of allNodes) {
|
|
398
|
+
if (node.backendDOMNodeId && node._frame_chain)
|
|
399
|
+
frameChainsByBackendId[node.backendDOMNodeId] = node._frame_chain;
|
|
400
|
+
}
|
|
401
|
+
const shadowNodes = await this.buildShadowHierarchy(
|
|
402
|
+
domNodes,
|
|
403
|
+
frameChainsByBackendId,
|
|
404
|
+
);
|
|
405
|
+
allNodes.push(...shadowNodes);
|
|
406
|
+
if (shadowNodes.length > 0) {
|
|
407
|
+
logger.debug(` -> Shadow DOM: ${shadowNodes.length} nodes added`);
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
logger.debug(
|
|
411
|
+
` -> Shadow DOM failed (${error instanceof Error ? error.message : String(error)})`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
return new import_internal.ChromiumAccessibilityTree({ nodes: allNodes });
|
|
415
|
+
}
|
|
416
|
+
async click(id) {
|
|
417
|
+
await this.#autoswitchToNewTabAction(async () => {
|
|
418
|
+
const element = await this.findElement(id);
|
|
419
|
+
await this.#scrollElementIntoCenter(element);
|
|
420
|
+
try {
|
|
421
|
+
const actions = this.driver.actions({ async: true });
|
|
422
|
+
await actions.move({ origin: element }).click().perform();
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (error instanceof import_error.ElementNotInteractableError) {
|
|
425
|
+
await element.click();
|
|
426
|
+
} else {
|
|
427
|
+
throw error;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
async dragSlider(id, value) {
|
|
433
|
+
const element = await this.findElement(id);
|
|
434
|
+
await this.#scrollElementIntoCenter(element);
|
|
435
|
+
await this.driver.executeScript(
|
|
436
|
+
"arguments[0].value = arguments[1];" +
|
|
437
|
+
"arguments[0].dispatchEvent(new Event('input', {bubbles: true}));" +
|
|
438
|
+
"arguments[0].dispatchEvent(new Event('change', {bubbles: true}));",
|
|
439
|
+
element,
|
|
440
|
+
String(value),
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
async dragAndDrop(fromId, toId) {
|
|
444
|
+
const fromElement = await this.findElement(fromId);
|
|
445
|
+
const toElement = await this.findElement(toId);
|
|
446
|
+
await this.#scrollElementIntoCenter(fromElement);
|
|
447
|
+
const actions = this.driver.actions({ async: true });
|
|
448
|
+
await actions.dragAndDrop(fromElement, toElement).perform();
|
|
449
|
+
}
|
|
450
|
+
async hover(id) {
|
|
451
|
+
const element = await this.findElement(id);
|
|
452
|
+
await this.#scrollElementIntoCenter(element);
|
|
453
|
+
const actions = this.driver.actions({ async: true });
|
|
454
|
+
await actions.move({ origin: element }).perform();
|
|
455
|
+
}
|
|
456
|
+
pressKey(key) {
|
|
457
|
+
return this.#autoswitchToNewTabAction(async () => {
|
|
458
|
+
const keyMap = {
|
|
459
|
+
Backspace: import_selenium_webdriver.Key.BACK_SPACE,
|
|
460
|
+
Enter: import_selenium_webdriver.Key.ENTER,
|
|
461
|
+
Escape: import_selenium_webdriver.Key.ESCAPE,
|
|
462
|
+
Space: import_selenium_webdriver.Key.SPACE,
|
|
463
|
+
Tab: import_selenium_webdriver.Key.TAB,
|
|
464
|
+
ArrowDown: import_selenium_webdriver.Key.ARROW_DOWN,
|
|
465
|
+
ArrowUp: import_selenium_webdriver.Key.ARROW_UP,
|
|
466
|
+
ArrowLeft: import_selenium_webdriver.Key.ARROW_LEFT,
|
|
467
|
+
ArrowRight: import_selenium_webdriver.Key.ARROW_RIGHT,
|
|
468
|
+
};
|
|
469
|
+
const actions = this.driver.actions({ async: true });
|
|
470
|
+
await actions.sendKeys(keyMap[key]).perform();
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
async quit() {
|
|
474
|
+
await this.#cdpReady;
|
|
475
|
+
this.#cdpConnection?.close();
|
|
476
|
+
try {
|
|
477
|
+
await this.driver.quit();
|
|
478
|
+
} catch (error) {
|
|
479
|
+
if (error instanceof import_error.NoSuchSessionError) {
|
|
480
|
+
logger.info("Selenium session already closed, ignoring quit error");
|
|
481
|
+
} else {
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async back() {
|
|
487
|
+
await this.driver.navigate().back();
|
|
488
|
+
}
|
|
489
|
+
async visit(url) {
|
|
490
|
+
await this.checkNavigationPolicy(url);
|
|
491
|
+
await this.driver.get(url);
|
|
492
|
+
}
|
|
493
|
+
async scrollTo(id) {
|
|
494
|
+
const element = await this.findElement(id);
|
|
495
|
+
await this.#scrollElementIntoCenter(element);
|
|
496
|
+
}
|
|
497
|
+
async screenshot() {
|
|
498
|
+
if (this.fullPageScreenshot) {
|
|
499
|
+
const result = await this.executeCdpCommand("Page.captureScreenshot", {
|
|
500
|
+
format: "png",
|
|
501
|
+
captureBeyondViewport: true,
|
|
502
|
+
});
|
|
503
|
+
return result.data;
|
|
504
|
+
} else {
|
|
505
|
+
return await this.driver.takeScreenshot();
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
title() {
|
|
509
|
+
return this.driver.getTitle();
|
|
510
|
+
}
|
|
511
|
+
async type(id, text) {
|
|
512
|
+
const element = await this.findElement(id);
|
|
513
|
+
await this.#scrollElementIntoCenter(element);
|
|
514
|
+
await element.clear();
|
|
515
|
+
await element.sendKeys(text);
|
|
516
|
+
}
|
|
517
|
+
async upload(id, paths) {
|
|
518
|
+
const element = await this.findElement(id);
|
|
519
|
+
await element.sendKeys(
|
|
520
|
+
paths.join(`
|
|
521
|
+
`),
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
url() {
|
|
525
|
+
return this.driver.getCurrentUrl();
|
|
526
|
+
}
|
|
527
|
+
async app() {
|
|
528
|
+
const currentUrl = await this.driver.getCurrentUrl();
|
|
529
|
+
return import_internal.AppId.parse(currentUrl);
|
|
530
|
+
}
|
|
531
|
+
async findElement(id) {
|
|
532
|
+
const tree = await this.getAccessibilityTree();
|
|
533
|
+
const accessibilityElement = tree.elementById(id);
|
|
534
|
+
const backendNodeId = accessibilityElement.backendNodeId;
|
|
535
|
+
const frameChain = accessibilityElement.frameChain;
|
|
536
|
+
if (frameChain && frameChain.length > 0) {
|
|
537
|
+
await this.switchToFrameChain(frameChain);
|
|
538
|
+
}
|
|
539
|
+
await this.executeCdpCommand("DOM.enable", {});
|
|
540
|
+
await this.executeCdpCommand("DOM.getFlattenedDocument", {});
|
|
541
|
+
const { nodeIds } = await this.executeCdpCommand(
|
|
542
|
+
"DOM.pushNodesByBackendIdsToFrontend",
|
|
543
|
+
{ backendNodeIds: [backendNodeId] },
|
|
544
|
+
);
|
|
545
|
+
const nodeId = nodeIds[0];
|
|
546
|
+
await this.executeCdpCommand("DOM.setAttributeValue", {
|
|
547
|
+
nodeId,
|
|
548
|
+
name: "data-alumnium-id",
|
|
549
|
+
value: String(backendNodeId),
|
|
550
|
+
});
|
|
551
|
+
const selector = `[data-alumnium-id='${backendNodeId}']`;
|
|
552
|
+
const hostBackendNodeId = this.#shadowChildToHostMap[backendNodeId];
|
|
553
|
+
const searchContext =
|
|
554
|
+
hostBackendNodeId !== undefined
|
|
555
|
+
? await this.findShadowRoot(hostBackendNodeId)
|
|
556
|
+
: this.driver;
|
|
557
|
+
const element = await searchContext.findElement(
|
|
558
|
+
import_selenium_webdriver.By.css(selector),
|
|
559
|
+
);
|
|
560
|
+
await this.executeCdpCommand("DOM.removeAttribute", {
|
|
561
|
+
nodeId,
|
|
562
|
+
name: "data-alumnium-id",
|
|
563
|
+
});
|
|
564
|
+
return element;
|
|
565
|
+
}
|
|
566
|
+
async executeScript(script) {
|
|
567
|
+
await this.driver.executeScript(script);
|
|
568
|
+
}
|
|
569
|
+
async printToPdf(filepath) {
|
|
570
|
+
const { data } = await this.executeCdpCommand("Page.printToPDF", {});
|
|
571
|
+
await fs.writeFile(filepath, Buffer.from(data, "base64"));
|
|
572
|
+
}
|
|
573
|
+
async switchToNextTab() {
|
|
574
|
+
const handles = await this.driver.getAllWindowHandles();
|
|
575
|
+
if (handles.length <= 1) return;
|
|
576
|
+
const current = await this.driver.getWindowHandle();
|
|
577
|
+
const currentIndex = handles.indexOf(current);
|
|
578
|
+
const nextIndex = (currentIndex + 1) % handles.length;
|
|
579
|
+
await this.switchToTab(handles, nextIndex);
|
|
580
|
+
logger.debug(
|
|
581
|
+
`Switched to next tab: ${await this.driver.getTitle()} (${await this.driver.getCurrentUrl()})`,
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
async switchToPreviousTab() {
|
|
585
|
+
const handles = await this.driver.getAllWindowHandles();
|
|
586
|
+
if (handles.length <= 1) return;
|
|
587
|
+
const current = await this.driver.getWindowHandle();
|
|
588
|
+
const currentIndex = handles.indexOf(current);
|
|
589
|
+
const prevIndex = (currentIndex - 1 + handles.length) % handles.length;
|
|
590
|
+
await this.switchToTab(handles, prevIndex);
|
|
591
|
+
logger.debug(
|
|
592
|
+
`Switched to previous tab: ${await this.driver.getTitle()} (${await this.driver.getCurrentUrl()})`,
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
async wait(seconds) {
|
|
596
|
+
const clampedSeconds = Math.max(1, Math.min(30, seconds));
|
|
597
|
+
await new Promise((resolve) => setTimeout(resolve, clampedSeconds * 1000));
|
|
598
|
+
}
|
|
599
|
+
async waitForSelector() {
|
|
600
|
+
throw new Error("waitForSelector not supported for this driver");
|
|
601
|
+
}
|
|
602
|
+
async #scrollElementIntoCenter(element) {
|
|
603
|
+
await this.driver.executeScript(
|
|
604
|
+
"arguments[0].scrollIntoView({block: 'center'});",
|
|
605
|
+
element,
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
async checkNavigationPolicy(url) {
|
|
609
|
+
const currentUrl = url ?? (await this.url());
|
|
610
|
+
this.navigationPolicy.check(currentUrl);
|
|
611
|
+
}
|
|
612
|
+
executeCdpCommand(cmd, params) {
|
|
613
|
+
return this.driver.sendAndGetDevToolsCommand(cmd, params);
|
|
614
|
+
}
|
|
615
|
+
async waitForPageToLoad() {
|
|
616
|
+
await this.#cdpReady;
|
|
617
|
+
try {
|
|
618
|
+
const result = await import_internal.waitForPageStability(() =>
|
|
619
|
+
this.waiterSnapshot(),
|
|
620
|
+
);
|
|
621
|
+
if (!result.loaded) {
|
|
622
|
+
logger.warn(
|
|
623
|
+
`Timed out waiting for page to load; pending requests: ${result.pending.join(", ")}`,
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
} catch (error) {
|
|
627
|
+
try {
|
|
628
|
+
await import_internal.waitForPageStability(() => this.waiterSnapshot());
|
|
629
|
+
} catch (retryError) {
|
|
630
|
+
logger.warn(
|
|
631
|
+
`Failed to wait for page to load after retry (${String(error)}): ${String(retryError)}`,
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
async switchToTab(handles, tabIndex) {
|
|
637
|
+
import_alwaysly.always(handles[tabIndex]);
|
|
638
|
+
await this.driver.switchTo().window(handles[tabIndex]);
|
|
639
|
+
}
|
|
640
|
+
async initCdpConnection() {
|
|
641
|
+
try {
|
|
642
|
+
const capabilities = await this.driver.getCapabilities();
|
|
643
|
+
this.#cdpConnection = await SeleniumCdpConnection.connect(
|
|
644
|
+
capabilities,
|
|
645
|
+
WAITER_SCRIPT,
|
|
646
|
+
);
|
|
647
|
+
} catch (error) {
|
|
648
|
+
logger.debug(
|
|
649
|
+
`Could not connect to CDP to inject the waiter script: ${error instanceof Error ? error.message : String(error)}`,
|
|
650
|
+
);
|
|
651
|
+
try {
|
|
652
|
+
await this.executeCdpCommand("Page.addScriptToEvaluateOnNewDocument", {
|
|
653
|
+
source: WAITER_SCRIPT,
|
|
654
|
+
runImmediately: true,
|
|
655
|
+
});
|
|
656
|
+
} catch (fallbackError) {
|
|
657
|
+
logger.debug(
|
|
658
|
+
`Could not install waiter script through Selenium: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`,
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
async waiterSnapshot() {
|
|
664
|
+
let snapshot = await this.driver.executeScript(
|
|
665
|
+
`return ${import_internal.WAITER_SNAPSHOT_SCRIPT}`,
|
|
666
|
+
);
|
|
667
|
+
if (!snapshot) {
|
|
668
|
+
await this.driver.executeScript(WAITER_SCRIPT);
|
|
669
|
+
snapshot = await this.driver.executeScript(
|
|
670
|
+
`return ${import_internal.WAITER_SNAPSHOT_SCRIPT}`,
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
return snapshot;
|
|
674
|
+
}
|
|
675
|
+
async #autoswitchToNewTabAction(fn) {
|
|
676
|
+
if (!this.autoswitchToNewTab) {
|
|
677
|
+
return await fn();
|
|
678
|
+
}
|
|
679
|
+
return tracer.span("driver.internal.switch_to_new_tab", async () => {
|
|
680
|
+
const currentHandles = await this.driver.getAllWindowHandles();
|
|
681
|
+
const result = await fn();
|
|
682
|
+
const newHandles = await this.driver.getAllWindowHandles();
|
|
683
|
+
const newTabs = newHandles.filter((h) => !currentHandles.includes(h));
|
|
684
|
+
if (newTabs.length) {
|
|
685
|
+
const lastNewTab = newTabs[newTabs.length - 1];
|
|
686
|
+
import_alwaysly.always(lastNewTab);
|
|
687
|
+
if (lastNewTab !== (await this.driver.getWindowHandle())) {
|
|
688
|
+
await this.driver.switchTo().window(lastNewTab);
|
|
689
|
+
logger.debug(
|
|
690
|
+
`Auto-switching to new tab: ${await this.driver.getTitle()} (${await this.driver.getCurrentUrl()})`,
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return result;
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
async switchToFrameChain(frameChain) {
|
|
698
|
+
await this.driver.switchTo().defaultContent();
|
|
699
|
+
for (const iframeBackendNodeId of frameChain) {
|
|
700
|
+
await this.switchToSingleFrame(iframeBackendNodeId);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
async switchToSingleFrame(iframeBackendNodeId) {
|
|
704
|
+
await this.executeCdpCommand("DOM.enable", {});
|
|
705
|
+
await this.executeCdpCommand("DOM.getFlattenedDocument", {});
|
|
706
|
+
const { nodeIds } = await this.executeCdpCommand(
|
|
707
|
+
"DOM.pushNodesByBackendIdsToFrontend",
|
|
708
|
+
{ backendNodeIds: [iframeBackendNodeId] },
|
|
709
|
+
);
|
|
710
|
+
const nodeId = nodeIds[0];
|
|
711
|
+
let iframeElement;
|
|
712
|
+
let set = false;
|
|
713
|
+
let failure;
|
|
714
|
+
try {
|
|
715
|
+
await this.executeCdpCommand("DOM.setAttributeValue", {
|
|
716
|
+
nodeId,
|
|
717
|
+
name: "data-alumnium-iframe-id",
|
|
718
|
+
value: String(iframeBackendNodeId),
|
|
719
|
+
});
|
|
720
|
+
set = true;
|
|
721
|
+
iframeElement = await this.driver.findElement(
|
|
722
|
+
import_selenium_webdriver.By.css(
|
|
723
|
+
`[data-alumnium-iframe-id='${iframeBackendNodeId}']`,
|
|
724
|
+
),
|
|
725
|
+
);
|
|
726
|
+
} catch (error) {
|
|
727
|
+
failure = error;
|
|
728
|
+
} finally {
|
|
729
|
+
if (set) {
|
|
730
|
+
try {
|
|
731
|
+
await this.executeCdpCommand("DOM.removeAttribute", {
|
|
732
|
+
nodeId,
|
|
733
|
+
name: "data-alumnium-iframe-id",
|
|
734
|
+
});
|
|
735
|
+
} catch (error) {
|
|
736
|
+
failure ??= error;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (failure) throw failure;
|
|
741
|
+
import_alwaysly.always(iframeElement);
|
|
742
|
+
await this.driver.switchTo().frame(iframeElement);
|
|
743
|
+
logger.debug(
|
|
744
|
+
`Switched to iframe with backendNodeId=${iframeBackendNodeId}`,
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
async buildFrameHierarchy(
|
|
748
|
+
frameInfo,
|
|
749
|
+
mainFrameId,
|
|
750
|
+
frameToIframeMap,
|
|
751
|
+
frameParentMap,
|
|
752
|
+
parentFrameId,
|
|
753
|
+
) {
|
|
754
|
+
const frameId = frameInfo.frame.id;
|
|
755
|
+
if (frameId !== mainFrameId) {
|
|
756
|
+
await this.executeCdpCommand("DOM.enable", {});
|
|
757
|
+
try {
|
|
758
|
+
const ownerInfo = await this.executeCdpCommand("DOM.getFrameOwner", {
|
|
759
|
+
frameId,
|
|
760
|
+
});
|
|
761
|
+
frameToIframeMap.set(frameId, ownerInfo.backendNodeId);
|
|
762
|
+
logger.debug(
|
|
763
|
+
`Frame ${frameId.slice(0, 20)}... owned by iframe backendNodeId=${ownerInfo.backendNodeId}`,
|
|
764
|
+
);
|
|
765
|
+
} catch (error) {
|
|
766
|
+
logger.debug(
|
|
767
|
+
`Could not get frame owner for ${frameId.slice(0, 20)}...: ${error instanceof Error ? error.message : String(error)}`,
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
if (parentFrameId) {
|
|
771
|
+
frameParentMap.set(frameId, parentFrameId);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
for (const child of frameInfo.childFrames || []) {
|
|
775
|
+
await this.buildFrameHierarchy(
|
|
776
|
+
child,
|
|
777
|
+
mainFrameId,
|
|
778
|
+
frameToIframeMap,
|
|
779
|
+
frameParentMap,
|
|
780
|
+
frameId,
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
getFrameChain(frameId, frameToIframeMap, frameParentMap) {
|
|
785
|
+
const chain = [];
|
|
786
|
+
let currentFrameId = frameId;
|
|
787
|
+
while (frameToIframeMap.has(currentFrameId)) {
|
|
788
|
+
const iframeBackendNodeId = frameToIframeMap.get(currentFrameId);
|
|
789
|
+
chain.unshift(iframeBackendNodeId);
|
|
790
|
+
if (frameParentMap.has(currentFrameId)) {
|
|
791
|
+
currentFrameId = frameParentMap.get(currentFrameId);
|
|
792
|
+
} else {
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return chain;
|
|
797
|
+
}
|
|
798
|
+
getAllFrameIds(frameInfo) {
|
|
799
|
+
const frameIds = [frameInfo.frame.id];
|
|
800
|
+
for (const child of frameInfo.childFrames || []) {
|
|
801
|
+
frameIds.push(...this.getAllFrameIds(child));
|
|
802
|
+
}
|
|
803
|
+
return frameIds;
|
|
804
|
+
}
|
|
805
|
+
async findShadowRoot(hostBackendNodeId) {
|
|
806
|
+
const { nodeIds: hostNodeIds } = await this.executeCdpCommand(
|
|
807
|
+
"DOM.pushNodesByBackendIdsToFrontend",
|
|
808
|
+
{ backendNodeIds: [hostBackendNodeId] },
|
|
809
|
+
);
|
|
810
|
+
await this.executeCdpCommand("DOM.setAttributeValue", {
|
|
811
|
+
nodeId: hostNodeIds[0],
|
|
812
|
+
name: "data-alumnium-host",
|
|
813
|
+
value: String(hostBackendNodeId),
|
|
814
|
+
});
|
|
815
|
+
const hostElement = await this.driver.findElement(
|
|
816
|
+
import_selenium_webdriver.By.css(
|
|
817
|
+
`[data-alumnium-host='${hostBackendNodeId}']`,
|
|
818
|
+
),
|
|
819
|
+
);
|
|
820
|
+
await this.executeCdpCommand("DOM.removeAttribute", {
|
|
821
|
+
nodeId: hostNodeIds[0],
|
|
822
|
+
name: "data-alumnium-host",
|
|
823
|
+
});
|
|
824
|
+
return hostElement.getShadowRoot();
|
|
825
|
+
}
|
|
826
|
+
async buildShadowHierarchy(domNodes, frameChainsByBackendId) {
|
|
827
|
+
const shadowNodes = [];
|
|
828
|
+
const processedNodes = new Set();
|
|
829
|
+
const nodeIdToBackendId = {};
|
|
830
|
+
const parentIdMap = {};
|
|
831
|
+
const shadowRootToHostBackendId = {};
|
|
832
|
+
for (const domNode of domNodes) {
|
|
833
|
+
nodeIdToBackendId[domNode.nodeId] = domNode.backendNodeId;
|
|
834
|
+
if (domNode.parentId !== undefined) {
|
|
835
|
+
parentIdMap[domNode.nodeId] = domNode.parentId;
|
|
836
|
+
}
|
|
837
|
+
if (domNode.shadowRoots) {
|
|
838
|
+
for (const sr of domNode.shadowRoots) {
|
|
839
|
+
shadowRootToHostBackendId[sr.nodeId] = domNode.backendNodeId;
|
|
840
|
+
parentIdMap[sr.nodeId] = domNode.nodeId;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
this.#shadowChildToHostMap = {};
|
|
845
|
+
for (const domNode of domNodes) {
|
|
846
|
+
const nodeBackendId = domNode.backendNodeId;
|
|
847
|
+
let currentId = domNode.nodeId;
|
|
848
|
+
while (currentId !== undefined) {
|
|
849
|
+
if (currentId in shadowRootToHostBackendId) {
|
|
850
|
+
this.#shadowChildToHostMap[nodeBackendId] =
|
|
851
|
+
shadowRootToHostBackendId[currentId];
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
854
|
+
currentId = parentIdMap[currentId];
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
for (const domNode of domNodes) {
|
|
858
|
+
if (domNode.shadowRoots && domNode.shadowRoots.length > 0) {
|
|
859
|
+
const frameChain = frameChainsByBackendId[domNode.backendNodeId];
|
|
860
|
+
try {
|
|
861
|
+
const axResponse = await this.executeCdpCommand(
|
|
862
|
+
"Accessibility.queryAXTree",
|
|
863
|
+
{ nodeId: domNode.nodeId },
|
|
864
|
+
);
|
|
865
|
+
if (axResponse.nodes) {
|
|
866
|
+
for (const axNode of axResponse.nodes) {
|
|
867
|
+
if (processedNodes.has(axNode.nodeId)) continue;
|
|
868
|
+
processedNodes.add(axNode.nodeId);
|
|
869
|
+
axNode._is_shadow_dom = true;
|
|
870
|
+
if (frameChain) axNode._frame_chain = frameChain;
|
|
871
|
+
if (!axNode.backendDOMNodeId) {
|
|
872
|
+
const backendId =
|
|
873
|
+
nodeIdToBackendId[Number.parseInt(axNode.nodeId)];
|
|
874
|
+
if (backendId !== undefined) {
|
|
875
|
+
axNode.backendDOMNodeId = backendId;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
shadowNodes.push(axNode);
|
|
879
|
+
if (axNode.childIds && Array.isArray(axNode.childIds)) {
|
|
880
|
+
for (const childId of axNode.childIds) {
|
|
881
|
+
const childNodes = await this.getShadowChildNodes(
|
|
882
|
+
String(childId),
|
|
883
|
+
processedNodes,
|
|
884
|
+
nodeIdToBackendId,
|
|
885
|
+
frameChain,
|
|
886
|
+
);
|
|
887
|
+
shadowNodes.push(...childNodes);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
} catch {}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return shadowNodes;
|
|
896
|
+
}
|
|
897
|
+
async getShadowChildNodes(
|
|
898
|
+
nodeId,
|
|
899
|
+
processedNodes,
|
|
900
|
+
nodeIdToBackendId,
|
|
901
|
+
frameChain,
|
|
902
|
+
) {
|
|
903
|
+
const nodes = [];
|
|
904
|
+
if (processedNodes.has(nodeId)) return nodes;
|
|
905
|
+
processedNodes.add(nodeId);
|
|
906
|
+
try {
|
|
907
|
+
const response = await this.executeCdpCommand(
|
|
908
|
+
"Accessibility.queryAXTree",
|
|
909
|
+
{ nodeId: Number.parseInt(nodeId) },
|
|
910
|
+
);
|
|
911
|
+
if (response.nodes) {
|
|
912
|
+
for (const node of response.nodes) {
|
|
913
|
+
node._is_shadow_dom = true;
|
|
914
|
+
if (frameChain) node._frame_chain = frameChain;
|
|
915
|
+
if (!node.backendDOMNodeId) {
|
|
916
|
+
const backendId = nodeIdToBackendId[Number.parseInt(node.nodeId)];
|
|
917
|
+
if (backendId !== undefined) {
|
|
918
|
+
node.backendDOMNodeId = backendId;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
nodes.push(node);
|
|
922
|
+
if (node.childIds && Array.isArray(node.childIds)) {
|
|
923
|
+
for (const childId of node.childIds) {
|
|
924
|
+
const childNodes = await this.getShadowChildNodes(
|
|
925
|
+
String(childId),
|
|
926
|
+
processedNodes,
|
|
927
|
+
nodeIdToBackendId,
|
|
928
|
+
frameChain,
|
|
929
|
+
);
|
|
930
|
+
nodes.push(...childNodes);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
} catch {}
|
|
936
|
+
return nodes;
|
|
937
|
+
}
|
|
938
|
+
async devDrillProbeTree(tree, rawId) {
|
|
939
|
+
let accessibilityElement;
|
|
940
|
+
try {
|
|
941
|
+
accessibilityElement = tree.elementById(rawId);
|
|
942
|
+
} catch (error) {
|
|
943
|
+
throw new import_internal.TreeDevDrillError("resolve", error);
|
|
944
|
+
}
|
|
945
|
+
const backendNodeId = accessibilityElement.backendNodeId;
|
|
946
|
+
if (backendNodeId === undefined) {
|
|
947
|
+
throw new import_internal.TreeDevDrillError(
|
|
948
|
+
"resolve",
|
|
949
|
+
new Error(`Element with raw_id=${rawId} has no backend node ID`),
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
const attribute = "data-alumnium-drill";
|
|
953
|
+
let nodeId;
|
|
954
|
+
let set = false;
|
|
955
|
+
let failure;
|
|
956
|
+
try {
|
|
957
|
+
const frameChain = accessibilityElement.frameChain;
|
|
958
|
+
if (frameChain?.length) await this.switchToFrameChain(frameChain);
|
|
959
|
+
else await this.driver.switchTo().defaultContent();
|
|
960
|
+
await this.executeCdpCommand("DOM.enable", {});
|
|
961
|
+
await this.executeCdpCommand("DOM.getFlattenedDocument", {});
|
|
962
|
+
const response = await this.executeCdpCommand(
|
|
963
|
+
"DOM.pushNodesByBackendIdsToFrontend",
|
|
964
|
+
{ backendNodeIds: [backendNodeId] },
|
|
965
|
+
);
|
|
966
|
+
nodeId = response.nodeIds[0];
|
|
967
|
+
if (!nodeId) {
|
|
968
|
+
throw new import_internal.TreeDevDrillError(
|
|
969
|
+
"resolve",
|
|
970
|
+
new Error(`No frontend node for backend node ID ${backendNodeId}`),
|
|
971
|
+
backendNodeId,
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
try {
|
|
975
|
+
await this.executeCdpCommand("DOM.setAttributeValue", {
|
|
976
|
+
nodeId,
|
|
977
|
+
name: attribute,
|
|
978
|
+
value: crypto.randomUUID(),
|
|
979
|
+
});
|
|
980
|
+
set = true;
|
|
981
|
+
} catch (error) {
|
|
982
|
+
throw new import_internal.TreeDevDrillError(
|
|
983
|
+
"probe",
|
|
984
|
+
error,
|
|
985
|
+
backendNodeId,
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
} catch (error) {
|
|
989
|
+
failure =
|
|
990
|
+
error instanceof import_internal.TreeDevDrillError
|
|
991
|
+
? error
|
|
992
|
+
: new import_internal.TreeDevDrillError(
|
|
993
|
+
"resolve",
|
|
994
|
+
error,
|
|
995
|
+
backendNodeId,
|
|
996
|
+
);
|
|
997
|
+
} finally {
|
|
998
|
+
if (set && nodeId) {
|
|
999
|
+
try {
|
|
1000
|
+
await this.executeCdpCommand("DOM.removeAttribute", {
|
|
1001
|
+
nodeId,
|
|
1002
|
+
name: attribute,
|
|
1003
|
+
});
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
failure ??= new import_internal.TreeDevDrillError(
|
|
1006
|
+
"probe",
|
|
1007
|
+
error,
|
|
1008
|
+
backendNodeId,
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
try {
|
|
1013
|
+
await this.driver.switchTo().defaultContent();
|
|
1014
|
+
} catch (error) {
|
|
1015
|
+
failure ??= new import_internal.TreeDevDrillError(
|
|
1016
|
+
"resolve",
|
|
1017
|
+
error,
|
|
1018
|
+
backendNodeId,
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
if (failure) throw failure;
|
|
1023
|
+
return backendNodeId;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
__legacyDecorateClassTS(
|
|
1027
|
+
[
|
|
1028
|
+
span(
|
|
1029
|
+
"driver.fetch_accessibility_tree",
|
|
1030
|
+
import_internal.BaseDriver.spanAttrs,
|
|
1031
|
+
),
|
|
1032
|
+
],
|
|
1033
|
+
SeleniumDriver.prototype,
|
|
1034
|
+
"fetchAccessibilityTree",
|
|
1035
|
+
null,
|
|
1036
|
+
);
|
|
1037
|
+
__legacyDecorateClassTS(
|
|
1038
|
+
[span("driver.click", import_internal.BaseDriver.spanAttrs), stateful],
|
|
1039
|
+
SeleniumDriver.prototype,
|
|
1040
|
+
"click",
|
|
1041
|
+
null,
|
|
1042
|
+
);
|
|
1043
|
+
__legacyDecorateClassTS(
|
|
1044
|
+
[span("driver.drag_slider", import_internal.BaseDriver.spanAttrs), stateful],
|
|
1045
|
+
SeleniumDriver.prototype,
|
|
1046
|
+
"dragSlider",
|
|
1047
|
+
null,
|
|
1048
|
+
);
|
|
1049
|
+
__legacyDecorateClassTS(
|
|
1050
|
+
[
|
|
1051
|
+
span("driver.drag_and_drop", import_internal.BaseDriver.spanAttrs),
|
|
1052
|
+
stateful,
|
|
1053
|
+
],
|
|
1054
|
+
SeleniumDriver.prototype,
|
|
1055
|
+
"dragAndDrop",
|
|
1056
|
+
null,
|
|
1057
|
+
);
|
|
1058
|
+
__legacyDecorateClassTS(
|
|
1059
|
+
[span("driver.hover", import_internal.BaseDriver.spanAttrs), stateful],
|
|
1060
|
+
SeleniumDriver.prototype,
|
|
1061
|
+
"hover",
|
|
1062
|
+
null,
|
|
1063
|
+
);
|
|
1064
|
+
__legacyDecorateClassTS(
|
|
1065
|
+
[span("driver.press_key", import_internal.BaseDriver.spanAttrs), stateful],
|
|
1066
|
+
SeleniumDriver.prototype,
|
|
1067
|
+
"pressKey",
|
|
1068
|
+
null,
|
|
1069
|
+
);
|
|
1070
|
+
__legacyDecorateClassTS(
|
|
1071
|
+
[span("driver.quit", import_internal.BaseDriver.spanAttrs)],
|
|
1072
|
+
SeleniumDriver.prototype,
|
|
1073
|
+
"quit",
|
|
1074
|
+
null,
|
|
1075
|
+
);
|
|
1076
|
+
__legacyDecorateClassTS(
|
|
1077
|
+
[span("driver.back", import_internal.BaseDriver.spanAttrs), stateful],
|
|
1078
|
+
SeleniumDriver.prototype,
|
|
1079
|
+
"back",
|
|
1080
|
+
null,
|
|
1081
|
+
);
|
|
1082
|
+
__legacyDecorateClassTS(
|
|
1083
|
+
[span("driver.visit", import_internal.BaseDriver.spanAttrs), stateful],
|
|
1084
|
+
SeleniumDriver.prototype,
|
|
1085
|
+
"visit",
|
|
1086
|
+
null,
|
|
1087
|
+
);
|
|
1088
|
+
__legacyDecorateClassTS(
|
|
1089
|
+
[span("driver.scroll_to", import_internal.BaseDriver.spanAttrs), stateful],
|
|
1090
|
+
SeleniumDriver.prototype,
|
|
1091
|
+
"scrollTo",
|
|
1092
|
+
null,
|
|
1093
|
+
);
|
|
1094
|
+
__legacyDecorateClassTS(
|
|
1095
|
+
[span("driver.screenshot", import_internal.BaseDriver.spanAttrs)],
|
|
1096
|
+
SeleniumDriver.prototype,
|
|
1097
|
+
"screenshot",
|
|
1098
|
+
null,
|
|
1099
|
+
);
|
|
1100
|
+
__legacyDecorateClassTS(
|
|
1101
|
+
[span("driver.title", import_internal.BaseDriver.spanAttrs)],
|
|
1102
|
+
SeleniumDriver.prototype,
|
|
1103
|
+
"title",
|
|
1104
|
+
null,
|
|
1105
|
+
);
|
|
1106
|
+
__legacyDecorateClassTS(
|
|
1107
|
+
[span("driver.type", import_internal.BaseDriver.spanAttrs), stateful],
|
|
1108
|
+
SeleniumDriver.prototype,
|
|
1109
|
+
"type",
|
|
1110
|
+
null,
|
|
1111
|
+
);
|
|
1112
|
+
__legacyDecorateClassTS(
|
|
1113
|
+
[span("driver.upload", import_internal.BaseDriver.spanAttrs), stateful],
|
|
1114
|
+
SeleniumDriver.prototype,
|
|
1115
|
+
"upload",
|
|
1116
|
+
null,
|
|
1117
|
+
);
|
|
1118
|
+
__legacyDecorateClassTS(
|
|
1119
|
+
[span("driver.url", import_internal.BaseDriver.spanAttrs)],
|
|
1120
|
+
SeleniumDriver.prototype,
|
|
1121
|
+
"url",
|
|
1122
|
+
null,
|
|
1123
|
+
);
|
|
1124
|
+
__legacyDecorateClassTS(
|
|
1125
|
+
[span("driver.app", import_internal.BaseDriver.spanAttrs)],
|
|
1126
|
+
SeleniumDriver.prototype,
|
|
1127
|
+
"app",
|
|
1128
|
+
null,
|
|
1129
|
+
);
|
|
1130
|
+
__legacyDecorateClassTS(
|
|
1131
|
+
[span("driver.find_element", import_internal.BaseDriver.spanAttrs)],
|
|
1132
|
+
SeleniumDriver.prototype,
|
|
1133
|
+
"findElement",
|
|
1134
|
+
null,
|
|
1135
|
+
);
|
|
1136
|
+
__legacyDecorateClassTS(
|
|
1137
|
+
[
|
|
1138
|
+
span("driver.execute_script", import_internal.BaseDriver.spanAttrs),
|
|
1139
|
+
stateful,
|
|
1140
|
+
],
|
|
1141
|
+
SeleniumDriver.prototype,
|
|
1142
|
+
"executeScript",
|
|
1143
|
+
null,
|
|
1144
|
+
);
|
|
1145
|
+
__legacyDecorateClassTS(
|
|
1146
|
+
[span("driver.print_to_pdf", import_internal.BaseDriver.spanAttrs)],
|
|
1147
|
+
SeleniumDriver.prototype,
|
|
1148
|
+
"printToPdf",
|
|
1149
|
+
null,
|
|
1150
|
+
);
|
|
1151
|
+
__legacyDecorateClassTS(
|
|
1152
|
+
[span("driver.switch_to_next_tab", import_internal.BaseDriver.spanAttrs)],
|
|
1153
|
+
SeleniumDriver.prototype,
|
|
1154
|
+
"switchToNextTab",
|
|
1155
|
+
null,
|
|
1156
|
+
);
|
|
1157
|
+
__legacyDecorateClassTS(
|
|
1158
|
+
[span("driver.switch_to_previous_tab", import_internal.BaseDriver.spanAttrs)],
|
|
1159
|
+
SeleniumDriver.prototype,
|
|
1160
|
+
"switchToPreviousTab",
|
|
1161
|
+
null,
|
|
1162
|
+
);
|
|
1163
|
+
__legacyDecorateClassTS(
|
|
1164
|
+
[span("driver.wait", import_internal.BaseDriver.spanAttrs), stateful],
|
|
1165
|
+
SeleniumDriver.prototype,
|
|
1166
|
+
"wait",
|
|
1167
|
+
null,
|
|
1168
|
+
);
|
|
1169
|
+
__legacyDecorateClassTS(
|
|
1170
|
+
[span("driver.wait_for_selector", import_internal.BaseDriver.spanAttrs)],
|
|
1171
|
+
SeleniumDriver.prototype,
|
|
1172
|
+
"waitForSelector",
|
|
1173
|
+
null,
|
|
1174
|
+
);
|
|
1175
|
+
__legacyDecorateClassTS(
|
|
1176
|
+
[
|
|
1177
|
+
span("driver.internal.cdp_command", (cmd) => ({
|
|
1178
|
+
"driver.kind": "selenium",
|
|
1179
|
+
"driver.platform": "chromium",
|
|
1180
|
+
"driver.internal.cdp_command.name": cmd,
|
|
1181
|
+
})),
|
|
1182
|
+
],
|
|
1183
|
+
SeleniumDriver.prototype,
|
|
1184
|
+
"executeCdpCommand",
|
|
1185
|
+
null,
|
|
1186
|
+
);
|
|
1187
|
+
__legacyDecorateClassTS(
|
|
1188
|
+
[span("driver.wait_for_page_to_load", import_internal.BaseDriver.spanAttrs)],
|
|
1189
|
+
SeleniumDriver.prototype,
|
|
1190
|
+
"waitForPageToLoad",
|
|
1191
|
+
null,
|
|
1192
|
+
);
|
|
1193
|
+
__legacyDecorateClassTS(
|
|
1194
|
+
[stateful("switchToTab")],
|
|
1195
|
+
SeleniumDriver.prototype,
|
|
1196
|
+
"switchToTab",
|
|
1197
|
+
null,
|
|
1198
|
+
);
|
|
1199
|
+
__legacyDecorateClassTS(
|
|
1200
|
+
[span("driver.internal.switch_to_frame_chain")],
|
|
1201
|
+
SeleniumDriver.prototype,
|
|
1202
|
+
"switchToFrameChain",
|
|
1203
|
+
null,
|
|
1204
|
+
);
|
|
1205
|
+
__legacyDecorateClassTS(
|
|
1206
|
+
[span("driver.internal.switch_to_single_frame")],
|
|
1207
|
+
SeleniumDriver.prototype,
|
|
1208
|
+
"switchToSingleFrame",
|
|
1209
|
+
null,
|
|
1210
|
+
);
|
|
1211
|
+
__legacyDecorateClassTS(
|
|
1212
|
+
[span("driver.internal.build_frame_hierarchy")],
|
|
1213
|
+
SeleniumDriver.prototype,
|
|
1214
|
+
"buildFrameHierarchy",
|
|
1215
|
+
null,
|
|
1216
|
+
);
|
|
1217
|
+
|
|
1218
|
+
// selenium/src/Alumni.ts
|
|
1219
|
+
class Alumni extends import_core.Alumni {
|
|
1220
|
+
constructor(driver, options = {}) {
|
|
1221
|
+
super(new SeleniumDriver(driver), options);
|
|
1222
|
+
}
|
|
1223
|
+
static accepts(driver) {
|
|
1224
|
+
return driver instanceof import_selenium_webdriver2.WebDriver;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
//# debugId=556752DC5E7931C764756E2164756E21
|
|
1229
|
+
//# sourceMappingURL=index.cjs.map
|