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.
Files changed (35) hide show
  1. package/README.md +232 -6
  2. package/build/browser-command-client.d.ts +19 -0
  3. package/build/browser-command-client.js +39 -0
  4. package/build/browser-command-runner.d.ts +34 -0
  5. package/build/browser-command-runner.js +155 -0
  6. package/build/browser-daemon-constants.d.ts +2 -0
  7. package/build/browser-daemon-constants.js +3 -0
  8. package/build/browser-process.d.ts +45 -0
  9. package/build/browser-process.js +134 -0
  10. package/build/browser-registry.d.ts +44 -0
  11. package/build/browser-registry.js +191 -0
  12. package/build/browser.d.ts +240 -0
  13. package/build/browser.js +375 -0
  14. package/build/cli-helpers.d.ts +16 -0
  15. package/build/cli-helpers.js +177 -0
  16. package/build/cli.d.ts +2 -0
  17. package/build/cli.js +81 -0
  18. package/build/drivers/appium-driver.js +21 -21
  19. package/build/drivers/webdriver-driver.d.ts +4 -4
  20. package/build/drivers/webdriver-driver.js +32 -28
  21. package/build/index.d.ts +9 -1
  22. package/build/index.js +10 -3
  23. package/build/system-test-browser-helper.d.ts +6 -12
  24. package/build/system-test-browser-helper.js +12 -13
  25. package/build/system-test.d.ts +3 -189
  26. package/build/system-test.js +6 -220
  27. package/build/use-system-test-expo.d.ts +16 -0
  28. package/build/use-system-test-expo.js +34 -0
  29. package/build/use-system-test-react-native.d.ts +25 -0
  30. package/build/use-system-test-react-native.js +20 -0
  31. package/build/use-system-test-shape-hook.d.ts +35 -0
  32. package/build/use-system-test-shape-hook.js +74 -0
  33. package/build/use-system-test.d.ts +19 -10
  34. package/build/use-system-test.js +26 -72
  35. 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJvd3Nlci1wcm9jZXNzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2Jyb3dzZXItcHJvY2Vzcy5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLE9BQU8sTUFBTSxjQUFjLENBQUE7QUFDbEMsT0FBTyxvQkFBb0IsTUFBTSw2QkFBNkIsQ0FBQTtBQUM5RCxPQUFPLEVBQUMsMEJBQTBCLEVBQUMsTUFBTSwrQkFBK0IsQ0FBQTtBQUN4RSxPQUFPLGVBQWUsTUFBTSx1QkFBdUIsQ0FBQTtBQUNuRCxPQUFPLEVBQUMsZUFBZSxFQUFDLE1BQU0sSUFBSSxDQUFBO0FBRWxDLDRFQUE0RTtBQUM1RSxNQUFNLENBQUMsT0FBTyxPQUFPLGNBQWM7SUFDakM7Ozs7Ozs7O09BUUc7SUFDSCxZQUFZLEVBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRSxXQUFXLEdBQUcsRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLEdBQUcsS0FBSyxFQUFFLElBQUksR0FBRyxDQUFDLEVBQUM7UUE4RS9FOzs7V0FHRztRQUNILGlCQUFZLEdBQUcsQ0FBQyxFQUFFLEVBQUUsRUFBRTtZQUNwQixFQUFFLENBQUMsRUFBRSxDQUFDLFNBQVMsRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUU7Z0JBQ2pDLE1BQU0sU0FBUyxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxJQUFJLElBQUksQ0FBQyxZQUFZLEVBQUUsRUFBRSxDQUFBO2dCQUV4RCxJQUFJLENBQUM7b0JBQ0gsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQTtvQkFDOUMsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsYUFBYSxDQUFDLE9BQU8sQ0FBQyxDQUFBO29CQUVoRCxFQUFFLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBQyxFQUFFLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxNQUFN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QUUsQ0FBQztZQUMxQyxXQUFXLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUE7UUFDckMsQ0FBQztRQUVELE9BQU8sTUFBTSxJQUFJLENBQUMsYUFBYSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsV0FBVyxDQUFDLENBQUE7SUFDM0QsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IEJyb3dzZXIgZnJvbSBcIi4vYnJvd3Nlci5qc1wiXG5pbXBvcnQgQnJvd3NlckNvbW1hbmRSdW5uZXIgZnJvbSBcIi4vYnJvd3Nlci1jb21tYW5kLXJ1bm5lci5qc1wiXG5pbXBvcnQge2Jyb3dzZXJEYWVtb25TdG9wVGltZW91dE1zfSBmcm9tIFwiLi9icm93c2VyLWRhZW1vbi1jb25zdGFudHMuanNcIlxuaW1wb3J0IEJyb3dzZXJSZWdpc3RyeSBmcm9tIFwiLi9icm93c2VyLXJlZ2lzdHJ5LmpzXCJcbmltcG9ydCB7V2ViU29ja2V0U2VydmVyfSBmcm9tIFwid3NcIlxuXG4vKiogTG9uZy1ydW5uaW5nIGJyb3dzZXIgZGFlbW9uIGV4cG9zaW5nIGJyb3dzZXIgY29tbWFuZHMgb3ZlciBXZWJTb2NrZXQuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBCcm93c2VyUHJvY2VzcyB7XG4gIC8qKlxuICAgKiBAcGFyYW0ge29iamVjdH0gYXJnc1xuICAgKiBAcGFyYW0ge3N0cmluZ30gYXJncy5uYW1lXG4gICAqIEBwYXJhbSB7QnJvd3Nlcn0gW2FyZ3MuYnJvd3Nlcl1cbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCBhbnk+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
+ };