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
package/README.md
CHANGED
|
@@ -8,6 +8,34 @@ Rails inspired system testing for Expo apps.
|
|
|
8
8
|
npm install --save-dev system-testing
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Choose the right layer
|
|
12
|
+
|
|
13
|
+
This package has three main entry points:
|
|
14
|
+
|
|
15
|
+
- `SystemTest`: full app-oriented system testing with selector helpers, app bootstrapping, WebSocket communication, screenshots, logs, and Scoundrel support.
|
|
16
|
+
- `Browser`: lower-level driver session for opening URLs, taking screenshots, and reading HTML/logs without the rest of the system-test flow.
|
|
17
|
+
- `system-testing` CLI browser daemon: a long-running named browser process that can be controlled from CLI commands or WebSocket messages.
|
|
18
|
+
- `useSystemTest*` hooks: browser-side integration that lets your app respond to `visit` / `dismissTo` commands from `SystemTest`.
|
|
19
|
+
|
|
20
|
+
Use `SystemTest` if you are testing your app. Use `Browser` if you just want a Selenium/Appium-backed browser session.
|
|
21
|
+
|
|
22
|
+
## Getting started
|
|
23
|
+
|
|
24
|
+
1. Add one of the browser-side hooks to your app:
|
|
25
|
+
`useSystemTestExpo` for Expo Router, or `useSystemTest` / `useSystemTestReactNative` for your own navigation stack.
|
|
26
|
+
2. Wrap your app in a root element with `testID="systemTestingComponent"`.
|
|
27
|
+
3. Make sure your root test route renders an element with `testID="blankText"`, or change `SystemTest.rootPath`.
|
|
28
|
+
4. Start tests with `SystemTest.run(...)` for app flows, or instantiate `Browser` directly for ordinary browsing/capture.
|
|
29
|
+
|
|
30
|
+
Minimal app-side requirements:
|
|
31
|
+
|
|
32
|
+
```jsx
|
|
33
|
+
<View testID="systemTestingComponent" dataSet={{focussed: "true"}}>
|
|
34
|
+
<Text testID="blankText">Blank</Text>
|
|
35
|
+
{children}
|
|
36
|
+
</View>
|
|
37
|
+
```
|
|
38
|
+
|
|
11
39
|
## Usage
|
|
12
40
|
|
|
13
41
|
```js
|
|
@@ -90,9 +118,160 @@ await SystemTest.run({
|
|
|
90
118
|
|
|
91
119
|
If you already run an Appium server, provide `serverUrl` instead of `serverArgs`. By default, `findByTestID` uses the Appium `accessibility id` strategy. To use CSS instead (for web contexts), set `options.testIdStrategy` to `"css"` and optionally `options.testIdAttribute` (defaults to `"data-testid"`).
|
|
92
120
|
|
|
93
|
-
|
|
121
|
+
For local or CI web runs against Chrome, `npm run test:appium:web` now resolves and downloads a matching Chrome for Testing `chromedriver` binary before it starts Appium. That keeps the Appium web path reproducible even when the installed Chrome patch version changes.
|
|
122
|
+
|
|
123
|
+
### Generic browser usage
|
|
124
|
+
|
|
125
|
+
`Browser` is the lower-level browser/session class behind `SystemTest`. Use it when you want driver-backed browsing, screenshots, logs, and HTML capture without the rest of the system-test bootstrapping.
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
import {Browser} from "system-testing/build/index.js"
|
|
129
|
+
|
|
130
|
+
const browser = new Browser()
|
|
131
|
+
|
|
132
|
+
browser.getDriverAdapter().setBaseUrl("https://example.com")
|
|
133
|
+
await browser.getDriverAdapter().start()
|
|
134
|
+
await browser.setTimeouts(10000)
|
|
135
|
+
|
|
136
|
+
await browser.visit("/")
|
|
137
|
+
|
|
138
|
+
const html = await browser.getHTML()
|
|
139
|
+
const logs = await browser.getBrowserLogs()
|
|
140
|
+
const screenshot = await browser.takeScreenshot()
|
|
141
|
+
|
|
142
|
+
await browser.stopDriver()
|
|
143
|
+
```
|
|
94
144
|
|
|
95
|
-
`
|
|
145
|
+
If `visit()`/`dismissTo()` should drive in-app navigation through the browser-side helper instead of direct URL loads, inject a communicator when constructing `Browser`. Without one, it falls back to direct driver navigation, which makes it usable for ordinary website browsing as well.
|
|
146
|
+
|
|
147
|
+
Common `Browser` flow:
|
|
148
|
+
|
|
149
|
+
1. Create the browser with the desired driver config.
|
|
150
|
+
2. Set the base URL on the driver adapter.
|
|
151
|
+
3. Start the driver and set timeouts.
|
|
152
|
+
4. Call `visit()`.
|
|
153
|
+
5. Read `getHTML()`, `getBrowserLogs()`, `getCurrentUrl()`, or `takeScreenshot()`.
|
|
154
|
+
6. Call `stopDriver()` during teardown.
|
|
155
|
+
|
|
156
|
+
Useful browser methods:
|
|
157
|
+
|
|
158
|
+
- `visit(pathOrUrl)`: uses the helper communicator if present, otherwise loads directly through Selenium/Appium.
|
|
159
|
+
- `dismissTo(pathOrUrl)`: same fallback behavior as `visit()`.
|
|
160
|
+
- `getHTML()`: returns the current page source.
|
|
161
|
+
- `getBrowserLogs()`: returns collected browser logs, or Appium logcat output for Android native runs.
|
|
162
|
+
- `takeScreenshot()`: writes screenshot, HTML, and logs to disk and returns the artifact paths.
|
|
163
|
+
|
|
164
|
+
If you want app-level navigation instead of direct URL loads, keep `Browser` for the driver/session side and use one of the `useSystemTest*` hooks in the app so the communicator has something to talk to.
|
|
165
|
+
|
|
166
|
+
`react` and `expo-router` are optional peer dependencies. Install them only in apps that import the React/Expo hook helpers; CLI/browser-daemon consumers should not need React just to use `system-testing`.
|
|
167
|
+
|
|
168
|
+
### Browser daemon CLI
|
|
169
|
+
|
|
170
|
+
If you want an external agent to drive a reusable browser process, start the browser daemon:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
npx system-testing browser my-browser
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Optional arguments:
|
|
177
|
+
|
|
178
|
+
- `--port 1991`: use a fixed WebSocket port instead of an ephemeral one
|
|
179
|
+
- `--base-url https://example.com`: set the browser base URL so relative `visit` paths work
|
|
180
|
+
- `--driver selenium|appium`: choose the driver type
|
|
181
|
+
- `--debug`: enable browser debug logging
|
|
182
|
+
|
|
183
|
+
The process stays running until you stop it. On start it prints JSON with at least the browser `name`, `pid`, and `port`.
|
|
184
|
+
|
|
185
|
+
List running browser daemons:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npx system-testing browser-list
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This prints one line per browser with the name and port. Use `--json` if you want machine-readable output.
|
|
192
|
+
|
|
193
|
+
Stop a running browser daemon:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
npx system-testing browser-stop --name my-browser
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
If only one browser daemon is running, `browser-stop` can omit `--name`.
|
|
200
|
+
|
|
201
|
+
Send commands from the CLI:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
npx system-testing browser-command --name my-browser --visit=https://example.com/path
|
|
205
|
+
npx system-testing browser-command --name my-browser --find-by-test-id saveButton
|
|
206
|
+
npx system-testing browser-command --name my-browser --find-by-test-id saveButton --timeout 15
|
|
207
|
+
npx system-testing browser-command --name my-browser --click='[data-testid="saveButton"]'
|
|
208
|
+
npx system-testing browser-command --name my-browser --get-html
|
|
209
|
+
npx system-testing browser-command --name my-browser --get-browser-logs
|
|
210
|
+
npx system-testing browser-command --name my-browser --take-screenshot
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
If only one browser daemon is running, `browser-command` can omit `--name`. Results are printed as JSON so automation tools can parse them easily.
|
|
214
|
+
|
|
215
|
+
CLI `--timeout` values are supported on navigation and selector-based commands. Bare numbers are interpreted as seconds, and explicit `ms` / `s` suffixes are also accepted.
|
|
216
|
+
|
|
217
|
+
Generic commands are also supported:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
npx system-testing browser-command \
|
|
221
|
+
--name my-browser \
|
|
222
|
+
--command=interact \
|
|
223
|
+
--selector='[data-testid="emailInput"]' \
|
|
224
|
+
--method=sendKeys \
|
|
225
|
+
--arg='user@example.com'
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The browser daemon is intended for agent-style development workflows where an AI or script needs to open the app, inspect HTML, locate elements, click controls, and read logs while validating layout or behavior changes.
|
|
229
|
+
|
|
230
|
+
### Browser daemon WebSocket protocol
|
|
231
|
+
|
|
232
|
+
The daemon also accepts WebSocket commands on its configured port. Send JSON payloads like:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{"type":"browser-command","command":"visit","url":"https://example.com/path"}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Another example:
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{"type":"browser-command","command":"findByTestID","args":{"testID":"saveButton"}}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The server responds with JSON:
|
|
245
|
+
|
|
246
|
+
```json
|
|
247
|
+
{"ok":true,"requestId":"...","type":"browser-command-result","result":{"ok":true}}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
If the command fails:
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
{"ok":false,"requestId":"...","type":"browser-command-result","error":"..."}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Supported daemon commands currently include:
|
|
257
|
+
|
|
258
|
+
- `visit`
|
|
259
|
+
- `dismissTo`
|
|
260
|
+
- `setBaseSelector`
|
|
261
|
+
- `getCurrentUrl`
|
|
262
|
+
- `getHTML`
|
|
263
|
+
- `getBrowserLogs`
|
|
264
|
+
- `takeScreenshot`
|
|
265
|
+
- `find`
|
|
266
|
+
- `findByTestID`
|
|
267
|
+
- `click`
|
|
268
|
+
- `waitForNoSelector`
|
|
269
|
+
- `expectNoElement`
|
|
270
|
+
- `interact`
|
|
271
|
+
|
|
272
|
+
### Using `useSystemTestExpo` in your Expo app
|
|
273
|
+
|
|
274
|
+
`useSystemTestExpo` wires your Expo app to the system-testing runner: it listens for WebSocket commands, initializes the browser helper, and lets tests navigate or reset state. Add it near the root layout of your Expo Router app (for example in `_layout.tsx` or a top-level provider component).
|
|
96
275
|
|
|
97
276
|
To enable system tests in native builds, set `EXPO_PUBLIC_SYSTEM_TEST=true` at build time (and optionally `EXPO_PUBLIC_SYSTEM_TEST_HOST` to reach the test runner from a device/emulator). For native Appium runs, set `SYSTEM_TEST_HOST=native` in the test environment and point Appium at your APK.
|
|
98
277
|
|
|
@@ -100,10 +279,12 @@ Minimal example:
|
|
|
100
279
|
|
|
101
280
|
```jsx
|
|
102
281
|
import {Stack} from "expo-router"
|
|
103
|
-
import
|
|
282
|
+
import useSystemTestExpo from "system-testing/build/use-system-test-expo.js"
|
|
104
283
|
|
|
105
284
|
export default function RootLayout() {
|
|
106
|
-
const {enabled, systemTestBrowserHelper} =
|
|
285
|
+
const {enabled, systemTestBrowserHelper} = useSystemTestExpo({
|
|
286
|
+
// Optional: inject your own helper instance instead of using the shared default
|
|
287
|
+
// browserHelper: mySystemTestBrowserHelper,
|
|
107
288
|
onFirstInitialize: () => {
|
|
108
289
|
// One-time setup the first time the helper initializes
|
|
109
290
|
},
|
|
@@ -129,12 +310,51 @@ export default function RootLayout() {
|
|
|
129
310
|
|
|
130
311
|
Notes:
|
|
131
312
|
- The hook auto-connects when the page is opened with `?systemTest=true` (as the runner does).
|
|
313
|
+
- Pass `browserHelper` if you want to inject a prebuilt `SystemTestBrowserHelper`; otherwise the hook creates and enables a shared default instance.
|
|
132
314
|
- `onFirstInitialize` runs only on the first `initialize` command; use it for one-time setup.
|
|
133
315
|
- `onInitialize` is registered once when the helper is ready, but it runs on every `initialize` command (each `SystemTest.run`); use it to reset globals/session.
|
|
134
316
|
- If you need scoundrel remote evaluation, wait for `systemTestBrowserHelper` and register your classes there, as shown in the commented snippet above.
|
|
135
317
|
- Add a root wrapper with `testID="systemTestingComponent"` (and optionally `data-focussed="true"`) around your app so the runner has a stable element to detect and scope selectors against.
|
|
136
318
|
- From your tests, use `await systemTest.getScoundrelClient()` to obtain the browser Scoundrel client for remote evaluation.
|
|
137
|
-
- `
|
|
319
|
+
- `useSystemTestExpo` calls `useRouter()` from `expo-router`.
|
|
320
|
+
|
|
321
|
+
### Using `useSystemTest` or `useSystemTestReactNative` without Expo Router
|
|
322
|
+
|
|
323
|
+
`useSystemTest` is the generic runtime-agnostic hook. Provide `onNavigate` and `onDismissTo` callbacks for your own navigation stack. `useSystemTestReactNative` is a convenience wrapper around the same generic API for non-Expo React Native apps.
|
|
324
|
+
|
|
325
|
+
Use these when:
|
|
326
|
+
|
|
327
|
+
- you are not using Expo Router
|
|
328
|
+
- you want to inject your own navigation behavior
|
|
329
|
+
- you want to share the same app-side helper integration across different routing setups
|
|
330
|
+
|
|
331
|
+
```js
|
|
332
|
+
import useSystemTestReactNative from "system-testing/build/use-system-test-react-native.js"
|
|
333
|
+
|
|
334
|
+
export default function App({navigation}) {
|
|
335
|
+
useSystemTestReactNative({
|
|
336
|
+
onDismissTo: ({path}) => {
|
|
337
|
+
navigation.reset({
|
|
338
|
+
index: 0,
|
|
339
|
+
routes: [{name: path}]
|
|
340
|
+
})
|
|
341
|
+
},
|
|
342
|
+
onNavigate: ({path}) => {
|
|
343
|
+
navigation.navigate(path)
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
return <Navigator />
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
The generic hook options are:
|
|
352
|
+
|
|
353
|
+
- `browserHelper`: inject an existing `SystemTestBrowserHelper` instance instead of using the shared default
|
|
354
|
+
- `onFirstInitialize`: one-time setup callback
|
|
355
|
+
- `onInitialize`: callback that runs on every `initialize` command
|
|
356
|
+
- `onNavigate`: handler for `visit(...)`
|
|
357
|
+
- `onDismissTo`: handler for `dismissTo(...)`
|
|
138
358
|
|
|
139
359
|
### Root path and `blankText`
|
|
140
360
|
|
|
@@ -208,4 +428,10 @@ This tears down the browser, servers, and sockets, then starts them again so sub
|
|
|
208
428
|
|
|
209
429
|
## Dummy Expo app
|
|
210
430
|
|
|
211
|
-
A ready-to-run Expo Router dummy app that uses `system-testing` lives in `spec/dummy`.
|
|
431
|
+
A ready-to-run Expo Router dummy app that uses `system-testing` lives in `spec/dummy`.
|
|
432
|
+
|
|
433
|
+
Useful commands from the package root:
|
|
434
|
+
|
|
435
|
+
- `npm run export:web`: build the dummy Expo app for web
|
|
436
|
+
- `SYSTEM_TEST_HOST=dist npx jasmine spec/system-test.spec.js`: run the sample system specs against the exported bundle
|
|
437
|
+
- `SYSTEM_TEST_HOST=dist npx jasmine spec/system-test-logging.spec.js`: run the browser-log capture spec
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Sends browser commands to a running browser daemon. */
|
|
2
|
+
export default class BrowserCommandClient {
|
|
3
|
+
/**
|
|
4
|
+
* @param {object} args
|
|
5
|
+
* @param {string} [args.name]
|
|
6
|
+
* @param {number} [args.port]
|
|
7
|
+
*/
|
|
8
|
+
constructor({ name, port }?: {
|
|
9
|
+
name?: string;
|
|
10
|
+
port?: number;
|
|
11
|
+
});
|
|
12
|
+
name: string;
|
|
13
|
+
port: number;
|
|
14
|
+
/**
|
|
15
|
+
* @param {Record<string, any>} payload
|
|
16
|
+
* @returns {Promise<any>}
|
|
17
|
+
*/
|
|
18
|
+
send(payload: Record<string, any>): Promise<any>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import BrowserRegistry from "./browser-registry.js";
|
|
2
|
+
import WebSocket from "ws";
|
|
3
|
+
/** Sends browser commands to a running browser daemon. */
|
|
4
|
+
export default class BrowserCommandClient {
|
|
5
|
+
/**
|
|
6
|
+
* @param {object} args
|
|
7
|
+
* @param {string} [args.name]
|
|
8
|
+
* @param {number} [args.port]
|
|
9
|
+
*/
|
|
10
|
+
constructor({ name, port } = {}) {
|
|
11
|
+
this.name = name;
|
|
12
|
+
this.port = port;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @param {Record<string, any>} payload
|
|
16
|
+
* @returns {Promise<any>}
|
|
17
|
+
*/
|
|
18
|
+
async send(payload) {
|
|
19
|
+
const resolvedPort = this.port ?? (await BrowserRegistry.resolve(this.name)).port;
|
|
20
|
+
const ws = new WebSocket(`ws://127.0.0.1:${resolvedPort}`);
|
|
21
|
+
return await new Promise((resolve, reject) => {
|
|
22
|
+
ws.on("open", () => {
|
|
23
|
+
ws.send(JSON.stringify(payload));
|
|
24
|
+
});
|
|
25
|
+
ws.on("message", (rawData) => {
|
|
26
|
+
const response = JSON.parse(rawData.toString());
|
|
27
|
+
ws.close();
|
|
28
|
+
if (response.ok) {
|
|
29
|
+
resolve(response.result);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
reject(new Error(response.error));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
ws.on("error", reject);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJvd3Nlci1jb21tYW5kLWNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9icm93c2VyLWNvbW1hbmQtY2xpZW50LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sZUFBZSxNQUFNLHVCQUF1QixDQUFBO0FBQ25ELE9BQU8sU0FBUyxNQUFNLElBQUksQ0FBQTtBQUUxQiwwREFBMEQ7QUFDMUQsTUFBTSxDQUFDLE9BQU8sT0FBTyxvQkFBb0I7SUFDdkM7Ozs7T0FJRztJQUNILFlBQVksRUFBQyxJQUFJLEVBQUUsSUFBSSxFQUFDLEdBQUcsRUFBRTtRQUMzQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQTtRQUNoQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQTtJQUNsQixDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsS0FBSyxDQUFDLElBQUksQ0FBQyxPQUFPO1FBQ2hCLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQyxNQUFNLGVBQWUsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFBO1FBQ2pGLE1BQU0sRUFBRSxHQUFHLElBQUksU0FBUyxDQUFDLGtCQUFrQixZQUFZLEVBQUUsQ0FBQyxDQUFBO1FBRTFELE9BQU8sTUFBTSxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtZQUMzQyxFQUFFLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUU7Z0JBQ2pCLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFBO1lBQ2xDLENBQUMsQ0FBQyxDQUFBO1lBRUYsRUFBRSxDQUFDLEVBQUUsQ0FBQyxTQUFTLEVBQUUsQ0FBQyxPQUFPLEVBQUUsRUFBRTtnQkFDM0IsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQTtnQkFFL0MsRUFBRSxDQUFDLEtBQUssRUFBRSxDQUFBO2dCQUVWLElBQUksUUFBUSxDQUFDLEVBQUUsRUFBRSxDQUFDO29CQUNoQixPQUFPLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFBO2dCQUMxQixDQUFDO3FCQUFNLENBQUM7b0JBQ04sTUFBTSxDQUFDLElBQUksS0FBSyxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFBO2dCQUNuQyxDQUFDO1lBQ0gsQ0FBQyxDQUFDLENBQUE7WUFFRixFQUFFLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQTtRQUN4QixDQUFDLENBQUMsQ0FBQTtJQUNKLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBCcm93c2VyUmVnaXN0cnkgZnJvbSBcIi4vYnJvd3Nlci1yZWdpc3RyeS5qc1wiXG5pbXBvcnQgV2ViU29ja2V0IGZyb20gXCJ3c1wiXG5cbi8qKiBTZW5kcyBicm93c2VyIGNvbW1hbmRzIHRvIGEgcnVubmluZyBicm93c2VyIGRhZW1vbi4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEJyb3dzZXJDb21tYW5kQ2xpZW50IHtcbiAgLyoqXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBbYXJncy5uYW1lXVxuICAgKiBAcGFyYW0ge251bWJlcn0gW2FyZ3MucG9ydF1cbiAgICovXG4gIGNvbnN0cnVjdG9yKHtuYW1lLCBwb3J0fSA9IHt9KSB7XG4gICAgdGhpcy5uYW1lID0gbmFtZVxuICAgIHRoaXMucG9ydCA9IHBvcnRcbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsIGFueT59IHBheWxvYWRcbiAgICogQHJldHVybnMge1Byb21pc2U8YW55Pn1cbiAgICovXG4gIGFzeW5jIHNlbmQocGF5bG9hZCkge1xuICAgIGNvbnN0IHJlc29sdmVkUG9ydCA9IHRoaXMucG9ydCA/PyAoYXdhaXQgQnJvd3NlclJlZ2lzdHJ5LnJlc29sdmUodGhpcy5uYW1lKSkucG9ydFxuICAgIGNvbnN0IHdzID0gbmV3IFdlYlNvY2tldChgd3M6Ly8xMjcuMC4wLjE6JHtyZXNvbHZlZFBvcnR9YClcblxuICAgIHJldHVybiBhd2FpdCBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICB3cy5vbihcIm9wZW5cIiwgKCkgPT4ge1xuICAgICAgICB3cy5zZW5kKEpTT04uc3RyaW5naWZ5KHBheWxvYWQpKVxuICAgICAgfSlcblxuICAgICAgd3Mub24oXCJtZXNzYWdlXCIsIChyYXdEYXRhKSA9PiB7XG4gICAgICAgIGNvbnN0IHJlc3BvbnNlID0gSlNPTi5wYXJzZShyYXdEYXRhLnRvU3RyaW5nKCkpXG5cbiAgICAgICAgd3MuY2xvc2UoKVxuXG4gICAgICAgIGlmIChyZXNwb25zZS5vaykge1xuICAgICAgICAgIHJlc29sdmUocmVzcG9uc2UucmVzdWx0KVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IocmVzcG9uc2UuZXJyb3IpKVxuICAgICAgICB9XG4gICAgICB9KVxuXG4gICAgICB3cy5vbihcImVycm9yXCIsIHJlamVjdClcbiAgICB9KVxuICB9XG59XG4iXX0=
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Runs browser commands across CLI and WebSocket transports. */
|
|
2
|
+
export default class BrowserCommandRunner {
|
|
3
|
+
/**
|
|
4
|
+
* @param {object} args
|
|
5
|
+
* @param {import("./browser.js").default} args.browser
|
|
6
|
+
*/
|
|
7
|
+
constructor({ browser }: {
|
|
8
|
+
browser: import("./browser.js").default;
|
|
9
|
+
});
|
|
10
|
+
browser: import("./browser.js").default;
|
|
11
|
+
/**
|
|
12
|
+
* @param {Record<string, any>} commandArgs
|
|
13
|
+
* @returns {{timeout?: number}}
|
|
14
|
+
*/
|
|
15
|
+
normalizeTimeoutArgs(commandArgs: Record<string, any>): {
|
|
16
|
+
timeout?: number;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* @param {Record<string, any>} commandArgs
|
|
20
|
+
* @returns {import("./system-test.js").FindArgs}
|
|
21
|
+
*/
|
|
22
|
+
normalizeFindArgs(commandArgs: Record<string, any>): import("./system-test.js").FindArgs;
|
|
23
|
+
/**
|
|
24
|
+
* @param {import("selenium-webdriver").WebElement} element
|
|
25
|
+
* @returns {Promise<Record<string, any>>}
|
|
26
|
+
*/
|
|
27
|
+
serializeElement(element: import("selenium-webdriver").WebElement): Promise<Record<string, any>>;
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} command
|
|
30
|
+
* @param {Record<string, any>} commandArgs
|
|
31
|
+
* @returns {Promise<any>}
|
|
32
|
+
*/
|
|
33
|
+
run(command: string, commandArgs?: Record<string, any>): Promise<any>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/** Runs browser commands across CLI and WebSocket transports. */
|
|
2
|
+
export default class BrowserCommandRunner {
|
|
3
|
+
/**
|
|
4
|
+
* @param {object} args
|
|
5
|
+
* @param {import("./browser.js").default} args.browser
|
|
6
|
+
*/
|
|
7
|
+
constructor({ browser }) {
|
|
8
|
+
this.browser = browser;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* @param {Record<string, any>} commandArgs
|
|
12
|
+
* @returns {{timeout?: number}}
|
|
13
|
+
*/
|
|
14
|
+
normalizeTimeoutArgs(commandArgs) {
|
|
15
|
+
const normalizedArgs = {};
|
|
16
|
+
if ("timeout" in commandArgs && commandArgs.timeout !== undefined) {
|
|
17
|
+
normalizedArgs.timeout = Number(commandArgs.timeout);
|
|
18
|
+
if (Number.isNaN(normalizedArgs.timeout)) {
|
|
19
|
+
throw new Error(`Invalid timeout: ${commandArgs.timeout}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return normalizedArgs;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* @param {Record<string, any>} commandArgs
|
|
26
|
+
* @returns {import("./system-test.js").FindArgs}
|
|
27
|
+
*/
|
|
28
|
+
normalizeFindArgs(commandArgs) {
|
|
29
|
+
const findArgs = /** @type {import("./system-test.js").FindArgs} */ (this.normalizeTimeoutArgs(commandArgs));
|
|
30
|
+
if ("visible" in commandArgs && commandArgs.visible !== undefined) {
|
|
31
|
+
if (commandArgs.visible === null || commandArgs.visible === "null") {
|
|
32
|
+
findArgs.visible = null;
|
|
33
|
+
}
|
|
34
|
+
else if (typeof commandArgs.visible === "boolean") {
|
|
35
|
+
findArgs.visible = commandArgs.visible;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
findArgs.visible = commandArgs.visible === "true";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if ("useBaseSelector" in commandArgs && commandArgs.useBaseSelector !== undefined) {
|
|
42
|
+
if (typeof commandArgs.useBaseSelector === "boolean") {
|
|
43
|
+
findArgs.useBaseSelector = commandArgs.useBaseSelector;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
findArgs.useBaseSelector = commandArgs.useBaseSelector === "true";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return findArgs;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* @param {import("selenium-webdriver").WebElement} element
|
|
53
|
+
* @returns {Promise<Record<string, any>>}
|
|
54
|
+
*/
|
|
55
|
+
async serializeElement(element) {
|
|
56
|
+
const text = await element.getText();
|
|
57
|
+
const tagName = await element.getTagName();
|
|
58
|
+
const displayed = await element.isDisplayed();
|
|
59
|
+
return { displayed, tagName, text };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* @param {string} command
|
|
63
|
+
* @param {Record<string, any>} commandArgs
|
|
64
|
+
* @returns {Promise<any>}
|
|
65
|
+
*/
|
|
66
|
+
async run(command, commandArgs = {}) {
|
|
67
|
+
if (command === "visit") {
|
|
68
|
+
const path = commandArgs.path ?? commandArgs.url;
|
|
69
|
+
if (!path) {
|
|
70
|
+
throw new Error("visit requires path or url");
|
|
71
|
+
}
|
|
72
|
+
await this.browser.visit(path, this.normalizeTimeoutArgs(commandArgs));
|
|
73
|
+
return { ok: true };
|
|
74
|
+
}
|
|
75
|
+
if (command === "dismissTo") {
|
|
76
|
+
const path = commandArgs.path ?? commandArgs.url;
|
|
77
|
+
if (!path) {
|
|
78
|
+
throw new Error("dismissTo requires path or url");
|
|
79
|
+
}
|
|
80
|
+
await this.browser.dismissTo(path, this.normalizeTimeoutArgs(commandArgs));
|
|
81
|
+
return { ok: true };
|
|
82
|
+
}
|
|
83
|
+
if (command === "setBaseSelector") {
|
|
84
|
+
if (!commandArgs.selector) {
|
|
85
|
+
throw new Error("setBaseSelector requires selector");
|
|
86
|
+
}
|
|
87
|
+
this.browser.setBaseSelector(commandArgs.selector);
|
|
88
|
+
return { ok: true };
|
|
89
|
+
}
|
|
90
|
+
if (command === "getCurrentUrl") {
|
|
91
|
+
return { currentUrl: await this.browser.getCurrentUrl() };
|
|
92
|
+
}
|
|
93
|
+
if (command === "getHTML") {
|
|
94
|
+
return { html: await this.browser.getHTML() };
|
|
95
|
+
}
|
|
96
|
+
if (command === "getBrowserLogs") {
|
|
97
|
+
return { logs: await this.browser.getBrowserLogs() };
|
|
98
|
+
}
|
|
99
|
+
if (command === "takeScreenshot") {
|
|
100
|
+
return await this.browser.takeScreenshot();
|
|
101
|
+
}
|
|
102
|
+
if (command === "find") {
|
|
103
|
+
if (!commandArgs.selector) {
|
|
104
|
+
throw new Error("find requires selector");
|
|
105
|
+
}
|
|
106
|
+
const element = await this.browser.find(commandArgs.selector, this.normalizeFindArgs(commandArgs));
|
|
107
|
+
return { element: await this.serializeElement(element) };
|
|
108
|
+
}
|
|
109
|
+
if (command === "findByTestID") {
|
|
110
|
+
const testID = commandArgs.testID ?? commandArgs.testId;
|
|
111
|
+
if (!testID) {
|
|
112
|
+
throw new Error("findByTestID requires testID");
|
|
113
|
+
}
|
|
114
|
+
const element = await this.browser.findByTestID(testID, this.normalizeFindArgs(commandArgs));
|
|
115
|
+
return { element: await this.serializeElement(element) };
|
|
116
|
+
}
|
|
117
|
+
if (command === "click") {
|
|
118
|
+
const selector = commandArgs.selector;
|
|
119
|
+
if (!selector) {
|
|
120
|
+
throw new Error("click requires selector");
|
|
121
|
+
}
|
|
122
|
+
await this.browser.click(selector, this.normalizeFindArgs(commandArgs));
|
|
123
|
+
return { ok: true };
|
|
124
|
+
}
|
|
125
|
+
if (command === "waitForNoSelector") {
|
|
126
|
+
if (!commandArgs.selector) {
|
|
127
|
+
throw new Error("waitForNoSelector requires selector");
|
|
128
|
+
}
|
|
129
|
+
await this.browser.waitForNoSelector(commandArgs.selector, this.normalizeFindArgs(commandArgs));
|
|
130
|
+
return { ok: true };
|
|
131
|
+
}
|
|
132
|
+
if (command === "expectNoElement") {
|
|
133
|
+
if (!commandArgs.selector) {
|
|
134
|
+
throw new Error("expectNoElement requires selector");
|
|
135
|
+
}
|
|
136
|
+
await this.browser.expectNoElement(commandArgs.selector, this.normalizeFindArgs(commandArgs));
|
|
137
|
+
return { ok: true };
|
|
138
|
+
}
|
|
139
|
+
if (command === "interact") {
|
|
140
|
+
const selector = commandArgs.selector;
|
|
141
|
+
const methodName = commandArgs.methodName ?? commandArgs.method;
|
|
142
|
+
const methodArgs = Array.isArray(commandArgs.args) ? commandArgs.args : [];
|
|
143
|
+
if (!selector) {
|
|
144
|
+
throw new Error("interact requires selector");
|
|
145
|
+
}
|
|
146
|
+
if (!methodName) {
|
|
147
|
+
throw new Error("interact requires methodName");
|
|
148
|
+
}
|
|
149
|
+
const result = await this.browser.interact({ selector, ...this.normalizeFindArgs(commandArgs) }, methodName, ...methodArgs);
|
|
150
|
+
return { result };
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`Unknown browser command: ${command}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJvd3Nlci1jb21tYW5kLXJ1bm5lci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9icm93c2VyLWNvbW1hbmQtcnVubmVyLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGlFQUFpRTtBQUNqRSxNQUFNLENBQUMsT0FBTyxPQUFPLG9CQUFvQjtJQUN2Qzs7O09BR0c7SUFDSCxZQUFZLEVBQUMsT0FBTyxFQUFDO1FBQ25CLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFBO0lBQ3hCLENBQUM7SUFFRDs7O09BR0c7SUFDSCxvQkFBb0IsQ0FBQyxXQUFXO1FBQzlCLE1BQU0sY0FBYyxHQUFHLEVBQUUsQ0FBQTtRQUV6QixJQUFJLFNBQVMsSUFBSSxXQUFXLElBQUksV0FBVyxDQUFDLE9BQU8sS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUNsRSxjQUFjLENBQUMsT0FBTyxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsT0FBTyxDQUFDLENBQUE7WUFFcEQsSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLGNBQWMsQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO2dCQUN6QyxNQUFNLElBQUksS0FBSyxDQUFDLG9CQUFvQixXQUFXLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQTtZQUM1RCxDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sY0FBYyxDQUFBO0lBQ3ZCLENBQUM7SUFFRDs7O09BR0c7SUFDSCxpQkFBaUIsQ0FBQyxXQUFXO1FBQzNCLE1BQU0sUUFBUSxHQUFHLGtEQUFrRCxDQUFDLENBQUMsSUFBSSxDQUFDLG9CQUFvQixDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUE7UUFFNUcsSUFBSSxTQUFTLElBQUksV0FBVyxJQUFJLFdBQVcsQ0FBQyxPQUFPLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDbEUsSUFBSSxXQUFXLENBQUMsT0FBTyxLQUFLLElBQUksSUFBSSxXQUFXLENBQUMsT0FBTyxLQUFLLE1BQU0sRUFBRSxDQUFDO2dCQUNuRSxRQUFRLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQTtZQUN6QixDQUFDO2lCQUFNLElBQUksT0FBTyxXQUFXLENBQUMsT0FBTyxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUNwRCxRQUFRLENBQUMsT0FBTyxHQUFHLFdBQVcsQ0FBQyxPQUFPLENBQUE7WUFDeEMsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLFFBQVEsQ0FBQyxPQUFPLEdBQUcsV0FBVyxDQUFDLE9BQU8sS0FBSyxNQUFNLENBQUE7WUFDbkQsQ0FBQztRQUNILENBQUM7UUFFRCxJQUFJLGlCQUFpQixJQUFJLFdBQVcsSUFBSSxXQUFXLENBQUMsZUFBZSxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQ2xGLElBQUksT0FBTyxXQUFXLENBQUMsZUFBZSxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUNyRCxRQUFRLENBQUMsZUFBZSxHQUFHLFdBQVcsQ0FBQyxlQUFlLENBQUE7WUFDeEQsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLFFBQVEsQ0FBQyxlQUFlLEdBQUcsV0FBVyxDQUFDLGVBQWUsS0FBSyxNQUFNLENBQUE7WUFDbkUsQ0FBQztRQUNILENBQUM7UUFFRCxPQUFPLFFBQVEsQ0FBQTtJQUNqQixDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsS0FBSyxDQUFDLGdCQUFnQixDQUFDLE9BQU87UUFDNUIsTUFBTSxJQUFJLEdBQUcsTUFBTSxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUE7UUFDcEMsTUFBTSxPQUFPLEdBQUcsTUFBTSxPQUFPLENBQUMsVUFBVSxFQUFFLENBQUE7UUFDMUMsTUFBTSxTQUFTLEdBQUcsTUFBTSxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUE7UUFFN0MsT0FBTyxFQUFDLFNBQVMsRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFDLENBQUE7SUFDbkMsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRSxXQUFXLEdBQUcsRUFBRTtRQUNqQyxJQUFJLE9BQU8sS0FBSyxPQUFPLEVBQUUsQ0FBQztZQUN4QixNQUFNLElBQUksR0FBRyxXQUFXLENBQUMsSUFBSSxJQUFJLFdBQVcsQ0FBQyxHQUFHLENBQUE7WUFFaEQsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO2dCQUNWLE1BQU0sSUFBSSxLQUFLLENBQUMsNEJBQTRCLENBQUMsQ0FBQTtZQUMvQyxDQUFDO1lBRUQsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLG9CQUFvQixDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUE7WUFDdEUsT0FBTyxFQUFDLEVBQUUsRUFBRSxJQUFJLEVBQUMsQ0FBQTtRQUNuQixDQUFDO1FBRUQsSUFBSSxPQUFPLEtBQUssV0FBVyxFQUFFLENBQUM7WUFDNUIsTUFBTSxJQUFJLEdBQUcsV0FBVyxDQUFDLElBQUksSUFBSSxXQUFXLENBQUMsR0FBRyxDQUFBO1lBRWhELElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztnQkFDVixNQUFNLElBQUksS0FBSyxDQUFDLGdDQUFnQyxDQUFDLENBQUE7WUFDbkQsQ0FBQztZQUVELE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFBO1lBQzFFLE9BQU8sRUFBQyxFQUFFLEVBQUUsSUFBSSxFQUFDLENBQUE7UUFDbkIsQ0FBQztRQUVELElBQUksT0FBTyxLQUFLLGlCQUFpQixFQUFFLENBQUM7WUFDbEMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxRQUFRLEVBQUUsQ0FBQztnQkFDMUIsTUFBTSxJQUFJLEtBQUssQ0FBQyxtQ0FBbUMsQ0FBQyxDQUFBO1lBQ3RELENBQUM7WUFFRCxJQUFJLENBQUMsT0FBTyxDQUFDLGVBQWUsQ0FBQyxXQUFXLENBQUMsUUFBUSxDQUFDLENBQUE7WUFDbEQsT0FBTyxFQUFDLEVBQUUsRUFBRSxJQUFJLEVBQUMsQ0FBQTtRQUNuQixDQUFDO1FBRUQsSUFBSSxPQUFPLEtBQUssZUFBZSxFQUFFLENBQUM7WUFDaEMsT0FBTyxFQUFDLFVBQVUsRUFBRSxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsYUFBYSxFQUFFLEVBQUMsQ0FBQTtRQUN6RCxDQUFDO1FBRUQsSUFBSSxPQUFPLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDMUIsT0FBTyxFQUFDLElBQUksRUFBRSxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLEVBQUMsQ0FBQTtRQUM3QyxDQUFDO1FBRUQsSUFBSSxPQUFPLEtBQUssZ0JBQWdCLEVBQUUsQ0FBQztZQUNqQyxPQUFPLEVBQUMsSUFBSSxFQUFFLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxjQUFjLEVBQUUsRUFBQyxDQUFBO1FBQ3BELENBQUM7UUFFRCxJQUFJLE9BQU8sS0FBSyxnQkFBZ0IsRUFBRSxDQUFDO1lBQ2pDLE9BQU8sTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLGNBQWMsRUFBRSxDQUFBO1FBQzVDLENBQUM7UUFFRCxJQUFJLE9BQU8sS0FBSyxNQUFNLEVBQUUsQ0FBQztZQUN2QixJQUFJLENBQUMsV0FBVyxDQUFDLFFBQVEsRUFBRSxDQUFDO2dCQUMxQixNQUFNLElBQUksS0FBSyxDQUFDLHdCQUF3QixDQUFDLENBQUE7WUFDM0MsQ0FBQztZQUVELE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsaUJBQWlCLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQTtZQUVsRyxPQUFPLEVBQUMsT0FBTyxFQUFFLE1BQU0sSUFBSSxDQUFDLGdCQUFnQixDQUFDLE9BQU8sQ0FBQyxFQUFDLENBQUE7UUFDeEQsQ0FBQztRQUVELElBQUksT0FBTyxLQUFLLGNBQWMsRUFBRSxDQUFDO1lBQy9CLE1BQU0sTUFBTSxHQUFHLFdBQVcsQ0FBQyxNQUFNLElBQUksV0FBVyxDQUFDLE1BQU0sQ0FBQTtZQUV2RCxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQ1osTUFBTSxJQUFJLEtBQUssQ0FBQyw4QkFBOEIsQ0FBQyxDQUFBO1lBQ2pELENBQUM7WUFFRCxNQUFNLE9BQU8sR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsaUJBQWlCLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQTtZQUU1RixPQUFPLEVBQUMsT0FBTyxFQUFFLE1BQU0sSUFBSSxDQUFDLGdCQUFnQixDQUFDLE9BQU8sQ0FBQyxFQUFDLENBQUE7UUFDeEQsQ0FBQztRQUVELElBQUksT0FBTyxLQUFLLE9BQU8sRUFBRSxDQUFDO1lBQ3hCLE1BQU0sUUFBUSxHQUFHLFdBQVcsQ0FBQyxRQUFRLENBQUE7WUFFckMsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO2dCQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMseUJBQXlCLENBQUMsQ0FBQTtZQUM1QyxDQUFDO1lBRUQsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLGlCQUFpQixDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUE7WUFDdkUsT0FBTyxFQUFDLEVBQUUsRUFBRSxJQUFJLEVBQUMsQ0FBQTtRQUNuQixDQUFDO1FBRUQsSUFBSSxPQUFPLEtBQUssbUJBQW1CLEVBQUUsQ0FBQztZQUNwQyxJQUFJLENBQUMsV0FBVyxDQUFDLFFBQVEsRUFBRSxDQUFDO2dCQUMxQixNQUFNLElBQUksS0FBSyxDQUFDLHFDQUFxQyxDQUFDLENBQUE7WUFDeEQsQ0FBQztZQUVELE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQyxXQUFXLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFBO1lBQy9GLE9BQU8sRUFBQyxFQUFFLEVBQUUsSUFBSSxFQUFDLENBQUE7UUFDbkIsQ0FBQztRQUVELElBQUksT0FBTyxLQUFLLGlCQUFpQixFQUFFLENBQUM7WUFDbEMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxRQUFRLEVBQUUsQ0FBQztnQkFDMUIsTUFBTSxJQUFJLEtBQUssQ0FBQyxtQ0FBbUMsQ0FBQyxDQUFBO1lBQ3RELENBQUM7WUFFRCxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsZUFBZSxDQUFDLFdBQVcsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLGlCQUFpQixDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUE7WUFDN0YsT0FBTyxFQUFDLEVBQUUsRUFBRSxJQUFJLEVBQUMsQ0FBQTtRQUNuQixDQUFDO1FBRUQsSUFBSSxPQUFPLEtBQUssVUFBVSxFQUFFLENBQUM7WUFDM0IsTUFBTSxRQUFRLEdBQUcsV0FBVyxDQUFDLFFBQVEsQ0FBQTtZQUNyQyxNQUFNLFVBQVUsR0FBRyxXQUFXLENBQUMsVUFBVSxJQUFJLFdBQVcsQ0FBQyxNQUFNLENBQUE7WUFDL0QsTUFBTSxVQUFVLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtZQUUxRSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7Z0JBQ2QsTUFBTSxJQUFJLEtBQUssQ0FBQyw0QkFBNEIsQ0FBQyxDQUFBO1lBQy9DLENBQUM7WUFFRCxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7Z0JBQ2hCLE1BQU0sSUFBSSxLQUFLLENBQUMsOEJBQThCLENBQUMsQ0FBQTtZQUNqRCxDQUFDO1lBRUQsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxFQUFDLFFBQVEsRUFBRSxHQUFHLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxXQUFXLENBQUMsRUFBQyxFQUFFLFVBQVUsRUFBRSxHQUFHLFVBQVUsQ0FBQyxDQUFBO1lBRXpILE9BQU8sRUFBQyxNQUFNLEVBQUMsQ0FBQTtRQUNqQixDQUFDO1FBRUQsTUFBTSxJQUFJLEtBQUssQ0FBQyw0QkFBNEIsT0FBTyxFQUFFLENBQUMsQ0FBQTtJQUN4RCxDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvKiogUnVucyBicm93c2VyIGNvbW1hbmRzIGFjcm9zcyBDTEkgYW5kIFdlYlNvY2tldCB0cmFuc3BvcnRzLiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgQnJvd3NlckNvbW1hbmRSdW5uZXIge1xuICAvKipcbiAgICogQHBhcmFtIHtvYmplY3R9IGFyZ3NcbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuL2Jyb3dzZXIuanNcIikuZGVmYXVsdH0gYXJncy5icm93c2VyXG4gICAqL1xuICBjb25zdHJ1Y3Rvcih7YnJvd3Nlcn0pIHtcbiAgICB0aGlzLmJyb3dzZXIgPSBicm93c2VyXG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCBhbnk+fSBjb21tYW5kQXJnc1xuICAgKiBAcmV0dXJucyB7e3RpbWVvdXQ/OiBudW1iZXJ9fVxuICAgKi9cbiAgbm9ybWFsaXplVGltZW91dEFyZ3MoY29tbWFuZEFyZ3MpIHtcbiAgICBjb25zdCBub3JtYWxpemVkQXJncyA9IHt9XG5cbiAgICBpZiAoXCJ0aW1lb3V0XCIgaW4gY29tbWFuZEFyZ3MgJiYgY29tbWFuZEFyZ3MudGltZW91dCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBub3JtYWxpemVkQXJncy50aW1lb3V0ID0gTnVtYmVyKGNvbW1hbmRBcmdzLnRpbWVvdXQpXG5cbiAgICAgIGlmIChOdW1iZXIuaXNOYU4obm9ybWFsaXplZEFyZ3MudGltZW91dCkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIHRpbWVvdXQ6ICR7Y29tbWFuZEFyZ3MudGltZW91dH1gKVxuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBub3JtYWxpemVkQXJnc1xuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7UmVjb3JkPHN0cmluZywgYW55Pn0gY29tbWFuZEFyZ3NcbiAgICogQHJldHVybnMge2ltcG9ydChcIi4vc3lzdGVtLXRlc3QuanNcIikuRmluZEFyZ3N9XG4gICAqL1xuICBub3JtYWxpemVGaW5kQXJncyhjb21tYW5kQXJncykge1xuICAgIGNvbnN0IGZpbmRBcmdzID0gLyoqIEB0eXBlIHtpbXBvcnQoXCIuL3N5c3RlbS10ZXN0LmpzXCIpLkZpbmRBcmdzfSAqLyAodGhpcy5ub3JtYWxpemVUaW1lb3V0QXJncyhjb21tYW5kQXJncykpXG5cbiAgICBpZiAoXCJ2aXNpYmxlXCIgaW4gY29tbWFuZEFyZ3MgJiYgY29tbWFuZEFyZ3MudmlzaWJsZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBpZiAoY29tbWFuZEFyZ3MudmlzaWJsZSA9PT0gbnVsbCB8fCBjb21tYW5kQXJncy52aXNpYmxlID09PSBcIm51bGxcIikge1xuICAgICAgICBmaW5kQXJncy52aXNpYmxlID0gbnVsbFxuICAgICAgfSBlbHNlIGlmICh0eXBlb2YgY29tbWFuZEFyZ3MudmlzaWJsZSA9PT0gXCJib29sZWFuXCIpIHtcbiAgICAgICAgZmluZEFyZ3MudmlzaWJsZSA9IGNvbW1hbmRBcmdzLnZpc2libGVcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGZpbmRBcmdzLnZpc2libGUgPSBjb21tYW5kQXJncy52aXNpYmxlID09PSBcInRydWVcIlxuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChcInVzZUJhc2VTZWxlY3RvclwiIGluIGNvbW1hbmRBcmdzICYmIGNvbW1hbmRBcmdzLnVzZUJhc2VTZWxlY3RvciAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBpZiAodHlwZW9mIGNvbW1hbmRBcmdzLnVzZUJhc2VTZWxlY3RvciA9PT0gXCJib29sZWFuXCIpIHtcbiAgICAgICAgZmluZEFyZ3MudXNlQmFzZVNlbGVjdG9yID0gY29tbWFuZEFyZ3MudXNlQmFzZVNlbGVjdG9yXG4gICAgICB9IGVsc2Uge1xuICAgICAgICBmaW5kQXJncy51c2VCYXNlU2VsZWN0b3IgPSBjb21tYW5kQXJncy51c2VCYXNlU2VsZWN0b3IgPT09IFwidHJ1ZVwiXG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIGZpbmRBcmdzXG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHtpbXBvcnQoXCJzZWxlbml1bS13ZWJkcml2ZXJcIikuV2ViRWxlbWVudH0gZWxlbWVudFxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCBhbnk+Pn1cbiAgICovXG4gIGFzeW5jIHNlcmlhbGl6ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgIGNvbnN0IHRleHQgPSBhd2FpdCBlbGVtZW50LmdldFRleHQoKVxuICAgIGNvbnN0IHRhZ05hbWUgPSBhd2FpdCBlbGVtZW50LmdldFRhZ05hbWUoKVxuICAgIGNvbnN0IGRpc3BsYXllZCA9IGF3YWl0IGVsZW1lbnQuaXNEaXNwbGF5ZWQoKVxuXG4gICAgcmV0dXJuIHtkaXNwbGF5ZWQsIHRhZ05hbWUsIHRleHR9XG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHtzdHJpbmd9IGNvbW1hbmRcbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCBhbnk+fSBjb21tYW5kQXJnc1xuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxhbnk+fVxuICAgKi9cbiAgYXN5bmMgcnVuKGNvbW1hbmQsIGNvbW1hbmRBcmdzID0ge30pIHtcbiAgICBpZiAoY29tbWFuZCA9PT0gXCJ2aXNpdFwiKSB7XG4gICAgICBjb25zdCBwYXRoID0gY29tbWFuZEFyZ3MucGF0aCA/PyBjb21tYW5kQXJncy51cmxcblxuICAgICAgaWYgKCFwYXRoKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcInZpc2l0IHJlcXVpcmVzIHBhdGggb3IgdXJsXCIpXG4gICAgICB9XG5cbiAgICAgIGF3YWl0IHRoaXMuYnJvd3Nlci52aXNpdChwYXRoLCB0aGlzLm5vcm1hbGl6ZVRpbWVvdXRBcmdzKGNvbW1hbmRBcmdzKSlcbiAgICAgIHJldHVybiB7b2s6IHRydWV9XG4gICAgfVxuXG4gICAgaWYgKGNvbW1hbmQgPT09IFwiZGlzbWlzc1RvXCIpIHtcbiAgICAgIGNvbnN0IHBhdGggPSBjb21tYW5kQXJncy5wYXRoID8/IGNvbW1hbmRBcmdzLnVybFxuXG4gICAgICBpZiAoIXBhdGgpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiZGlzbWlzc1RvIHJlcXVpcmVzIHBhdGggb3IgdXJsXCIpXG4gICAgICB9XG5cbiAgICAgIGF3YWl0IHRoaXMuYnJvd3Nlci5kaXNtaXNzVG8ocGF0aCwgdGhpcy5ub3JtYWxpemVUaW1lb3V0QXJncyhjb21tYW5kQXJncykpXG4gICAgICByZXR1cm4ge29rOiB0cnVlfVxuICAgIH1cblxuICAgIGlmIChjb21tYW5kID09PSBcInNldEJhc2VTZWxlY3RvclwiKSB7XG4gICAgICBpZiAoIWNvbW1hbmRBcmdzLnNlbGVjdG9yKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcInNldEJhc2VTZWxlY3RvciByZXF1aXJlcyBzZWxlY3RvclwiKVxuICAgICAgfVxuXG4gICAgICB0aGlzLmJyb3dzZXIuc2V0QmFzZVNlbGVjdG9yKGNvbW1hbmRBcmdzLnNlbGVjdG9yKVxuICAgICAgcmV0dXJuIHtvazogdHJ1ZX1cbiAgICB9XG5cbiAgICBpZiAoY29tbWFuZCA9PT0gXCJnZXRDdXJyZW50VXJsXCIpIHtcbiAgICAgIHJldHVybiB7Y3VycmVudFVybDogYXdhaXQgdGhpcy5icm93c2VyLmdldEN1cnJlbnRVcmwoKX1cbiAgICB9XG5cbiAgICBpZiAoY29tbWFuZCA9PT0gXCJnZXRIVE1MXCIpIHtcbiAgICAgIHJldHVybiB7aHRtbDogYXdhaXQgdGhpcy5icm93c2VyLmdldEhUTUwoKX1cbiAgICB9XG5cbiAgICBpZiAoY29tbWFuZCA9PT0gXCJnZXRCcm93c2VyTG9nc1wiKSB7XG4gICAgICByZXR1cm4ge2xvZ3M6IGF3YWl0IHRoaXMuYnJvd3Nlci5nZXRCcm93c2VyTG9ncygpfVxuICAgIH1cblxuICAgIGlmIChjb21tYW5kID09PSBcInRha2VTY3JlZW5zaG90XCIpIHtcbiAgICAgIHJldHVybiBhd2FpdCB0aGlzLmJyb3dzZXIudGFrZVNjcmVlbnNob3QoKVxuICAgIH1cblxuICAgIGlmIChjb21tYW5kID09PSBcImZpbmRcIikge1xuICAgICAgaWYgKCFjb21tYW5kQXJncy5zZWxlY3Rvcikge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJmaW5kIHJlcXVpcmVzIHNlbGVjdG9yXCIpXG4gICAgICB9XG5cbiAgICAgIGNvbnN0IGVsZW1lbnQgPSBhd2FpdCB0aGlzLmJyb3dzZXIuZmluZChjb21tYW5kQXJncy5zZWxlY3RvciwgdGhpcy5ub3JtYWxpemVGaW5kQXJncyhjb21tYW5kQXJncykpXG5cbiAgICAgIHJldHVybiB7ZWxlbWVudDogYXdhaXQgdGhpcy5zZXJpYWxpemVFbGVtZW50KGVsZW1lbnQpfVxuICAgIH1cblxuICAgIGlmIChjb21tYW5kID09PSBcImZpbmRCeVRlc3RJRFwiKSB7XG4gICAgICBjb25zdCB0ZXN0SUQgPSBjb21tYW5kQXJncy50ZXN0SUQgPz8gY29tbWFuZEFyZ3MudGVzdElkXG5cbiAgICAgIGlmICghdGVzdElEKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcImZpbmRCeVRlc3RJRCByZXF1aXJlcyB0ZXN0SURcIilcbiAgICAgIH1cblxuICAgICAgY29uc3QgZWxlbWVudCA9IGF3YWl0IHRoaXMuYnJvd3Nlci5maW5kQnlUZXN0SUQodGVzdElELCB0aGlzLm5vcm1hbGl6ZUZpbmRBcmdzKGNvbW1hbmRBcmdzKSlcblxuICAgICAgcmV0dXJuIHtlbGVtZW50OiBhd2FpdCB0aGlzLnNlcmlhbGl6ZUVsZW1lbnQoZWxlbWVudCl9XG4gICAgfVxuXG4gICAgaWYgKGNvbW1hbmQgPT09IFwiY2xpY2tcIikge1xuICAgICAgY29uc3Qgc2VsZWN0b3IgPSBjb21tYW5kQXJncy5zZWxlY3RvclxuXG4gICAgICBpZiAoIXNlbGVjdG9yKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcImNsaWNrIHJlcXVpcmVzIHNlbGVjdG9yXCIpXG4gICAgICB9XG5cbiAgICAgIGF3YWl0IHRoaXMuYnJvd3Nlci5jbGljayhzZWxlY3RvciwgdGhpcy5ub3JtYWxpemVGaW5kQXJncyhjb21tYW5kQXJncykpXG4gICAgICByZXR1cm4ge29rOiB0cnVlfVxuICAgIH1cblxuICAgIGlmIChjb21tYW5kID09PSBcIndhaXRGb3JOb1NlbGVjdG9yXCIpIHtcbiAgICAgIGlmICghY29tbWFuZEFyZ3Muc2VsZWN0b3IpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwid2FpdEZvck5vU2VsZWN0b3IgcmVxdWlyZXMgc2VsZWN0b3JcIilcbiAgICAgIH1cblxuICAgICAgYXdhaXQgdGhpcy5icm93c2VyLndhaXRGb3JOb1NlbGVjdG9yKGNvbW1hbmRBcmdzLnNlbGVjdG9yLCB0aGlzLm5vcm1hbGl6ZUZpbmRBcmdzKGNvbW1hbmRBcmdzKSlcbiAgICAgIHJldHVybiB7b2s6IHRydWV9XG4gICAgfVxuXG4gICAgaWYgKGNvbW1hbmQgPT09IFwiZXhwZWN0Tm9FbGVtZW50XCIpIHtcbiAgICAgIGlmICghY29tbWFuZEFyZ3Muc2VsZWN0b3IpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiZXhwZWN0Tm9FbGVtZW50IHJlcXVpcmVzIHNlbGVjdG9yXCIpXG4gICAgICB9XG5cbiAgICAgIGF3YWl0IHRoaXMuYnJvd3Nlci5leHBlY3ROb0VsZW1lbnQoY29tbWFuZEFyZ3Muc2VsZWN0b3IsIHRoaXMubm9ybWFsaXplRmluZEFyZ3MoY29tbWFuZEFyZ3MpKVxuICAgICAgcmV0dXJuIHtvazogdHJ1ZX1cbiAgICB9XG5cbiAgICBpZiAoY29tbWFuZCA9PT0gXCJpbnRlcmFjdFwiKSB7XG4gICAgICBjb25zdCBzZWxlY3RvciA9IGNvbW1hbmRBcmdzLnNlbGVjdG9yXG4gICAgICBjb25zdCBtZXRob2ROYW1lID0gY29tbWFuZEFyZ3MubWV0aG9kTmFtZSA/PyBjb21tYW5kQXJncy5tZXRob2RcbiAgICAgIGNvbnN0IG1ldGhvZEFyZ3MgPSBBcnJheS5pc0FycmF5KGNvbW1hbmRBcmdzLmFyZ3MpID8gY29tbWFuZEFyZ3MuYXJncyA6IFtdXG5cbiAgICAgIGlmICghc2VsZWN0b3IpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiaW50ZXJhY3QgcmVxdWlyZXMgc2VsZWN0b3JcIilcbiAgICAgIH1cblxuICAgICAgaWYgKCFtZXRob2ROYW1lKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcImludGVyYWN0IHJlcXVpcmVzIG1ldGhvZE5hbWVcIilcbiAgICAgIH1cblxuICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdGhpcy5icm93c2VyLmludGVyYWN0KHtzZWxlY3RvciwgLi4udGhpcy5ub3JtYWxpemVGaW5kQXJncyhjb21tYW5kQXJncyl9LCBtZXRob2ROYW1lLCAuLi5tZXRob2RBcmdzKVxuXG4gICAgICByZXR1cm4ge3Jlc3VsdH1cbiAgICB9XG5cbiAgICB0aHJvdyBuZXcgRXJyb3IoYFVua25vd24gYnJvd3NlciBjb21tYW5kOiAke2NvbW1hbmR9YClcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const browserDaemonStopTimeoutMs = 10000;
|
|
2
|
+
export const browserDaemonVerifyTimeoutMs = 1000;
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJvd3Nlci1kYWVtb24tY29uc3RhbnRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2Jyb3dzZXItZGFlbW9uLWNvbnN0YW50cy5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxNQUFNLENBQUMsTUFBTSwwQkFBMEIsR0FBRyxLQUFLLENBQUE7QUFDL0MsTUFBTSxDQUFDLE1BQU0sNEJBQTRCLEdBQUcsSUFBSSxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGNvbnN0IGJyb3dzZXJEYWVtb25TdG9wVGltZW91dE1zID0gMTAwMDBcbmV4cG9ydCBjb25zdCBicm93c2VyRGFlbW9uVmVyaWZ5VGltZW91dE1zID0gMTAwMFxuIl19
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Long-running browser daemon exposing browser commands over WebSocket. */
|
|
2
|
+
export default class BrowserProcess {
|
|
3
|
+
/**
|
|
4
|
+
* @param {object} args
|
|
5
|
+
* @param {string} args.name
|
|
6
|
+
* @param {Browser} [args.browser]
|
|
7
|
+
* @param {Record<string, any>} [args.browserArgs]
|
|
8
|
+
* @param {string} [args.baseUrl]
|
|
9
|
+
* @param {boolean} [args.debug]
|
|
10
|
+
* @param {number} [args.port]
|
|
11
|
+
*/
|
|
12
|
+
constructor({ name, browser, browserArgs, baseUrl, debug, port }: {
|
|
13
|
+
name: string;
|
|
14
|
+
browser?: Browser;
|
|
15
|
+
browserArgs?: Record<string, any>;
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
debug?: boolean;
|
|
18
|
+
port?: number;
|
|
19
|
+
});
|
|
20
|
+
name: string;
|
|
21
|
+
browser: Browser;
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
debug: boolean;
|
|
24
|
+
requestRunner: BrowserCommandRunner;
|
|
25
|
+
requestCount: number;
|
|
26
|
+
port: number;
|
|
27
|
+
/** @returns {Promise<void>} */
|
|
28
|
+
start(): Promise<void>;
|
|
29
|
+
wss: import("ws").Server<typeof import("ws").default, typeof import("node:http").IncomingMessage>;
|
|
30
|
+
/** @returns {Promise<void>} */
|
|
31
|
+
stop(): Promise<void>;
|
|
32
|
+
stopped: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* @param {import("ws").WebSocket} ws
|
|
35
|
+
* @returns {void}
|
|
36
|
+
*/
|
|
37
|
+
onConnection: (ws: import("ws").WebSocket) => void;
|
|
38
|
+
/**
|
|
39
|
+
* @param {Record<string, any>} payload
|
|
40
|
+
* @returns {Promise<any>}
|
|
41
|
+
*/
|
|
42
|
+
handlePayload(payload: Record<string, any>): Promise<any>;
|
|
43
|
+
}
|
|
44
|
+
import Browser from "./browser.js";
|
|
45
|
+
import BrowserCommandRunner from "./browser-command-runner.js";
|