system-testing 1.0.78 → 1.0.81
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 +232 -6
- package/build/browser-command-client.d.ts +19 -0
- package/build/browser-command-client.js +39 -0
- package/build/browser-command-runner.d.ts +34 -0
- package/build/browser-command-runner.js +155 -0
- package/build/browser-daemon-constants.d.ts +2 -0
- package/build/browser-daemon-constants.js +3 -0
- package/build/browser-process.d.ts +45 -0
- package/build/browser-process.js +134 -0
- package/build/browser-registry.d.ts +44 -0
- package/build/browser-registry.js +191 -0
- package/build/browser.d.ts +240 -0
- package/build/browser.js +375 -0
- package/build/cli-helpers.d.ts +16 -0
- package/build/cli-helpers.js +177 -0
- package/build/cli.d.ts +2 -0
- package/build/cli.js +81 -0
- package/build/drivers/appium-driver.js +21 -21
- package/build/drivers/webdriver-driver.d.ts +4 -4
- package/build/drivers/webdriver-driver.js +32 -28
- package/build/index.d.ts +9 -1
- package/build/index.js +10 -3
- package/build/system-test-browser-helper.d.ts +6 -12
- package/build/system-test-browser-helper.js +12 -13
- package/build/system-test.d.ts +3 -189
- package/build/system-test.js +6 -220
- package/build/use-system-test-expo.d.ts +16 -0
- package/build/use-system-test-expo.js +34 -0
- package/build/use-system-test-react-native.d.ts +25 -0
- package/build/use-system-test-react-native.js +20 -0
- package/build/use-system-test-shape-hook.d.ts +35 -0
- package/build/use-system-test-shape-hook.js +74 -0
- package/build/use-system-test.d.ts +19 -10
- package/build/use-system-test.js +26 -72
- package/package.json +17 -8
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import Browser from "./browser.js";
|
|
2
|
+
import BrowserCommandRunner from "./browser-command-runner.js";
|
|
3
|
+
import { browserDaemonStopTimeoutMs } from "./browser-daemon-constants.js";
|
|
4
|
+
import BrowserRegistry from "./browser-registry.js";
|
|
5
|
+
import { WebSocketServer } from "ws";
|
|
6
|
+
/** Long-running browser daemon exposing browser commands over WebSocket. */
|
|
7
|
+
export default class BrowserProcess {
|
|
8
|
+
/**
|
|
9
|
+
* @param {object} args
|
|
10
|
+
* @param {string} args.name
|
|
11
|
+
* @param {Browser} [args.browser]
|
|
12
|
+
* @param {Record<string, any>} [args.browserArgs]
|
|
13
|
+
* @param {string} [args.baseUrl]
|
|
14
|
+
* @param {boolean} [args.debug]
|
|
15
|
+
* @param {number} [args.port]
|
|
16
|
+
*/
|
|
17
|
+
constructor({ name, browser, browserArgs = {}, baseUrl, debug = false, port = 0 }) {
|
|
18
|
+
/**
|
|
19
|
+
* @param {import("ws").WebSocket} ws
|
|
20
|
+
* @returns {void}
|
|
21
|
+
*/
|
|
22
|
+
this.onConnection = (ws) => {
|
|
23
|
+
ws.on("message", async (rawData) => {
|
|
24
|
+
const requestId = `${Date.now()}-${this.requestCount++}`;
|
|
25
|
+
try {
|
|
26
|
+
const payload = JSON.parse(rawData.toString());
|
|
27
|
+
const result = await this.handlePayload(payload);
|
|
28
|
+
ws.send(JSON.stringify({ ok: true, requestId, result, type: "browser-command-result" }));
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
ws.send(JSON.stringify({
|
|
32
|
+
error: error instanceof Error ? error.message : String(error),
|
|
33
|
+
ok: false,
|
|
34
|
+
requestId,
|
|
35
|
+
type: "browser-command-result"
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
this.name = name;
|
|
41
|
+
this.browser = browser ?? new Browser({ debug, ...browserArgs });
|
|
42
|
+
this.baseUrl = baseUrl;
|
|
43
|
+
this.debug = debug;
|
|
44
|
+
this.requestRunner = new BrowserCommandRunner({ browser: this.browser });
|
|
45
|
+
this.requestCount = 0;
|
|
46
|
+
this.port = port;
|
|
47
|
+
}
|
|
48
|
+
/** @returns {Promise<void>} */
|
|
49
|
+
async start() {
|
|
50
|
+
if (!this.name) {
|
|
51
|
+
throw new Error("Browser process requires a name");
|
|
52
|
+
}
|
|
53
|
+
if (this.baseUrl) {
|
|
54
|
+
this.browser.getDriverAdapter().setBaseUrl(this.baseUrl);
|
|
55
|
+
}
|
|
56
|
+
await this.browser.getDriverAdapter().start();
|
|
57
|
+
await this.browser.setTimeouts(browserDaemonStopTimeoutMs);
|
|
58
|
+
this.wss = new WebSocketServer({ port: this.port });
|
|
59
|
+
await new Promise((resolve) => {
|
|
60
|
+
this.wss.once("listening", resolve);
|
|
61
|
+
});
|
|
62
|
+
const address = this.wss.address();
|
|
63
|
+
if (!address || typeof address === "string") {
|
|
64
|
+
throw new Error("Could not resolve browser process port");
|
|
65
|
+
}
|
|
66
|
+
this.port = address.port;
|
|
67
|
+
this.wss.on("connection", this.onConnection);
|
|
68
|
+
await BrowserRegistry.register({
|
|
69
|
+
baseUrl: this.baseUrl,
|
|
70
|
+
name: this.name,
|
|
71
|
+
pid: process.pid,
|
|
72
|
+
port: this.port,
|
|
73
|
+
startedAt: new Date().toISOString()
|
|
74
|
+
});
|
|
75
|
+
const stop = async () => {
|
|
76
|
+
await this.stop();
|
|
77
|
+
process.exit(0);
|
|
78
|
+
};
|
|
79
|
+
process.once("SIGINT", stop);
|
|
80
|
+
process.once("SIGTERM", stop);
|
|
81
|
+
}
|
|
82
|
+
/** @returns {Promise<void>} */
|
|
83
|
+
async stop() {
|
|
84
|
+
if (this.stopped) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.stopped = true;
|
|
88
|
+
await BrowserRegistry.unregister(this.name);
|
|
89
|
+
if (this.wss) {
|
|
90
|
+
await new Promise((resolve, reject) => {
|
|
91
|
+
this.wss.close((error) => {
|
|
92
|
+
if (error) {
|
|
93
|
+
reject(error);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
resolve(undefined);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
await this.browser.stopDriver();
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* @param {Record<string, any>} payload
|
|
105
|
+
* @returns {Promise<any>}
|
|
106
|
+
*/
|
|
107
|
+
async handlePayload(payload) {
|
|
108
|
+
if (payload.type === "browser-daemon") {
|
|
109
|
+
if (payload.command !== "describe") {
|
|
110
|
+
throw new Error(`Unknown browser daemon command: ${payload.command}`);
|
|
111
|
+
}
|
|
112
|
+
return { name: this.name, pid: process.pid, port: this.port };
|
|
113
|
+
}
|
|
114
|
+
if (payload.type !== "browser-command") {
|
|
115
|
+
throw new Error(`Unknown payload type: ${payload.type}`);
|
|
116
|
+
}
|
|
117
|
+
const command = payload.command;
|
|
118
|
+
const commandArgs = payload.args ? { ...payload.args } : {};
|
|
119
|
+
if (payload.url && !commandArgs.url) {
|
|
120
|
+
commandArgs.url = payload.url;
|
|
121
|
+
}
|
|
122
|
+
if (payload.path && !commandArgs.path) {
|
|
123
|
+
commandArgs.path = payload.path;
|
|
124
|
+
}
|
|
125
|
+
if (payload.selector && !commandArgs.selector) {
|
|
126
|
+
commandArgs.selector = payload.selector;
|
|
127
|
+
}
|
|
128
|
+
if (payload.testID && !commandArgs.testID) {
|
|
129
|
+
commandArgs.testID = payload.testID;
|
|
130
|
+
}
|
|
131
|
+
return await this.requestRunner.run(command, commandArgs);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJvd3Nlci1wcm9jZXNzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2Jyb3dzZXItcHJvY2Vzcy5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLE9BQU8sTUFBTSxjQUFjLENBQUE7QUFDbEMsT0FBTyxvQkFBb0IsTUFBTSw2QkFBNkIsQ0FBQTtBQUM5RCxPQUFPLEVBQUMsMEJBQTBCLEVBQUMsTUFBTSwrQkFBK0IsQ0FBQTtBQUN4RSxPQUFPLGVBQWUsTUFBTSx1QkFBdUIsQ0FBQTtBQUNuRCxPQUFPLEVBQUMsZUFBZSxFQUFDLE1BQU0sSUFBSSxDQUFBO0FBRWxDLDRFQUE0RTtBQUM1RSxNQUFNLENBQUMsT0FBTyxPQUFPLGNBQWM7SUFDakM7Ozs7Ozs7O09BUUc7SUFDSCxZQUFZLEVBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRSxXQUFXLEdBQUcsRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLEdBQUcsS0FBSyxFQUFFLElBQUksR0FBRyxDQUFDLEVBQUM7UUE4RS9FOzs7V0FHRztRQUNILGlCQUFZLEdBQUcsQ0FBQyxFQUFFLEVBQUUsRUFBRTtZQUNwQixFQUFFLENBQUMsRUFBRSxDQUFDLFNBQVMsRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUU7Z0JBQ2pDLE1BQU0sU0FBUyxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxJQUFJLElBQUksQ0FBQyxZQUFZLEVBQUUsRUFBRSxDQUFBO2dCQUV4RCxJQUFJLENBQUM7b0JBQ0gsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQTtvQkFDOUMsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsYUFBYSxDQUFDLE9BQU8sQ0FBQyxDQUFBO29CQUVoRCxFQUFFLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBQyxFQUFFLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLHdCQUF3QixFQUFDLENBQUMsQ0FBQyxDQUFBO2dCQUN4RixDQUFDO2dCQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7b0JBQ2YsRUFBRSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDO3dCQUNyQixLQUFLLEVBQUUsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQzt3QkFDN0QsRUFBRSxFQUFFLEtBQUs7d0JBQ1QsU0FBUzt3QkFDVCxJQUFJLEVBQUUsd0JBQXdCO3FCQUMvQixDQUFDLENBQUMsQ0FBQTtnQkFDTCxDQUFDO1lBQ0gsQ0FBQyxDQUFDLENBQUE7UUFDSixDQUFDLENBQUE7UUFuR0MsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUE7UUFDaEIsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLElBQUksSUFBSSxPQUFPLENBQUMsRUFBQyxLQUFLLEVBQUUsR0FBRyxXQUFXLEVBQUMsQ0FBQyxDQUFBO1FBQzlELElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFBO1FBQ3RCLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFBO1FBQ2xCLElBQUksQ0FBQyxhQUFhLEdBQUcsSUFBSSxvQkFBb0IsQ0FBQyxFQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsT0FBTyxFQUFDLENBQUMsQ0FBQTtRQUN0RSxJQUFJLENBQUMsWUFBWSxHQUFHLENBQUMsQ0FBQTtRQUNyQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQTtJQUNsQixDQUFDO0lBRUQsK0JBQStCO0lBQy9CLEtBQUssQ0FBQyxLQUFLO1FBQ1QsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNmLE1BQU0sSUFBSSxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQTtRQUNwRCxDQUFDO1FBRUQsSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDakIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDMUQsQ0FBQztRQUVELE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDLEtBQUssRUFBRSxDQUFBO1FBQzdDLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsMEJBQTBCLENBQUMsQ0FBQTtRQUUxRCxJQUFJLENBQUMsR0FBRyxHQUFHLElBQUksZUFBZSxDQUFDLEVBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUMsQ0FBQyxDQUFBO1FBQ2pELE1BQU0sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsRUFBRTtZQUM1QixJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsT0FBTyxDQUFDLENBQUE7UUFDckMsQ0FBQyxDQUFDLENBQUE7UUFFRixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRSxDQUFBO1FBRWxDLElBQUksQ0FBQyxPQUFPLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDNUMsTUFBTSxJQUFJLEtBQUssQ0FBQyx3Q0FBd0MsQ0FBQyxDQUFBO1FBQzNELENBQUM7UUFFRCxJQUFJLENBQUMsSUFBSSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUE7UUFDeEIsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQTtRQUU1QyxNQUFNLGVBQWUsQ0FBQyxRQUFRLENBQUM7WUFDN0IsT0FBTyxFQUFFLElBQUksQ0FBQyxPQUFPO1lBQ3JCLElBQUksRUFBRSxJQUFJLENBQUMsSUFBSTtZQUNmLEdBQUcsRUFBRSxPQUFPLENBQUMsR0FBRztZQUNoQixJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUk7WUFDZixTQUFTLEVBQUUsSUFBSSxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUU7U0FDcEMsQ0FBQyxDQUFBO1FBRUYsTUFBTSxJQUFJLEdBQUcsS0FBSyxJQUFJLEVBQUU7WUFDdEIsTUFBTSxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUE7WUFDakIsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQTtRQUNqQixDQUFDLENBQUE7UUFFRCxPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsQ0FBQTtRQUM1QixPQUFPLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxJQUFJLENBQUMsQ0FBQTtJQUMvQixDQUFDO0lBRUQsK0JBQStCO0lBQy9CLEtBQUssQ0FBQyxJQUFJO1FBQ1IsSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDakIsT0FBTTtRQUNSLENBQUM7UUFFRCxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQTtRQUNuQixNQUFNLGVBQWUsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFBO1FBRTNDLElBQUksSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1lBQ2IsTUFBTSxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtnQkFDcEMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRTtvQkFDdkIsSUFBSSxLQUFLLEVBQUUsQ0FBQzt3QkFDVixNQUFNLENBQUMsS0FBSyxDQUFDLENBQUE7b0JBQ2YsQ0FBQzt5QkFBTSxDQUFDO3dCQUNOLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQTtvQkFDcEIsQ0FBQztnQkFDSCxDQUFDLENBQUMsQ0FBQTtZQUNKLENBQUMsQ0FBQyxDQUFBO1FBQ0osQ0FBQztRQUVELE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsQ0FBQTtJQUNqQyxDQUFDO0lBMEJEOzs7T0FHRztJQUNILEtBQUssQ0FBQyxhQUFhLENBQUMsT0FBTztRQUN6QixJQUFJLE9BQU8sQ0FBQyxJQUFJLEtBQUssZ0JBQWdCLEVBQUUsQ0FBQztZQUN0QyxJQUFJLE9BQU8sQ0FBQyxPQUFPLEtBQUssVUFBVSxFQUFFLENBQUM7Z0JBQ25DLE1BQU0sSUFBSSxLQUFLLENBQUMsbUNBQW1DLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFBO1lBQ3ZFLENBQUM7WUFFRCxPQUFPLEVBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUUsR0FBRyxFQUFFLE9BQU8sQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUMsQ0FBQTtRQUM3RCxDQUFDO1FBRUQsSUFBSSxPQUFPLENBQUMsSUFBSSxLQUFLLGlCQUFpQixFQUFFLENBQUM7WUFDdkMsTUFBTSxJQUFJLEtBQUssQ0FBQyx5QkFBeUIsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUE7UUFDMUQsQ0FBQztRQUVELE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUE7UUFDL0IsTUFBTSxXQUFXLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBQyxHQUFHLE9BQU8sQ0FBQyxJQUFJLEVBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFBO1FBRXpELElBQUksT0FBTyxDQUFDLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUUsQ0FBQztZQUNwQyxXQUFXLENBQUMsR0FBRyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUE7UUFDL0IsQ0FBQztRQUVELElBQUksT0FBTyxDQUFDLElBQUksSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUN0QyxXQUFXLENBQUMsSUFBSSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUE7UUFDakMsQ0FBQztRQUVELElBQUksT0FBTyxDQUFDLFFBQVEsSUFBSSxDQUFDLFdBQVcsQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUM5QyxXQUFXLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUE7UUFDekMsQ0FBQztRQUVELElBQUksT0FBTyxDQUFDLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUMxQyxXQUFXLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUE7UUFDckMsQ0FBQztRQUVELE9BQU8sTUFBTSxJQUFJLENBQUMsYUFBYSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsV0FBVyxDQUFDLENBQUE7SUFDM0QsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IEJyb3dzZXIgZnJvbSBcIi4vYnJvd3Nlci5qc1wiXG5pbXBvcnQgQnJvd3NlckNvbW1hbmRSdW5uZXIgZnJvbSBcIi4vYnJvd3Nlci1jb21tYW5kLXJ1bm5lci5qc1wiXG5pbXBvcnQge2Jyb3dzZXJEYWVtb25TdG9wVGltZW91dE1zfSBmcm9tIFwiLi9icm93c2VyLWRhZW1vbi1jb25zdGFudHMuanNcIlxuaW1wb3J0IEJyb3dzZXJSZWdpc3RyeSBmcm9tIFwiLi9icm93c2VyLXJlZ2lzdHJ5LmpzXCJcbmltcG9ydCB7V2ViU29ja2V0U2VydmVyfSBmcm9tIFwid3NcIlxuXG4vKiogTG9uZy1ydW5uaW5nIGJyb3dzZXIgZGFlbW9uIGV4cG9zaW5nIGJyb3dzZXIgY29tbWFuZHMgb3ZlciBXZWJTb2NrZXQuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBCcm93c2VyUHJvY2VzcyB7XG4gIC8qKlxuICAgKiBAcGFyYW0ge29iamVjdH0gYXJnc1xuICAgKiBAcGFyYW0ge3N0cmluZ30gYXJncy5uYW1lXG4gICAqIEBwYXJhbSB7QnJvd3Nlcn0gW2FyZ3MuYnJvd3Nlcl1cbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCBhbnk+fSBbYXJncy5icm93c2VyQXJnc11cbiAgICogQHBhcmFtIHtzdHJpbmd9IFthcmdzLmJhc2VVcmxdXG4gICAqIEBwYXJhbSB7Ym9vbGVhbn0gW2FyZ3MuZGVidWddXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBbYXJncy5wb3J0XVxuICAgKi9cbiAgY29uc3RydWN0b3Ioe25hbWUsIGJyb3dzZXIsIGJyb3dzZXJBcmdzID0ge30sIGJhc2VVcmwsIGRlYnVnID0gZmFsc2UsIHBvcnQgPSAwfSkge1xuICAgIHRoaXMubmFtZSA9IG5hbWVcbiAgICB0aGlzLmJyb3dzZXIgPSBicm93c2VyID8/IG5ldyBCcm93c2VyKHtkZWJ1ZywgLi4uYnJvd3NlckFyZ3N9KVxuICAgIHRoaXMuYmFzZVVybCA9IGJhc2VVcmxcbiAgICB0aGlzLmRlYnVnID0gZGVidWdcbiAgICB0aGlzLnJlcXVlc3RSdW5uZXIgPSBuZXcgQnJvd3NlckNvbW1hbmRSdW5uZXIoe2Jyb3dzZXI6IHRoaXMuYnJvd3Nlcn0pXG4gICAgdGhpcy5yZXF1ZXN0Q291bnQgPSAwXG4gICAgdGhpcy5wb3J0ID0gcG9ydFxuICB9XG5cbiAgLyoqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAqL1xuICBhc3luYyBzdGFydCgpIHtcbiAgICBpZiAoIXRoaXMubmFtZSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiQnJvd3NlciBwcm9jZXNzIHJlcXVpcmVzIGEgbmFtZVwiKVxuICAgIH1cblxuICAgIGlmICh0aGlzLmJhc2VVcmwpIHtcbiAgICAgIHRoaXMuYnJvd3Nlci5nZXREcml2ZXJBZGFwdGVyKCkuc2V0QmFzZVVybCh0aGlzLmJhc2VVcmwpXG4gICAgfVxuXG4gICAgYXdhaXQgdGhpcy5icm93c2VyLmdldERyaXZlckFkYXB0ZXIoKS5zdGFydCgpXG4gICAgYXdhaXQgdGhpcy5icm93c2VyLnNldFRpbWVvdXRzKGJyb3dzZXJEYWVtb25TdG9wVGltZW91dE1zKVxuXG4gICAgdGhpcy53c3MgPSBuZXcgV2ViU29ja2V0U2VydmVyKHtwb3J0OiB0aGlzLnBvcnR9KVxuICAgIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiB7XG4gICAgICB0aGlzLndzcy5vbmNlKFwibGlzdGVuaW5nXCIsIHJlc29sdmUpXG4gICAgfSlcblxuICAgIGNvbnN0IGFkZHJlc3MgPSB0aGlzLndzcy5hZGRyZXNzKClcblxuICAgIGlmICghYWRkcmVzcyB8fCB0eXBlb2YgYWRkcmVzcyA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiQ291bGQgbm90IHJlc29sdmUgYnJvd3NlciBwcm9jZXNzIHBvcnRcIilcbiAgICB9XG5cbiAgICB0aGlzLnBvcnQgPSBhZGRyZXNzLnBvcnRcbiAgICB0aGlzLndzcy5vbihcImNvbm5lY3Rpb25cIiwgdGhpcy5vbkNvbm5lY3Rpb24pXG5cbiAgICBhd2FpdCBCcm93c2VyUmVnaXN0cnkucmVnaXN0ZXIoe1xuICAgICAgYmFzZVVybDogdGhpcy5iYXNlVXJsLFxuICAgICAgbmFtZTogdGhpcy5uYW1lLFxuICAgICAgcGlkOiBwcm9jZXNzLnBpZCxcbiAgICAgIHBvcnQ6IHRoaXMucG9ydCxcbiAgICAgIHN0YXJ0ZWRBdDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpXG4gICAgfSlcblxuICAgIGNvbnN0IHN0b3AgPSBhc3luYyAoKSA9PiB7XG4gICAgICBhd2FpdCB0aGlzLnN0b3AoKVxuICAgICAgcHJvY2Vzcy5leGl0KDApXG4gICAgfVxuXG4gICAgcHJvY2Vzcy5vbmNlKFwiU0lHSU5UXCIsIHN0b3ApXG4gICAgcHJvY2Vzcy5vbmNlKFwiU0lHVEVSTVwiLCBzdG9wKVxuICB9XG5cbiAgLyoqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAqL1xuICBhc3luYyBzdG9wKCkge1xuICAgIGlmICh0aGlzLnN0b3BwZWQpIHtcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIHRoaXMuc3RvcHBlZCA9IHRydWVcbiAgICBhd2FpdCBCcm93c2VyUmVnaXN0cnkudW5yZWdpc3Rlcih0aGlzLm5hbWUpXG5cbiAgICBpZiAodGhpcy53c3MpIHtcbiAgICAgIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgdGhpcy53c3MuY2xvc2UoKGVycm9yKSA9PiB7XG4gICAgICAgICAgaWYgKGVycm9yKSB7XG4gICAgICAgICAgICByZWplY3QoZXJyb3IpXG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHJlc29sdmUodW5kZWZpbmVkKVxuICAgICAgICAgIH1cbiAgICAgICAgfSlcbiAgICAgIH0pXG4gICAgfVxuXG4gICAgYXdhaXQgdGhpcy5icm93c2VyLnN0b3BEcml2ZXIoKVxuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwid3NcIikuV2ViU29ja2V0fSB3c1xuICAgKiBAcmV0dXJucyB7dm9pZH1cbiAgICovXG4gIG9uQ29ubmVjdGlvbiA9ICh3cykgPT4ge1xuICAgIHdzLm9uKFwibWVzc2FnZVwiLCBhc3luYyAocmF3RGF0YSkgPT4ge1xuICAgICAgY29uc3QgcmVxdWVzdElkID0gYCR7RGF0ZS5ub3coKX0tJHt0aGlzLnJlcXVlc3RDb3VudCsrfWBcblxuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgcGF5bG9hZCA9IEpTT04ucGFyc2UocmF3RGF0YS50b1N0cmluZygpKVxuICAgICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB0aGlzLmhhbmRsZVBheWxvYWQocGF5bG9hZClcblxuICAgICAgICB3cy5zZW5kKEpTT04uc3RyaW5naWZ5KHtvazogdHJ1ZSwgcmVxdWVzdElkLCByZXN1bHQsIHR5cGU6IFwiYnJvd3Nlci1jb21tYW5kLXJlc3VsdFwifSkpXG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICB3cy5zZW5kKEpTT04uc3RyaW5naWZ5KHtcbiAgICAgICAgICBlcnJvcjogZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpLFxuICAgICAgICAgIG9rOiBmYWxzZSxcbiAgICAgICAgICByZXF1ZXN0SWQsXG4gICAgICAgICAgdHlwZTogXCJicm93c2VyLWNvbW1hbmQtcmVzdWx0XCJcbiAgICAgICAgfSkpXG4gICAgICB9XG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsIGFueT59IHBheWxvYWRcbiAgICogQHJldHVybnMge1Byb21pc2U8YW55Pn1cbiAgICovXG4gIGFzeW5jIGhhbmRsZVBheWxvYWQocGF5bG9hZCkge1xuICAgIGlmIChwYXlsb2FkLnR5cGUgPT09IFwiYnJvd3Nlci1kYWVtb25cIikge1xuICAgICAgaWYgKHBheWxvYWQuY29tbWFuZCAhPT0gXCJkZXNjcmliZVwiKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgVW5rbm93biBicm93c2VyIGRhZW1vbiBjb21tYW5kOiAke3BheWxvYWQuY29tbWFuZH1gKVxuICAgICAgfVxuXG4gICAgICByZXR1cm4ge25hbWU6IHRoaXMubmFtZSwgcGlkOiBwcm9jZXNzLnBpZCwgcG9ydDogdGhpcy5wb3J0fVxuICAgIH1cblxuICAgIGlmIChwYXlsb2FkLnR5cGUgIT09IFwiYnJvd3Nlci1jb21tYW5kXCIpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgVW5rbm93biBwYXlsb2FkIHR5cGU6ICR7cGF5bG9hZC50eXBlfWApXG4gICAgfVxuXG4gICAgY29uc3QgY29tbWFuZCA9IHBheWxvYWQuY29tbWFuZFxuICAgIGNvbnN0IGNvbW1hbmRBcmdzID0gcGF5bG9hZC5hcmdzID8gey4uLnBheWxvYWQuYXJnc30gOiB7fVxuXG4gICAgaWYgKHBheWxvYWQudXJsICYmICFjb21tYW5kQXJncy51cmwpIHtcbiAgICAgIGNvbW1hbmRBcmdzLnVybCA9IHBheWxvYWQudXJsXG4gICAgfVxuXG4gICAgaWYgKHBheWxvYWQucGF0aCAmJiAhY29tbWFuZEFyZ3MucGF0aCkge1xuICAgICAgY29tbWFuZEFyZ3MucGF0aCA9IHBheWxvYWQucGF0aFxuICAgIH1cblxuICAgIGlmIChwYXlsb2FkLnNlbGVjdG9yICYmICFjb21tYW5kQXJncy5zZWxlY3Rvcikge1xuICAgICAgY29tbWFuZEFyZ3Muc2VsZWN0b3IgPSBwYXlsb2FkLnNlbGVjdG9yXG4gICAgfVxuXG4gICAgaWYgKHBheWxvYWQudGVzdElEICYmICFjb21tYW5kQXJncy50ZXN0SUQpIHtcbiAgICAgIGNvbW1hbmRBcmdzLnRlc3RJRCA9IHBheWxvYWQudGVzdElEXG4gICAgfVxuXG4gICAgcmV0dXJuIGF3YWl0IHRoaXMucmVxdWVzdFJ1bm5lci5ydW4oY29tbWFuZCwgY29tbWFuZEFyZ3MpXG4gIH1cbn1cbiJdfQ==
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Browser process registry. */
|
|
2
|
+
export default class BrowserRegistry {
|
|
3
|
+
/** @returns {string} */
|
|
4
|
+
static getRegistryPath(): string;
|
|
5
|
+
/** @returns {Promise<Array<Record<string, any>>>} */
|
|
6
|
+
static list(): Promise<Array<Record<string, any>>>;
|
|
7
|
+
/**
|
|
8
|
+
* @param {Record<string, any>} entry
|
|
9
|
+
* @returns {Promise<void>}
|
|
10
|
+
*/
|
|
11
|
+
static register(entry: Record<string, any>): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} name
|
|
14
|
+
* @returns {Promise<void>}
|
|
15
|
+
*/
|
|
16
|
+
static unregister(name: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} [name]
|
|
19
|
+
* @returns {Promise<Record<string, any>>}
|
|
20
|
+
*/
|
|
21
|
+
static resolve(name?: string): Promise<Record<string, any>>;
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} [name]
|
|
24
|
+
* @returns {Promise<Record<string, any>>}
|
|
25
|
+
*/
|
|
26
|
+
static stop(name?: string): Promise<Record<string, any>>;
|
|
27
|
+
/**
|
|
28
|
+
* @param {Record<string, any>} entry
|
|
29
|
+
* @returns {Promise<boolean>}
|
|
30
|
+
*/
|
|
31
|
+
static verifyEntry(entry: Record<string, any>): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* @param {number} pid
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
static isProcessAlive(pid: number): boolean;
|
|
37
|
+
/** @returns {Promise<Array<Record<string, any>>>} */
|
|
38
|
+
static _readRegistry(): Promise<Array<Record<string, any>>>;
|
|
39
|
+
/**
|
|
40
|
+
* @param {Array<Record<string, any>>} entries
|
|
41
|
+
* @returns {Promise<void>}
|
|
42
|
+
*/
|
|
43
|
+
static _writeRegistry(entries: Array<Record<string, any>>): Promise<void>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import wait from "awaitery/build/wait.js";
|
|
5
|
+
import { WebSocket } from "ws";
|
|
6
|
+
import { browserDaemonStopTimeoutMs, browserDaemonVerifyTimeoutMs } from "./browser-daemon-constants.js";
|
|
7
|
+
const registryPath = path.join(os.tmpdir(), "system-testing-browser-registry.json");
|
|
8
|
+
/** Browser process registry. */
|
|
9
|
+
export default class BrowserRegistry {
|
|
10
|
+
/** @returns {string} */
|
|
11
|
+
static getRegistryPath() {
|
|
12
|
+
return registryPath;
|
|
13
|
+
}
|
|
14
|
+
/** @returns {Promise<Array<Record<string, any>>>} */
|
|
15
|
+
static async list() {
|
|
16
|
+
const entries = await this._readRegistry();
|
|
17
|
+
const aliveEntries = [];
|
|
18
|
+
let dirty = false;
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (this.isProcessAlive(entry.pid)) {
|
|
21
|
+
aliveEntries.push(entry);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
dirty = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (dirty) {
|
|
28
|
+
await this._writeRegistry(aliveEntries);
|
|
29
|
+
}
|
|
30
|
+
return aliveEntries;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* @param {Record<string, any>} entry
|
|
34
|
+
* @returns {Promise<void>}
|
|
35
|
+
*/
|
|
36
|
+
static async register(entry) {
|
|
37
|
+
const entries = await this.list();
|
|
38
|
+
const filteredEntries = entries.filter((existingEntry) => existingEntry.name !== entry.name);
|
|
39
|
+
filteredEntries.push(entry);
|
|
40
|
+
await this._writeRegistry(filteredEntries);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} name
|
|
44
|
+
* @returns {Promise<void>}
|
|
45
|
+
*/
|
|
46
|
+
static async unregister(name) {
|
|
47
|
+
const entries = await this.list();
|
|
48
|
+
const filteredEntries = entries.filter((entry) => entry.name !== name);
|
|
49
|
+
await this._writeRegistry(filteredEntries);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} [name]
|
|
53
|
+
* @returns {Promise<Record<string, any>>}
|
|
54
|
+
*/
|
|
55
|
+
static async resolve(name) {
|
|
56
|
+
const entries = await this.list();
|
|
57
|
+
if (name) {
|
|
58
|
+
const entry = entries.find((candidate) => candidate.name === name);
|
|
59
|
+
if (!entry) {
|
|
60
|
+
throw new Error(`No running browser process found with name: ${name}`);
|
|
61
|
+
}
|
|
62
|
+
return entry;
|
|
63
|
+
}
|
|
64
|
+
if (entries.length === 1) {
|
|
65
|
+
return entries[0];
|
|
66
|
+
}
|
|
67
|
+
if (entries.length === 0) {
|
|
68
|
+
throw new Error("No running browser processes found");
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Multiple browser processes are running (${entries.length}); pass --name`);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} [name]
|
|
74
|
+
* @returns {Promise<Record<string, any>>}
|
|
75
|
+
*/
|
|
76
|
+
static async stop(name) {
|
|
77
|
+
const entry = await this.resolve(name);
|
|
78
|
+
if (!(await this.verifyEntry(entry))) {
|
|
79
|
+
await this.unregister(entry.name);
|
|
80
|
+
throw new Error(`Browser registry entry ${entry.name} no longer matches a running browser daemon`);
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
process.kill(entry.pid, "SIGTERM");
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof Error && "code" in error && error.code === "ESRCH") {
|
|
87
|
+
await this.unregister(entry.name);
|
|
88
|
+
return entry;
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
for (let attemptNumber = 1; attemptNumber <= browserDaemonStopTimeoutMs / 50; attemptNumber += 1) {
|
|
93
|
+
if (!this.isProcessAlive(entry.pid)) {
|
|
94
|
+
await this.unregister(entry.name);
|
|
95
|
+
return entry;
|
|
96
|
+
}
|
|
97
|
+
await wait(50);
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`Timed out waiting for browser process ${entry.name} (${entry.pid}) to stop`);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* @param {Record<string, any>} entry
|
|
103
|
+
* @returns {Promise<boolean>}
|
|
104
|
+
*/
|
|
105
|
+
static async verifyEntry(entry) {
|
|
106
|
+
if (typeof entry.port !== "number" || entry.port <= 0) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const ws = new WebSocket(`ws://127.0.0.1:${entry.port}`);
|
|
110
|
+
return await new Promise((resolve) => {
|
|
111
|
+
let settled = false;
|
|
112
|
+
const finish = (result) => {
|
|
113
|
+
if (settled) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
settled = true;
|
|
117
|
+
clearTimeout(timeoutId);
|
|
118
|
+
try {
|
|
119
|
+
ws.close();
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Ignore close errors while validating a registry entry.
|
|
123
|
+
}
|
|
124
|
+
resolve(result);
|
|
125
|
+
};
|
|
126
|
+
const timeoutId = setTimeout(() => {
|
|
127
|
+
finish(false);
|
|
128
|
+
}, browserDaemonVerifyTimeoutMs);
|
|
129
|
+
ws.on("open", () => {
|
|
130
|
+
ws.send(JSON.stringify({ command: "describe", type: "browser-daemon" }));
|
|
131
|
+
});
|
|
132
|
+
ws.on("message", (rawData) => {
|
|
133
|
+
try {
|
|
134
|
+
const response = JSON.parse(rawData.toString());
|
|
135
|
+
const result = response?.result;
|
|
136
|
+
finish(response?.ok === true
|
|
137
|
+
&& result?.name === entry.name
|
|
138
|
+
&& result?.pid === entry.pid
|
|
139
|
+
&& result?.port === entry.port);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
finish(false);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
ws.on("error", () => {
|
|
146
|
+
finish(false);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* @param {number} pid
|
|
152
|
+
* @returns {boolean}
|
|
153
|
+
*/
|
|
154
|
+
static isProcessAlive(pid) {
|
|
155
|
+
if (!pid || typeof pid !== "number") {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
process.kill(pid, 0);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** @returns {Promise<Array<Record<string, any>>>} */
|
|
167
|
+
static async _readRegistry() {
|
|
168
|
+
try {
|
|
169
|
+
const fileContent = await fs.readFile(this.getRegistryPath(), "utf8");
|
|
170
|
+
const parsed = JSON.parse(fileContent);
|
|
171
|
+
if (!Array.isArray(parsed)) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
return parsed;
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* @param {Array<Record<string, any>>} entries
|
|
185
|
+
* @returns {Promise<void>}
|
|
186
|
+
*/
|
|
187
|
+
static async _writeRegistry(entries) {
|
|
188
|
+
await fs.writeFile(this.getRegistryPath(), JSON.stringify(entries, null, 2));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJvd3Nlci1yZWdpc3RyeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9icm93c2VyLXJlZ2lzdHJ5LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxNQUFNLGtCQUFrQixDQUFBO0FBQ2pDLE9BQU8sRUFBRSxNQUFNLFNBQVMsQ0FBQTtBQUN4QixPQUFPLElBQUksTUFBTSxXQUFXLENBQUE7QUFDNUIsT0FBTyxJQUFJLE1BQU0sd0JBQXdCLENBQUE7QUFDekMsT0FBTyxFQUFDLFNBQVMsRUFBQyxNQUFNLElBQUksQ0FBQTtBQUU1QixPQUFPLEVBQUMsMEJBQTBCLEVBQUUsNEJBQTRCLEVBQUMsTUFBTSwrQkFBK0IsQ0FBQTtBQUV0RyxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsRUFBRSxzQ0FBc0MsQ0FBQyxDQUFBO0FBRW5GLGdDQUFnQztBQUNoQyxNQUFNLENBQUMsT0FBTyxPQUFPLGVBQWU7SUFDbEMsd0JBQXdCO0lBQ3hCLE1BQU0sQ0FBQyxlQUFlO1FBQ3BCLE9BQU8sWUFBWSxDQUFBO0lBQ3JCLENBQUM7SUFFRCxxREFBcUQ7SUFDckQsTUFBTSxDQUFDLEtBQUssQ0FBQyxJQUFJO1FBQ2YsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUE7UUFDMUMsTUFBTSxZQUFZLEdBQUcsRUFBRSxDQUFBO1FBQ3ZCLElBQUksS0FBSyxHQUFHLEtBQUssQ0FBQTtRQUVqQixLQUFLLE1BQU0sS0FBSyxJQUFJLE9BQU8sRUFBRSxDQUFDO1lBQzVCLElBQUksSUFBSSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztnQkFDbkMsWUFBWSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQTtZQUMxQixDQUFDO2lCQUFNLENBQUM7Z0JBQ04sS0FBSyxHQUFHLElBQUksQ0FBQTtZQUNkLENBQUM7UUFD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QUFFLENBQUM7Z0JBQ3pFLE9BQU8sRUFBRSxDQUFBO1lBQ1gsQ0FBQztZQUVELE1BQU0sS0FBSyxDQUFBO1FBQ2IsQ0FBQztJQUNILENBQUM7SUFFRDs7O09BR0c7SUFDSCxNQUFNLENBQUMsS0FBSyxDQUFDLGNBQWMsQ0FBQyxPQUFPO1FBQ2pDLE1BQU0sRUFBRSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsZUFBZSxFQUFFLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUE7SUFDOUUsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGZzIGZyb20gXCJub2RlOmZzL3Byb21pc2VzXCJcbmltcG9ydCBvcyBmcm9tIFwibm9kZTpvc1wiXG5pbXBvcnQgcGF0aCBmcm9tIFwibm9kZTpwYXRoXCJcbmltcG9ydCB3YWl0IGZyb20gXCJhd2FpdGVyeS9idWlsZC93YWl0LmpzXCJcbmltcG9ydCB7V2ViU29ja2V0fSBmcm9tIFwid3NcIlxuXG5pbXBvcnQge2Jyb3dzZXJEYWVtb25TdG9wVGltZW91dE1zLCBicm93c2VyRGFlbW9uVmVyaWZ5VGltZW91dE1zfSBmcm9tIFwiLi9icm93c2VyLWRhZW1vbi1jb25zdGFudHMuanNcIlxuXG5jb25zdCByZWdpc3RyeVBhdGggPSBwYXRoLmpvaW4ob3MudG1wZGlyKCksIFwic3lzdGVtLXRlc3RpbmctYnJvd3Nlci1yZWdpc3RyeS5qc29uXCIpXG5cbi8qKiBCcm93c2VyIHByb2Nlc3MgcmVnaXN0cnkuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBCcm93c2VyUmVnaXN0cnkge1xuICAvKiogQHJldHVybnMge3N0cmluZ30gKi9cbiAgc3RhdGljIGdldFJlZ2lzdHJ5UGF0aCgpIHtcbiAgICByZXR1cm4gcmVnaXN0cnlQYXRoXG4gIH1cblxuICAvKiogQHJldHVybnMge1Byb21pc2U8QXJyYXk8UmVjb3JkPHN0cmluZywgYW55Pj4+fSAqL1xuICBzdGF0aWMgYXN5bmMgbGlzdCgpIHtcbiAgICBjb25zdCBlbnRyaWVzID0gYXdhaXQgdGhpcy5fcmVhZFJlZ2lzdHJ5KClcbiAgICBjb25zdCBhbGl2ZUVudHJpZXMgPSBbXVxuICAgIGxldCBkaXJ0eSA9IGZhbHNlXG5cbiAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIGVudHJpZXMpIHtcbiAgICAgIGlmICh0aGlzLmlzUHJvY2Vzc0FsaXZlKGVudHJ5LnBpZCkpIHtcbiAgICAgICAgYWxpdmVFbnRyaWVzLnB1c2goZW50cnkpXG4gICAgICB9IGVsc2Uge1xuICAgICAgICBkaXJ0eSA9IHRydWVcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoZGlydHkpIHtcbiAgICAgIGF3YWl0IHRoaXMuX3dyaXRlUmVnaXN0cnkoYWxpdmVFbnRyaWVzKVxuICAgIH1cblxuICAgIHJldHVybiBhbGl2ZUVudHJpZXNcbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsIGFueT59IGVudHJ5XG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgc3RhdGljIGFzeW5jIHJlZ2lzdGVyKGVudHJ5KSB7XG4gICAgY29uc3QgZW50cmllcyA9IGF3YWl0IHRoaXMubGlzdCgpXG4gICAgY29uc3QgZmlsdGVyZWRFbnRyaWVzID0gZW50cmllcy5maWx0ZXIoKGV4aXN0aW5nRW50cnkpID0+IGV4aXN0aW5nRW50cnkubmFtZSAhPT0gZW50cnkubmFtZSlcblxuICAgIGZpbHRlcmVkRW50cmllcy5wdXNoKGVudHJ5KVxuICAgIGF3YWl0IHRoaXMuX3dyaXRlUmVnaXN0cnkoZmlsdGVyZWRFbnRyaWVzKVxuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBuYW1lXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgc3RhdGljIGFzeW5jIHVucmVnaXN0ZXIobmFtZSkge1xuICAgIGNvbnN0IGVudHJpZXMgPSBhd2FpdCB0aGlzLmxpc3QoKVxuICAgIGNvbnN0IGZpbHRlcmVkRW50cmllcyA9IGVudHJpZXMuZmlsdGVyKChlbnRyeSkgPT4gZW50cnkubmFtZSAhPT0gbmFtZSlcblxuICAgIGF3YWl0IHRoaXMuX3dyaXRlUmVnaXN0cnkoZmlsdGVyZWRFbnRyaWVzKVxuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBbbmFtZV1cbiAgICogQHJldHVybnMge1Byb21pc2U8UmVjb3JkPHN0cmluZywgYW55Pj59XG4gICAqL1xuICBzdGF0aWMgYXN5bmMgcmVzb2x2ZShuYW1lKSB7XG4gICAgY29uc3QgZW50cmllcyA9IGF3YWl0IHRoaXMubGlzdCgpXG5cbiAgICBpZiAobmFtZSkge1xuICAgICAgY29uc3QgZW50cnkgPSBlbnRyaWVzLmZpbmQoKGNhbmRpZGF0ZSkgPT4gY2FuZGlkYXRlLm5hbWUgPT09IG5hbWUpXG5cbiAgICAgIGlmICghZW50cnkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBObyBydW5uaW5nIGJyb3dzZXIgcHJvY2VzcyBmb3VuZCB3aXRoIG5hbWU6ICR7bmFtZX1gKVxuICAgICAgfVxuXG4gICAgICByZXR1cm4gZW50cnlcbiAgICB9XG5cbiAgICBpZiAoZW50cmllcy5sZW5ndGggPT09IDEpIHtcbiAgICAgIHJldHVybiBlbnRyaWVzWzBdXG4gICAgfVxuXG4gICAgaWYgKGVudHJpZXMubGVuZ3RoID09PSAwKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJObyBydW5uaW5nIGJyb3dzZXIgcHJvY2Vzc2VzIGZvdW5kXCIpXG4gICAgfVxuXG4gICAgdGhyb3cgbmV3IEVycm9yKGBNdWx0aXBsZSBicm93c2VyIHByb2Nlc3NlcyBhcmUgcnVubmluZyAoJHtlbnRyaWVzLmxlbmd0aH0pOyBwYXNzIC0tbmFtZWApXG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHtzdHJpbmd9IFtuYW1lXVxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCBhbnk+Pn1cbiAgICovXG4gIHN0YXRpYyBhc3luYyBzdG9wKG5hbWUpIHtcbiAgICBjb25zdCBlbnRyeSA9IGF3YWl0IHRoaXMucmVzb2x2ZShuYW1lKVxuXG4gICAgaWYgKCEoYXdhaXQgdGhpcy52ZXJpZnlFbnRyeShlbnRyeSkpKSB7XG4gICAgICBhd2FpdCB0aGlzLnVucmVnaXN0ZXIoZW50cnkubmFtZSlcbiAgICAgIHRocm93IG5ldyBFcnJvcihgQnJvd3NlciByZWdpc3RyeSBlbnRyeSAke2VudHJ5Lm5hbWV9IG5vIGxvbmdlciBtYXRjaGVzIGEgcnVubmluZyBicm93c2VyIGRhZW1vbmApXG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgIHByb2Nlc3Mua2lsbChlbnRyeS5waWQsIFwiU0lHVEVSTVwiKVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBpZiAoZXJyb3IgaW5zdGFuY2VvZiBFcnJvciAmJiBcImNvZGVcIiBpbiBlcnJvciAmJiBlcnJvci5jb2RlID09PSBcIkVTUkNIXCIpIHtcbiAgICAgICAgYXdhaXQgdGhpcy51bnJlZ2lzdGVyKGVudHJ5Lm5hbWUpXG4gICAgICAgIHJldHVybiBlbnRyeVxuICAgICAgfVxuXG4gICAgICB0aHJvdyBlcnJvclxuICAgIH1cblxuICAgIGZvciAobGV0IGF0dGVtcHROdW1iZXIgPSAxOyBhdHRlbXB0TnVtYmVyIDw9IGJyb3dzZXJEYWVtb25TdG9wVGltZW91dE1zIC8gNTA7IGF0dGVtcHROdW1iZXIgKz0gMSkge1xuICAgICAgaWYgKCF0aGlzLmlzUHJvY2Vzc0FsaXZlKGVudHJ5LnBpZCkpIHtcbiAgICAgICAgYXdhaXQgdGhpcy51bnJlZ2lzdGVyKGVudHJ5Lm5hbWUpXG4gICAgICAgIHJldHVybiBlbnRyeVxuICAgICAgfVxuXG4gICAgICBhd2FpdCB3YWl0KDUwKVxuICAgIH1cblxuICAgIHRocm93IG5ldyBFcnJvcihgVGltZWQgb3V0IHdhaXRpbmcgZm9yIGJyb3dzZXIgcHJvY2VzcyAke2VudHJ5Lm5hbWV9ICgke2VudHJ5LnBpZH0pIHRvIHN0b3BgKVxuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7UmVjb3JkPHN0cmluZywgYW55Pn0gZW50cnlcbiAgICogQHJldHVybnMge1Byb21pc2U8Ym9vbGVhbj59XG4gICAqL1xuICBzdGF0aWMgYXN5bmMgdmVyaWZ5RW50cnkoZW50cnkpIHtcbiAgICBpZiAodHlwZW9mIGVudHJ5LnBvcnQgIT09IFwibnVtYmVyXCIgfHwgZW50cnkucG9ydCA8PSAwKSB7XG4gICAgICByZXR1cm4gZmFsc2VcbiAgICB9XG5cbiAgICBjb25zdCB3cyA9IG5ldyBXZWJTb2NrZXQoYHdzOi8vMTI3LjAuMC4xOiR7ZW50cnkucG9ydH1gKVxuXG4gICAgcmV0dXJuIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiB7XG4gICAgICBsZXQgc2V0dGxlZCA9IGZhbHNlXG5cbiAgICAgIGNvbnN0IGZpbmlzaCA9IChyZXN1bHQpID0+IHtcbiAgICAgICAgaWYgKHNldHRsZWQpIHtcbiAgICAgICAgICByZXR1cm5cbiAgICAgICAgfVxuXG4gICAgICAgIHNldHRsZWQgPSB0cnVlXG4gICAgICAgIGNsZWFyVGltZW91dCh0aW1lb3V0SWQpXG5cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICB3cy5jbG9zZSgpXG4gICAgICAgIH0gY2F0Y2gge1xuICAgICAgICAgIC8vIElnbm9yZSBjbG9zZSBlcnJvcnMgd2hpbGUgdmFsaWRhdGluZyBhIHJlZ2lzdHJ5IGVudHJ5LlxuICAgICAgICB9XG5cbiAgICAgICAgcmVzb2x2ZShyZXN1bHQpXG4gICAgICB9XG5cbiAgICAgIGNvbnN0IHRpbWVvdXRJZCA9IHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICBmaW5pc2goZmFsc2UpXG4gICAgICB9LCBicm93c2VyRGFlbW9uVmVyaWZ5VGltZW91dE1zKVxuXG4gICAgICB3cy5vbihcIm9wZW5cIiwgKCkgPT4ge1xuICAgICAgICB3cy5zZW5kKEpTT04uc3RyaW5naWZ5KHtjb21tYW5kOiBcImRlc2NyaWJlXCIsIHR5cGU6IFwiYnJvd3Nlci1kYWVtb25cIn0pKVxuICAgICAgfSlcblxuICAgICAgd3Mub24oXCJtZXNzYWdlXCIsIChyYXdEYXRhKSA9PiB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgY29uc3QgcmVzcG9uc2UgPSBKU09OLnBhcnNlKHJhd0RhdGEudG9TdHJpbmcoKSlcbiAgICAgICAgICBjb25zdCByZXN1bHQgPSByZXNwb25zZT8ucmVzdWx0XG5cbiAgICAgICAgICBmaW5pc2goXG4gICAgICAgICAgICByZXNwb25zZT8ub2sgPT09IHRydWVcbiAgICAgICAgICAgICYmIHJlc3VsdD8ubmFtZSA9PT0gZW50cnkubmFtZVxuICAgICAgICAgICAgJiYgcmVzdWx0Py5waWQgPT09IGVudHJ5LnBpZFxuICAgICAgICAgICAgJiYgcmVzdWx0Py5wb3J0ID09PSBlbnRyeS5wb3J0XG4gICAgICAgICAgKVxuICAgICAgICB9IGNhdGNoIHtcbiAgICAgICAgICBmaW5pc2goZmFsc2UpXG4gICAgICAgIH1cbiAgICAgIH0pXG5cbiAgICAgIHdzLm9uKFwiZXJyb3JcIiwgKCkgPT4ge1xuICAgICAgICBmaW5pc2goZmFsc2UpXG4gICAgICB9KVxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHtudW1iZXJ9IHBpZFxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn1cbiAgICovXG4gIHN0YXRpYyBpc1Byb2Nlc3NBbGl2ZShwaWQpIHtcbiAgICBpZiAoIXBpZCB8fCB0eXBlb2YgcGlkICE9PSBcIm51bWJlclwiKSB7XG4gICAgICByZXR1cm4gZmFsc2VcbiAgICB9XG5cbiAgICB0cnkge1xuICAgICAgcHJvY2Vzcy5raWxsKHBpZCwgMClcbiAgICAgIHJldHVybiB0cnVlXG4gICAgfSBjYXRjaCB7XG4gICAgICByZXR1cm4gZmFsc2VcbiAgICB9XG4gIH1cblxuICAvKiogQHJldHVybnMge1Byb21pc2U8QXJyYXk8UmVjb3JkPHN0cmluZywgYW55Pj4+fSAqL1xuICBzdGF0aWMgYXN5bmMgX3JlYWRSZWdpc3RyeSgpIHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgZmlsZUNvbnRlbnQgPSBhd2FpdCBmcy5yZWFkRmlsZSh0aGlzLmdldFJlZ2lzdHJ5UGF0aCgpLCBcInV0ZjhcIilcbiAgICAgIGNvbnN0IHBhcnNlZCA9IEpTT04ucGFyc2UoZmlsZUNvbnRlbnQpXG5cbiAgICAgIGlmICghQXJyYXkuaXNBcnJheShwYXJzZWQpKSB7XG4gICAgICAgIHJldHVybiBbXVxuICAgICAgfVxuXG4gICAgICByZXR1cm4gcGFyc2VkXG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGlmIChlcnJvciBpbnN0YW5jZW9mIEVycm9yICYmIFwiY29kZVwiIGluIGVycm9yICYmIGVycm9yLmNvZGUgPT09IFwiRU5PRU5UXCIpIHtcbiAgICAgICAgcmV0dXJuIFtdXG4gICAgICB9XG5cbiAgICAgIHRocm93IGVycm9yXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7QXJyYXk8UmVjb3JkPHN0cmluZywgYW55Pj59IGVudHJpZXNcbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gICAqL1xuICBzdGF0aWMgYXN5bmMgX3dyaXRlUmVnaXN0cnkoZW50cmllcykge1xuICAgIGF3YWl0IGZzLndyaXRlRmlsZSh0aGlzLmdldFJlZ2lzdHJ5UGF0aCgpLCBKU09OLnN0cmluZ2lmeShlbnRyaWVzLCBudWxsLCAyKSlcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} BrowserArgs
|
|
3
|
+
* @property {boolean} [debug] Enable debug logging.
|
|
4
|
+
* @property {BrowserDriverConfig} [driver] Driver configuration.
|
|
5
|
+
* @property {import("./system-test-communicator.js").default} [communicator] Optional command communicator for helper-driven navigation.
|
|
6
|
+
* @property {string} [screenshotsPath] Directory used for saved screenshots and browser artifacts.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} BrowserDriverConfig
|
|
10
|
+
* @property {"selenium"|"appium"} [type] Driver implementation to use.
|
|
11
|
+
* @property {Record<string, any>} [options] Driver-specific options.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {object} BrowserNavigationArgs
|
|
15
|
+
* @property {number} [timeout] Override the timeout for this navigation command.
|
|
16
|
+
*/
|
|
17
|
+
/** Generic browser session wrapper around the configured driver. */
|
|
18
|
+
export default class Browser {
|
|
19
|
+
/** @param {BrowserArgs} [args] */
|
|
20
|
+
constructor({ debug, driver, communicator, screenshotsPath, ...restArgs }?: BrowserArgs);
|
|
21
|
+
/** @type {import("selenium-webdriver").WebDriver | undefined} */
|
|
22
|
+
driver: import("selenium-webdriver").WebDriver | undefined;
|
|
23
|
+
/** @type {import("./drivers/webdriver-driver.js").default | undefined} */
|
|
24
|
+
driverAdapter: import("./drivers/webdriver-driver.js").default | undefined;
|
|
25
|
+
_debug: boolean;
|
|
26
|
+
/** @type {BrowserDriverConfig | undefined} */
|
|
27
|
+
_driverConfig: BrowserDriverConfig | undefined;
|
|
28
|
+
/** @type {Error | undefined} */
|
|
29
|
+
_httpServerError: Error | undefined;
|
|
30
|
+
_screenshotsPath: string;
|
|
31
|
+
communicator: import("./system-test-communicator.js").default;
|
|
32
|
+
/**
|
|
33
|
+
* @param {BrowserDriverConfig} [driverConfig]
|
|
34
|
+
* @returns {import("./drivers/webdriver-driver.js").default}
|
|
35
|
+
*/
|
|
36
|
+
createDriver(driverConfig?: BrowserDriverConfig): import("./drivers/webdriver-driver.js").default;
|
|
37
|
+
/**
|
|
38
|
+
* @param {import("./system-test-communicator.js").default | undefined} communicator
|
|
39
|
+
* @returns {void}
|
|
40
|
+
*/
|
|
41
|
+
setCommunicator(communicator: import("./system-test-communicator.js").default | undefined): void;
|
|
42
|
+
/** @returns {boolean} */
|
|
43
|
+
communicatorExists(): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} baseSelector
|
|
46
|
+
* @returns {void}
|
|
47
|
+
*/
|
|
48
|
+
setBaseSelector(baseSelector: string): void;
|
|
49
|
+
_baseSelector: string;
|
|
50
|
+
/** @returns {string | undefined} */
|
|
51
|
+
getBaseSelector(): string | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} selector
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
getSelector(selector: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* @param {...any} args
|
|
59
|
+
* @returns {void}
|
|
60
|
+
*/
|
|
61
|
+
debugError(...args: any[]): void;
|
|
62
|
+
/**
|
|
63
|
+
* @param {...any} args
|
|
64
|
+
* @returns {void}
|
|
65
|
+
*/
|
|
66
|
+
debugLog(...args: any[]): void;
|
|
67
|
+
/** @returns {void} */
|
|
68
|
+
throwIfHttpServerError(): void;
|
|
69
|
+
/**
|
|
70
|
+
* @param {Error} error
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
onHttpServerError: (error: Error) => void;
|
|
74
|
+
/** @returns {import("selenium-webdriver").WebDriver} */
|
|
75
|
+
getDriver(): import("selenium-webdriver").WebDriver;
|
|
76
|
+
/** @returns {import("./drivers/webdriver-driver.js").default} */
|
|
77
|
+
getDriverAdapter(): import("./drivers/webdriver-driver.js").default;
|
|
78
|
+
/** @returns {number} */
|
|
79
|
+
getTimeouts(): number;
|
|
80
|
+
/** @returns {Promise<void>} */
|
|
81
|
+
restoreTimeouts(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* @param {number} newTimeout
|
|
84
|
+
* @returns {Promise<void>}
|
|
85
|
+
*/
|
|
86
|
+
driverSetTimeouts(newTimeout: number): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* @param {number} newTimeout
|
|
89
|
+
* @returns {Promise<void>}
|
|
90
|
+
*/
|
|
91
|
+
setTimeouts(newTimeout: number): Promise<void>;
|
|
92
|
+
/** @returns {Promise<string[]>} */
|
|
93
|
+
getBrowserLogs(): Promise<string[]>;
|
|
94
|
+
/** @returns {Promise<string>} */
|
|
95
|
+
getCurrentUrl(): Promise<string>;
|
|
96
|
+
/**
|
|
97
|
+
* @param {string} selector
|
|
98
|
+
* @param {import("./system-test.js").FindArgs} [args]
|
|
99
|
+
* @returns {Promise<import("selenium-webdriver").WebElement[]>}
|
|
100
|
+
*/
|
|
101
|
+
all(selector: string, args?: import("./system-test.js").FindArgs): Promise<import("selenium-webdriver").WebElement[]>;
|
|
102
|
+
/**
|
|
103
|
+
* @param {string} selector
|
|
104
|
+
* @param {import("./system-test.js").FindArgs} [args]
|
|
105
|
+
* @returns {Promise<import("selenium-webdriver").WebElement>}
|
|
106
|
+
*/
|
|
107
|
+
find(selector: string, args?: import("./system-test.js").FindArgs): Promise<import("selenium-webdriver").WebElement>;
|
|
108
|
+
/**
|
|
109
|
+
* @param {string} testID
|
|
110
|
+
* @param {import("./system-test.js").FindArgs} [args]
|
|
111
|
+
* @returns {Promise<import("selenium-webdriver").WebElement>}
|
|
112
|
+
*/
|
|
113
|
+
findByTestID(testID: string, args?: import("./system-test.js").FindArgs): Promise<import("selenium-webdriver").WebElement>;
|
|
114
|
+
/**
|
|
115
|
+
* @param {string} selector
|
|
116
|
+
* @param {import("./system-test.js").FindArgs} [args]
|
|
117
|
+
* @returns {Promise<import("selenium-webdriver").WebElement>}
|
|
118
|
+
*/
|
|
119
|
+
findNoWait(selector: string, args?: import("./system-test.js").FindArgs): Promise<import("selenium-webdriver").WebElement>;
|
|
120
|
+
/**
|
|
121
|
+
* @param {string | import("selenium-webdriver").WebElement} elementOrIdentifier
|
|
122
|
+
* @param {import("./system-test.js").FindArgs} [args]
|
|
123
|
+
* @returns {Promise<void>}
|
|
124
|
+
*/
|
|
125
|
+
click(elementOrIdentifier: string | import("selenium-webdriver").WebElement, args?: import("./system-test.js").FindArgs): Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* @param {import("selenium-webdriver").WebElement|string|{selector: string} & import("./system-test.js").FindArgs} elementOrIdentifier
|
|
128
|
+
* @param {string} methodName
|
|
129
|
+
* @param {...any} args
|
|
130
|
+
* @returns {Promise<any>}
|
|
131
|
+
*/
|
|
132
|
+
interact(elementOrIdentifier: import("selenium-webdriver").WebElement | string | ({
|
|
133
|
+
selector: string;
|
|
134
|
+
} & import("./system-test.js").FindArgs), methodName: string, ...args: any[]): Promise<any>;
|
|
135
|
+
/**
|
|
136
|
+
* @param {string} selector
|
|
137
|
+
* @param {import("./system-test.js").WaitForNoSelectorArgs} [args]
|
|
138
|
+
* @returns {Promise<void>}
|
|
139
|
+
*/
|
|
140
|
+
waitForNoSelector(selector: string, args?: import("./system-test.js").WaitForNoSelectorArgs): Promise<void>;
|
|
141
|
+
/**
|
|
142
|
+
* @param {string} selector
|
|
143
|
+
* @param {import("./system-test.js").FindArgs} [args]
|
|
144
|
+
* @returns {Promise<void>}
|
|
145
|
+
*/
|
|
146
|
+
expectNoElement(selector: string, args?: import("./system-test.js").FindArgs): Promise<void>;
|
|
147
|
+
/** @returns {Promise<string>} */
|
|
148
|
+
getHTML(): Promise<string>;
|
|
149
|
+
/**
|
|
150
|
+
* @param {number | undefined} timeoutOverride
|
|
151
|
+
* @returns {number}
|
|
152
|
+
*/
|
|
153
|
+
getCommandTimeout(timeoutOverride: number | undefined): number;
|
|
154
|
+
/**
|
|
155
|
+
* @param {string} path
|
|
156
|
+
* @returns {Promise<void>}
|
|
157
|
+
*/
|
|
158
|
+
driverVisit(path: string): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* @param {string} type
|
|
161
|
+
* @param {string} path
|
|
162
|
+
* @param {BrowserNavigationArgs} [args]
|
|
163
|
+
* @returns {Promise<void>}
|
|
164
|
+
*/
|
|
165
|
+
sendBrowserCommand(type: string, path: string, args?: BrowserNavigationArgs): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Visits a path using the injected browser helper when available, otherwise navigates directly with the driver.
|
|
168
|
+
* @param {string} path
|
|
169
|
+
* @param {BrowserNavigationArgs} [args]
|
|
170
|
+
* @returns {Promise<void>}
|
|
171
|
+
*/
|
|
172
|
+
visit(path: string, args?: BrowserNavigationArgs): Promise<void>;
|
|
173
|
+
/**
|
|
174
|
+
* Dismisses to a path via the injected browser helper when available, otherwise navigates directly with the driver.
|
|
175
|
+
* @param {string} path
|
|
176
|
+
* @param {BrowserNavigationArgs} [args]
|
|
177
|
+
* @returns {Promise<void>}
|
|
178
|
+
*/
|
|
179
|
+
dismissTo(path: string, args?: BrowserNavigationArgs): Promise<void>;
|
|
180
|
+
/**
|
|
181
|
+
* Formats browser logs for console output and truncates overly long output.
|
|
182
|
+
* @param {string[]} logs
|
|
183
|
+
* @param {number} [maxLines]
|
|
184
|
+
* @returns {string[]}
|
|
185
|
+
*/
|
|
186
|
+
formatBrowserLogsForConsole(logs: string[], maxLines?: number): string[];
|
|
187
|
+
/**
|
|
188
|
+
* @param {string[]} logs
|
|
189
|
+
* @returns {void}
|
|
190
|
+
*/
|
|
191
|
+
printBrowserLogsForFailure(logs: string[]): void;
|
|
192
|
+
/**
|
|
193
|
+
* Takes a screenshot, writes HTML/browser logs to disk, and returns the collected artifacts.
|
|
194
|
+
* @returns {Promise<{currentUrl: string, html: string, htmlPath: string, logs: string[], logsPath: string, screenshotPath: string}>}
|
|
195
|
+
*/
|
|
196
|
+
takeScreenshot(): Promise<{
|
|
197
|
+
currentUrl: string;
|
|
198
|
+
html: string;
|
|
199
|
+
htmlPath: string;
|
|
200
|
+
logs: string[];
|
|
201
|
+
logsPath: string;
|
|
202
|
+
screenshotPath: string;
|
|
203
|
+
}>;
|
|
204
|
+
/** @returns {Promise<void>} */
|
|
205
|
+
stopDriver(): Promise<void>;
|
|
206
|
+
}
|
|
207
|
+
export type BrowserArgs = {
|
|
208
|
+
/**
|
|
209
|
+
* Enable debug logging.
|
|
210
|
+
*/
|
|
211
|
+
debug?: boolean;
|
|
212
|
+
/**
|
|
213
|
+
* Driver configuration.
|
|
214
|
+
*/
|
|
215
|
+
driver?: BrowserDriverConfig;
|
|
216
|
+
/**
|
|
217
|
+
* Optional command communicator for helper-driven navigation.
|
|
218
|
+
*/
|
|
219
|
+
communicator?: import("./system-test-communicator.js").default;
|
|
220
|
+
/**
|
|
221
|
+
* Directory used for saved screenshots and browser artifacts.
|
|
222
|
+
*/
|
|
223
|
+
screenshotsPath?: string;
|
|
224
|
+
};
|
|
225
|
+
export type BrowserDriverConfig = {
|
|
226
|
+
/**
|
|
227
|
+
* Driver implementation to use.
|
|
228
|
+
*/
|
|
229
|
+
type?: "selenium" | "appium";
|
|
230
|
+
/**
|
|
231
|
+
* Driver-specific options.
|
|
232
|
+
*/
|
|
233
|
+
options?: Record<string, any>;
|
|
234
|
+
};
|
|
235
|
+
export type BrowserNavigationArgs = {
|
|
236
|
+
/**
|
|
237
|
+
* Override the timeout for this navigation command.
|
|
238
|
+
*/
|
|
239
|
+
timeout?: number;
|
|
240
|
+
};
|