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