system-testing 1.0.35 → 1.0.37

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,696 @@
1
+ // @ts-check
2
+ import { Builder, By } from "selenium-webdriver";
3
+ import chrome from "selenium-webdriver/chrome.js";
4
+ import { digg } from "diggerize";
5
+ import fs from "node:fs/promises";
6
+ import logging from "selenium-webdriver/lib/logging.js";
7
+ import moment from "moment";
8
+ import { prettify } from "htmlfy";
9
+ import Server from "scoundrel-remote-eval/build/server/index.js";
10
+ import ServerWebSocket from "scoundrel-remote-eval/build/server/connections/web-socket/index.js";
11
+ import SystemTestCommunicator from "./system-test-communicator.js";
12
+ import SystemTestHttpServer from "./system-test-http-server.js";
13
+ import { wait, waitFor } from "awaitery";
14
+ import { WebSocketServer } from "ws";
15
+ class ElementNotFoundError extends Error {
16
+ }
17
+ class SystemTest {
18
+ /**
19
+ * Gets the current system test instance
20
+ * @param {object} [args]
21
+ * @param {string} [args.host]
22
+ * @param {number} [args.port]
23
+ * @returns {SystemTest}
24
+ */
25
+ static current(args) {
26
+ if (!globalThis.systemTest) {
27
+ globalThis.systemTest = new SystemTest(args);
28
+ }
29
+ return globalThis.systemTest;
30
+ }
31
+ /** @returns {SystemTestCommunicator} */
32
+ getCommunicator() {
33
+ if (!this.communicator) {
34
+ throw new Error("Communicator hasn't been initialized yet");
35
+ }
36
+ return this.communicator;
37
+ }
38
+ /**
39
+ * Runs a system test
40
+ * @param {function(SystemTest): Promise<void>} callback
41
+ * @returns {Promise<void>}
42
+ */
43
+ static async run(callback) {
44
+ const systemTest = this.current();
45
+ await systemTest.getCommunicator().sendCommand({ type: "initialize" });
46
+ await systemTest.dismissTo(SystemTest.rootPath);
47
+ try {
48
+ await systemTest.findByTestID("blankText", { useBaseSelector: false });
49
+ await callback(systemTest);
50
+ }
51
+ catch (error) {
52
+ await systemTest.takeScreenshot();
53
+ throw error;
54
+ }
55
+ }
56
+ /**
57
+ * Creates a new SystemTest instance
58
+ * @param {object} [args]
59
+ * @param {string} [args.host]
60
+ * @param {number} [args.port]
61
+ */
62
+ constructor({ host = "localhost", port = 8081, ...restArgs } = { host: "localhost", port: 8081 }) {
63
+ /** @type {SystemTestCommunicator | undefined} */
64
+ this.communicator = undefined;
65
+ /** @type {import("selenium-webdriver").WebDriver | undefined} */
66
+ this.driver = undefined;
67
+ this._started = false;
68
+ this._driverTimeouts = 5000;
69
+ this._timeouts = 5000;
70
+ /**
71
+ * Handles a command received from the browser
72
+ * @param {{data: {message: string, backtrace: string, type: string, value: any[]}}} args
73
+ * @returns {Promise<any>}
74
+ */
75
+ this.onCommandReceived = async ({ data }) => {
76
+ const type = data.type;
77
+ let result;
78
+ if (type == "console.error") {
79
+ const errorMessage = data.value[0];
80
+ let showMessage = true;
81
+ if (errorMessage.includes("Minified React error #419")) {
82
+ showMessage = false;
83
+ }
84
+ if (showMessage) {
85
+ console.error("Browser error", ...data.value);
86
+ }
87
+ }
88
+ else if (type == "console.log") {
89
+ console.log("Browser log", ...data.value);
90
+ }
91
+ else if (type == "error" || data.type == "unhandledrejection") {
92
+ this.handleError(data);
93
+ }
94
+ else if (this._onCommandCallback) {
95
+ result = await this._onCommandCallback({ data, type });
96
+ }
97
+ else {
98
+ console.error(`onWebSocketClientMessage unknown data (type ${type})`, data);
99
+ }
100
+ return result;
101
+ };
102
+ /**
103
+ * Handles a new web socket connection
104
+ * @param {WebSocket} ws
105
+ * @returns {Promise<void>}
106
+ */
107
+ this.onWebSocketConnection = async (ws) => {
108
+ this.ws = ws;
109
+ this.getCommunicator().ws = ws;
110
+ this.getCommunicator().onOpen();
111
+ // @ts-expect-error
112
+ this.ws.on("error", digg(this, "communicator", "onError"));
113
+ // @ts-expect-error
114
+ this.ws.on("message", digg(this, "communicator", "onMessage"));
115
+ if (this.waitForClientWebSocketPromiseResolve) {
116
+ this.waitForClientWebSocketPromiseResolve();
117
+ delete this.waitForClientWebSocketPromiseResolve;
118
+ }
119
+ };
120
+ /** @returns {void} */
121
+ this.onWebSocketClose = () => {
122
+ this.ws = null;
123
+ this.getCommunicator().ws = null;
124
+ };
125
+ const restArgsKeys = Object.keys(restArgs);
126
+ if (restArgsKeys.length > 0) {
127
+ throw new Error(`Unknown arguments: ${restArgsKeys.join(", ")}`);
128
+ }
129
+ this._host = host;
130
+ this._port = port;
131
+ /** @type {Record<number, object>} */
132
+ this._responses = {};
133
+ this._sendCount = 0;
134
+ this.startScoundrel();
135
+ this.communicator = new SystemTestCommunicator({ onCommand: this.onCommandReceived });
136
+ }
137
+ /**
138
+ * Gets the base selector for scoping element searches
139
+ * @returns {string | undefined}
140
+ */
141
+ getBaseSelector() { return this._baseSelector; }
142
+ /** @returns {import("selenium-webdriver").WebDriver} */
143
+ getDriver() {
144
+ if (!this)
145
+ throw new Error("No this?");
146
+ if (!this.driver)
147
+ throw new Error("Driver hasn't been initialized yet");
148
+ return this.driver;
149
+ }
150
+ /**
151
+ * Sets the base selector for scoping element searches
152
+ * @param {string} baseSelector
153
+ */
154
+ setBaseSelector(baseSelector) { this._baseSelector = baseSelector; }
155
+ /**
156
+ * Gets a selector scoped to the base selector
157
+ * @param {string} selector
158
+ * @returns {string}
159
+ */
160
+ getSelector(selector) {
161
+ return this.getBaseSelector() ? `${this.getBaseSelector()} ${selector}` : selector;
162
+ }
163
+ /**
164
+ * Starts Scoundrel server which the browser connects to for remote evaluation in the browser
165
+ * @returns {void}
166
+ */
167
+ startScoundrel() {
168
+ if (this.wss)
169
+ throw new Error("Scoundrel server already started");
170
+ this.wss = new WebSocketServer({ port: 8090 });
171
+ this.serverWebSocket = new ServerWebSocket(this.wss);
172
+ this.server = new Server(this.serverWebSocket);
173
+ }
174
+ /**
175
+ * @returns {void}
176
+ */
177
+ stopScoundrel() {
178
+ this.server?.close();
179
+ this.wss?.close();
180
+ }
181
+ /**
182
+ * Finds all elements by CSS selector
183
+ * @param {string} selector
184
+ * @param {object} [args]
185
+ * @param {number} [args.timeout]
186
+ * @param {boolean} [args.visible]
187
+ * @param {boolean} [args.useBaseSelector]
188
+ * @returns {Promise<import("selenium-webdriver").WebElement[]>}
189
+ */
190
+ async all(selector, args = {}) {
191
+ const { visible = true, timeout, useBaseSelector = true, ...restArgs } = args;
192
+ const restArgsKeys = Object.keys(restArgs);
193
+ let actualTimeout;
194
+ if (timeout === undefined) {
195
+ actualTimeout = this._driverTimeouts;
196
+ }
197
+ else {
198
+ actualTimeout = timeout;
199
+ }
200
+ if (restArgsKeys.length > 0)
201
+ throw new Error(`Unknown arguments: ${restArgsKeys.join(", ")}`);
202
+ const actualSelector = useBaseSelector ? this.getSelector(selector) : selector;
203
+ const getElements = async () => await this.getDriver().findElements(By.css(actualSelector));
204
+ let elements = [];
205
+ if (actualTimeout == 0) {
206
+ elements = await getElements();
207
+ }
208
+ else {
209
+ await this.getDriver().wait(async () => {
210
+ elements = await getElements();
211
+ return elements.length > 0;
212
+ }, actualTimeout);
213
+ }
214
+ const activeElements = [];
215
+ for (const element of elements) {
216
+ let keep = true;
217
+ if (visible === true || visible === false) {
218
+ const isDisplayed = await element.isDisplayed();
219
+ if (visible && !isDisplayed)
220
+ keep = false;
221
+ if (!visible && isDisplayed)
222
+ keep = false;
223
+ }
224
+ if (keep)
225
+ activeElements.push(element);
226
+ }
227
+ return activeElements;
228
+ }
229
+ /**
230
+ * Clicks an element that has children which fills out the element and would otherwise have caused a ElementClickInterceptedError
231
+ * @param {string|import("selenium-webdriver").WebElement} elementOrIdentifier
232
+ * @returns {Promise<void>}
233
+ */
234
+ async click(elementOrIdentifier) {
235
+ let tries = 0;
236
+ while (true) {
237
+ tries++;
238
+ try {
239
+ const element = await this._findElement(elementOrIdentifier);
240
+ const actions = this.getDriver().actions({ async: true });
241
+ await actions.move({ origin: element }).click().perform();
242
+ break;
243
+ }
244
+ catch (error) {
245
+ if (error instanceof Error) {
246
+ if (error.constructor.name === "ElementNotInteractableError") {
247
+ if (tries >= 3) {
248
+ throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed after ${tries} tries - ${error.constructor.name}: ${error.message}`);
249
+ }
250
+ else {
251
+ await wait(50);
252
+ }
253
+ }
254
+ else {
255
+ // Re-throw with un-corrupted stack trace
256
+ throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed - ${error.constructor.name}: ${error.message}`);
257
+ }
258
+ }
259
+ else {
260
+ throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed - ${typeof error}: ${error}`);
261
+ }
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * Finds a single element by CSS selector
267
+ * @param {string} selector
268
+ * @param {object} [args]
269
+ * @returns {Promise<import("selenium-webdriver").WebElement>}
270
+ */
271
+ async find(selector, args = {}) {
272
+ let elements = [];
273
+ try {
274
+ elements = await this.all(selector, args);
275
+ }
276
+ catch (error) {
277
+ // Re-throw to recover stack trace
278
+ if (error instanceof Error) {
279
+ if (error.message.startsWith("Wait timed out after")) {
280
+ elements = [];
281
+ }
282
+ throw new Error(`${error.message} (selector: ${this.getSelector(selector)})`);
283
+ }
284
+ else {
285
+ throw new Error(`${error} (selector: ${this.getSelector(selector)})`);
286
+ }
287
+ }
288
+ if (elements.length > 1) {
289
+ throw new Error(`More than 1 elements (${elements.length}) was found by CSS: ${this.getSelector(selector)}`);
290
+ }
291
+ if (!elements[0]) {
292
+ throw new ElementNotFoundError(`Element couldn't be found after ${(this.getTimeouts() / 1000).toFixed(2)}s by CSS: ${this.getSelector(selector)}`);
293
+ }
294
+ return elements[0];
295
+ }
296
+ /**
297
+ * Finds a single element by test ID
298
+ * @param {string} testID
299
+ * @param {object} [args]
300
+ * @returns {Promise<import("selenium-webdriver").WebElement>}
301
+ */
302
+ async findByTestID(testID, args) { return await this.find(`[data-testid='${testID}']`, args); }
303
+ /**
304
+ * @param {string|import("selenium-webdriver").WebElement} elementOrIdentifier
305
+ * @returns {Promise<import("selenium-webdriver").WebElement>}
306
+ */
307
+ async _findElement(elementOrIdentifier) {
308
+ let element;
309
+ if (typeof elementOrIdentifier == "string") {
310
+ element = await this.find(elementOrIdentifier);
311
+ }
312
+ else {
313
+ element = elementOrIdentifier;
314
+ }
315
+ return element;
316
+ }
317
+ /**
318
+ * Finds a single element by CSS selector without waiting
319
+ * @param {string} selector
320
+ * @param {object} [args]
321
+ * @returns {Promise<import("selenium-webdriver").WebElement>}
322
+ */
323
+ async findNoWait(selector, args) {
324
+ await this.driverSetTimeouts(0);
325
+ try {
326
+ return await this.find(selector, args);
327
+ }
328
+ finally {
329
+ await this.restoreTimeouts();
330
+ }
331
+ }
332
+ /**
333
+ * Gets browser logs
334
+ * @returns {Promise<string[]>}
335
+ */
336
+ async getBrowserLogs() {
337
+ const entries = await this.getDriver().manage().logs().get(logging.Type.BROWSER);
338
+ const browserLogs = [];
339
+ for (const entry of entries) {
340
+ const messageMatch = entry.message.match(/^(.+) (\d+):(\d+) (.+)$/);
341
+ let message;
342
+ if (messageMatch) {
343
+ message = messageMatch[4];
344
+ }
345
+ else {
346
+ message = entry.message;
347
+ }
348
+ browserLogs.push(`${entry.level.name}: ${message}`);
349
+ }
350
+ return browserLogs;
351
+ }
352
+ /** @returns {Promise<string>} */
353
+ async getCurrentUrl() {
354
+ return await this.getDriver().getCurrentUrl();
355
+ }
356
+ /** @returns {number} */
357
+ getTimeouts() { return this._timeouts; }
358
+ /**
359
+ * Interacts with an element by calling a method on it with the given arguments.
360
+ * Retrying on ElementNotInteractableError.
361
+ * @param {import("selenium-webdriver").WebElement|string} elementOrIdentifier The element or a CSS selector to find the element.
362
+ * @param {string} methodName The method name to call on the element.
363
+ * @param {...any} args Arguments to pass to the method.
364
+ * @returns {Promise<any>}
365
+ */
366
+ async interact(elementOrIdentifier, methodName, ...args) {
367
+ let tries = 0;
368
+ while (true) {
369
+ tries++;
370
+ const element = await this._findElement(elementOrIdentifier);
371
+ if (!element[methodName]) {
372
+ throw new Error(`${element.constructor.name} hasn't an attribute named: ${methodName}`);
373
+ }
374
+ else if (typeof element[methodName] != "function") {
375
+ throw new Error(`${element.constructor.name}#${methodName} is not a function`);
376
+ }
377
+ try {
378
+ // Dont call with candidate, because that will bind the function wrong.
379
+ return await element[methodName](...args);
380
+ }
381
+ catch (error) {
382
+ if (error instanceof Error) {
383
+ if (error.constructor.name === "ElementNotInteractableError") {
384
+ // Retry finding the element and interacting with it
385
+ if (tries >= 3) {
386
+ let elementDescription;
387
+ if (typeof elementOrIdentifier == "string") {
388
+ elementDescription = `CSS selector ${elementOrIdentifier}`;
389
+ }
390
+ else {
391
+ elementDescription = `${element.constructor.name}`;
392
+ }
393
+ throw new Error(`${elementDescription} ${methodName} failed after ${tries} tries - ${error.constructor.name}: ${error.message}`);
394
+ }
395
+ else {
396
+ await wait(50);
397
+ }
398
+ }
399
+ else {
400
+ // Re-throw with un-corrupted stack trace
401
+ throw new Error(`${element.constructor.name} ${methodName} failed - ${error.constructor.name}: ${error.message}`);
402
+ }
403
+ }
404
+ else {
405
+ throw new Error(`${element.constructor.name} ${methodName} failed - ${typeof error}: ${error}`);
406
+ }
407
+ }
408
+ }
409
+ }
410
+ /**
411
+ * Expects no element to be found by CSS selector
412
+ * @param {string} selector
413
+ * @returns {Promise<void>}
414
+ */
415
+ async expectNoElement(selector) {
416
+ let found = false;
417
+ try {
418
+ await this.findNoWait(selector);
419
+ found = true;
420
+ }
421
+ catch (error) {
422
+ if (error instanceof Error && error.message.startsWith("Element couldn't be found after ")) {
423
+ // Ignore
424
+ }
425
+ else {
426
+ throw error;
427
+ }
428
+ }
429
+ if (found) {
430
+ throw new Error(`Expected not to find: ${selector}`);
431
+ }
432
+ }
433
+ /**
434
+ * @param {string} selector
435
+ * @param {object} [args]
436
+ * @param {boolean} [args.useBaseSelector]
437
+ * @returns {Promise<void>}
438
+ */
439
+ async waitForNoSelector(selector, args) {
440
+ const { useBaseSelector, ...restArgs } = args;
441
+ if (Object.keys(restArgs).length > 0) {
442
+ throw new Error(`Unexpected args: ${Object.keys(restArgs).join(", ")}`);
443
+ }
444
+ const actualSelector = useBaseSelector ? this.getSelector(selector) : selector;
445
+ await this.getDriver().wait(async () => {
446
+ const elements = await this.getDriver().findElements(By.css(actualSelector));
447
+ // Not found at all
448
+ if (elements.length === 0) {
449
+ return true;
450
+ }
451
+ // Found but not visible
452
+ const isDisplayed = await elements[0].isDisplayed();
453
+ return !isDisplayed;
454
+ }, this.getTimeouts());
455
+ }
456
+ /**
457
+ * Gets notification messages
458
+ * @returns {Promise<string[]>}
459
+ */
460
+ async notificationMessages() {
461
+ const notificationMessageElements = await this.all("[data-class='notification-message']", { useBaseSelector: false });
462
+ const notificationMessageTexts = [];
463
+ for (const notificationMessageElement of notificationMessageElements) {
464
+ const text = await notificationMessageElement.getText();
465
+ notificationMessageTexts.push(text);
466
+ }
467
+ return notificationMessageTexts;
468
+ }
469
+ /**
470
+ * Expects a notification message to appear and waits for it if necessary.
471
+ * @param {string} expectedNotificationMessage
472
+ * @returns {Promise<void>}
473
+ */
474
+ async expectNotificationMessage(expectedNotificationMessage) {
475
+ /** @type {string[]} */
476
+ const allDetectedNotificationMessages = [];
477
+ let foundNotificationMessageElement;
478
+ await waitFor(async () => {
479
+ const notificationMessageElements = await this.all("[data-class='notification-message']", { useBaseSelector: false });
480
+ for (const notificationMessageElement of notificationMessageElements) {
481
+ const notificationMessage = await notificationMessageElement.getText();
482
+ if (!allDetectedNotificationMessages.includes(notificationMessage)) {
483
+ allDetectedNotificationMessages.push(notificationMessage);
484
+ }
485
+ if (notificationMessage == expectedNotificationMessage) {
486
+ foundNotificationMessageElement = notificationMessageElement;
487
+ return;
488
+ }
489
+ }
490
+ throw new Error(`Notification message ${expectedNotificationMessage} wasn't included in: ${allDetectedNotificationMessages.join(", ")}`);
491
+ });
492
+ if (foundNotificationMessageElement) {
493
+ await this.interact(foundNotificationMessageElement, "click"); // Dismiss the notification message
494
+ }
495
+ }
496
+ /** @returns {Promise<void>} */
497
+ async dismissNotificationMessages() {
498
+ const notificationMessageElements = await this.all("[data-class='notification-message']", { useBaseSelector: false });
499
+ for (const notificationMessageElement of notificationMessageElements) {
500
+ await this.interact(notificationMessageElement, "click");
501
+ }
502
+ await this.waitForNoSelector("[data-class='notification-message']", { useBaseSelector: false });
503
+ }
504
+ /**
505
+ * Indicates whether the system test has been started
506
+ * @returns {boolean}
507
+ */
508
+ isStarted() { return this._started; }
509
+ /**
510
+ * Gets the HTML of the current page
511
+ * @returns {Promise<string>}
512
+ */
513
+ async getHTML() { return await this.getDriver().getPageSource(); }
514
+ /**
515
+ * Starts the system test
516
+ * @returns {Promise<void>}
517
+ */
518
+ async start() {
519
+ if (process.env.SYSTEM_TEST_HOST == "expo-dev-server") {
520
+ this.currentUrl = `http://${this._host}:${this._port}`;
521
+ }
522
+ else if (process.env.SYSTEM_TEST_HOST == "dist") {
523
+ this.currentUrl = `http://${this._host}:1984`;
524
+ this.systemTestHttpServer = new SystemTestHttpServer();
525
+ await this.systemTestHttpServer.start();
526
+ }
527
+ else {
528
+ throw new Error("Please set SYSTEM_TEST_HOST to 'expo-dev-server' or 'dist'");
529
+ }
530
+ const options = new chrome.Options();
531
+ options.addArguments("--disable-dev-shm-usage");
532
+ options.addArguments("--disable-gpu");
533
+ options.addArguments("--headless=new");
534
+ options.addArguments("--no-sandbox");
535
+ options.addArguments("--window-size=1920,1080");
536
+ this.driver = new Builder()
537
+ .forBrowser("chrome")
538
+ .setChromeOptions(options)
539
+ // @ts-expect-error
540
+ .setCapability("goog:loggingPrefs", { browser: "ALL" })
541
+ .build();
542
+ await this.setTimeouts(5000);
543
+ // Web socket server to communicate with browser
544
+ await this.startWebSocketServer();
545
+ // Visit the root page and wait for Expo to be loaded and the app to appear
546
+ await this.driverVisit(SystemTest.rootPath);
547
+ try {
548
+ await this.find("body > #root", { useBaseSelector: false });
549
+ await this.find("[data-testid='systemTestingComponent']", { visible: null, useBaseSelector: false });
550
+ }
551
+ catch (error) {
552
+ await this.takeScreenshot();
553
+ throw error;
554
+ }
555
+ // Wait for client to connect
556
+ await this.waitForClientWebSocket();
557
+ this._started = true;
558
+ this.setBaseSelector("[data-testid='systemTestingComponent'][data-focussed='true']");
559
+ }
560
+ /**
561
+ * Restores previously set timeouts
562
+ * @returns {Promise<void>}
563
+ */
564
+ async restoreTimeouts() {
565
+ if (!this.getTimeouts()) {
566
+ throw new Error("Timeouts haven't previously been set");
567
+ }
568
+ await this.driverSetTimeouts(this.getTimeouts());
569
+ }
570
+ /**
571
+ * Sets driver timeouts
572
+ * @param {number} newTimeout
573
+ * @returns {Promise<void>}
574
+ */
575
+ async driverSetTimeouts(newTimeout) {
576
+ this._driverTimeouts = newTimeout;
577
+ await this.getDriver().manage().setTimeouts({ implicit: newTimeout });
578
+ }
579
+ /**
580
+ * Sets timeouts and stores the previous timeouts
581
+ * @param {number} newTimeout
582
+ * @returns {Promise<void>}
583
+ */
584
+ async setTimeouts(newTimeout) {
585
+ this._timeouts = newTimeout;
586
+ await this.restoreTimeouts();
587
+ }
588
+ /**
589
+ * Waits for the client web socket to connect
590
+ * @returns {Promise<void>}
591
+ */
592
+ waitForClientWebSocket() {
593
+ return new Promise((resolve) => {
594
+ if (this.ws) {
595
+ resolve();
596
+ }
597
+ this.waitForClientWebSocketPromiseResolve = resolve;
598
+ });
599
+ }
600
+ /**
601
+ * Starts the web socket server
602
+ * @returns {void}
603
+ */
604
+ startWebSocketServer() {
605
+ this.wss = new WebSocketServer({ port: 1985 });
606
+ this.wss.on("connection", this.onWebSocketConnection);
607
+ this.wss.on("close", this.onWebSocketClose);
608
+ }
609
+ /**
610
+ * Sets the on command callback
611
+ * @param {function({type: string, data: Record<string, any>}): Promise<void>} callback
612
+ * @returns {void}
613
+ */
614
+ onCommand(callback) {
615
+ this._onCommandCallback = callback;
616
+ }
617
+ /**
618
+ * Handles an error reported from the browser
619
+ * @param {object} data
620
+ * @param {string} data.message
621
+ * @param {string} [data.backtrace]
622
+ * @returns {void}
623
+ */
624
+ handleError(data) {
625
+ if (data.message.includes("Minified React error #419")) {
626
+ // Ignore this error message
627
+ return;
628
+ }
629
+ const error = new Error(`Browser error: ${data.message}`);
630
+ if (data.backtrace) {
631
+ error.stack = `${error.message}\n${data.backtrace}`;
632
+ }
633
+ console.error(error);
634
+ }
635
+ /**
636
+ * Stops the system test
637
+ * @returns {Promise<void>}
638
+ */
639
+ async stop() {
640
+ this.stopScoundrel();
641
+ this.systemTestHttpServer?.close();
642
+ this.wss?.close();
643
+ await this.driver?.quit();
644
+ }
645
+ /**
646
+ * Visits a path in the browser
647
+ * @param {string} path
648
+ * @returns {Promise<void>}
649
+ */
650
+ async driverVisit(path) {
651
+ const url = `${this.currentUrl}${path}`;
652
+ await this.getDriver().get(url);
653
+ }
654
+ /**
655
+ * Takes a screenshot, saves HTML and browser logs
656
+ * @returns {Promise<void>}
657
+ */
658
+ async takeScreenshot() {
659
+ const path = `${process.cwd()}/tmp/screenshots`;
660
+ await fs.mkdir(path, { recursive: true });
661
+ const imageContent = await this.getDriver().takeScreenshot();
662
+ const now = new Date();
663
+ const screenshotPath = `${path}/${moment(now).format("YYYY-MM-DD-HH-MM-SS")}.png`;
664
+ const htmlPath = `${path}/${moment(now).format("YYYY-MM-DD-HH-MM-SS")}.html`;
665
+ const logsPath = `${path}/${moment(now).format("YYYY-MM-DD-HH-MM-SS")}.logs.txt`;
666
+ const logsText = await this.getBrowserLogs();
667
+ const html = await this.getHTML();
668
+ const htmlPretty = prettify(html);
669
+ await fs.writeFile(htmlPath, htmlPretty);
670
+ await fs.writeFile(logsPath, logsText.join("\n"));
671
+ await fs.writeFile(screenshotPath, imageContent, "base64");
672
+ console.log("Current URL:", await this.getCurrentUrl());
673
+ console.log("Logs:", logsPath);
674
+ console.log("Screenshot:", screenshotPath);
675
+ console.log("HTML:", htmlPath);
676
+ }
677
+ /**
678
+ * Visits a path in the browser
679
+ * @param {string} path
680
+ * @returns {Promise<void>}
681
+ */
682
+ async visit(path) {
683
+ await this.getCommunicator().sendCommand({ type: "visit", path });
684
+ }
685
+ /**
686
+ * Dismisses to a path in the browser
687
+ * @param {string} path
688
+ * @returns {Promise<void>}
689
+ */
690
+ async dismissTo(path) {
691
+ await this.getCommunicator().sendCommand({ type: "dismissTo", path });
692
+ }
693
+ }
694
+ SystemTest.rootPath = "/blank?systemTest=true";
695
+ export default SystemTest;
696
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3lzdGVtLXRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc3lzdGVtLXRlc3QuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWTtBQUVaLE9BQU8sRUFBQyxPQUFPLEVBQUUsRUFBRSxFQUFDLE1BQU0sb0JBQW9CLENBQUE7QUFDOUMsT0FBTyxNQUFNLE1BQU0sOEJBQThCLENBQUE7QUFDakQsT0FBTyxFQUFDLElBQUksRUFBQyxNQUFNLFdBQVcsQ0FBQTtBQUM5QixPQUFPLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQTtBQUNqQyxPQUFPLE9BQU8sTUFBTSxtQ0FBbUMsQ0FBQTtBQUN2RCxPQUFPLE1BQU0sTUFBTSxRQUFRLENBQUE7QUFDM0IsT0FBTyxFQUFDLFFBQVEsRUFBQyxNQUFNLFFBQVEsQ0FBQTtBQUMvQixPQUFPLE1BQU0sTUFBTSw2Q0FBNkMsQ0FBQTtBQUNoRSxPQUFPLGVBQWUsTUFBTSxvRUFBb0UsQ0FBQTtBQUNoRyxPQUFPLHNCQUFzQixNQUFNLCtCQUErQixDQUFBO0FBQ2xFLE9BQU8sb0JBQW9CLE1BQU0sOEJBQThCLENBQUE7QUFDL0QsT0FBTyxFQUFDLElBQUksRUFBRSxPQUFPLEVBQUMsTUFBTSxVQUFVLENBQUE7QUFDdEMsT0FBTyxFQUFDLGVBQWUsRUFBQyxNQUFNLElBQUksQ0FBQTtBQUVsQyxNQUFNLG9CQUFxQixTQUFRLEtBQUs7Q0FBSTtBQUU1QyxNQUFxQixVQUFVO0lBYTdCOzs7Ozs7T0FNRztJQUNILE1BQU0sQ0FBQyxPQUFPLENBQUMsSUFBSTtRQUNqQixJQUFJLENBQUMsVUFBVSxDQUFDLFVBQVUsRUFBRSxDQUFDO1lBQzNCLFVBQVUsQ0FBQyxVQUFVLEdBQUcsSUFBSSxVQUFVLENBQUMsSUFBSSxDQUFDLENBQUE7UUFDOUMsQ0FBQztRQUVELE9BQU8sVUFBVSxDQUFDLFVBQVUsQ0FBQTtJQUM5QixDQUFDO0lBRUQsd0NBQXdDO0lBQ3hDLGVBQWU7UUFDYixJQUFJLENBQUMsSUFB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QTtJQUNyRSxDQUFDOztBQTV3Qk0sbUJBQVEsR0FBRyx3QkFBd0IsQUFBM0IsQ0FBMkI7ZUFEdkIsVUFBVSIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG5pbXBvcnQge0J1aWxkZXIsIEJ5fSBmcm9tIFwic2VsZW5pdW0td2ViZHJpdmVyXCJcbmltcG9ydCBjaHJvbWUgZnJvbSBcInNlbGVuaXVtLXdlYmRyaXZlci9jaHJvbWUuanNcIlxuaW1wb3J0IHtkaWdnfSBmcm9tIFwiZGlnZ2VyaXplXCJcbmltcG9ydCBmcyBmcm9tIFwibm9kZTpmcy9wcm9taXNlc1wiXG5pbXBvcnQgbG9nZ2luZyBmcm9tIFwic2VsZW5pdW0td2ViZHJpdmVyL2xpYi9sb2dnaW5nLmpzXCJcbmltcG9ydCBtb21lbnQgZnJvbSBcIm1vbWVudFwiXG5pbXBvcnQge3ByZXR0aWZ5fSBmcm9tIFwiaHRtbGZ5XCJcbmltcG9ydCBTZXJ2ZXIgZnJvbSBcInNjb3VuZHJlbC1yZW1vdGUtZXZhbC9idWlsZC9zZXJ2ZXIvaW5kZXguanNcIlxuaW1wb3J0IFNlcnZlcldlYlNvY2tldCBmcm9tIFwic2NvdW5kcmVsLXJlbW90ZS1ldmFsL2J1aWxkL3NlcnZlci9jb25uZWN0aW9ucy93ZWItc29ja2V0L2luZGV4LmpzXCJcbmltcG9ydCBTeXN0ZW1UZXN0Q29tbXVuaWNhdG9yIGZyb20gXCIuL3N5c3RlbS10ZXN0LWNvbW11bmljYXRvci5qc1wiXG5pbXBvcnQgU3lzdGVtVGVzdEh0dHBTZXJ2ZXIgZnJvbSBcIi4vc3lzdGVtLXRlc3QtaHR0cC1zZXJ2ZXIuanNcIlxuaW1wb3J0IHt3YWl0LCB3YWl0Rm9yfSBmcm9tIFwiYXdhaXRlcnlcIlxuaW1wb3J0IHtXZWJTb2NrZXRTZXJ2ZXJ9IGZyb20gXCJ3c1wiXG5cbmNsYXNzIEVsZW1lbnROb3RGb3VuZEVycm9yIGV4dGVuZHMgRXJyb3IgeyB9XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFN5c3RlbVRlc3Qge1xuICBzdGF0aWMgcm9vdFBhdGggPSBcIi9ibGFuaz9zeXN0ZW1UZXN0PXRydWVcIlxuXG4gIC8qKiBAdHlwZSB7U3lzdGVtVGVzdENvbW11bmljYXRvciB8IHVuZGVmaW5lZH0gKi9cbiAgY29tbXVuaWNhdG9yID0gdW5kZWZpbmVkXG5cbiAgLyoqIEB0eXBlIHtpbXBvcnQoXCJzZWxlbml1bS13ZWJkcml2ZXJcIikuV2ViRHJpdmVyIHwgdW5kZWZpbmVkfSAqL1xuICBkcml2ZXIgPSB1bmRlZmluZWRcblxuICBfc3RhcnRlZCA9IGZhbHNlXG4gIF9kcml2ZXJUaW1lb3V0cyA9IDUwMDBcbiAgX3RpbWVvdXRzID0gNTAwMFxuXG4gIC8qKlxuICAgKiBHZXRzIHRoZSBjdXJyZW50IHN5c3RlbSB0ZXN0IGluc3RhbmNlXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBbYXJnc11cbiAgICogQHBhcmFtIHtzdHJpbmd9IFthcmdzLmhvc3RdXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBbYXJncy5wb3J0XVxuICAgKiBAcmV0dXJucyB7U3lzdGVtVGVzdH1cbiAgICovXG4gIHN0YXRpYyBjdXJyZW50KGFyZ3MpIHtcbiAgICBpZiAoIWdsb2JhbFRoaXMuc3lzdGVtVGVzdCkge1xuICAgICAgZ2xvYmFsVGhpcy5zeXN0ZW1UZXN0ID0gbmV3IFN5c3RlbVRlc3QoYXJncylcbiAgICB9XG5cbiAgICByZXR1cm4gZ2xvYmFsVGhpcy5zeXN0ZW1UZXN0XG4gIH1cblxuICAvKiogQHJldHVybnMge1N5c3RlbVRlc3RDb21tdW5pY2F0b3J9ICovXG4gIGdldENvbW11bmljYXRvcigpIHtcbiAgICBpZiAoIXRoaXMuY29tbXVuaWNhdG9yKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJDb21tdW5pY2F0b3IgaGFzbid0IGJlZW4gaW5pdGlhbGl6ZWQgeWV0XCIpXG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXMuY29tbXVuaWNhdG9yXG4gIH1cblxuICAvKipcbiAgICogUnVucyBhIHN5c3RlbSB0ZXN0XG4gICAqIEBwYXJhbSB7ZnVuY3Rpb24oU3lzdGVtVGVzdCk6IFByb21pc2U8dm9pZD59IGNhbGxiYWNrXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgc3RhdGljIGFzeW5jIHJ1bihjYWxsYmFjaykge1xuICAgIGNvbnN0IHN5c3RlbVRlc3QgPSB0aGlzLmN1cnJlbnQoKVxuXG4gICAgYXdhaXQgc3lzdGVtVGVzdC5nZXRDb21tdW5pY2F0b3IoKS5zZW5kQ29tbWFuZCh7dHlwZTogXCJpbml0aWFsaXplXCJ9KVxuICAgIGF3YWl0IHN5c3RlbVRlc3QuZGlzbWlzc1RvKFN5c3RlbVRlc3Qucm9vdFBhdGgpXG5cbiAgICB0cnkge1xuICAgICAgYXdhaXQgc3lzdGVtVGVzdC5maW5kQnlUZXN0SUQoXCJibGFua1RleHRcIiwge3VzZUJhc2VTZWxlY3RvcjogZmFsc2V9KVxuICAgICAgYXdhaXQgY2FsbGJhY2soc3lzdGVtVGVzdClcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgYXdhaXQgc3lzdGVtVGVzdC50YWtlU2NyZWVuc2hvdCgpXG5cbiAgICAgIHRocm93IGVycm9yXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIENyZWF0ZXMgYSBuZXcgU3lzdGVtVGVzdCBpbnN0YW5jZVxuICAgKiBAcGFyYW0ge29iamVjdH0gW2FyZ3NdXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBbYXJncy5ob3N0XVxuICAgKiBAcGFyYW0ge251bWJlcn0gW2FyZ3MucG9ydF1cbiAgICovXG4gIGNvbnN0cnVjdG9yKHtob3N0ID0gXCJsb2NhbGhvc3RcIiwgcG9ydCA9IDgwODEsIC4uLnJlc3RBcmdzfSA9IHtob3N0OiBcImxvY2FsaG9zdFwiLCBwb3J0OiA4MDgxfSkge1xuICAgIGNvbnN0IHJlc3RBcmdzS2V5cyA9IE9iamVjdC5rZXlzKHJlc3RBcmdzKVxuXG4gICAgaWYgKHJlc3RBcmdzS2V5cy5sZW5ndGggPiAwKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFVua25vd24gYXJndW1lbnRzOiAke3Jlc3RBcmdzS2V5cy5qb2luKFwiLCBcIil9YClcbiAgICB9XG5cbiAgICB0aGlzLl9ob3N0ID0gaG9zdFxuICAgIHRoaXMuX3BvcnQgPSBwb3J0XG5cbiAgICAvKiogQHR5cGUge1JlY29yZDxudW1iZXIsIG9iamVjdD59ICovXG4gICAgdGhpcy5fcmVzcG9uc2VzID0ge31cblxuICAgIHRoaXMuX3NlbmRDb3VudCA9IDBcbiAgICB0aGlzLnN0YXJ0U2NvdW5kcmVsKClcbiAgICB0aGlzLmNvbW11bmljYXRvciA9IG5ldyBTeXN0ZW1UZXN0Q29tbXVuaWNhdG9yKHtvbkNvbW1hbmQ6IHRoaXMub25Db21tYW5kUmVjZWl2ZWR9KVxuICB9XG5cbiAgLyoqXG4gICAqIEdldHMgdGhlIGJhc2Ugc2VsZWN0b3IgZm9yIHNjb3BpbmcgZWxlbWVudCBzZWFyY2hlc1xuICAgKiBAcmV0dXJucyB7c3RyaW5nIHwgdW5kZWZpbmVkfVxuICAgKi9cbiAgZ2V0QmFzZVNlbGVjdG9yKCkgeyByZXR1cm4gdGhpcy5fYmFzZVNlbGVjdG9yIH1cblxuICAvKiogQHJldHVybnMge2ltcG9ydChcInNlbGVuaXVtLXdlYmRyaXZlclwiKS5XZWJEcml2ZXJ9ICovXG4gIGdldERyaXZlcigpIHtcbiAgICBpZiAoIXRoaXMpIHRocm93IG5ldyBFcnJvcihcIk5vIHRoaXM/XCIpXG4gICAgaWYgKCF0aGlzLmRyaXZlcikgdGhyb3cgbmV3IEVycm9yKFwiRHJpdmVyIGhhc24ndCBiZWVuIGluaXRpYWxpemVkIHlldFwiKVxuXG4gICAgcmV0dXJuIHRoaXMuZHJpdmVyXG4gIH1cblxuICAvKipcbiAgICogU2V0cyB0aGUgYmFzZSBzZWxlY3RvciBmb3Igc2NvcGluZyBlbGVtZW50IHNlYXJjaGVzXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBiYXNlU2VsZWN0b3JcbiAgICovXG4gIHNldEJhc2VTZWxlY3RvcihiYXNlU2VsZWN0b3IpIHsgdGhpcy5fYmFzZVNlbGVjdG9yID0gYmFzZVNlbGVjdG9yIH1cblxuICAvKipcbiAgICogR2V0cyBhIHNlbGVjdG9yIHNjb3BlZCB0byB0aGUgYmFzZSBzZWxlY3RvclxuICAgKiBAcGFyYW0ge3N0cmluZ30gc2VsZWN0b3JcbiAgICogQHJldHVybnMge3N0cmluZ31cbiAgICovXG4gIGdldFNlbGVjdG9yKHNlbGVjdG9yKSB7XG4gICAgcmV0dXJuIHRoaXMuZ2V0QmFzZVNlbGVjdG9yKCkgPyBgJHt0aGlzLmdldEJhc2VTZWxlY3RvcigpfSAke3NlbGVjdG9yfWAgOiBzZWxlY3RvclxuICB9XG5cbiAgLyoqXG4gICAqIFN0YXJ0cyBTY291bmRyZWwgc2VydmVyIHdoaWNoIHRoZSBicm93c2VyIGNvbm5lY3RzIHRvIGZvciByZW1vdGUgZXZhbHVhdGlvbiBpbiB0aGUgYnJvd3NlclxuICAgKiBAcmV0dXJucyB7dm9pZH1cbiAgICovXG4gIHN0YXJ0U2NvdW5kcmVsKCkge1xuICAgIGlmICh0aGlzLndzcykgdGhyb3cgbmV3IEVycm9yKFwiU2NvdW5kcmVsIHNlcnZlciBhbHJlYWR5IHN0YXJ0ZWRcIilcblxuICAgIHRoaXMud3NzID0gbmV3IFdlYlNvY2tldFNlcnZlcih7cG9ydDogODA5MH0pXG4gICAgdGhpcy5zZXJ2ZXJXZWJTb2NrZXQgPSBuZXcgU2VydmVyV2ViU29ja2V0KHRoaXMud3NzKVxuICAgIHRoaXMuc2VydmVyID0gbmV3IFNlcnZlcih0aGlzLnNlcnZlcldlYlNvY2tldClcbiAgfVxuXG4gIC8qKlxuICAgKiBAcmV0dXJucyB7dm9pZH1cbiAgICovXG4gIHN0b3BTY291bmRyZWwoKSB7XG4gICAgdGhpcy5zZXJ2ZXI/LmNsb3NlKClcbiAgICB0aGlzLndzcz8uY2xvc2UoKVxuICB9XG5cbiAgLyoqXG4gICAqIEZpbmRzIGFsbCBlbGVtZW50cyBieSBDU1Mgc2VsZWN0b3JcbiAgICogQHBhcmFtIHtzdHJpbmd9IHNlbGVjdG9yXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBbYXJnc11cbiAgICogQHBhcmFtIHtudW1iZXJ9IFthcmdzLnRpbWVvdXRdXG4gICAqIEBwYXJhbSB7Ym9vbGVhbn0gW2FyZ3MudmlzaWJsZV1cbiAgICogQHBhcmFtIHtib29sZWFufSBbYXJncy51c2VCYXNlU2VsZWN0b3JdXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPGltcG9ydChcInNlbGVuaXVtLXdlYmRyaXZlclwiKS5XZWJFbGVtZW50W10+fVxuICAgKi9cbiAgYXN5bmMgYWxsKHNlbGVjdG9yLCBhcmdzID0ge30pIHtcbiAgICBjb25zdCB7dmlzaWJsZSA9IHRydWUsIHRpbWVvdXQsIHVzZUJhc2VTZWxlY3RvciA9IHRydWUsIC4uLnJlc3RBcmdzfSA9IGFyZ3NcbiAgICBjb25zdCByZXN0QXJnc0tleXMgPSBPYmplY3Qua2V5cyhyZXN0QXJncylcbiAgICBsZXQgYWN0dWFsVGltZW91dFxuXG4gICAgaWYgKHRpbWVvdXQgPT09IHVuZGVmaW5lZCkge1xuICAgICAgYWN0dWFsVGltZW91dCA9IHRoaXMuX2RyaXZlclRpbWVvdXRzXG4gICAgfSBlbHNlIHtcbiAgICAgIGFjdHVhbFRpbWVvdXQgPSB0aW1lb3V0XG4gICAgfVxuXG4gICAgaWYgKHJlc3RBcmdzS2V5cy5sZW5ndGggPiAwKSB0aHJvdyBuZXcgRXJyb3IoYFVua25vd24gYXJndW1lbnRzOiAke3Jlc3RBcmdzS2V5cy5qb2luKFwiLCBcIil9YClcblxuICAgIGNvbnN0IGFjdHVhbFNlbGVjdG9yID0gdXNlQmFzZVNlbGVjdG9yID8gdGhpcy5nZXRTZWxlY3RvcihzZWxlY3RvcikgOiBzZWxlY3RvclxuICAgIGNvbnN0IGdldEVsZW1lbnRzID0gYXN5bmMgKCkgPT4gYXdhaXQgdGhpcy5nZXREcml2ZXIoKS5maW5kRWxlbWVudHMoQnkuY3NzKGFjdHVhbFNlbGVjdG9yKSlcbiAgICBsZXQgZWxlbWVudHMgPSBbXVxuXG4gICAgaWYgKGFjdHVhbFRpbWVvdXQgPT0gMCkge1xuICAgICAgZWxlbWVudHMgPSBhd2FpdCBnZXRFbGVtZW50cygpXG4gICAgfSBlbHNlIHtcbiAgICAgIGF3YWl0IHRoaXMuZ2V0RHJpdmVyKCkud2FpdChhc3luYyAoKSA9PiB7XG4gICAgICAgIGVsZW1lbnRzID0gYXdhaXQgZ2V0RWxlbWVudHMoKVxuXG4gICAgICAgIHJldHVybiBlbGVtZW50cy5sZW5ndGggPiAwXG4gICAgICB9LCBhY3R1YWxUaW1lb3V0KVxuICAgIH1cblxuICAgIGNvbnN0IGFjdGl2ZUVsZW1lbnRzID0gW11cblxuICAgIGZvciAoY29uc3QgZWxlbWVudCBvZiBlbGVtZW50cykge1xuICAgICAgbGV0IGtlZXAgPSB0cnVlXG5cbiAgICAgIGlmICh2aXNpYmxlID09PSB0cnVlIHx8IHZpc2libGUgPT09IGZhbHNlKSB7XG4gICAgICAgIGNvbnN0IGlzRGlzcGxheWVkID0gYXdhaXQgZWxlbWVudC5pc0Rpc3BsYXllZCgpXG5cbiAgICAgICAgaWYgKHZpc2libGUgJiYgIWlzRGlzcGxheWVkKSBrZWVwID0gZmFsc2VcbiAgICAgICAgaWYgKCF2aXNpYmxlICYmIGlzRGlzcGxheWVkKSBrZWVwID0gZmFsc2VcbiAgICAgIH1cblxuICAgICAgaWYgKGtlZXApIGFjdGl2ZUVsZW1lbnRzLnB1c2goZWxlbWVudClcbiAgICB9XG5cbiAgICByZXR1cm4gYWN0aXZlRWxlbWVudHNcbiAgfVxuXG4gIC8qKlxuICAgKiBDbGlja3MgYW4gZWxlbWVudCB0aGF0IGhhcyBjaGlsZHJlbiB3aGljaCBmaWxscyBvdXQgdGhlIGVsZW1lbnQgYW5kIHdvdWxkIG90aGVyd2lzZSBoYXZlIGNhdXNlZCBhIEVsZW1lbnRDbGlja0ludGVyY2VwdGVkRXJyb3JcbiAgICogQHBhcmFtIHtzdHJpbmd8aW1wb3J0KFwic2VsZW5pdW0td2ViZHJpdmVyXCIpLldlYkVsZW1lbnR9IGVsZW1lbnRPcklkZW50aWZpZXJcbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gICAqL1xuICBhc3luYyBjbGljayhlbGVtZW50T3JJZGVudGlmaWVyKSB7XG4gICAgbGV0IHRyaWVzID0gMFxuXG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIHRyaWVzKytcblxuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgZWxlbWVudCA9IGF3YWl0IHRoaXMuX2ZpbmRFbGVtZW50KGVsZW1lbnRPcklkZW50aWZpZXIpXG4gICAgICAgIGNvbnN0IGFjdGlvbnMgPSB0aGlzLmdldERyaXZlcigpLmFjdGlvbnMoe2FzeW5jOiB0cnVlfSlcblxuICAgICAgICBhd2FpdCBhY3Rpb25zLm1vdmUoe29yaWdpbjogZWxlbWVudH0pLmNsaWNrKCkucGVyZm9ybSgpXG4gICAgICAgIGJyZWFrXG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICBpZiAoZXJyb3IgaW5zdGFuY2VvZiBFcnJvcikge1xuICAgICAgICAgIGlmIChlcnJvci5jb25zdHJ1Y3Rvci5uYW1lID09PSBcIkVsZW1lbnROb3RJbnRlcmFjdGFibGVFcnJvclwiKSB7XG4gICAgICAgICAgICBpZiAodHJpZXMgPj0gMykge1xuICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEVsZW1lbnQgJHtlbGVtZW50T3JJZGVudGlmaWVyLmNvbnN0cnVjdG9yLm5hbWV9IGNsaWNrIGZhaWxlZCBhZnRlciAke3RyaWVzfSB0cmllcyAtICR7ZXJyb3IuY29uc3RydWN0b3IubmFtZX06ICR7ZXJyb3IubWVzc2FnZX1gKVxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgYXdhaXQgd2FpdCg1MClcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gUmUtdGhyb3cgd2l0aCB1bi1jb3JydXB0ZWQgc3RhY2sgdHJhY2VcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgRWxlbWVudCAke2VsZW1lbnRPcklkZW50aWZpZXIuY29uc3RydWN0b3IubmFtZX0gY2xpY2sgZmFpbGVkIC0gJHtlcnJvci5jb25zdHJ1Y3Rvci5uYW1lfTogJHtlcnJvci5tZXNzYWdlfWApXG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgRWxlbWVudCAke2VsZW1lbnRPcklkZW50aWZpZXIuY29uc3RydWN0b3IubmFtZX0gY2xpY2sgZmFpbGVkIC0gJHt0eXBlb2YgZXJyb3J9OiAke2Vycm9yfWApXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRmluZHMgYSBzaW5nbGUgZWxlbWVudCBieSBDU1Mgc2VsZWN0b3JcbiAgICogQHBhcmFtIHtzdHJpbmd9IHNlbGVjdG9yXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBbYXJnc11cbiAgICogQHJldHVybnMge1Byb21pc2U8aW1wb3J0KFwic2VsZW5pdW0td2ViZHJpdmVyXCIpLldlYkVsZW1lbnQ+fVxuICAgKi9cbiAgYXN5bmMgZmluZChzZWxlY3RvciwgYXJncyA9IHt9KSB7XG4gICAgbGV0IGVsZW1lbnRzID0gW11cblxuICAgIHRyeSB7XG4gICAgICBlbGVtZW50cyA9IGF3YWl0IHRoaXMuYWxsKHNlbGVjdG9yLCBhcmdzKVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAvLyBSZS10aHJvdyB0byByZWNvdmVyIHN0YWNrIHRyYWNlXG4gICAgICBpZiAoZXJyb3IgaW5zdGFuY2VvZiBFcnJvcikge1xuICAgICAgICBpZiAoZXJyb3IubWVzc2FnZS5zdGFydHNXaXRoKFwiV2FpdCB0aW1lZCBvdXQgYWZ0ZXJcIikpIHtcbiAgICAgICAgICBlbGVtZW50cyA9IFtdXG4gICAgICAgIH1cblxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYCR7ZXJyb3IubWVzc2FnZX0gKHNlbGVjdG9yOiAke3RoaXMuZ2V0U2VsZWN0b3Ioc2VsZWN0b3IpfSlgKVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGAke2Vycm9yfSAoc2VsZWN0b3I6ICR7dGhpcy5nZXRTZWxlY3RvcihzZWxlY3Rvcil9KWApXG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKGVsZW1lbnRzLmxlbmd0aCA+IDEpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgTW9yZSB0aGFuIDEgZWxlbWVudHMgKCR7ZWxlbWVudHMubGVuZ3RofSkgd2FzIGZvdW5kIGJ5IENTUzogJHt0aGlzLmdldFNlbGVjdG9yKHNlbGVjdG9yKX1gKVxuICAgIH1cblxuICAgIGlmICghZWxlbWVudHNbMF0pIHtcbiAgICAgIHRocm93IG5ldyBFbGVtZW50Tm90Rm91bmRFcnJvcihgRWxlbWVudCBjb3VsZG4ndCBiZSBmb3VuZCBhZnRlciAkeyh0aGlzLmdldFRpbWVvdXRzKCkgLyAxMDAwKS50b0ZpeGVkKDIpfXMgYnkgQ1NTOiAke3RoaXMuZ2V0U2VsZWN0b3Ioc2VsZWN0b3IpfWApXG4gICAgfVxuXG4gICAgcmV0dXJuIGVsZW1lbnRzWzBdXG4gIH1cblxuICAvKipcbiAgICogRmluZHMgYSBzaW5nbGUgZWxlbWVudCBieSB0ZXN0IElEXG4gICAqIEBwYXJhbSB7c3RyaW5nfSB0ZXN0SURcbiAgICogQHBhcmFtIHtvYmplY3R9IFthcmdzXVxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxpbXBvcnQoXCJzZWxlbml1bS13ZWJkcml2ZXJcIikuV2ViRWxlbWVudD59XG4gICAqL1xuICBhc3luYyBmaW5kQnlUZXN0SUQodGVzdElELCBhcmdzKSB7IHJldHVybiBhd2FpdCB0aGlzLmZpbmQoYFtkYXRhLXRlc3RpZD0nJHt0ZXN0SUR9J11gLCBhcmdzKSB9XG5cblxuICAvKipcbiAgICogQHBhcmFtIHtzdHJpbmd8aW1wb3J0KFwic2VsZW5pdW0td2ViZHJpdmVyXCIpLldlYkVsZW1lbnR9IGVsZW1lbnRPcklkZW50aWZpZXJcbiAgICogQHJldHVybnMge1Byb21pc2U8aW1wb3J0KFwic2VsZW5pdW0td2ViZHJpdmVyXCIpLldlYkVsZW1lbnQ+fVxuICAgKi9cbiAgYXN5bmMgX2ZpbmRFbGVtZW50KGVsZW1lbnRPcklkZW50aWZpZXIpIHtcbiAgICBsZXQgZWxlbWVudFxuXG4gICAgaWYgKHR5cGVvZiBlbGVtZW50T3JJZGVudGlmaWVyID09IFwic3RyaW5nXCIpIHtcbiAgICAgIGVsZW1lbnQgPSBhd2FpdCB0aGlzLmZpbmQoZWxlbWVudE9ySWRlbnRpZmllcilcbiAgICB9IGVsc2Uge1xuICAgICAgZWxlbWVudCA9IGVsZW1lbnRPcklkZW50aWZpZXJcbiAgICB9XG5cbiAgICByZXR1cm4gZWxlbWVudFxuICB9XG5cbiAgLyoqXG4gICAqIEZpbmRzIGEgc2luZ2xlIGVsZW1lbnQgYnkgQ1NTIHNlbGVjdG9yIHdpdGhvdXQgd2FpdGluZ1xuICAgKiBAcGFyYW0ge3N0cmluZ30gc2VsZWN0b3JcbiAgICogQHBhcmFtIHtvYmplY3R9IFthcmdzXVxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxpbXBvcnQoXCJzZWxlbml1bS13ZWJkcml2ZXJcIikuV2ViRWxlbWVudD59XG4gICAqL1xuICBhc3luYyBmaW5kTm9XYWl0KHNlbGVjdG9yLCBhcmdzKSB7XG4gICAgYXdhaXQgdGhpcy5kcml2ZXJTZXRUaW1lb3V0cygwKVxuXG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiBhd2FpdCB0aGlzLmZpbmQoc2VsZWN0b3IsIGFyZ3MpXG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIGF3YWl0IHRoaXMucmVzdG9yZVRpbWVvdXRzKClcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogR2V0cyBicm93c2VyIGxvZ3NcbiAgICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nW10+fVxuICAgKi9cbiAgYXN5bmMgZ2V0QnJvd3NlckxvZ3MoKSB7XG4gICAgY29uc3QgZW50cmllcyA9IGF3YWl0IHRoaXMuZ2V0RHJpdmVyKCkubWFuYWdlKCkubG9ncygpLmdldChsb2dnaW5nLlR5cGUuQlJPV1NFUilcbiAgICBjb25zdCBicm93c2VyTG9ncyA9IFtdXG5cbiAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIGVudHJpZXMpIHtcbiAgICAgIGNvbnN0IG1lc3NhZ2VNYXRjaCA9IGVudHJ5Lm1lc3NhZ2UubWF0Y2goL14oLispIChcXGQrKTooXFxkKykgKC4rKSQvKVxuICAgICAgbGV0IG1lc3NhZ2VcblxuICAgICAgaWYgKG1lc3NhZ2VNYXRjaCkge1xuICAgICAgICBtZXNzYWdlID0gbWVzc2FnZU1hdGNoWzRdXG4gICAgICB9IGVsc2Uge1xuICAgICAgICBtZXNzYWdlID0gZW50cnkubWVzc2FnZVxuICAgICAgfVxuXG4gICAgICBicm93c2VyTG9ncy5wdXNoKGAke2VudHJ5LmxldmVsLm5hbWV9OiAke21lc3NhZ2V9YClcbiAgICB9XG5cbiAgICByZXR1cm4gYnJvd3NlckxvZ3NcbiAgfVxuXG4gIC8qKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSAqL1xuICBhc3luYyBnZXRDdXJyZW50VXJsKCkge1xuICAgIHJldHVybiBhd2FpdCB0aGlzLmdldERyaXZlcigpLmdldEN1cnJlbnRVcmwoKVxuICB9XG5cbiAgLyoqIEByZXR1cm5zIHtudW1iZXJ9ICovXG4gIGdldFRpbWVvdXRzKCkgeyByZXR1cm4gdGhpcy5fdGltZW91dHMgfVxuXG4gIC8qKlxuICAgKiBJbnRlcmFjdHMgd2l0aCBhbiBlbGVtZW50IGJ5IGNhbGxpbmcgYSBtZXRob2Qgb24gaXQgd2l0aCB0aGUgZ2l2ZW4gYXJndW1lbnRzLlxuICAgKiBSZXRyeWluZyBvbiBFbGVtZW50Tm90SW50ZXJhY3RhYmxlRXJyb3IuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwic2VsZW5pdW0td2ViZHJpdmVyXCIpLldlYkVsZW1lbnR8c3RyaW5nfSBlbGVtZW50T3JJZGVudGlmaWVyIFRoZSBlbGVtZW50IG9yIGEgQ1NTIHNlbGVjdG9yIHRvIGZpbmQgdGhlIGVsZW1lbnQuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBtZXRob2ROYW1lIFRoZSBtZXRob2QgbmFtZSB0byBjYWxsIG9uIHRoZSBlbGVtZW50LlxuICAgKiBAcGFyYW0gey4uLmFueX0gYXJncyBBcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgbWV0aG9kLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxhbnk+fVxuICAgKi9cbiAgYXN5bmMgaW50ZXJhY3QoZWxlbWVudE9ySWRlbnRpZmllciwgbWV0aG9kTmFtZSwgLi4uYXJncykge1xuICAgIGxldCB0cmllcyA9IDBcblxuICAgIHdoaWxlICh0cnVlKSB7XG4gICAgICB0cmllcysrXG5cbiAgICAgIGNvbnN0IGVsZW1lbnQgPSBhd2FpdCB0aGlzLl9maW5kRWxlbWVudChlbGVtZW50T3JJZGVudGlmaWVyKVxuXG4gICAgICBpZiAoIWVsZW1lbnRbbWV0aG9kTmFtZV0pIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGAke2VsZW1lbnQuY29uc3RydWN0b3IubmFtZX0gaGFzbid0IGFuIGF0dHJpYnV0ZSBuYW1lZDogJHttZXRob2ROYW1lfWApXG4gICAgICB9IGVsc2UgaWYgKHR5cGVvZiBlbGVtZW50W21ldGhvZE5hbWVdICE9IFwiZnVuY3Rpb25cIikge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYCR7ZWxlbWVudC5jb25zdHJ1Y3Rvci5uYW1lfSMke21ldGhvZE5hbWV9IGlzIG5vdCBhIGZ1bmN0aW9uYClcbiAgICAgIH1cblxuICAgICAgdHJ5IHtcbiAgICAgICAgLy8gRG9udCBjYWxsIHdpdGggY2FuZGlkYXRlLCBiZWNhdXNlIHRoYXQgd2lsbCBiaW5kIHRoZSBmdW5jdGlvbiB3cm9uZy5cbiAgICAgICAgcmV0dXJuIGF3YWl0IGVsZW1lbnRbbWV0aG9kTmFtZV0oLi4uYXJncylcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGlmIChlcnJvciBpbnN0YW5jZW9mIEVycm9yKSB7XG4gICAgICAgICAgaWYgKGVycm9yLmNvbnN0cnVjdG9yLm5hbWUgPT09IFwiRWxlbWVudE5vdEludGVyYWN0YWJsZUVycm9yXCIpIHtcbiAgICAgICAgICAgIC8vIFJldHJ5IGZpbmRpbmcgdGhlIGVsZW1lbnQgYW5kIGludGVyYWN0aW5nIHdpdGggaXRcbiAgICAgICAgICAgIGlmICh0cmllcyA+PSAzKSB7XG4gICAgICAgICAgICAgIGxldCBlbGVtZW50RGVzY3JpcHRpb25cblxuICAgICAgICAgICAgICBpZiAodHlwZW9mIGVsZW1lbnRPcklkZW50aWZpZXIgPT0gXCJzdHJpbmdcIikge1xuICAgICAgICAgICAgICAgIGVsZW1lbnREZXNjcmlwdGlvbiA9IGBDU1Mgc2VsZWN0b3IgJHtlbGVtZW50T3JJZGVudGlmaWVyfWBcbiAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBlbGVtZW50RGVzY3JpcHRpb24gPSBgJHtlbGVtZW50LmNvbnN0cnVjdG9yLm5hbWV9YFxuICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGAke2VsZW1lbnREZXNjcmlwdGlvbn0gJHttZXRob2ROYW1lfSBmYWlsZWQgYWZ0ZXIgJHt0cmllc30gdHJpZXMgLSAke2Vycm9yLmNvbnN0cnVjdG9yLm5hbWV9OiAke2Vycm9yLm1lc3NhZ2V9YClcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIGF3YWl0IHdhaXQoNTApXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIC8vIFJlLXRocm93IHdpdGggdW4tY29ycnVwdGVkIHN0YWNrIHRyYWNlXG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYCR7ZWxlbWVudC5jb25zdHJ1Y3Rvci5uYW1lfSAke21ldGhvZE5hbWV9IGZhaWxlZCAtICR7ZXJyb3IuY29uc3RydWN0b3IubmFtZX06ICR7ZXJyb3IubWVzc2FnZX1gKVxuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYCR7ZWxlbWVudC5jb25zdHJ1Y3Rvci5uYW1lfSAke21ldGhvZE5hbWV9IGZhaWxlZCAtICR7dHlwZW9mIGVycm9yfTogJHtlcnJvcn1gKVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEV4cGVjdHMgbm8gZWxlbWVudCB0byBiZSBmb3VuZCBieSBDU1Mgc2VsZWN0b3JcbiAgICogQHBhcmFtIHtzdHJpbmd9IHNlbGVjdG9yXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgZXhwZWN0Tm9FbGVtZW50KHNlbGVjdG9yKSB7XG4gICAgbGV0IGZvdW5kID0gZmFsc2VcblxuICAgIHRyeSB7XG4gICAgICBhd2FpdCB0aGlzLmZpbmROb1dhaXQoc2VsZWN0b3IpXG4gICAgICBmb3VuZCA9IHRydWVcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgaWYgKGVycm9yIGluc3RhbmNlb2YgRXJyb3IgJiYgZXJyb3IubWVzc2FnZS5zdGFydHNXaXRoKFwiRWxlbWVudCBjb3VsZG4ndCBiZSBmb3VuZCBhZnRlciBcIikpIHtcbiAgICAgICAgLy8gSWdub3JlXG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aHJvdyBlcnJvclxuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChmb3VuZCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBub3QgdG8gZmluZDogJHtzZWxlY3Rvcn1gKVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0ge3N0cmluZ30gc2VsZWN0b3JcbiAgICogQHBhcmFtIHtvYmplY3R9IFthcmdzXVxuICAgKiBAcGFyYW0ge2Jvb2xlYW59IFthcmdzLnVzZUJhc2VTZWxlY3Rvcl1cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gICAqL1xuICBhc3luYyB3YWl0Rm9yTm9TZWxlY3RvcihzZWxlY3RvciwgYXJncykge1xuICAgIGNvbnN0IHt1c2VCYXNlU2VsZWN0b3IsIC4uLnJlc3RBcmdzfSA9IGFyZ3NcblxuICAgIGlmIChPYmplY3Qua2V5cyhyZXN0QXJncykubGVuZ3RoID4gMCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBVbmV4cGVjdGVkIGFyZ3M6ICR7T2JqZWN0LmtleXMocmVzdEFyZ3MpLmpvaW4oXCIsIFwiKX1gKVxuICAgIH1cblxuICAgIGNvbnN0IGFjdHVhbFNlbGVjdG9yID0gdXNlQmFzZVNlbGVjdG9yID8gdGhpcy5nZXRTZWxlY3RvcihzZWxlY3RvcikgOiBzZWxlY3RvclxuXG4gICAgYXdhaXQgdGhpcy5nZXREcml2ZXIoKS53YWl0KFxuICAgICAgYXN5bmMgKCkgPT4ge1xuICAgICAgICBjb25zdCBlbGVtZW50cyA9IGF3YWl0IHRoaXMuZ2V0RHJpdmVyKCkuZmluZEVsZW1lbnRzKEJ5LmNzcyhhY3R1YWxTZWxlY3RvcikpXG5cbiAgICAgICAgLy8gTm90IGZvdW5kIGF0IGFsbFxuICAgICAgICBpZiAoZWxlbWVudHMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgcmV0dXJuIHRydWVcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIEZvdW5kIGJ1dCBub3QgdmlzaWJsZVxuICAgICAgICBjb25zdCBpc0Rpc3BsYXllZCA9IGF3YWl0IGVsZW1lbnRzWzBdLmlzRGlzcGxheWVkKClcblxuICAgICAgICByZXR1cm4gIWlzRGlzcGxheWVkXG4gICAgICB9LFxuICAgICAgdGhpcy5nZXRUaW1lb3V0cygpXG4gICAgKVxuICB9XG5cbiAgLyoqXG4gICAqIEdldHMgbm90aWZpY2F0aW9uIG1lc3NhZ2VzXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHN0cmluZ1tdPn1cbiAgICovXG4gIGFzeW5jIG5vdGlmaWNhdGlvbk1lc3NhZ2VzKCkge1xuICAgIGNvbnN0IG5vdGlmaWNhdGlvbk1lc3NhZ2VFbGVtZW50cyA9IGF3YWl0IHRoaXMuYWxsKFwiW2RhdGEtY2xhc3M9J25vdGlmaWNhdGlvbi1tZXNzYWdlJ11cIiwge3VzZUJhc2VTZWxlY3RvcjogZmFsc2V9KVxuICAgIGNvbnN0IG5vdGlmaWNhdGlvbk1lc3NhZ2VUZXh0cyA9IFtdXG5cbiAgICBmb3IgKGNvbnN0IG5vdGlmaWNhdGlvbk1lc3NhZ2VFbGVtZW50IG9mIG5vdGlmaWNhdGlvbk1lc3NhZ2VFbGVtZW50cykge1xuICAgICAgY29uc3QgdGV4dCA9IGF3YWl0IG5vdGlmaWNhdGlvbk1lc3NhZ2VFbGVtZW50LmdldFRleHQoKVxuXG4gICAgICBub3RpZmljYXRpb25NZXNzYWdlVGV4dHMucHVzaCh0ZXh0KVxuICAgIH1cblxuICAgIHJldHVybiBub3RpZmljYXRpb25NZXNzYWdlVGV4dHNcbiAgfVxuXG4gIC8qKlxuICAgKiBFeHBlY3RzIGEgbm90aWZpY2F0aW9uIG1lc3NhZ2UgdG8gYXBwZWFyIGFuZCB3YWl0cyBmb3IgaXQgaWYgbmVjZXNzYXJ5LlxuICAgKiBAcGFyYW0ge3N0cmluZ30gZXhwZWN0ZWROb3RpZmljYXRpb25NZXNzYWdlXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgZXhwZWN0Tm90aWZpY2F0aW9uTWVzc2FnZShleHBlY3RlZE5vdGlmaWNhdGlvbk1lc3NhZ2UpIHtcbiAgICAvKiogQHR5cGUge3N0cmluZ1tdfSAqL1xuICAgIGNvbnN0IGFsbERldGVjdGVkTm90aWZpY2F0aW9uTWVzc2FnZXMgPSBbXVxuICAgIGxldCBmb3VuZE5vdGlmaWNhdGlvbk1lc3NhZ2VFbGVtZW50XG5cbiAgICBhd2FpdCB3YWl0Rm9yKGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IG5vdGlmaWNhdGlvbk1lc3NhZ2VFbGVtZW50cyA9IGF3YWl0IHRoaXMuYWxsKFwiW2RhdGEtY2xhc3M9J25vdGlmaWNhdGlvbi1tZXNzYWdlJ11cIiwge3VzZUJhc2VTZWxlY3RvcjogZmFsc2V9KVxuXG4gICAgICBmb3IgKGNvbnN0IG5vdGlmaWNhdGlvbk1lc3NhZ2VFbGVtZW50IG9mIG5vdGlmaWNhdGlvbk1lc3NhZ2VFbGVtZW50cykge1xuICAgICAgICBjb25zdCBub3RpZmljYXRpb25NZXNzYWdlID0gYXdhaXQgbm90aWZpY2F0aW9uTWVzc2FnZUVsZW1lbnQuZ2V0VGV4dCgpXG5cbiAgICAgICAgaWYgKCFhbGxEZXRlY3RlZE5vdGlmaWNhdGlvbk1lc3NhZ2VzLmluY2x1ZGVzKG5vdGlmaWNhdGlvbk1lc3NhZ2UpKSB7XG4gICAgICAgICAgYWxsRGV0ZWN0ZWROb3RpZmljYXRpb25NZXNzYWdlcy5wdXNoKG5vdGlmaWNhdGlvbk1lc3NhZ2UpXG4gICAgICAgIH1cblxuICAgICAgICBpZiAobm90aWZpY2F0aW9uTWVzc2FnZSA9PSBleHBlY3RlZE5vdGlmaWNhdGlvbk1lc3NhZ2UpIHtcbiAgICAgICAgICBmb3VuZE5vdGlmaWNhdGlvbk1lc3NhZ2VFbGVtZW50ID0gbm90aWZpY2F0aW9uTWVzc2FnZUVsZW1lbnRcbiAgICAgICAgICByZXR1cm5cbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYE5vdGlmaWNhdGlvbiBtZXNzYWdlICR7ZXhwZWN0ZWROb3RpZmljYXRpb25NZXNzYWdlfSB3YXNuJ3QgaW5jbHVkZWQgaW46ICR7YWxsRGV0ZWN0ZWROb3RpZmljYXRpb25NZXNzYWdlcy5qb2luKFwiLCBcIil9YClcbiAgICB9KVxuXG4gICAgaWYgKGZvdW5kTm90aWZpY2F0aW9uTWVzc2FnZUVsZW1lbnQpIHtcbiAgICAgIGF3YWl0IHRoaXMuaW50ZXJhY3QoZm91bmROb3RpZmljYXRpb25NZXNzYWdlRWxlbWVudCwgXCJjbGlja1wiKSAvLyBEaXNtaXNzIHRoZSBub3RpZmljYXRpb24gbWVzc2FnZVxuICAgIH1cbiAgfVxuXG4gIC8qKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gKi9cbiAgYXN5bmMgZGlzbWlzc05vdGlmaWNhdGlvbk1lc3NhZ2VzKCkge1xuICAgIGNvbnN0IG5vdGlmaWNhdGlvbk1lc3NhZ2VFbGVtZW50cyA9IGF3YWl0IHRoaXMuYWxsKFwiW2RhdGEtY2xhc3M9J25vdGlmaWNhdGlvbi1tZXNzYWdlJ11cIiwge3VzZUJhc2VTZWxlY3RvcjogZmFsc2V9KVxuXG4gICAgZm9yIChjb25zdCBub3RpZmljYXRpb25NZXNzYWdlRWxlbWVudCBvZiBub3RpZmljYXRpb25NZXNzYWdlRWxlbWVudHMpIHtcbiAgICAgIGF3YWl0IHRoaXMuaW50ZXJhY3Qobm90aWZpY2F0aW9uTWVzc2FnZUVsZW1lbnQsIFwiY2xpY2tcIilcbiAgICB9XG5cbiAgICBhd2FpdCB0aGlzLndhaXRGb3JOb1NlbGVjdG9yKFwiW2RhdGEtY2xhc3M9J25vdGlmaWNhdGlvbi1tZXNzYWdlJ11cIiwge3VzZUJhc2VTZWxlY3RvcjogZmFsc2V9KVxuICB9XG5cbiAgLyoqXG4gICAqIEluZGljYXRlcyB3aGV0aGVyIHRoZSBzeXN0ZW0gdGVzdCBoYXMgYmVlbiBzdGFydGVkXG4gICAqIEByZXR1cm5zIHtib29sZWFufVxuICAgKi9cbiAgaXNTdGFydGVkKCkgeyByZXR1cm4gdGhpcy5fc3RhcnRlZCB9XG5cbiAgLyoqXG4gICAqIEdldHMgdGhlIEhUTUwgb2YgdGhlIGN1cnJlbnQgcGFnZVxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fVxuICAgKi9cbiAgYXN5bmMgZ2V0SFRNTCgpIHsgcmV0dXJuIGF3YWl0IHRoaXMuZ2V0RHJpdmVyKCkuZ2V0UGFnZVNvdXJjZSgpIH1cblxuICAvKipcbiAgICogU3RhcnRzIHRoZSBzeXN0ZW0gdGVzdFxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAgICovXG4gIGFzeW5jIHN0YXJ0KCkge1xuICAgIGlmIChwcm9jZXNzLmVudi5TWVNURU1fVEVTVF9IT1NUID09IFwiZXhwby1kZXYtc2VydmVyXCIpIHtcbiAgICAgIHRoaXMuY3VycmVudFVybCA9IGBodHRwOi8vJHt0aGlzLl9ob3N0fToke3RoaXMuX3BvcnR9YFxuICAgIH0gZWxzZSBpZiAocHJvY2Vzcy5lbnYuU1lTVEVNX1RFU1RfSE9TVCA9PSBcImRpc3RcIikge1xuICAgICAgdGhpcy5jdXJyZW50VXJsID0gYGh0dHA6Ly8ke3RoaXMuX2hvc3R9OjE5ODRgXG4gICAgICB0aGlzLnN5c3RlbVRlc3RIdHRwU2VydmVyID0gbmV3IFN5c3RlbVRlc3RIdHRwU2VydmVyKClcblxuICAgICAgYXdhaXQgdGhpcy5zeXN0ZW1UZXN0SHR0cFNlcnZlci5zdGFydCgpXG4gICAgfSBlbHNlIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIlBsZWFzZSBzZXQgU1lTVEVNX1RFU1RfSE9TVCB0byAnZXhwby1kZXYtc2VydmVyJyBvciAnZGlzdCdcIilcbiAgICB9XG5cbiAgICBjb25zdCBvcHRpb25zID0gbmV3IGNocm9tZS5PcHRpb25zKClcblxuICAgIG9wdGlvbnMuYWRkQXJndW1lbnRzKFwiLS1kaXNhYmxlLWRldi1zaG0tdXNhZ2VcIilcbiAgICBvcHRpb25zLmFkZEFyZ3VtZW50cyhcIi0tZGlzYWJsZS1ncHVcIilcbiAgICBvcHRpb25zLmFkZEFyZ3VtZW50cyhcIi0taGVhZGxlc3M9bmV3XCIpXG4gICAgb3B0aW9ucy5hZGRBcmd1bWVudHMoXCItLW5vLXNhbmRib3hcIilcbiAgICBvcHRpb25zLmFkZEFyZ3VtZW50cyhcIi0td2luZG93LXNpemU9MTkyMCwxMDgwXCIpXG5cbiAgICB0aGlzLmRyaXZlciA9IG5ldyBCdWlsZGVyKClcbiAgICAgIC5mb3JCcm93c2VyKFwiY2hyb21lXCIpXG4gICAgICAuc2V0Q2hyb21lT3B0aW9ucyhvcHRpb25zKVxuICAgICAgLy8gQHRzLWV4cGVjdC1lcnJvclxuICAgICAgLnNldENhcGFiaWxpdHkoXCJnb29nOmxvZ2dpbmdQcmVmc1wiLCB7YnJvd3NlcjogXCJBTExcIn0pXG4gICAgICAuYnVpbGQoKVxuXG4gICAgYXdhaXQgdGhpcy5zZXRUaW1lb3V0cyg1MDAwKVxuXG4gICAgLy8gV2ViIHNvY2tldCBzZXJ2ZXIgdG8gY29tbXVuaWNhdGUgd2l0aCBicm93c2VyXG4gICAgYXdhaXQgdGhpcy5zdGFydFdlYlNvY2tldFNlcnZlcigpXG5cbiAgICAvLyBWaXNpdCB0aGUgcm9vdCBwYWdlIGFuZCB3YWl0IGZvciBFeHBvIHRvIGJlIGxvYWRlZCBhbmQgdGhlIGFwcCB0byBhcHBlYXJcbiAgICBhd2FpdCB0aGlzLmRyaXZlclZpc2l0KFN5c3RlbVRlc3Qucm9vdFBhdGgpXG5cbiAgICB0cnkge1xuICAgICAgYXdhaXQgdGhpcy5maW5kKFwiYm9keSA+ICNyb290XCIsIHt1c2VCYXNlU2VsZWN0b3I6IGZhbHNlfSlcbiAgICAgIGF3YWl0IHRoaXMuZmluZChcIltkYXRhLXRlc3RpZD0nc3lzdGVtVGVzdGluZ0NvbXBvbmVudCddXCIsIHt2aXNpYmxlOiBudWxsLCB1c2VCYXNlU2VsZWN0b3I6IGZhbHNlfSlcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgYXdhaXQgdGhpcy50YWtlU2NyZWVuc2hvdCgpXG4gICAgICB0aHJvdyBlcnJvclxuICAgIH1cblxuICAgIC8vIFdhaXQgZm9yIGNsaWVudCB0byBjb25uZWN0XG4gICAgYXdhaXQgdGhpcy53YWl0Rm9yQ2xpZW50V2ViU29ja2V0KClcblxuICAgIHRoaXMuX3N0YXJ0ZWQgPSB0cnVlXG4gICAgdGhpcy5zZXRCYXNlU2VsZWN0b3IoXCJbZGF0YS10ZXN0aWQ9J3N5c3RlbVRlc3RpbmdDb21wb25lbnQnXVtkYXRhLWZvY3Vzc2VkPSd0cnVlJ11cIilcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXN0b3JlcyBwcmV2aW91c2x5IHNldCB0aW1lb3V0c1xuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAgICovXG4gIGFzeW5jIHJlc3RvcmVUaW1lb3V0cygpIHtcbiAgICBpZiAoIXRoaXMuZ2V0VGltZW91dHMoKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiVGltZW91dHMgaGF2ZW4ndCBwcmV2aW91c2x5IGJlZW4gc2V0XCIpXG4gICAgfVxuXG4gICAgYXdhaXQgdGhpcy5kcml2ZXJTZXRUaW1lb3V0cyh0aGlzLmdldFRpbWVvdXRzKCkpXG4gIH1cblxuICAvKipcbiAgICogU2V0cyBkcml2ZXIgdGltZW91dHNcbiAgICogQHBhcmFtIHtudW1iZXJ9IG5ld1RpbWVvdXRcbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gICAqL1xuICBhc3luYyBkcml2ZXJTZXRUaW1lb3V0cyhuZXdUaW1lb3V0KSB7XG4gICAgdGhpcy5fZHJpdmVyVGltZW91dHMgPSBuZXdUaW1lb3V0XG4gICAgYXdhaXQgdGhpcy5nZXREcml2ZXIoKS5tYW5hZ2UoKS5zZXRUaW1lb3V0cyh7aW1wbGljaXQ6IG5ld1RpbWVvdXR9KVxuICB9XG5cbiAgLyoqXG4gICAqIFNldHMgdGltZW91dHMgYW5kIHN0b3JlcyB0aGUgcHJldmlvdXMgdGltZW91dHNcbiAgICogQHBhcmFtIHtudW1iZXJ9IG5ld1RpbWVvdXRcbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gICAqL1xuICBhc3luYyBzZXRUaW1lb3V0cyhuZXdUaW1lb3V0KSB7XG4gICAgdGhpcy5fdGltZW91dHMgPSBuZXdUaW1lb3V0XG4gICAgYXdhaXQgdGhpcy5yZXN0b3JlVGltZW91dHMoKVxuICB9XG5cbiAgLyoqXG4gICAqIFdhaXRzIGZvciB0aGUgY2xpZW50IHdlYiBzb2NrZXQgdG8gY29ubmVjdFxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAgICovXG4gIHdhaXRGb3JDbGllbnRXZWJTb2NrZXQoKSB7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiB7XG4gICAgICBpZiAodGhpcy53cykge1xuICAgICAgICByZXNvbHZlKClcbiAgICAgIH1cblxuICAgICAgdGhpcy53YWl0Rm9yQ2xpZW50V2ViU29ja2V0UHJvbWlzZVJlc29sdmUgPSByZXNvbHZlXG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBTdGFydHMgdGhlIHdlYiBzb2NrZXQgc2VydmVyXG4gICAqIEByZXR1cm5zIHt2b2lkfVxuICAgKi9cbiAgc3RhcnRXZWJTb2NrZXRTZXJ2ZXIoKSB7XG4gICAgdGhpcy53c3MgPSBuZXcgV2ViU29ja2V0U2VydmVyKHtwb3J0OiAxOTg1fSlcbiAgICB0aGlzLndzcy5vbihcImNvbm5lY3Rpb25cIiwgdGhpcy5vbldlYlNvY2tldENvbm5lY3Rpb24pXG4gICAgdGhpcy53c3Mub24oXCJjbG9zZVwiLCB0aGlzLm9uV2ViU29ja2V0Q2xvc2UpXG4gIH1cblxuICAvKipcbiAgICogU2V0cyB0aGUgb24gY29tbWFuZCBjYWxsYmFja1xuICAgKiBAcGFyYW0ge2Z1bmN0aW9uKHt0eXBlOiBzdHJpbmcsIGRhdGE6IFJlY29yZDxzdHJpbmcsIGFueT59KTogUHJvbWlzZTx2b2lkPn0gY2FsbGJhY2tcbiAgICogQHJldHVybnMge3ZvaWR9XG4gICAqL1xuICBvbkNvbW1hbmQoY2FsbGJhY2spIHtcbiAgICB0aGlzLl9vbkNvbW1hbmRDYWxsYmFjayA9IGNhbGxiYWNrXG4gIH1cblxuICAvKipcbiAgICogSGFuZGxlcyBhIGNvbW1hbmQgcmVjZWl2ZWQgZnJvbSB0aGUgYnJvd3NlclxuICAgKiBAcGFyYW0ge3tkYXRhOiB7bWVzc2FnZTogc3RyaW5nLCBiYWNrdHJhY2U6IHN0cmluZywgdHlwZTogc3RyaW5nLCB2YWx1ZTogYW55W119fX0gYXJnc1xuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxhbnk+fVxuICAgKi9cbiAgb25Db21tYW5kUmVjZWl2ZWQgPSBhc3luYyAoe2RhdGF9KSA9PiB7XG4gICAgY29uc3QgdHlwZSA9IGRhdGEudHlwZVxuICAgIGxldCByZXN1bHRcblxuICAgIGlmICh0eXBlID09IFwiY29uc29sZS5lcnJvclwiKSB7XG4gICAgICBjb25zdCBlcnJvck1lc3NhZ2UgPSBkYXRhLnZhbHVlWzBdXG4gICAgICBsZXQgc2hvd01lc3NhZ2UgPSB0cnVlXG5cbiAgICAgIGlmIChlcnJvck1lc3NhZ2UuaW5jbHVkZXMoXCJNaW5pZmllZCBSZWFjdCBlcnJvciAjNDE5XCIpKSB7XG4gICAgICAgIHNob3dNZXNzYWdlID0gZmFsc2VcbiAgICAgIH1cblxuICAgICAgaWYgKHNob3dNZXNzYWdlKSB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoXCJCcm93c2VyIGVycm9yXCIsIC4uLmRhdGEudmFsdWUpXG4gICAgICB9XG4gICAgfSBlbHNlIGlmICh0eXBlID09IFwiY29uc29sZS5sb2dcIikge1xuICAgICAgY29uc29sZS5sb2coXCJCcm93c2VyIGxvZ1wiLCAuLi5kYXRhLnZhbHVlKVxuICAgIH0gZWxzZSBpZiAodHlwZSA9PSBcImVycm9yXCIgfHwgZGF0YS50eXBlID09IFwidW5oYW5kbGVkcmVqZWN0aW9uXCIpIHtcbiAgICAgIHRoaXMuaGFuZGxlRXJyb3IoZGF0YSlcbiAgICB9IGVsc2UgaWYgKHRoaXMuX29uQ29tbWFuZENhbGxiYWNrKSB7XG4gICAgICByZXN1bHQgPSBhd2FpdCB0aGlzLl9vbkNvbW1hbmRDYWxsYmFjayh7ZGF0YSwgdHlwZX0pXG4gICAgfSBlbHNlIHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoYG9uV2ViU29ja2V0Q2xpZW50TWVzc2FnZSB1bmtub3duIGRhdGEgKHR5cGUgJHt0eXBlfSlgLCBkYXRhKVxuICAgIH1cblxuICAgIHJldHVybiByZXN1bHRcbiAgfVxuXG4gIC8qKlxuICAgKiBIYW5kbGVzIGEgbmV3IHdlYiBzb2NrZXQgY29ubmVjdGlvblxuICAgKiBAcGFyYW0ge1dlYlNvY2tldH0gd3NcbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gICAqL1xuICBvbldlYlNvY2tldENvbm5lY3Rpb24gPSBhc3luYyAod3MpID0+IHtcbiAgICB0aGlzLndzID0gd3NcbiAgICB0aGlzLmdldENvbW11bmljYXRvcigpLndzID0gd3NcbiAgICB0aGlzLmdldENvbW11bmljYXRvcigpLm9uT3BlbigpXG5cbiAgICAvLyBAdHMtZXhwZWN0LWVycm9yXG4gICAgdGhpcy53cy5vbihcImVycm9yXCIsIGRpZ2codGhpcywgXCJjb21tdW5pY2F0b3JcIiwgXCJvbkVycm9yXCIpKVxuXG4gICAgLy8gQHRzLWV4cGVjdC1lcnJvclxuICAgIHRoaXMud3Mub24oXCJtZXNzYWdlXCIsIGRpZ2codGhpcywgXCJjb21tdW5pY2F0b3JcIiwgXCJvbk1lc3NhZ2VcIikpXG5cbiAgICBpZiAodGhpcy53YWl0Rm9yQ2xpZW50V2ViU29ja2V0UHJvbWlzZVJlc29sdmUpIHtcbiAgICAgIHRoaXMud2FpdEZvckNsaWVudFdlYlNvY2tldFByb21pc2VSZXNvbHZlKClcbiAgICAgIGRlbGV0ZSB0aGlzLndhaXRGb3JDbGllbnRXZWJTb2NrZXRQcm9taXNlUmVzb2x2ZVxuICAgIH1cbiAgfVxuXG4gIC8qKiBAcmV0dXJucyB7dm9pZH0gKi9cbiAgb25XZWJTb2NrZXRDbG9zZSA9ICgpID0+IHtcbiAgICB0aGlzLndzID0gbnVsbFxuICAgIHRoaXMuZ2V0Q29tbXVuaWNhdG9yKCkud3MgPSBudWxsXG4gIH1cblxuICAvKipcbiAgICogSGFuZGxlcyBhbiBlcnJvciByZXBvcnRlZCBmcm9tIHRoZSBicm93c2VyXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBkYXRhXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBkYXRhLm1lc3NhZ2VcbiAgICogQHBhcmFtIHtzdHJpbmd9IFtkYXRhLmJhY2t0cmFjZV1cbiAgICogQHJldHVybnMge3ZvaWR9XG4gICAqL1xuICBoYW5kbGVFcnJvcihkYXRhKSB7XG4gICAgaWYgKGRhdGEubWVzc2FnZS5pbmNsdWRlcyhcIk1pbmlmaWVkIFJlYWN0IGVycm9yICM0MTlcIikpIHtcbiAgICAgIC8vIElnbm9yZSB0aGlzIGVycm9yIG1lc3NhZ2VcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGNvbnN0IGVycm9yID0gbmV3IEVycm9yKGBCcm93c2VyIGVycm9yOiAke2RhdGEubWVzc2FnZX1gKVxuXG4gICAgaWYgKGRhdGEuYmFja3RyYWNlKSB7XG4gICAgICBlcnJvci5zdGFjayA9IGAke2Vycm9yLm1lc3NhZ2V9XFxuJHtkYXRhLmJhY2t0cmFjZX1gXG4gICAgfVxuXG4gICAgY29uc29sZS5lcnJvcihlcnJvcilcbiAgfVxuXG4gIC8qKlxuICAgKiBTdG9wcyB0aGUgc3lzdGVtIHRlc3RcbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gICAqL1xuICBhc3luYyBzdG9wKCkge1xuICAgIHRoaXMuc3RvcFNjb3VuZHJlbCgpXG4gICAgdGhpcy5zeXN0ZW1UZXN0SHR0cFNlcnZlcj8uY2xvc2UoKVxuICAgIHRoaXMud3NzPy5jbG9zZSgpXG4gICAgYXdhaXQgdGhpcy5kcml2ZXI/LnF1aXQoKVxuICB9XG5cbiAgLyoqXG4gICAqIFZpc2l0cyBhIHBhdGggaW4gdGhlIGJyb3dzZXJcbiAgICogQHBhcmFtIHtzdHJpbmd9IHBhdGhcbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gICAqL1xuICBhc3luYyBkcml2ZXJWaXNpdChwYXRoKSB7XG4gICAgY29uc3QgdXJsID0gYCR7dGhpcy5jdXJyZW50VXJsfSR7cGF0aH1gXG5cbiAgICBhd2FpdCB0aGlzLmdldERyaXZlcigpLmdldCh1cmwpXG4gIH1cblxuICAvKipcbiAgICogVGFrZXMgYSBzY3JlZW5zaG90LCBzYXZlcyBIVE1MIGFuZCBicm93c2VyIGxvZ3NcbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gICAqL1xuICBhc3luYyB0YWtlU2NyZWVuc2hvdCgpIHtcbiAgICBjb25zdCBwYXRoID0gYCR7cHJvY2Vzcy5jd2QoKX0vdG1wL3NjcmVlbnNob3RzYFxuXG4gICAgYXdhaXQgZnMubWtkaXIocGF0aCwge3JlY3Vyc2l2ZTogdHJ1ZX0pXG5cbiAgICBjb25zdCBpbWFnZUNvbnRlbnQgPSBhd2FpdCB0aGlzLmdldERyaXZlcigpLnRha2VTY3JlZW5zaG90KClcbiAgICBjb25zdCBub3cgPSBuZXcgRGF0ZSgpXG4gICAgY29uc3Qgc2NyZWVuc2hvdFBhdGggPSBgJHtwYXRofS8ke21vbWVudChub3cpLmZvcm1hdChcIllZWVktTU0tREQtSEgtTU0tU1NcIil9LnBuZ2BcbiAgICBjb25zdCBodG1sUGF0aCA9IGAke3BhdGh9LyR7bW9tZW50KG5vdykuZm9ybWF0KFwiWVlZWS1NTS1ERC1ISC1NTS1TU1wiKX0uaHRtbGBcbiAgICBjb25zdCBsb2dzUGF0aCA9IGAke3BhdGh9LyR7bW9tZW50KG5vdykuZm9ybWF0KFwiWVlZWS1NTS1ERC1ISC1NTS1TU1wiKX0ubG9ncy50eHRgXG4gICAgY29uc3QgbG9nc1RleHQgPSBhd2FpdCB0aGlzLmdldEJyb3dzZXJMb2dzKClcbiAgICBjb25zdCBodG1sID0gYXdhaXQgdGhpcy5nZXRIVE1MKClcbiAgICBjb25zdCBodG1sUHJldHR5ID0gcHJldHRpZnkoaHRtbClcblxuICAgIGF3YWl0IGZzLndyaXRlRmlsZShodG1sUGF0aCwgaHRtbFByZXR0eSlcbiAgICBhd2FpdCBmcy53cml0ZUZpbGUobG9nc1BhdGgsIGxvZ3NUZXh0LmpvaW4oXCJcXG5cIikpXG4gICAgYXdhaXQgZnMud3JpdGVGaWxlKHNjcmVlbnNob3RQYXRoLCBpbWFnZUNvbnRlbnQsIFwiYmFzZTY0XCIpXG5cbiAgICBjb25zb2xlLmxvZyhcIkN1cnJlbnQgVVJMOlwiLCBhd2FpdCB0aGlzLmdldEN1cnJlbnRVcmwoKSlcbiAgICBjb25zb2xlLmxvZyhcIkxvZ3M6XCIsIGxvZ3NQYXRoKVxuICAgIGNvbnNvbGUubG9nKFwiU2NyZWVuc2hvdDpcIiwgc2NyZWVuc2hvdFBhdGgpXG4gICAgY29uc29sZS5sb2coXCJIVE1MOlwiLCBodG1sUGF0aClcbiAgfVxuXG4gIC8qKlxuICAgKiBWaXNpdHMgYSBwYXRoIGluIHRoZSBicm93c2VyXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBwYXRoXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgdmlzaXQocGF0aCkge1xuICAgIGF3YWl0IHRoaXMuZ2V0Q29tbXVuaWNhdG9yKCkuc2VuZENvbW1hbmQoe3R5cGU6IFwidmlzaXRcIiwgcGF0aH0pXG4gIH1cblxuICAvKipcbiAgICogRGlzbWlzc2VzIHRvIGEgcGF0aCBpbiB0aGUgYnJvd3NlclxuICAgKiBAcGFyYW0ge3N0cmluZ30gcGF0aFxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAgICovXG4gIGFzeW5jIGRpc21pc3NUbyhwYXRoKSB7XG4gICAgYXdhaXQgdGhpcy5nZXRDb21tdW5pY2F0b3IoKS5zZW5kQ29tbWFuZCh7dHlwZTogXCJkaXNtaXNzVG9cIiwgcGF0aH0pXG4gIH1cbn1cbiJdfQ==