system-testing 1.0.77 → 1.0.79

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