@ada-mcp/mcp-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -0
- package/dist/cli.cjs +8598 -0
- package/package.json +54 -0
- package/plugins/driver-appium.cjs +927 -0
- package/plugins/driver-playwright.cjs +805 -0
- package/plugins/driver-selenium.cjs +880 -0
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// ../../plugins/driver-selenium/src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
default: () => index_default
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// ../../packages/driver-rpc/src/index.ts
|
|
38
|
+
var PLAYWRIGHT_OBJECT_TYPES = /* @__PURE__ */ new Set([
|
|
39
|
+
"Page",
|
|
40
|
+
"Frame",
|
|
41
|
+
"Locator",
|
|
42
|
+
"BrowserContext",
|
|
43
|
+
"Browser",
|
|
44
|
+
"Response",
|
|
45
|
+
"CDPSession",
|
|
46
|
+
"ElementHandle",
|
|
47
|
+
"JSHandle",
|
|
48
|
+
"Worker",
|
|
49
|
+
"Request",
|
|
50
|
+
"Route",
|
|
51
|
+
"WebSocket"
|
|
52
|
+
]);
|
|
53
|
+
function asRecord(value) {
|
|
54
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
55
|
+
}
|
|
56
|
+
function getString(value) {
|
|
57
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
58
|
+
}
|
|
59
|
+
function normalizeInvokePayload(raw, defaultMode) {
|
|
60
|
+
const payload = asRecord(raw);
|
|
61
|
+
const legacyCustom = asRecord(payload.custom);
|
|
62
|
+
const httpBlock = asRecord(payload.http);
|
|
63
|
+
const httpMethod = getString(httpBlock.method) ?? getString(legacyCustom.method);
|
|
64
|
+
const httpPath = getString(httpBlock.path) ?? getString(legacyCustom.path);
|
|
65
|
+
const hasHttp = Boolean(httpMethod && httpPath);
|
|
66
|
+
const method = getString(payload.method);
|
|
67
|
+
const target = getString(payload.target);
|
|
68
|
+
const hasMethod = Boolean(method);
|
|
69
|
+
let mode = getString(payload.mode);
|
|
70
|
+
if (mode !== "method" && mode !== "http") {
|
|
71
|
+
mode = hasHttp ? "http" : hasMethod ? "method" : defaultMode;
|
|
72
|
+
}
|
|
73
|
+
if (mode === "http" && !hasHttp && hasMethod) {
|
|
74
|
+
mode = "method";
|
|
75
|
+
}
|
|
76
|
+
if (mode === "method" && !hasMethod && hasHttp) {
|
|
77
|
+
mode = "http";
|
|
78
|
+
}
|
|
79
|
+
if (mode === "http") {
|
|
80
|
+
if (!httpMethod || !httpPath) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
mode: "http",
|
|
85
|
+
http: {
|
|
86
|
+
method: httpMethod,
|
|
87
|
+
path: httpPath,
|
|
88
|
+
body: httpBlock.body ?? legacyCustom.body
|
|
89
|
+
},
|
|
90
|
+
options: asRecord(payload.options)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (!method) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
mode: "method",
|
|
98
|
+
target: target ?? "page",
|
|
99
|
+
method,
|
|
100
|
+
args: Array.isArray(payload.args) ? payload.args : [],
|
|
101
|
+
locator: asRecord(payload.locator),
|
|
102
|
+
options: asRecord(payload.options)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function pickPayloadString(payload, options, key, aliases = [], envKey) {
|
|
106
|
+
const keys = [key, ...aliases];
|
|
107
|
+
for (const k of keys) {
|
|
108
|
+
const top = getString(payload[k]);
|
|
109
|
+
if (top) {
|
|
110
|
+
return top;
|
|
111
|
+
}
|
|
112
|
+
const nested = getString(options[k]);
|
|
113
|
+
if (nested) {
|
|
114
|
+
return nested;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (envKey && typeof process.env[envKey] === "string" && process.env[envKey].length > 0) {
|
|
118
|
+
return process.env[envKey];
|
|
119
|
+
}
|
|
120
|
+
return "";
|
|
121
|
+
}
|
|
122
|
+
function resolveSeleniumBrowserFields(payload) {
|
|
123
|
+
const p = asRecord(payload);
|
|
124
|
+
const options = asRecord(p.options);
|
|
125
|
+
const browserName = getString(p.browserName) ?? getString(options.browserName) ?? getString(p.browser) ?? getString(options.browser) ?? process.env.ADA_SELENIUM_BROWSER ?? "firefox";
|
|
126
|
+
return {
|
|
127
|
+
browserName: browserName.toLowerCase(),
|
|
128
|
+
browserBinary: pickPayloadString(
|
|
129
|
+
p,
|
|
130
|
+
options,
|
|
131
|
+
"browserBinary",
|
|
132
|
+
["executablePath", "browserPath", "browserExecutable"],
|
|
133
|
+
"ADA_SELENIUM_BROWSER_BINARY"
|
|
134
|
+
),
|
|
135
|
+
profile: pickPayloadString(p, options, "profile", ["userDataDir"], "ADA_SELENIUM_PROFILE"),
|
|
136
|
+
seleniumServerUrl: pickPayloadString(
|
|
137
|
+
p,
|
|
138
|
+
options,
|
|
139
|
+
"seleniumServerUrl",
|
|
140
|
+
["serverUrl", "gridUrl"],
|
|
141
|
+
"ADA_SELENIUM_SERVER_URL"
|
|
142
|
+
)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function buildSeleniumSessionKey(payload) {
|
|
146
|
+
const p = asRecord(payload);
|
|
147
|
+
const fields = resolveSeleniumBrowserFields(p);
|
|
148
|
+
const headless = typeof p.headless === "boolean" ? p.headless : typeof asRecord(p.options).headless === "boolean" ? asRecord(p.options).headless : "env";
|
|
149
|
+
const caps = p.capabilities ?? asRecord(p.options).capabilities;
|
|
150
|
+
const capsKey = caps !== void 0 ? JSON.stringify(caps) : "";
|
|
151
|
+
return `selenium|${fields.browserName}|${headless}|${fields.browserBinary}|${fields.profile}|${fields.seleniumServerUrl}|${capsKey}`;
|
|
152
|
+
}
|
|
153
|
+
function serializeRpcResult(value, depth = 0) {
|
|
154
|
+
if (depth > 10) {
|
|
155
|
+
return "[MaxDepth]";
|
|
156
|
+
}
|
|
157
|
+
if (value === void 0) {
|
|
158
|
+
return { __undefined: true };
|
|
159
|
+
}
|
|
160
|
+
if (value === null || typeof value !== "function") {
|
|
161
|
+
if (value === null || typeof value !== "object") {
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
return { __type: "Function", hint: "Functions are not serializable over invoke RPC" };
|
|
166
|
+
}
|
|
167
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
168
|
+
return { __type: "Buffer", encoding: "base64", data: value.toString("base64") };
|
|
169
|
+
}
|
|
170
|
+
if (Array.isArray(value)) {
|
|
171
|
+
return value.map((item) => serializeRpcResult(item, depth + 1));
|
|
172
|
+
}
|
|
173
|
+
const ctor = value.constructor?.name;
|
|
174
|
+
if (ctor && PLAYWRIGHT_OBJECT_TYPES.has(ctor)) {
|
|
175
|
+
return { __type: ctor, hint: "Live Playwright object; chain further invoke calls on page/context" };
|
|
176
|
+
}
|
|
177
|
+
if (value instanceof Map) {
|
|
178
|
+
const out = {};
|
|
179
|
+
for (const [k, v] of value.entries()) {
|
|
180
|
+
out[String(k)] = serializeRpcResult(v, depth + 1);
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const out = {};
|
|
186
|
+
for (const [k, v] of Object.entries(value)) {
|
|
187
|
+
if (typeof v === "function") {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
out[k] = serializeRpcResult(v, depth + 1);
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
} catch {
|
|
194
|
+
return String(value);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function mergeOptionsIntoPayload(payload) {
|
|
198
|
+
const p = { ...asRecord(payload) };
|
|
199
|
+
const options = asRecord(p.options);
|
|
200
|
+
for (const key of [
|
|
201
|
+
"browser",
|
|
202
|
+
"headless",
|
|
203
|
+
"userDataDir",
|
|
204
|
+
"storageStatePath",
|
|
205
|
+
"storageState",
|
|
206
|
+
"launchOptions",
|
|
207
|
+
"contextOptions",
|
|
208
|
+
"cdpEndpoint",
|
|
209
|
+
"browserURL",
|
|
210
|
+
"cdpUrl",
|
|
211
|
+
"executablePath",
|
|
212
|
+
"browserPath",
|
|
213
|
+
"browserExecutable",
|
|
214
|
+
"channel",
|
|
215
|
+
"engine",
|
|
216
|
+
"browserName",
|
|
217
|
+
"browserBinary",
|
|
218
|
+
"profile",
|
|
219
|
+
"seleniumServerUrl",
|
|
220
|
+
"geckodriverVersion",
|
|
221
|
+
"chromedriverVersion"
|
|
222
|
+
]) {
|
|
223
|
+
if (p[key] === void 0 && options[key] !== void 0) {
|
|
224
|
+
p[key] = options[key];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return p;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ../../packages/native-drivers/src/index.ts
|
|
231
|
+
var import_node_child_process = require("node:child_process");
|
|
232
|
+
var import_promises = __toESM(require("node:fs/promises"), 1);
|
|
233
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
234
|
+
var DEFAULT_NATIVE_DRIVERS_DIR = "dirver";
|
|
235
|
+
async function fileExists(filePath) {
|
|
236
|
+
try {
|
|
237
|
+
await import_promises.default.access(filePath);
|
|
238
|
+
return true;
|
|
239
|
+
} catch {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function dirExists(dirPath) {
|
|
244
|
+
try {
|
|
245
|
+
const stat = await import_promises.default.stat(dirPath);
|
|
246
|
+
return stat.isDirectory();
|
|
247
|
+
} catch {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function resolveWorkspaceRoot(cwd = process.cwd()) {
|
|
252
|
+
let current = import_node_path.default.resolve(cwd);
|
|
253
|
+
for (let i = 0; i < 8; i += 1) {
|
|
254
|
+
const pkg = import_node_path.default.join(current, "package.json");
|
|
255
|
+
if (await fileExists(pkg)) {
|
|
256
|
+
try {
|
|
257
|
+
const raw = await import_promises.default.readFile(pkg, "utf8");
|
|
258
|
+
const parsed = JSON.parse(raw);
|
|
259
|
+
if (parsed.workspaces) {
|
|
260
|
+
return current;
|
|
261
|
+
}
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const parent = import_node_path.default.dirname(current);
|
|
266
|
+
if (parent === current) {
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
current = parent;
|
|
270
|
+
}
|
|
271
|
+
return import_node_path.default.resolve(cwd);
|
|
272
|
+
}
|
|
273
|
+
async function resolveNativeDriversDir(workspaceRoot) {
|
|
274
|
+
const root = workspaceRoot ? import_node_path.default.resolve(workspaceRoot) : await resolveWorkspaceRoot();
|
|
275
|
+
const fromEnv = process.env.ADA_DRIVERS_DIR?.trim();
|
|
276
|
+
if (fromEnv) {
|
|
277
|
+
return import_node_path.default.isAbsolute(fromEnv) ? fromEnv : import_node_path.default.join(root, fromEnv);
|
|
278
|
+
}
|
|
279
|
+
for (const name of [DEFAULT_NATIVE_DRIVERS_DIR, "driver", "drivers"]) {
|
|
280
|
+
const candidate = import_node_path.default.join(root, name);
|
|
281
|
+
if (await dirExists(candidate)) {
|
|
282
|
+
return candidate;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return import_node_path.default.join(root, DEFAULT_NATIVE_DRIVERS_DIR);
|
|
286
|
+
}
|
|
287
|
+
function geckodriverExeName() {
|
|
288
|
+
return process.platform === "win32" ? "geckodriver.exe" : "geckodriver";
|
|
289
|
+
}
|
|
290
|
+
function chromedriverExeName(version) {
|
|
291
|
+
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
292
|
+
if (version && version !== "latest") {
|
|
293
|
+
const major = version.replace(/^v/i, "").split(".")[0];
|
|
294
|
+
return `chromedriver${major}${suffix}`;
|
|
295
|
+
}
|
|
296
|
+
return `chromedriver${suffix}`;
|
|
297
|
+
}
|
|
298
|
+
async function listExecutablesInDir(driversDir) {
|
|
299
|
+
if (!await dirExists(driversDir)) {
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
const entries = await import_promises.default.readdir(driversDir, { withFileTypes: true });
|
|
303
|
+
const files = [];
|
|
304
|
+
for (const ent of entries) {
|
|
305
|
+
if (ent.isFile()) {
|
|
306
|
+
files.push(ent.name);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return files;
|
|
310
|
+
}
|
|
311
|
+
async function listLocalChromedriverVersions(driversDir) {
|
|
312
|
+
const files = await listExecutablesInDir(driversDir);
|
|
313
|
+
const versions = /* @__PURE__ */ new Set();
|
|
314
|
+
for (const name of files) {
|
|
315
|
+
const m = /^chromedriver(\d{2,4})(?:\.exe)?$/i.exec(name);
|
|
316
|
+
if (m) {
|
|
317
|
+
versions.add(m[1]);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return Array.from(versions).sort((a, b) => Number(b) - Number(a));
|
|
321
|
+
}
|
|
322
|
+
async function listLocalGeckodriverCandidates(driversDir) {
|
|
323
|
+
const files = await listExecutablesInDir(driversDir);
|
|
324
|
+
return files.filter((n) => /^geckodriver/i.test(n) && (n.endsWith(".exe") || !n.includes(".")));
|
|
325
|
+
}
|
|
326
|
+
async function findGeckodriverInDir(driversDir, version) {
|
|
327
|
+
if (!await dirExists(driversDir)) {
|
|
328
|
+
return void 0;
|
|
329
|
+
}
|
|
330
|
+
const names = await listLocalGeckodriverCandidates(driversDir);
|
|
331
|
+
const prefer = geckodriverExeName();
|
|
332
|
+
if (names.includes(prefer)) {
|
|
333
|
+
return import_node_path.default.join(driversDir, prefer);
|
|
334
|
+
}
|
|
335
|
+
if (version) {
|
|
336
|
+
const tagged = names.find((n) => n.includes(version.replace(/^v/, "")));
|
|
337
|
+
if (tagged) {
|
|
338
|
+
return import_node_path.default.join(driversDir, tagged);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (names.length > 0) {
|
|
342
|
+
return import_node_path.default.join(driversDir, names[0]);
|
|
343
|
+
}
|
|
344
|
+
return void 0;
|
|
345
|
+
}
|
|
346
|
+
async function findChromedriverInDir(driversDir, version) {
|
|
347
|
+
if (!await dirExists(driversDir)) {
|
|
348
|
+
return void 0;
|
|
349
|
+
}
|
|
350
|
+
const wantMajor = version && version !== "latest" ? version.replace(/^v/i, "").split(".")[0] : void 0;
|
|
351
|
+
if (wantMajor) {
|
|
352
|
+
const named = import_node_path.default.join(driversDir, chromedriverExeName(wantMajor));
|
|
353
|
+
if (await fileExists(named)) {
|
|
354
|
+
return { path: named, version: wantMajor };
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const generic = import_node_path.default.join(driversDir, chromedriverExeName());
|
|
358
|
+
if (await fileExists(generic)) {
|
|
359
|
+
return { path: generic, version: wantMajor ?? "generic" };
|
|
360
|
+
}
|
|
361
|
+
const locals = await listLocalChromedriverVersions(driversDir);
|
|
362
|
+
if (locals.length > 0) {
|
|
363
|
+
const pick = wantMajor && locals.includes(wantMajor) ? wantMajor : locals[0];
|
|
364
|
+
return { path: import_node_path.default.join(driversDir, chromedriverExeName(pick)), version: pick };
|
|
365
|
+
}
|
|
366
|
+
return void 0;
|
|
367
|
+
}
|
|
368
|
+
async function resolveGeckodriverPath(options) {
|
|
369
|
+
const fromEnv = process.env.ADA_GECKODRIVER_PATH?.trim();
|
|
370
|
+
if (fromEnv && await fileExists(fromEnv)) {
|
|
371
|
+
return fromEnv;
|
|
372
|
+
}
|
|
373
|
+
const driversDir = options?.driversDir ?? await resolveNativeDriversDir(options?.workspaceRoot);
|
|
374
|
+
const local = await findGeckodriverInDir(driversDir, options?.version);
|
|
375
|
+
if (local && await fileExists(local)) {
|
|
376
|
+
return local;
|
|
377
|
+
}
|
|
378
|
+
if (await commandOnPath("geckodriver")) {
|
|
379
|
+
return "geckodriver";
|
|
380
|
+
}
|
|
381
|
+
return void 0;
|
|
382
|
+
}
|
|
383
|
+
async function resolveChromedriverPath(options) {
|
|
384
|
+
const fromEnv = process.env.ADA_CHROMEDRIVER_PATH?.trim();
|
|
385
|
+
if (fromEnv && await fileExists(fromEnv)) {
|
|
386
|
+
return { path: fromEnv, version: options?.version ?? "env" };
|
|
387
|
+
}
|
|
388
|
+
const driversDir = options?.driversDir ?? await resolveNativeDriversDir(options?.workspaceRoot);
|
|
389
|
+
const local = await findChromedriverInDir(driversDir, options?.version);
|
|
390
|
+
if (local && await fileExists(local.path)) {
|
|
391
|
+
return local;
|
|
392
|
+
}
|
|
393
|
+
if (await commandOnPath("chromedriver")) {
|
|
394
|
+
return { path: "chromedriver", version: options?.version ?? "path" };
|
|
395
|
+
}
|
|
396
|
+
return void 0;
|
|
397
|
+
}
|
|
398
|
+
async function commandOnPath(command) {
|
|
399
|
+
return new Promise((resolve) => {
|
|
400
|
+
const checker = process.platform === "win32" ? "where" : "which";
|
|
401
|
+
const child = (0, import_node_child_process.spawn)(checker, [command], {
|
|
402
|
+
stdio: "ignore",
|
|
403
|
+
shell: process.platform === "win32"
|
|
404
|
+
});
|
|
405
|
+
child.on("exit", (code) => resolve(code === 0));
|
|
406
|
+
child.on("error", () => resolve(false));
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ../../plugins/driver-selenium/src/index.ts
|
|
411
|
+
var import_node_module = require("node:module");
|
|
412
|
+
var import_promises2 = __toESM(require("node:fs/promises"), 1);
|
|
413
|
+
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
414
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
415
|
+
var localRequire = (0, import_node_module.createRequire)(typeof __filename === "string" ? __filename : process.cwd());
|
|
416
|
+
var SEMANTIC_COMMANDS = [
|
|
417
|
+
"click",
|
|
418
|
+
"type",
|
|
419
|
+
"assertVisible",
|
|
420
|
+
"screenshot",
|
|
421
|
+
"navigate",
|
|
422
|
+
"hover",
|
|
423
|
+
"press",
|
|
424
|
+
"select",
|
|
425
|
+
"scroll",
|
|
426
|
+
"forward",
|
|
427
|
+
"newTab",
|
|
428
|
+
"switchTab",
|
|
429
|
+
"wait",
|
|
430
|
+
"assertText",
|
|
431
|
+
"getText",
|
|
432
|
+
"back",
|
|
433
|
+
"reload",
|
|
434
|
+
"closeTab",
|
|
435
|
+
"invoke"
|
|
436
|
+
];
|
|
437
|
+
function getNumber(value) {
|
|
438
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
439
|
+
}
|
|
440
|
+
async function loadSeleniumModule() {
|
|
441
|
+
try {
|
|
442
|
+
return localRequire("selenium-webdriver");
|
|
443
|
+
} catch {
|
|
444
|
+
return await new Function('return import("selenium-webdriver")')();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function failResult(command, code, message) {
|
|
448
|
+
return { requestId: command.requestId, success: false, errorCode: code, errorMessage: message };
|
|
449
|
+
}
|
|
450
|
+
function runMock(command, reason) {
|
|
451
|
+
const fields = resolveSeleniumBrowserFields(command.payload);
|
|
452
|
+
return {
|
|
453
|
+
requestId: command.requestId,
|
|
454
|
+
success: true,
|
|
455
|
+
data: {
|
|
456
|
+
driver: "selenium",
|
|
457
|
+
command: command.command,
|
|
458
|
+
mode: "mock",
|
|
459
|
+
reason: reason ?? "mock",
|
|
460
|
+
browserName: fields.browserName,
|
|
461
|
+
engine: "selenium"
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function wantsMock(payload) {
|
|
466
|
+
const merged = mergeOptionsIntoPayload(payload);
|
|
467
|
+
if (merged.mock === true) {
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
if (merged.real === true) {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
return process.env.ADA_SELENIUM_MOCK === "true";
|
|
474
|
+
}
|
|
475
|
+
async function buildDriver(payload) {
|
|
476
|
+
const merged = mergeOptionsIntoPayload(payload);
|
|
477
|
+
const fields = resolveSeleniumBrowserFields(merged);
|
|
478
|
+
const sw = await loadSeleniumModule();
|
|
479
|
+
const { Builder, Browser } = sw;
|
|
480
|
+
if (fields.seleniumServerUrl) {
|
|
481
|
+
const caps = merged.capabilities ?? {
|
|
482
|
+
browserName: fields.browserName
|
|
483
|
+
};
|
|
484
|
+
return await new Builder().usingServer(fields.seleniumServerUrl).withCapabilities(caps).build();
|
|
485
|
+
}
|
|
486
|
+
const browserName = fields.browserName;
|
|
487
|
+
const driverVersion = getString(merged.geckodriverVersion) ?? getString(merged.chromedriverVersion) ?? getString(asRecord(merged.options).geckodriverVersion) ?? getString(asRecord(merged.options).chromedriverVersion);
|
|
488
|
+
if (browserName === "firefox") {
|
|
489
|
+
const firefox = await import("selenium-webdriver/firefox.js");
|
|
490
|
+
const options = new firefox.Options();
|
|
491
|
+
if (fields.browserBinary) {
|
|
492
|
+
options.setBinary(fields.browserBinary);
|
|
493
|
+
}
|
|
494
|
+
if (fields.profile) {
|
|
495
|
+
options.addArguments("-profile", fields.profile);
|
|
496
|
+
}
|
|
497
|
+
if (merged.headless === true) {
|
|
498
|
+
options.addArguments("-headless");
|
|
499
|
+
}
|
|
500
|
+
const builder = new Builder().forBrowser(Browser.FIREFOX).setFirefoxOptions(options);
|
|
501
|
+
const geckoPath = await resolveGeckodriverPath({ version: driverVersion });
|
|
502
|
+
if (geckoPath && geckoPath !== "geckodriver") {
|
|
503
|
+
builder.setFirefoxService(new firefox.ServiceBuilder(geckoPath));
|
|
504
|
+
}
|
|
505
|
+
return await builder.build();
|
|
506
|
+
}
|
|
507
|
+
if (browserName === "chrome" || browserName === "chromium") {
|
|
508
|
+
const chrome = await import("selenium-webdriver/chrome.js");
|
|
509
|
+
const options = new chrome.Options();
|
|
510
|
+
if (fields.browserBinary) {
|
|
511
|
+
options.setBinary(fields.browserBinary);
|
|
512
|
+
}
|
|
513
|
+
if (fields.profile) {
|
|
514
|
+
options.addArguments(`--user-data-dir=${fields.profile}`);
|
|
515
|
+
}
|
|
516
|
+
if (merged.headless === true) {
|
|
517
|
+
options.addArguments("--headless=new");
|
|
518
|
+
}
|
|
519
|
+
const builder = new Builder().forBrowser(Browser.CHROME).setChromeOptions(options);
|
|
520
|
+
const chromeDriver = await resolveChromedriverPath({ version: driverVersion });
|
|
521
|
+
if (chromeDriver?.path && chromeDriver.path !== "chromedriver") {
|
|
522
|
+
builder.setChromeService(new chrome.ServiceBuilder(chromeDriver.path));
|
|
523
|
+
}
|
|
524
|
+
return await builder.build();
|
|
525
|
+
}
|
|
526
|
+
if (browserName === "microsoftedge" || browserName === "edge") {
|
|
527
|
+
const edge = await import("selenium-webdriver/edge.js");
|
|
528
|
+
const options = new edge.Options();
|
|
529
|
+
if (fields.browserBinary) {
|
|
530
|
+
options.setBinary(fields.browserBinary);
|
|
531
|
+
}
|
|
532
|
+
if (merged.headless === true) {
|
|
533
|
+
options.addArguments("--headless=new");
|
|
534
|
+
}
|
|
535
|
+
return await new Builder().forBrowser(Browser.EDGE).setEdgeOptions(options).build();
|
|
536
|
+
}
|
|
537
|
+
throw new Error(`Unsupported browserName for selenium: ${browserName}`);
|
|
538
|
+
}
|
|
539
|
+
async function getOrCreateDriver(sessionId, payload) {
|
|
540
|
+
const merged = mergeOptionsIntoPayload({ ...payload, engine: "selenium" });
|
|
541
|
+
const sessionKey = buildSeleniumSessionKey(merged);
|
|
542
|
+
const fields = resolveSeleniumBrowserFields(merged);
|
|
543
|
+
const mock = wantsMock(merged);
|
|
544
|
+
const existed = sessions.get(sessionId);
|
|
545
|
+
if (existed && existed.sessionKey === sessionKey) {
|
|
546
|
+
return existed;
|
|
547
|
+
}
|
|
548
|
+
if (existed?.driver) {
|
|
549
|
+
await existed.driver.quit().catch(() => void 0);
|
|
550
|
+
sessions.delete(sessionId);
|
|
551
|
+
}
|
|
552
|
+
const state = {
|
|
553
|
+
driver: mock ? null : await buildDriver(merged),
|
|
554
|
+
sessionKey,
|
|
555
|
+
browserName: fields.browserName,
|
|
556
|
+
mock
|
|
557
|
+
};
|
|
558
|
+
sessions.set(sessionId, state);
|
|
559
|
+
return state;
|
|
560
|
+
}
|
|
561
|
+
async function findElement(driver, payload) {
|
|
562
|
+
const sw = await loadSeleniumModule();
|
|
563
|
+
const { By, until } = sw;
|
|
564
|
+
const locator = asRecord(payload?.locator);
|
|
565
|
+
const timeoutMs = typeof payload?.timeoutMs === "number" ? payload.timeoutMs : 15e3;
|
|
566
|
+
const id = getString(locator.id) ?? getString(locator.accessibilityId);
|
|
567
|
+
if (id) {
|
|
568
|
+
return await driver.wait(until.elementLocated(By.id(id)), timeoutMs);
|
|
569
|
+
}
|
|
570
|
+
const css = getString(locator.css);
|
|
571
|
+
if (css) {
|
|
572
|
+
return await driver.wait(until.elementLocated(By.css(css)), timeoutMs);
|
|
573
|
+
}
|
|
574
|
+
const xpath = getString(locator.xpath);
|
|
575
|
+
if (xpath) {
|
|
576
|
+
return await driver.wait(until.elementLocated(By.xpath(xpath)), timeoutMs);
|
|
577
|
+
}
|
|
578
|
+
const text = getString(locator.text);
|
|
579
|
+
if (text) {
|
|
580
|
+
return await driver.wait(until.elementLocated(By.xpath(`//*[contains(text(),'${text.replace(/'/g, "")}')]`)), timeoutMs);
|
|
581
|
+
}
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
async function runInvoke(command, state) {
|
|
585
|
+
const invoke = normalizeInvokePayload(command.payload, "method");
|
|
586
|
+
if (!invoke) {
|
|
587
|
+
return failResult(command, "INVOKE_INVALID_PAYLOAD", "invoke requires method or http block");
|
|
588
|
+
}
|
|
589
|
+
if (invoke.mode === "http") {
|
|
590
|
+
const serverUrl = resolveSeleniumBrowserFields(command.payload).seleniumServerUrl;
|
|
591
|
+
if (!serverUrl) {
|
|
592
|
+
return failResult(
|
|
593
|
+
command,
|
|
594
|
+
"INVOKE_HTTP_REQUIRES_GRID",
|
|
595
|
+
"Selenium invoke http mode requires seleniumServerUrl (remote Grid)"
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
const http = invoke.http;
|
|
599
|
+
const sessionId = await state.driver?.getSession().then((s) => s.getId());
|
|
600
|
+
if (!sessionId) {
|
|
601
|
+
return failResult(command, "SESSION_MISSING", "no active selenium session");
|
|
602
|
+
}
|
|
603
|
+
const base = serverUrl.replace(/\/$/, "");
|
|
604
|
+
const path3 = http.path.startsWith("/") ? http.path : `/${http.path}`;
|
|
605
|
+
const url = `${base}/session/${sessionId}${path3.replace(":sessionId", sessionId)}`;
|
|
606
|
+
const res = await fetch(url, {
|
|
607
|
+
method: http.method.toUpperCase(),
|
|
608
|
+
headers: { "Content-Type": "application/json" },
|
|
609
|
+
body: http.body !== void 0 ? JSON.stringify(http.body) : void 0
|
|
610
|
+
});
|
|
611
|
+
const raw = await res.json().catch(() => ({}));
|
|
612
|
+
if (!res.ok) {
|
|
613
|
+
return failResult(command, "INVOKE_HTTP_FAILED", JSON.stringify(raw));
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
requestId: command.requestId,
|
|
617
|
+
success: true,
|
|
618
|
+
data: {
|
|
619
|
+
driver: "selenium",
|
|
620
|
+
mode: "real",
|
|
621
|
+
invokeMode: "http",
|
|
622
|
+
status: res.status,
|
|
623
|
+
value: serializeRpcResult(raw.value ?? raw)
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
const driver = state.driver;
|
|
628
|
+
if (!driver) {
|
|
629
|
+
return runMock(command, "invoke without driver");
|
|
630
|
+
}
|
|
631
|
+
const target = invoke.target ?? "driver";
|
|
632
|
+
const method = invoke.method ?? "";
|
|
633
|
+
const args = invoke.args ?? [];
|
|
634
|
+
if (target === "element") {
|
|
635
|
+
const el = await findElement(driver, command.payload);
|
|
636
|
+
if (!el) {
|
|
637
|
+
return failResult(command, "LOCATOR_NOT_FOUND", "element locator required for target=element");
|
|
638
|
+
}
|
|
639
|
+
const fn = el[method];
|
|
640
|
+
if (typeof fn !== "function") {
|
|
641
|
+
return failResult(command, "INVOKE_METHOD_UNSUPPORTED", `element.${method}`);
|
|
642
|
+
}
|
|
643
|
+
const value2 = await fn.apply(el, args);
|
|
644
|
+
return {
|
|
645
|
+
requestId: command.requestId,
|
|
646
|
+
success: true,
|
|
647
|
+
data: { driver: "selenium", mode: "real", invokeMode: "method", target, method, value: serializeRpcResult(value2) }
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
const driverFn = driver[method];
|
|
651
|
+
if (typeof driverFn !== "function") {
|
|
652
|
+
return failResult(command, "INVOKE_METHOD_UNSUPPORTED", `driver.${method}`);
|
|
653
|
+
}
|
|
654
|
+
const value = await driverFn.apply(driver, args);
|
|
655
|
+
return {
|
|
656
|
+
requestId: command.requestId,
|
|
657
|
+
success: true,
|
|
658
|
+
data: { driver: "selenium", mode: "real", invokeMode: "method", target, method, value: serializeRpcResult(value) }
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
var seleniumPlugin = {
|
|
662
|
+
manifest: {
|
|
663
|
+
id: "@ada/driver-selenium",
|
|
664
|
+
version: "0.1.0",
|
|
665
|
+
platforms: ["web"],
|
|
666
|
+
capabilities: [...SEMANTIC_COMMANDS],
|
|
667
|
+
engine: "selenium",
|
|
668
|
+
semanticCommands: [...SEMANTIC_COMMANDS],
|
|
669
|
+
invoke: { modes: ["method", "http"], targets: ["driver", "element"] }
|
|
670
|
+
},
|
|
671
|
+
async init() {
|
|
672
|
+
return;
|
|
673
|
+
},
|
|
674
|
+
async createSession() {
|
|
675
|
+
const id = `selenium-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
676
|
+
return { id, platform: "web" };
|
|
677
|
+
},
|
|
678
|
+
async execute(session, command) {
|
|
679
|
+
const payload = mergeOptionsIntoPayload({ ...command.payload, engine: "selenium" });
|
|
680
|
+
const cmd = command.command;
|
|
681
|
+
if (cmd === "invoke") {
|
|
682
|
+
const invokeCheck = normalizeInvokePayload(command.payload, "method");
|
|
683
|
+
if (!invokeCheck) {
|
|
684
|
+
return failResult(command, "INVOKE_INVALID_PAYLOAD", "invoke requires method or http block");
|
|
685
|
+
}
|
|
686
|
+
if (wantsMock(payload)) {
|
|
687
|
+
return runMock(command);
|
|
688
|
+
}
|
|
689
|
+
let state2;
|
|
690
|
+
try {
|
|
691
|
+
state2 = await getOrCreateDriver(session.id, payload);
|
|
692
|
+
} catch (error) {
|
|
693
|
+
return failResult(
|
|
694
|
+
command,
|
|
695
|
+
"DRIVER_START_FAILED",
|
|
696
|
+
error instanceof Error ? error.message : String(error)
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
try {
|
|
700
|
+
return await runInvoke(command, state2);
|
|
701
|
+
} catch (error) {
|
|
702
|
+
return failResult(command, "INVOKE_FAILED", error instanceof Error ? error.message : String(error));
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (wantsMock(payload)) {
|
|
706
|
+
return runMock(command);
|
|
707
|
+
}
|
|
708
|
+
let state;
|
|
709
|
+
try {
|
|
710
|
+
state = await getOrCreateDriver(session.id, payload);
|
|
711
|
+
} catch (error) {
|
|
712
|
+
return failResult(
|
|
713
|
+
command,
|
|
714
|
+
"DRIVER_START_FAILED",
|
|
715
|
+
error instanceof Error ? error.message : String(error)
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
const driver = state.driver;
|
|
719
|
+
if (!driver) {
|
|
720
|
+
return runMock(command);
|
|
721
|
+
}
|
|
722
|
+
try {
|
|
723
|
+
if (cmd === "navigate") {
|
|
724
|
+
const url = getString(payload.url);
|
|
725
|
+
if (!url) {
|
|
726
|
+
return failResult(command, "INVALID_PAYLOAD", "navigate requires url");
|
|
727
|
+
}
|
|
728
|
+
await driver.get(url);
|
|
729
|
+
} else if (cmd === "click") {
|
|
730
|
+
const el = await findElement(driver, payload);
|
|
731
|
+
if (!el) {
|
|
732
|
+
return failResult(command, "LOCATOR_NOT_FOUND", "click requires locator");
|
|
733
|
+
}
|
|
734
|
+
await el.click();
|
|
735
|
+
} else if (cmd === "type") {
|
|
736
|
+
const el = await findElement(driver, payload);
|
|
737
|
+
const text = getString(payload.text);
|
|
738
|
+
if (!el || !text) {
|
|
739
|
+
return failResult(command, "INVALID_PAYLOAD", "type requires locator and text");
|
|
740
|
+
}
|
|
741
|
+
await el.clear();
|
|
742
|
+
await el.sendKeys(text);
|
|
743
|
+
} else if (cmd === "screenshot") {
|
|
744
|
+
const screenshotPath = getString(payload.screenshotPath) ?? import_node_path2.default.join(process.cwd(), "artifacts", `selenium-${Date.now()}.png`);
|
|
745
|
+
await import_promises2.default.mkdir(import_node_path2.default.dirname(screenshotPath), { recursive: true });
|
|
746
|
+
const image = await driver.takeScreenshot();
|
|
747
|
+
await import_promises2.default.writeFile(screenshotPath, image, "base64");
|
|
748
|
+
return {
|
|
749
|
+
requestId: command.requestId,
|
|
750
|
+
success: true,
|
|
751
|
+
data: { driver: "selenium", command: cmd, mode: "real", screenshotPath }
|
|
752
|
+
};
|
|
753
|
+
} else if (cmd === "wait") {
|
|
754
|
+
const timeoutMs = typeof payload.timeoutMs === "number" ? payload.timeoutMs : 3e3;
|
|
755
|
+
await driver.sleep(timeoutMs);
|
|
756
|
+
} else if (cmd === "back") {
|
|
757
|
+
await driver.navigate().back();
|
|
758
|
+
} else if (cmd === "reload") {
|
|
759
|
+
await driver.navigate().refresh();
|
|
760
|
+
} else if (cmd === "forward") {
|
|
761
|
+
await driver.navigate().forward();
|
|
762
|
+
} else if (cmd === "newTab") {
|
|
763
|
+
const sw = await loadSeleniumModule();
|
|
764
|
+
await driver.switchTo().newWindow(sw.WindowType.TAB);
|
|
765
|
+
const url = getString(payload.url);
|
|
766
|
+
if (url) {
|
|
767
|
+
await driver.get(url);
|
|
768
|
+
}
|
|
769
|
+
} else if (cmd === "switchTab") {
|
|
770
|
+
const handles = await driver.getAllWindowHandles();
|
|
771
|
+
const tabIndex = getNumber(payload.tabIndex) ?? 0;
|
|
772
|
+
if (handles.length === 0) {
|
|
773
|
+
return failResult(command, "TAB_NOT_FOUND", "no window handles");
|
|
774
|
+
}
|
|
775
|
+
const safeIndex = Math.max(0, Math.min(handles.length - 1, tabIndex));
|
|
776
|
+
await driver.switchTo().window(handles[safeIndex]);
|
|
777
|
+
} else if (cmd === "closeTab") {
|
|
778
|
+
await driver.close();
|
|
779
|
+
const handles = await driver.getAllWindowHandles();
|
|
780
|
+
if (handles.length > 0) {
|
|
781
|
+
await driver.switchTo().window(handles[handles.length - 1]);
|
|
782
|
+
}
|
|
783
|
+
} else if (cmd === "hover") {
|
|
784
|
+
const el = await findElement(driver, payload);
|
|
785
|
+
if (!el) {
|
|
786
|
+
return failResult(command, "LOCATOR_NOT_FOUND", "hover requires locator");
|
|
787
|
+
}
|
|
788
|
+
const sw = await loadSeleniumModule();
|
|
789
|
+
await sw.driver.actions({ bridge: true }).move({ origin: el }).perform();
|
|
790
|
+
} else if (cmd === "press") {
|
|
791
|
+
const key = getString(payload.key) ?? "Enter";
|
|
792
|
+
const el = await findElement(driver, payload);
|
|
793
|
+
const sw = await loadSeleniumModule();
|
|
794
|
+
const keys = sw.Key ?? (await import("selenium-webdriver")).Key;
|
|
795
|
+
const keyConst = keys[key] ?? key;
|
|
796
|
+
if (el) {
|
|
797
|
+
await el.sendKeys(keyConst);
|
|
798
|
+
} else {
|
|
799
|
+
await driver.actions().sendKeys(keyConst).perform();
|
|
800
|
+
}
|
|
801
|
+
} else if (cmd === "select") {
|
|
802
|
+
const el = await findElement(driver, payload);
|
|
803
|
+
const value = getString(payload.value) ?? getString(payload.text);
|
|
804
|
+
if (!el || !value) {
|
|
805
|
+
return failResult(command, "INVALID_PAYLOAD", "select requires locator and value");
|
|
806
|
+
}
|
|
807
|
+
const { Select } = await import("selenium-webdriver/lib/select.js");
|
|
808
|
+
const select = new Select(el);
|
|
809
|
+
await select.selectByVisibleText(value);
|
|
810
|
+
} else if (cmd === "scroll") {
|
|
811
|
+
const deltaX = getNumber(payload.deltaX) ?? 0;
|
|
812
|
+
const deltaY = getNumber(payload.deltaY) ?? 400;
|
|
813
|
+
await driver.executeScript(`window.scrollBy(${deltaX}, ${deltaY});`);
|
|
814
|
+
} else if (cmd === "getText") {
|
|
815
|
+
const el = await findElement(driver, payload);
|
|
816
|
+
if (!el) {
|
|
817
|
+
return failResult(command, "LOCATOR_NOT_FOUND", "getText requires locator");
|
|
818
|
+
}
|
|
819
|
+
const text = await el.getText();
|
|
820
|
+
return {
|
|
821
|
+
requestId: command.requestId,
|
|
822
|
+
success: true,
|
|
823
|
+
data: { driver: "selenium", command: cmd, mode: "real", text }
|
|
824
|
+
};
|
|
825
|
+
} else if (cmd === "assertVisible") {
|
|
826
|
+
const el = await findElement(driver, payload);
|
|
827
|
+
if (!el) {
|
|
828
|
+
return failResult(command, "ASSERT_FAILED", "element not visible");
|
|
829
|
+
}
|
|
830
|
+
const displayed = await el.isDisplayed();
|
|
831
|
+
if (!displayed) {
|
|
832
|
+
return failResult(command, "ASSERT_FAILED", "element not displayed");
|
|
833
|
+
}
|
|
834
|
+
} else if (cmd === "assertText") {
|
|
835
|
+
const el = await findElement(driver, payload);
|
|
836
|
+
const expected = getString(payload.expectedText) ?? getString(payload.text);
|
|
837
|
+
if (!el || !expected) {
|
|
838
|
+
return failResult(command, "INVALID_PAYLOAD", "assertText requires locator and expectedText");
|
|
839
|
+
}
|
|
840
|
+
const actual = await el.getText();
|
|
841
|
+
if (!actual.includes(expected)) {
|
|
842
|
+
return failResult(command, "ASSERT_FAILED", `expected text "${expected}", got "${actual}"`);
|
|
843
|
+
}
|
|
844
|
+
} else {
|
|
845
|
+
return runMock(command, `unsupported command: ${cmd}`);
|
|
846
|
+
}
|
|
847
|
+
return {
|
|
848
|
+
requestId: command.requestId,
|
|
849
|
+
success: true,
|
|
850
|
+
data: {
|
|
851
|
+
driver: "selenium",
|
|
852
|
+
command: cmd,
|
|
853
|
+
mode: "real",
|
|
854
|
+
browserName: state.browserName,
|
|
855
|
+
engine: "selenium"
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
} catch (error) {
|
|
859
|
+
return failResult(command, "COMMAND_FAILED", error instanceof Error ? error.message : String(error));
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
async destroySession(session) {
|
|
863
|
+
const state = sessions.get(session.id);
|
|
864
|
+
if (!state?.driver) {
|
|
865
|
+
sessions.delete(session.id);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
await state.driver.quit().catch(() => void 0);
|
|
869
|
+
sessions.delete(session.id);
|
|
870
|
+
},
|
|
871
|
+
async dispose() {
|
|
872
|
+
for (const [, state] of sessions) {
|
|
873
|
+
if (state.driver) {
|
|
874
|
+
await state.driver.quit().catch(() => void 0);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
sessions.clear();
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
var index_default = seleniumPlugin;
|