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.
- package/build/index.js +3 -0
- package/{dist → build}/system-test-browser-helper.d.ts +2 -2
- package/build/system-test-browser-helper.js +238 -0
- package/build/system-test-communicator.js +105 -0
- package/build/system-test-http-server.js +66 -0
- package/{dist → build}/system-test.d.ts +15 -24
- package/build/system-test.js +696 -0
- package/build/use-system-test.js +79 -0
- package/package.json +10 -6
- package/dist/index.js +0 -3
- package/dist/index.js.map +0 -1
- package/dist/system-test-browser-helper.js +0 -238
- package/dist/system-test-browser-helper.js.map +0 -1
- package/dist/system-test-communicator.js +0 -105
- package/dist/system-test-communicator.js.map +0 -1
- package/dist/system-test-http-server.js +0 -66
- package/dist/system-test-http-server.js.map +0 -1
- package/dist/system-test.js +0 -705
- package/dist/system-test.js.map +0 -1
- package/dist/use-system-test.js +0 -79
- package/dist/use-system-test.js.map +0 -1
- /package/{dist → build}/index.d.ts +0 -0
- /package/{dist → build}/system-test-communicator.d.ts +0 -0
- /package/{dist → build}/system-test-http-server.d.ts +0 -0
- /package/{dist → build}/use-system-test.d.ts +0 -0
|
@@ -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==
|