automation_model 1.0.498-dev → 1.0.498-stage
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/lib/api.d.ts +2 -1
- package/lib/api.js +95 -98
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +2 -1
- package/lib/auto_page.js +19 -2
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +6 -3
- package/lib/browser_manager.js +108 -41
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -0
- package/lib/command_common.js +54 -6
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.d.ts +6 -0
- package/lib/error-messages.js +188 -0
- package/lib/error-messages.js.map +1 -0
- package/lib/generation_scripts.d.ts +4 -0
- package/lib/generation_scripts.js +2 -0
- package/lib/generation_scripts.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +20 -6
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +15 -13
- package/lib/locate_element.js.map +1 -1
- package/lib/locator.d.ts +36 -0
- package/lib/locator.js +165 -0
- package/lib/locator.js.map +1 -1
- package/lib/locator_log.d.ts +26 -0
- package/lib/locator_log.js +69 -0
- package/lib/locator_log.js.map +1 -0
- package/lib/network.js +16 -3
- package/lib/network.js.map +1 -1
- package/lib/scripts/find_text.js +126 -0
- package/lib/stable_browser.d.ts +50 -18
- package/lib/stable_browser.js +632 -579
- package/lib/stable_browser.js.map +1 -1
- package/lib/table.d.ts +13 -0
- package/lib/table.js +187 -0
- package/lib/table.js.map +1 -0
- package/lib/test_context.d.ts +1 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +12 -1
- package/lib/utils.js +235 -3
- package/lib/utils.js.map +1 -1
- package/package.json +7 -5
- /package/lib/{axe → scripts}/axe.mini.js +0 -0
package/lib/stable_browser.js
CHANGED
|
@@ -10,15 +10,17 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import { maskValue, replaceWithLocalTestData } from "./utils.js";
|
|
13
|
+
import { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
17
|
import { getContext } from "./init_browser.js";
|
|
18
18
|
import { locate_element } from "./locate_element.js";
|
|
19
|
-
import {
|
|
19
|
+
import { randomUUID } from "crypto";
|
|
20
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
20
21
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
21
|
-
|
|
22
|
+
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
export const Types = {
|
|
22
24
|
CLICK: "click_element",
|
|
23
25
|
NAVIGATE: "navigate",
|
|
24
26
|
FILL: "fill_element",
|
|
@@ -29,6 +31,8 @@ const Types = {
|
|
|
29
31
|
GET_PAGE_STATUS: "get_page_status",
|
|
30
32
|
CLICK_ROW_ACTION: "click_row_action",
|
|
31
33
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
34
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
35
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
32
36
|
ANALYZE_TABLE: "analyze_table",
|
|
33
37
|
SELECT: "select_combobox",
|
|
34
38
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -44,6 +48,9 @@ const Types = {
|
|
|
44
48
|
VERIFY_VISUAL: "verify_visual",
|
|
45
49
|
LOAD_DATA: "load_data",
|
|
46
50
|
SET_INPUT: "set_input",
|
|
51
|
+
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
52
|
+
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
53
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
47
54
|
};
|
|
48
55
|
export const apps = {};
|
|
49
56
|
class StableBrowser {
|
|
@@ -57,6 +64,7 @@ class StableBrowser {
|
|
|
57
64
|
networkLogger = null;
|
|
58
65
|
configuration = null;
|
|
59
66
|
appName = "main";
|
|
67
|
+
tags = null;
|
|
60
68
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
61
69
|
this.browser = browser;
|
|
62
70
|
this.page = page;
|
|
@@ -87,17 +95,17 @@ class StableBrowser {
|
|
|
87
95
|
catch (e) {
|
|
88
96
|
this.logger.error("unable to read ai_config.json");
|
|
89
97
|
}
|
|
90
|
-
context.pageLoading = { status: false };
|
|
91
|
-
context.pages = [this.page];
|
|
92
98
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
93
99
|
this.world = world;
|
|
100
|
+
context.pages = [this.page];
|
|
101
|
+
context.pageLoading = { status: false };
|
|
94
102
|
this.registerEventListeners(this.context);
|
|
95
103
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
96
104
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
97
105
|
}
|
|
98
106
|
registerEventListeners(context) {
|
|
99
107
|
this.registerConsoleLogListener(this.page, context);
|
|
100
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
108
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
101
109
|
if (!context.pageLoading) {
|
|
102
110
|
context.pageLoading = { status: false };
|
|
103
111
|
}
|
|
@@ -142,9 +150,9 @@ class StableBrowser {
|
|
|
142
150
|
if (this.appName === appName) {
|
|
143
151
|
return;
|
|
144
152
|
}
|
|
145
|
-
let
|
|
153
|
+
let navigate = false;
|
|
146
154
|
if (!apps[appName]) {
|
|
147
|
-
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this);
|
|
155
|
+
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
148
156
|
newContextCreated = true;
|
|
149
157
|
apps[appName] = {
|
|
150
158
|
context: newContext,
|
|
@@ -153,32 +161,15 @@ class StableBrowser {
|
|
|
153
161
|
};
|
|
154
162
|
}
|
|
155
163
|
const tempContext = {};
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
_copyContext(this, tempContext);
|
|
165
|
+
_copyContext(apps[appName], this);
|
|
158
166
|
apps[this.appName] = tempContext;
|
|
159
167
|
this.appName = appName;
|
|
160
|
-
if (
|
|
161
|
-
this.registerEventListeners(this.context);
|
|
168
|
+
if (navigate) {
|
|
162
169
|
await this.goto(this.context.environment.baseUrl);
|
|
163
170
|
await this.waitForPageLoad();
|
|
164
171
|
}
|
|
165
172
|
}
|
|
166
|
-
_copyContext(from, to) {
|
|
167
|
-
to.browser = from.browser;
|
|
168
|
-
to.page = from.page;
|
|
169
|
-
to.context = from.context;
|
|
170
|
-
}
|
|
171
|
-
getWebLogFile(logFolder) {
|
|
172
|
-
if (!fs.existsSync(logFolder)) {
|
|
173
|
-
fs.mkdirSync(logFolder, { recursive: true });
|
|
174
|
-
}
|
|
175
|
-
let nextIndex = 1;
|
|
176
|
-
while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
|
|
177
|
-
nextIndex++;
|
|
178
|
-
}
|
|
179
|
-
const fileName = nextIndex + ".json";
|
|
180
|
-
return path.join(logFolder, fileName);
|
|
181
|
-
}
|
|
182
173
|
registerConsoleLogListener(page, context) {
|
|
183
174
|
if (!this.context.webLogger) {
|
|
184
175
|
this.context.webLogger = [];
|
|
@@ -228,7 +219,7 @@ class StableBrowser {
|
|
|
228
219
|
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
229
220
|
}
|
|
230
221
|
catch (error) {
|
|
231
|
-
console.error("Error in request listener", error);
|
|
222
|
+
// console.error("Error in request listener", error);
|
|
232
223
|
context.networkLogger.push({
|
|
233
224
|
error: "not able to listen",
|
|
234
225
|
message: error.message,
|
|
@@ -242,55 +233,48 @@ class StableBrowser {
|
|
|
242
233
|
// async closeUnexpectedPopups() {
|
|
243
234
|
// await closeUnexpectedPopups(this.page);
|
|
244
235
|
// }
|
|
245
|
-
async goto(url) {
|
|
236
|
+
async goto(url, world = null) {
|
|
246
237
|
if (!url.startsWith("http")) {
|
|
247
238
|
url = "https://" + url;
|
|
248
239
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
240
|
+
const state = {
|
|
241
|
+
value: url,
|
|
242
|
+
world: world,
|
|
243
|
+
type: Types.NAVIGATE,
|
|
244
|
+
text: `Navigate Page to: ${url}`,
|
|
245
|
+
operation: "goto",
|
|
246
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
247
|
+
info: {},
|
|
248
|
+
locate: false,
|
|
249
|
+
scroll: false,
|
|
250
|
+
screenshot: false,
|
|
251
|
+
highlight: false,
|
|
252
|
+
};
|
|
253
|
+
try {
|
|
254
|
+
await _preCommand(state, this);
|
|
255
|
+
await this.page.goto(url, {
|
|
256
|
+
timeout: 60000,
|
|
257
|
+
});
|
|
258
|
+
await _screenshot(state, this);
|
|
256
259
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// remove the _ prefix
|
|
261
|
-
regValue = key.substring(1);
|
|
262
|
-
}
|
|
263
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.error("Error on goto", error);
|
|
262
|
+
_commandError(state, error, this);
|
|
264
263
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
268
|
-
// check if not null
|
|
269
|
-
if (!locator) {
|
|
270
|
-
return locator;
|
|
264
|
+
finally {
|
|
265
|
+
_commandFinally(state, this);
|
|
271
266
|
}
|
|
272
|
-
// clone the locator
|
|
273
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
274
|
-
this.scanAndManipulate(locator, _params);
|
|
275
|
-
return locator;
|
|
276
|
-
}
|
|
277
|
-
_isObject(value) {
|
|
278
|
-
return value && typeof value === "object" && value.constructor === Object;
|
|
279
267
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
268
|
+
async _getLocator(locator, scope, _params) {
|
|
269
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
270
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
271
|
+
for (let key in locator) {
|
|
272
|
+
if (typeof locator[key] !== "string")
|
|
273
|
+
continue;
|
|
274
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
275
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
289
276
|
}
|
|
290
277
|
}
|
|
291
|
-
}
|
|
292
|
-
_getLocator(locator, scope, _params) {
|
|
293
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
294
278
|
let locatorReturn;
|
|
295
279
|
if (locator.role) {
|
|
296
280
|
if (locator.role[1].nameReg) {
|
|
@@ -298,7 +282,7 @@ class StableBrowser {
|
|
|
298
282
|
delete locator.role[1].nameReg;
|
|
299
283
|
}
|
|
300
284
|
// if (locator.role[1].name) {
|
|
301
|
-
// locator.role[1].name =
|
|
285
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
302
286
|
// }
|
|
303
287
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
304
288
|
}
|
|
@@ -341,7 +325,7 @@ class StableBrowser {
|
|
|
341
325
|
if (css && css.locator) {
|
|
342
326
|
css = css.locator;
|
|
343
327
|
}
|
|
344
|
-
let result = await this._locateElementByText(scope,
|
|
328
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
345
329
|
if (result.elementCount === 0) {
|
|
346
330
|
return;
|
|
347
331
|
}
|
|
@@ -357,116 +341,42 @@ class StableBrowser {
|
|
|
357
341
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
358
342
|
//const stringifyText = JSON.stringify(text);
|
|
359
343
|
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (currentNode === parent) {
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
currentNode = currentNode.parentNode;
|
|
367
|
-
}
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
document.isParent = isParent;
|
|
371
|
-
function getRegex(str) {
|
|
372
|
-
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
373
|
-
if (!match) {
|
|
374
|
-
return null;
|
|
375
|
-
}
|
|
376
|
-
let [_, pattern, flags] = match;
|
|
377
|
-
return new RegExp(pattern, flags);
|
|
378
|
-
}
|
|
379
|
-
document.getRegex = getRegex;
|
|
380
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
381
|
-
// Check and add the element if it has a shadow root
|
|
382
|
-
if (element.shadowRoot) {
|
|
383
|
-
result.push(element);
|
|
384
|
-
// Also search within the shadow root
|
|
385
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
386
|
-
}
|
|
387
|
-
// Iterate over child nodes
|
|
388
|
-
element.childNodes.forEach((child) => {
|
|
389
|
-
// Recursively call the function for each child node
|
|
390
|
-
document.collectAllShadowDomElements(child, result);
|
|
391
|
-
});
|
|
392
|
-
return result;
|
|
393
|
-
}
|
|
394
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
395
|
-
if (!tag) {
|
|
396
|
-
tag = "*";
|
|
397
|
-
}
|
|
398
|
-
let regexpSearch = document.getRegex(text);
|
|
399
|
-
if (regexpSearch) {
|
|
400
|
-
regex = true;
|
|
401
|
-
}
|
|
402
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
403
|
-
let shadowHosts = [];
|
|
404
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
405
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
406
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
407
|
-
if (!shadowElement) {
|
|
408
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
412
|
-
elements = elements.concat(shadowElements);
|
|
413
|
-
}
|
|
414
|
-
let randomToken = null;
|
|
415
|
-
const foundElements = [];
|
|
344
|
+
const options = {
|
|
345
|
+
innerText: true,
|
|
346
|
+
};
|
|
416
347
|
if (regex) {
|
|
417
|
-
|
|
418
|
-
regexpSearch = new RegExp(text, "im");
|
|
419
|
-
}
|
|
420
|
-
for (let i = 0; i < elements.length; i++) {
|
|
421
|
-
const element = elements[i];
|
|
422
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
423
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
424
|
-
foundElements.push(element);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
348
|
+
options.singleRegex = true;
|
|
427
349
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
for (let i = 0; i < elements.length; i++) {
|
|
431
|
-
const element = elements[i];
|
|
432
|
-
if (partial) {
|
|
433
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
434
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
435
|
-
foundElements.push(element);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
440
|
-
(element.value && element.value === text)) {
|
|
441
|
-
foundElements.push(element);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
350
|
+
if (tag) {
|
|
351
|
+
options.tag = tag;
|
|
445
352
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
353
|
+
if (!(partial === true)) {
|
|
354
|
+
options.exactMatch = true;
|
|
355
|
+
}
|
|
356
|
+
if (text.startsWith("/") && text.endsWith("/")) {
|
|
357
|
+
if (text.length < 3) {
|
|
358
|
+
throw new Error("Invalid regex pattern: empty pattern");
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
const pattern = text.slice(1, -1);
|
|
362
|
+
new RegExp(pattern); // Validate pattern
|
|
363
|
+
text = pattern;
|
|
364
|
+
options.singleRegex = true;
|
|
458
365
|
}
|
|
459
|
-
|
|
460
|
-
|
|
366
|
+
catch (e) {
|
|
367
|
+
throw new Error(`Invalid regex pattern: ${e.message}`);
|
|
461
368
|
}
|
|
462
369
|
}
|
|
370
|
+
const elements = window.findMatchingElements(text, options);
|
|
371
|
+
let randomToken = null;
|
|
372
|
+
const foundElements = [];
|
|
463
373
|
let elementCount = 0;
|
|
464
|
-
if (
|
|
465
|
-
for (let i = 0; i <
|
|
374
|
+
if (elements.length > 0) {
|
|
375
|
+
for (let i = 0; i < elements.length; i++) {
|
|
466
376
|
if (randomToken === null) {
|
|
467
377
|
randomToken = Math.random().toString(36).substring(7);
|
|
468
378
|
}
|
|
469
|
-
let element =
|
|
379
|
+
let element = elements[i];
|
|
470
380
|
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
471
381
|
elementCount++;
|
|
472
382
|
}
|
|
@@ -475,9 +385,21 @@ class StableBrowser {
|
|
|
475
385
|
}, [text1, tag1, regex1, partial1]);
|
|
476
386
|
}
|
|
477
387
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
388
|
+
if (!info) {
|
|
389
|
+
info = {};
|
|
390
|
+
}
|
|
391
|
+
if (!info.failCause) {
|
|
392
|
+
info.failCause = {};
|
|
393
|
+
}
|
|
394
|
+
if (!info.log) {
|
|
395
|
+
info.log = "";
|
|
396
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
397
|
+
}
|
|
478
398
|
let locatorSearch = selectorHierarchy[index];
|
|
399
|
+
let originalLocatorSearch = "";
|
|
479
400
|
try {
|
|
480
|
-
|
|
401
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
402
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
481
403
|
}
|
|
482
404
|
catch (e) {
|
|
483
405
|
console.error(e);
|
|
@@ -487,36 +409,50 @@ class StableBrowser {
|
|
|
487
409
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
488
410
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
489
411
|
if (!locatorString) {
|
|
412
|
+
info.failCause.textNotFound = true;
|
|
413
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
490
414
|
return;
|
|
491
415
|
}
|
|
492
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
416
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
493
417
|
}
|
|
494
418
|
else if (locatorSearch.text) {
|
|
495
|
-
let
|
|
419
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
420
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
496
421
|
if (result.elementCount === 0) {
|
|
422
|
+
info.failCause.textNotFound = true;
|
|
423
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
497
424
|
return;
|
|
498
425
|
}
|
|
499
426
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
500
427
|
if (locatorSearch.childCss) {
|
|
501
428
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
502
429
|
}
|
|
503
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
430
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
504
431
|
}
|
|
505
432
|
else {
|
|
506
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
433
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
507
434
|
}
|
|
508
435
|
// let cssHref = false;
|
|
509
436
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
510
437
|
// cssHref = true;
|
|
511
438
|
// }
|
|
512
439
|
let count = await locator.count();
|
|
440
|
+
if (count > 0 && !info.failCause.count) {
|
|
441
|
+
info.failCause.count = count;
|
|
442
|
+
}
|
|
513
443
|
//info.log += "total elements found " + count + "\n";
|
|
514
444
|
//let visibleCount = 0;
|
|
515
445
|
let visibleLocator = null;
|
|
516
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
446
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
517
447
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
448
|
+
if (info.locatorLog) {
|
|
449
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
450
|
+
}
|
|
518
451
|
return;
|
|
519
452
|
}
|
|
453
|
+
if (info.locatorLog && count === 0) {
|
|
454
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
455
|
+
}
|
|
520
456
|
for (let j = 0; j < count; j++) {
|
|
521
457
|
let visible = await locator.nth(j).isVisible();
|
|
522
458
|
const enabled = await locator.nth(j).isEnabled();
|
|
@@ -525,24 +461,40 @@ class StableBrowser {
|
|
|
525
461
|
}
|
|
526
462
|
if (visible && enabled) {
|
|
527
463
|
foundLocators.push(locator.nth(j));
|
|
464
|
+
if (info.locatorLog) {
|
|
465
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
466
|
+
}
|
|
528
467
|
}
|
|
529
468
|
else {
|
|
469
|
+
info.failCause.visible = visible;
|
|
470
|
+
info.failCause.enabled = enabled;
|
|
530
471
|
if (!info.printMessages) {
|
|
531
472
|
info.printMessages = {};
|
|
532
473
|
}
|
|
474
|
+
if (info.locatorLog && !visible) {
|
|
475
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
476
|
+
}
|
|
477
|
+
if (info.locatorLog && !enabled) {
|
|
478
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
479
|
+
}
|
|
533
480
|
if (!info.printMessages[j.toString()]) {
|
|
534
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
481
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
535
482
|
info.printMessages[j.toString()] = true;
|
|
536
483
|
}
|
|
537
484
|
}
|
|
538
485
|
}
|
|
539
486
|
}
|
|
540
487
|
async closeUnexpectedPopups(info, _params) {
|
|
488
|
+
if (!info) {
|
|
489
|
+
info = {};
|
|
490
|
+
info.failCause = {};
|
|
491
|
+
info.log = "";
|
|
492
|
+
}
|
|
541
493
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
542
494
|
if (!info) {
|
|
543
495
|
info = {};
|
|
544
496
|
}
|
|
545
|
-
info.log += "scan for popup handlers" + "\n";
|
|
497
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
546
498
|
const handlerGroup = [];
|
|
547
499
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
548
500
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -569,9 +521,21 @@ class StableBrowser {
|
|
|
569
521
|
}
|
|
570
522
|
if (result.foundElements.length > 0) {
|
|
571
523
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
524
|
+
try {
|
|
525
|
+
await scope?.evaluate(() => {
|
|
526
|
+
window.__isClosingPopups = true;
|
|
527
|
+
});
|
|
528
|
+
await dialogCloseLocator.click();
|
|
529
|
+
// wait for the dialog to close
|
|
530
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
531
|
+
}
|
|
532
|
+
catch (e) {
|
|
533
|
+
}
|
|
534
|
+
finally {
|
|
535
|
+
await scope?.evaluate(() => {
|
|
536
|
+
window.__isClosingPopups = false;
|
|
537
|
+
});
|
|
538
|
+
}
|
|
575
539
|
return { rerun: true };
|
|
576
540
|
}
|
|
577
541
|
}
|
|
@@ -595,7 +559,13 @@ class StableBrowser {
|
|
|
595
559
|
}
|
|
596
560
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
597
561
|
}
|
|
598
|
-
async _findFrameScope(selectors, timeout = 30000) {
|
|
562
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
563
|
+
if (!info) {
|
|
564
|
+
info = {};
|
|
565
|
+
info.failCause = {};
|
|
566
|
+
info.log = "";
|
|
567
|
+
}
|
|
568
|
+
let startTime = Date.now();
|
|
599
569
|
let scope = this.page;
|
|
600
570
|
if (selectors.frame) {
|
|
601
571
|
return selectors.frame;
|
|
@@ -626,9 +596,11 @@ class StableBrowser {
|
|
|
626
596
|
}
|
|
627
597
|
return framescope;
|
|
628
598
|
};
|
|
599
|
+
let fLocator = null;
|
|
629
600
|
while (true) {
|
|
630
601
|
let frameFound = false;
|
|
631
602
|
if (selectors.nestFrmLoc) {
|
|
603
|
+
fLocator = selectors.nestFrmLoc;
|
|
632
604
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
633
605
|
frameFound = true;
|
|
634
606
|
break;
|
|
@@ -637,6 +609,7 @@ class StableBrowser {
|
|
|
637
609
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
638
610
|
let frameLocator = selectors.frameLocators[i];
|
|
639
611
|
if (frameLocator.css) {
|
|
612
|
+
fLocator = frameLocator.css;
|
|
640
613
|
scope = scope.frameLocator(frameLocator.css);
|
|
641
614
|
frameFound = true;
|
|
642
615
|
break;
|
|
@@ -644,16 +617,25 @@ class StableBrowser {
|
|
|
644
617
|
}
|
|
645
618
|
}
|
|
646
619
|
if (!frameFound && selectors.iframe_src) {
|
|
620
|
+
fLocator = selectors.iframe_src;
|
|
647
621
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
648
622
|
}
|
|
649
623
|
if (!scope) {
|
|
650
|
-
info
|
|
651
|
-
|
|
624
|
+
if (info && info.locatorLog) {
|
|
625
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
626
|
+
}
|
|
627
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
628
|
+
if (Date.now() - startTime > timeout) {
|
|
629
|
+
info.failCause.iframeNotFound = true;
|
|
630
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
652
631
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
653
632
|
}
|
|
654
633
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
655
634
|
}
|
|
656
635
|
else {
|
|
636
|
+
if (info && info.locatorLog) {
|
|
637
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
638
|
+
}
|
|
657
639
|
break;
|
|
658
640
|
}
|
|
659
641
|
}
|
|
@@ -663,20 +645,27 @@ class StableBrowser {
|
|
|
663
645
|
}
|
|
664
646
|
return scope;
|
|
665
647
|
}
|
|
666
|
-
async _getDocumentBody(selectors, timeout = 30000) {
|
|
667
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
648
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
649
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
668
650
|
return scope.evaluate(() => {
|
|
669
651
|
var bodyContent = document.body.innerHTML;
|
|
670
652
|
return bodyContent;
|
|
671
653
|
});
|
|
672
654
|
}
|
|
673
655
|
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
656
|
+
if (!info) {
|
|
657
|
+
info = {};
|
|
658
|
+
info.failCause = {};
|
|
659
|
+
info.log = "";
|
|
660
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
661
|
+
}
|
|
674
662
|
let highPriorityTimeout = 5000;
|
|
675
663
|
let visibleOnlyTimeout = 6000;
|
|
676
|
-
let startTime =
|
|
664
|
+
let startTime = Date.now();
|
|
677
665
|
let locatorsCount = 0;
|
|
666
|
+
let lazy_scroll = false;
|
|
678
667
|
//let arrayMode = Array.isArray(selectors);
|
|
679
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
668
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
680
669
|
let selectorsLocators = null;
|
|
681
670
|
selectorsLocators = selectors.locators;
|
|
682
671
|
// group selectors by priority
|
|
@@ -763,21 +752,33 @@ class StableBrowser {
|
|
|
763
752
|
return maxCountElement.locator;
|
|
764
753
|
}
|
|
765
754
|
}
|
|
766
|
-
if (
|
|
755
|
+
if (Date.now() - startTime > timeout) {
|
|
767
756
|
break;
|
|
768
757
|
}
|
|
769
|
-
if (
|
|
758
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
770
759
|
info.log += "high priority timeout, will try all elements" + "\n";
|
|
771
760
|
highPriorityOnly = false;
|
|
761
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
762
|
+
lazy_scroll = true;
|
|
763
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
764
|
+
}
|
|
772
765
|
}
|
|
773
|
-
if (
|
|
766
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
774
767
|
info.log += "visible only timeout, will try all elements" + "\n";
|
|
775
768
|
visibleOnly = false;
|
|
776
769
|
}
|
|
777
770
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
778
771
|
}
|
|
779
772
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
780
|
-
info.
|
|
773
|
+
// if (info.locatorLog) {
|
|
774
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
775
|
+
// for (let line of lines) {
|
|
776
|
+
// this.logger.debug(line);
|
|
777
|
+
// }
|
|
778
|
+
// }
|
|
779
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
780
|
+
info.failCause.locatorNotFound = true;
|
|
781
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
781
782
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
782
783
|
}
|
|
783
784
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -791,8 +792,9 @@ class StableBrowser {
|
|
|
791
792
|
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
792
793
|
}
|
|
793
794
|
catch (e) {
|
|
794
|
-
this
|
|
795
|
-
this.logger.debug(
|
|
795
|
+
// this call can fail it the browser is navigating
|
|
796
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
797
|
+
// this.logger.debug(e);
|
|
796
798
|
foundLocators = [];
|
|
797
799
|
try {
|
|
798
800
|
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
@@ -809,10 +811,29 @@ class StableBrowser {
|
|
|
809
811
|
});
|
|
810
812
|
result.locatorIndex = i;
|
|
811
813
|
}
|
|
814
|
+
if (foundLocators.length > 1) {
|
|
815
|
+
info.failCause.foundMultiple = true;
|
|
816
|
+
if (info.locatorLog) {
|
|
817
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
818
|
+
}
|
|
819
|
+
}
|
|
812
820
|
}
|
|
813
821
|
return result;
|
|
814
822
|
}
|
|
815
823
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
824
|
+
const state = {
|
|
825
|
+
locate: false,
|
|
826
|
+
scroll: false,
|
|
827
|
+
highlight: false,
|
|
828
|
+
_params,
|
|
829
|
+
options,
|
|
830
|
+
world,
|
|
831
|
+
type: Types.CLICK,
|
|
832
|
+
text: "Click element",
|
|
833
|
+
operation: "simpleClick",
|
|
834
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
835
|
+
};
|
|
836
|
+
_preCommand(state, this);
|
|
816
837
|
const startTime = Date.now();
|
|
817
838
|
let timeout = 30000;
|
|
818
839
|
if (options && options.timeout) {
|
|
@@ -836,13 +857,32 @@ class StableBrowser {
|
|
|
836
857
|
}
|
|
837
858
|
catch (e) {
|
|
838
859
|
if (performance.now() - startTime > timeout) {
|
|
839
|
-
throw e;
|
|
860
|
+
// throw e;
|
|
861
|
+
try {
|
|
862
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
863
|
+
}
|
|
864
|
+
finally {
|
|
865
|
+
_commandFinally(state, this);
|
|
866
|
+
}
|
|
840
867
|
}
|
|
841
868
|
}
|
|
842
869
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
843
870
|
}
|
|
844
871
|
}
|
|
845
872
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
873
|
+
const state = {
|
|
874
|
+
locate: false,
|
|
875
|
+
scroll: false,
|
|
876
|
+
highlight: false,
|
|
877
|
+
_params,
|
|
878
|
+
options,
|
|
879
|
+
world,
|
|
880
|
+
type: Types.FILL,
|
|
881
|
+
text: "Fill element",
|
|
882
|
+
operation: "simpleClickType",
|
|
883
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
884
|
+
};
|
|
885
|
+
_preCommand(state, this);
|
|
846
886
|
const startTime = Date.now();
|
|
847
887
|
let timeout = 30000;
|
|
848
888
|
if (options && options.timeout) {
|
|
@@ -866,7 +906,13 @@ class StableBrowser {
|
|
|
866
906
|
}
|
|
867
907
|
catch (e) {
|
|
868
908
|
if (performance.now() - startTime > timeout) {
|
|
869
|
-
throw e;
|
|
909
|
+
// throw e;
|
|
910
|
+
try {
|
|
911
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
912
|
+
}
|
|
913
|
+
finally {
|
|
914
|
+
_commandFinally(state, this);
|
|
915
|
+
}
|
|
870
916
|
}
|
|
871
917
|
}
|
|
872
918
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -885,18 +931,18 @@ class StableBrowser {
|
|
|
885
931
|
};
|
|
886
932
|
try {
|
|
887
933
|
await _preCommand(state, this);
|
|
888
|
-
if (state.options && state.options.context) {
|
|
889
|
-
|
|
890
|
-
}
|
|
934
|
+
// if (state.options && state.options.context) {
|
|
935
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
936
|
+
// }
|
|
891
937
|
try {
|
|
892
938
|
await state.element.click();
|
|
893
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
939
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
894
940
|
}
|
|
895
941
|
catch (e) {
|
|
896
942
|
// await this.closeUnexpectedPopups();
|
|
897
943
|
state.element = await this._locate(selectors, state.info, _params);
|
|
898
944
|
await state.element.dispatchEvent("click");
|
|
899
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
945
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
900
946
|
}
|
|
901
947
|
await this.waitForPageLoad();
|
|
902
948
|
return state.info;
|
|
@@ -975,7 +1021,6 @@ class StableBrowser {
|
|
|
975
1021
|
await state.element.hover({ timeout: 10000 });
|
|
976
1022
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
977
1023
|
}
|
|
978
|
-
await _screenshot(state, this);
|
|
979
1024
|
await this.waitForPageLoad();
|
|
980
1025
|
return state.info;
|
|
981
1026
|
}
|
|
@@ -1103,33 +1148,30 @@ class StableBrowser {
|
|
|
1103
1148
|
}
|
|
1104
1149
|
}
|
|
1105
1150
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1151
|
+
const state = {
|
|
1152
|
+
selectors,
|
|
1153
|
+
_params,
|
|
1154
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1155
|
+
options,
|
|
1156
|
+
world,
|
|
1157
|
+
type: Types.SET_DATE_TIME,
|
|
1158
|
+
text: `Set date time value: ${value}`,
|
|
1159
|
+
operation: "setDateTime",
|
|
1160
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1161
|
+
throwError: false,
|
|
1162
|
+
};
|
|
1116
1163
|
try {
|
|
1117
|
-
|
|
1118
|
-
let element = await this._locate(selectors, info, _params);
|
|
1119
|
-
//insert red border around the element
|
|
1120
|
-
await this.scrollIfNeeded(element, info);
|
|
1121
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1122
|
-
await this._highlightElements(element);
|
|
1164
|
+
await _preCommand(state, this);
|
|
1123
1165
|
try {
|
|
1124
|
-
await element.click();
|
|
1166
|
+
await state.element.click();
|
|
1125
1167
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1126
1168
|
if (format) {
|
|
1127
|
-
value = dayjs(value).format(format);
|
|
1128
|
-
await element.fill(value);
|
|
1169
|
+
state.value = dayjs(state.value).format(format);
|
|
1170
|
+
await state.element.fill(state.value);
|
|
1129
1171
|
}
|
|
1130
1172
|
else {
|
|
1131
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1132
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1173
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1174
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1133
1175
|
el.value = ""; // clear input
|
|
1134
1176
|
el.value = dateTimeValue;
|
|
1135
1177
|
}, dateTimeValue);
|
|
@@ -1142,20 +1184,19 @@ class StableBrowser {
|
|
|
1142
1184
|
}
|
|
1143
1185
|
catch (err) {
|
|
1144
1186
|
//await this.closeUnexpectedPopups();
|
|
1145
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1187
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1146
1188
|
this.logger.info("Trying again");
|
|
1147
|
-
|
|
1148
|
-
info.
|
|
1149
|
-
Object.assign(err, { info: info });
|
|
1189
|
+
await _screenshot(state, this);
|
|
1190
|
+
Object.assign(err, { info: state.info });
|
|
1150
1191
|
await element.click();
|
|
1151
1192
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1152
1193
|
if (format) {
|
|
1153
|
-
value = dayjs(value).format(format);
|
|
1154
|
-
await element.fill(value);
|
|
1194
|
+
state.value = dayjs(state.value).format(format);
|
|
1195
|
+
await state.element.fill(state.value);
|
|
1155
1196
|
}
|
|
1156
1197
|
else {
|
|
1157
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1158
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1198
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1199
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1159
1200
|
el.value = ""; // clear input
|
|
1160
1201
|
el.value = dateTimeValue;
|
|
1161
1202
|
}, dateTimeValue);
|
|
@@ -1168,31 +1209,10 @@ class StableBrowser {
|
|
|
1168
1209
|
}
|
|
1169
1210
|
}
|
|
1170
1211
|
catch (e) {
|
|
1171
|
-
|
|
1172
|
-
throw e;
|
|
1212
|
+
await _commandError(state, e, this);
|
|
1173
1213
|
}
|
|
1174
1214
|
finally {
|
|
1175
|
-
|
|
1176
|
-
this._reportToWorld(world, {
|
|
1177
|
-
element_name: selectors.element_name,
|
|
1178
|
-
type: Types.SET_DATE_TIME,
|
|
1179
|
-
screenshotId,
|
|
1180
|
-
value: value,
|
|
1181
|
-
text: `setDateTime input with value: ${value}`,
|
|
1182
|
-
result: error
|
|
1183
|
-
? {
|
|
1184
|
-
status: "FAILED",
|
|
1185
|
-
startTime,
|
|
1186
|
-
endTime,
|
|
1187
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1188
|
-
}
|
|
1189
|
-
: {
|
|
1190
|
-
status: "PASSED",
|
|
1191
|
-
startTime,
|
|
1192
|
-
endTime,
|
|
1193
|
-
},
|
|
1194
|
-
info: info,
|
|
1195
|
-
});
|
|
1215
|
+
_commandFinally(state, this);
|
|
1196
1216
|
}
|
|
1197
1217
|
}
|
|
1198
1218
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1329,6 +1349,7 @@ class StableBrowser {
|
|
|
1329
1349
|
let screenshotPath = null;
|
|
1330
1350
|
if (!info.log) {
|
|
1331
1351
|
info.log = "";
|
|
1352
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1332
1353
|
}
|
|
1333
1354
|
info.operation = "getText";
|
|
1334
1355
|
info.selectors = selectors;
|
|
@@ -1667,11 +1688,9 @@ class StableBrowser {
|
|
|
1667
1688
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1668
1689
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1669
1690
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
}
|
|
1674
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1691
|
+
// to make sure the path doesn't start with -
|
|
1692
|
+
const uuidStr = "id_" + randomUUID();
|
|
1693
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1675
1694
|
try {
|
|
1676
1695
|
await this.takeScreenshot(screenshotPath);
|
|
1677
1696
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1685,7 +1704,7 @@ class StableBrowser {
|
|
|
1685
1704
|
catch (e) {
|
|
1686
1705
|
this.logger.info("unable to take screenshot, ignored");
|
|
1687
1706
|
}
|
|
1688
|
-
result.screenshotId =
|
|
1707
|
+
result.screenshotId = uuidStr;
|
|
1689
1708
|
result.screenshotPath = screenshotPath;
|
|
1690
1709
|
if (info && info.box) {
|
|
1691
1710
|
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
@@ -1783,74 +1802,105 @@ class StableBrowser {
|
|
|
1783
1802
|
}
|
|
1784
1803
|
}
|
|
1785
1804
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1805
|
+
const state = {
|
|
1806
|
+
selectors,
|
|
1807
|
+
_params,
|
|
1808
|
+
attribute,
|
|
1809
|
+
variable,
|
|
1810
|
+
options,
|
|
1811
|
+
world,
|
|
1812
|
+
type: Types.EXTRACT,
|
|
1813
|
+
text: `Extract attribute from element`,
|
|
1814
|
+
operation: "extractAttribute",
|
|
1815
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1816
|
+
};
|
|
1791
1817
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1792
|
-
const info = {};
|
|
1793
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1794
|
-
info.operation = "extract";
|
|
1795
|
-
info.selectors = selectors;
|
|
1796
1818
|
try {
|
|
1797
|
-
|
|
1798
|
-
await this._highlightElements(element);
|
|
1799
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1819
|
+
await _preCommand(state, this);
|
|
1800
1820
|
switch (attribute) {
|
|
1801
1821
|
case "inner_text":
|
|
1802
|
-
|
|
1822
|
+
state.value = await state.element.innerText();
|
|
1803
1823
|
break;
|
|
1804
1824
|
case "href":
|
|
1805
|
-
|
|
1825
|
+
state.value = await state.element.getAttribute("href");
|
|
1806
1826
|
break;
|
|
1807
1827
|
case "value":
|
|
1808
|
-
|
|
1828
|
+
state.value = await state.element.inputValue();
|
|
1809
1829
|
break;
|
|
1810
1830
|
default:
|
|
1811
|
-
|
|
1831
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1812
1832
|
break;
|
|
1813
1833
|
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1834
|
+
state.info.value = state.value;
|
|
1835
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1836
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1837
|
+
return state.info;
|
|
1838
|
+
}
|
|
1839
|
+
catch (e) {
|
|
1840
|
+
await _commandError(state, e, this);
|
|
1841
|
+
}
|
|
1842
|
+
finally {
|
|
1843
|
+
_commandFinally(state, this);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1847
|
+
const state = {
|
|
1848
|
+
selectors,
|
|
1849
|
+
_params,
|
|
1850
|
+
attribute,
|
|
1851
|
+
value,
|
|
1852
|
+
options,
|
|
1853
|
+
world,
|
|
1854
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1855
|
+
text: `Verify element attribute`,
|
|
1856
|
+
operation: "verifyAttribute",
|
|
1857
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1858
|
+
};
|
|
1859
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1860
|
+
let val;
|
|
1861
|
+
try {
|
|
1862
|
+
await _preCommand(state, this);
|
|
1863
|
+
switch (attribute) {
|
|
1864
|
+
case "innerText":
|
|
1865
|
+
val = String(await state.element.innerText());
|
|
1866
|
+
break;
|
|
1867
|
+
case "value":
|
|
1868
|
+
val = String(await state.element.inputValue());
|
|
1869
|
+
break;
|
|
1870
|
+
case "checked":
|
|
1871
|
+
val = String(await state.element.isChecked());
|
|
1872
|
+
break;
|
|
1873
|
+
case "disabled":
|
|
1874
|
+
val = String(await state.element.isDisabled());
|
|
1875
|
+
break;
|
|
1876
|
+
case "readOnly":
|
|
1877
|
+
const isEditable = await state.element.isEditable();
|
|
1878
|
+
val = String(!isEditable);
|
|
1879
|
+
break;
|
|
1880
|
+
default:
|
|
1881
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1882
|
+
break;
|
|
1817
1883
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1884
|
+
state.info.expectedValue = val;
|
|
1885
|
+
let regex;
|
|
1886
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1887
|
+
const patternBody = value.slice(1, -1);
|
|
1888
|
+
regex = new RegExp(patternBody, "g");
|
|
1889
|
+
}
|
|
1890
|
+
else {
|
|
1891
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1892
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1893
|
+
}
|
|
1894
|
+
if (!val.match(regex)) {
|
|
1895
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
1896
|
+
}
|
|
1897
|
+
return state.info;
|
|
1821
1898
|
}
|
|
1822
1899
|
catch (e) {
|
|
1823
|
-
|
|
1824
|
-
this.logger.error("extract failed " + info.log);
|
|
1825
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1826
|
-
info.screenshotPath = screenshotPath;
|
|
1827
|
-
Object.assign(e, { info: info });
|
|
1828
|
-
error = e;
|
|
1829
|
-
throw e;
|
|
1900
|
+
await _commandError(state, e, this);
|
|
1830
1901
|
}
|
|
1831
1902
|
finally {
|
|
1832
|
-
|
|
1833
|
-
this._reportToWorld(world, {
|
|
1834
|
-
element_name: selectors.element_name,
|
|
1835
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1836
|
-
variable: variable,
|
|
1837
|
-
value: info.value,
|
|
1838
|
-
text: "Extract attribute from element",
|
|
1839
|
-
screenshotId,
|
|
1840
|
-
result: error
|
|
1841
|
-
? {
|
|
1842
|
-
status: "FAILED",
|
|
1843
|
-
startTime,
|
|
1844
|
-
endTime,
|
|
1845
|
-
message: error?.message,
|
|
1846
|
-
}
|
|
1847
|
-
: {
|
|
1848
|
-
status: "PASSED",
|
|
1849
|
-
startTime,
|
|
1850
|
-
endTime,
|
|
1851
|
-
},
|
|
1852
|
-
info: info,
|
|
1853
|
-
});
|
|
1903
|
+
_commandFinally(state, this);
|
|
1854
1904
|
}
|
|
1855
1905
|
}
|
|
1856
1906
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1871,7 +1921,7 @@ class StableBrowser {
|
|
|
1871
1921
|
if (options && options.timeout) {
|
|
1872
1922
|
timeout = options.timeout;
|
|
1873
1923
|
}
|
|
1874
|
-
const serviceUrl =
|
|
1924
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1875
1925
|
const request = {
|
|
1876
1926
|
method: "POST",
|
|
1877
1927
|
url: serviceUrl,
|
|
@@ -1927,7 +1977,8 @@ class StableBrowser {
|
|
|
1927
1977
|
catch (e) {
|
|
1928
1978
|
errorCount++;
|
|
1929
1979
|
if (errorCount > 3) {
|
|
1930
|
-
throw e;
|
|
1980
|
+
// throw e;
|
|
1981
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1931
1982
|
}
|
|
1932
1983
|
// ignore
|
|
1933
1984
|
}
|
|
@@ -2038,11 +2089,12 @@ class StableBrowser {
|
|
|
2038
2089
|
info.screenshotPath = screenshotPath;
|
|
2039
2090
|
Object.assign(e, { info: info });
|
|
2040
2091
|
error = e;
|
|
2041
|
-
throw e;
|
|
2092
|
+
// throw e;
|
|
2093
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2042
2094
|
}
|
|
2043
2095
|
finally {
|
|
2044
2096
|
const endTime = Date.now();
|
|
2045
|
-
|
|
2097
|
+
_reportToWorld(world, {
|
|
2046
2098
|
type: Types.VERIFY_PAGE_PATH,
|
|
2047
2099
|
text: "Verify page path",
|
|
2048
2100
|
screenshotId,
|
|
@@ -2062,54 +2114,64 @@ class StableBrowser {
|
|
|
2062
2114
|
});
|
|
2063
2115
|
}
|
|
2064
2116
|
}
|
|
2117
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2118
|
+
const frames = this.page.frames();
|
|
2119
|
+
let results = [];
|
|
2120
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2121
|
+
if (dateAlternatives.date) {
|
|
2122
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2123
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
|
|
2124
|
+
result.frame = frames[i];
|
|
2125
|
+
results.push(result);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
else if (numberAlternatives.number) {
|
|
2129
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2130
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
|
|
2131
|
+
result.frame = frames[i];
|
|
2132
|
+
results.push(result);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
else {
|
|
2136
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
|
|
2137
|
+
result.frame = frames[i];
|
|
2138
|
+
results.push(result);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
state.info.results = results;
|
|
2142
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2143
|
+
return resultWithElementsFound;
|
|
2144
|
+
}
|
|
2065
2145
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2066
2146
|
text = unEscapeString(text);
|
|
2067
|
-
const
|
|
2147
|
+
const state = {
|
|
2148
|
+
text_search: text,
|
|
2149
|
+
options,
|
|
2150
|
+
world,
|
|
2151
|
+
locate: false,
|
|
2152
|
+
scroll: false,
|
|
2153
|
+
highlight: false,
|
|
2154
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2155
|
+
text: `Verify text exists in page`,
|
|
2156
|
+
operation: "verifyTextExistInPage",
|
|
2157
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2158
|
+
};
|
|
2068
2159
|
const timeout = this._getLoadTimeout(options);
|
|
2069
|
-
let error = null;
|
|
2070
|
-
let screenshotId = null;
|
|
2071
|
-
let screenshotPath = null;
|
|
2072
2160
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2073
|
-
const info = {};
|
|
2074
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2075
|
-
info.operation = "verifyTextExistInPage";
|
|
2076
2161
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2077
2162
|
if (newValue !== text) {
|
|
2078
2163
|
this.logger.info(text + "=" + newValue);
|
|
2079
2164
|
text = newValue;
|
|
2080
2165
|
}
|
|
2081
|
-
info.text = text;
|
|
2082
2166
|
let dateAlternatives = findDateAlternatives(text);
|
|
2083
2167
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2084
2168
|
try {
|
|
2169
|
+
await _preCommand(state, this);
|
|
2170
|
+
state.info.text = text;
|
|
2085
2171
|
while (true) {
|
|
2086
|
-
const
|
|
2087
|
-
let results = [];
|
|
2088
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2089
|
-
if (dateAlternatives.date) {
|
|
2090
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2091
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
|
|
2092
|
-
result.frame = frames[i];
|
|
2093
|
-
results.push(result);
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
else if (numberAlternatives.number) {
|
|
2097
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2098
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
|
|
2099
|
-
result.frame = frames[i];
|
|
2100
|
-
results.push(result);
|
|
2101
|
-
}
|
|
2102
|
-
}
|
|
2103
|
-
else {
|
|
2104
|
-
const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
|
|
2105
|
-
result.frame = frames[i];
|
|
2106
|
-
results.push(result);
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
info.results = results;
|
|
2110
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2172
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2111
2173
|
if (resultWithElementsFound.length === 0) {
|
|
2112
|
-
if (Date.now() - startTime > timeout) {
|
|
2174
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2113
2175
|
throw new Error(`Text ${text} not found in page`);
|
|
2114
2176
|
}
|
|
2115
2177
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2119,57 +2181,165 @@ class StableBrowser {
|
|
|
2119
2181
|
const frame = resultWithElementsFound[0].frame;
|
|
2120
2182
|
const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
|
|
2121
2183
|
await this._highlightElements(frame, dataAttribute);
|
|
2122
|
-
const element = await frame
|
|
2184
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2123
2185
|
if (element) {
|
|
2124
|
-
await this.scrollIfNeeded(element, info);
|
|
2186
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2125
2187
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2126
2188
|
}
|
|
2127
2189
|
}
|
|
2128
|
-
|
|
2129
|
-
return info;
|
|
2190
|
+
await _screenshot(state, this);
|
|
2191
|
+
return state.info;
|
|
2130
2192
|
}
|
|
2131
2193
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2132
2194
|
}
|
|
2133
2195
|
catch (e) {
|
|
2134
|
-
|
|
2135
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2136
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2137
|
-
info.screenshotPath = screenshotPath;
|
|
2138
|
-
Object.assign(e, { info: info });
|
|
2139
|
-
error = e;
|
|
2140
|
-
throw e;
|
|
2196
|
+
await _commandError(state, e, this);
|
|
2141
2197
|
}
|
|
2142
2198
|
finally {
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2199
|
+
_commandFinally(state, this);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2203
|
+
text = unEscapeString(text);
|
|
2204
|
+
const state = {
|
|
2205
|
+
text_search: text,
|
|
2206
|
+
options,
|
|
2207
|
+
world,
|
|
2208
|
+
locate: false,
|
|
2209
|
+
scroll: false,
|
|
2210
|
+
highlight: false,
|
|
2211
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2212
|
+
text: `Verify text does not exist in page`,
|
|
2213
|
+
operation: "verifyTextNotExistInPage",
|
|
2214
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2215
|
+
};
|
|
2216
|
+
const timeout = this._getLoadTimeout(options);
|
|
2217
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2218
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2219
|
+
if (newValue !== text) {
|
|
2220
|
+
this.logger.info(text + "=" + newValue);
|
|
2221
|
+
text = newValue;
|
|
2222
|
+
}
|
|
2223
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2224
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2225
|
+
try {
|
|
2226
|
+
await _preCommand(state, this);
|
|
2227
|
+
state.info.text = text;
|
|
2228
|
+
while (true) {
|
|
2229
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2230
|
+
if (resultWithElementsFound.length === 0) {
|
|
2231
|
+
await _screenshot(state, this);
|
|
2232
|
+
return state.info;
|
|
2233
|
+
}
|
|
2234
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2235
|
+
throw new Error(`Text ${text} found in page`);
|
|
2236
|
+
}
|
|
2237
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
catch (e) {
|
|
2241
|
+
await _commandError(state, e, this);
|
|
2242
|
+
}
|
|
2243
|
+
finally {
|
|
2244
|
+
_commandFinally(state, this);
|
|
2162
2245
|
}
|
|
2163
2246
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2247
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2248
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2249
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2250
|
+
const state = {
|
|
2251
|
+
text_search: textToVerify,
|
|
2252
|
+
options,
|
|
2253
|
+
world,
|
|
2254
|
+
locate: false,
|
|
2255
|
+
scroll: false,
|
|
2256
|
+
highlight: false,
|
|
2257
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2258
|
+
text: `Verify text with relation to another text`,
|
|
2259
|
+
operation: "verify_text_with_relation",
|
|
2260
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2261
|
+
};
|
|
2262
|
+
const timeout = this._getLoadTimeout(options);
|
|
2263
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2264
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2265
|
+
if (newValue !== textAnchor) {
|
|
2266
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2267
|
+
textAnchor = newValue;
|
|
2268
|
+
}
|
|
2269
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2270
|
+
if (newValue !== textToVerify) {
|
|
2271
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2272
|
+
textToVerify = newValue;
|
|
2273
|
+
}
|
|
2274
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2275
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2276
|
+
let foundAncore = false;
|
|
2277
|
+
try {
|
|
2278
|
+
await _preCommand(state, this);
|
|
2279
|
+
state.info.text = textToVerify;
|
|
2280
|
+
while (true) {
|
|
2281
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2282
|
+
if (resultWithElementsFound.length === 0) {
|
|
2283
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2284
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2285
|
+
}
|
|
2286
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2287
|
+
continue;
|
|
2288
|
+
}
|
|
2289
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2290
|
+
foundAncore = true;
|
|
2291
|
+
const result = resultWithElementsFound[i];
|
|
2292
|
+
const token = result.randomToken;
|
|
2293
|
+
const frame = result.frame;
|
|
2294
|
+
const css = `[data-blinq-id="blinq-id-${token}"]`;
|
|
2295
|
+
const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
|
|
2296
|
+
const elements = Array.from(document.querySelectorAll(css));
|
|
2297
|
+
for (let i = 0; i < elements.length; i++) {
|
|
2298
|
+
const element = elements[i];
|
|
2299
|
+
let climbParent = element;
|
|
2300
|
+
for (let j = 0; j < climb; j++) {
|
|
2301
|
+
climbParent = climbParent.parentElement;
|
|
2302
|
+
if (!climbParent) {
|
|
2303
|
+
break;
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
if (!climbParent) {
|
|
2307
|
+
continue;
|
|
2308
|
+
}
|
|
2309
|
+
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2310
|
+
if (foundElements.length > 0) {
|
|
2311
|
+
// set the container element attribute
|
|
2312
|
+
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2313
|
+
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2314
|
+
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2315
|
+
return { found: true };
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
return { found: false };
|
|
2319
|
+
}, [css, climb, textToVerify, result.randomToken]);
|
|
2320
|
+
if (findResult.found === true) {
|
|
2321
|
+
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2322
|
+
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2323
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2324
|
+
await this._highlightElements(frame, cssAnchor);
|
|
2325
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2326
|
+
if (element) {
|
|
2327
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2328
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2329
|
+
}
|
|
2330
|
+
await _screenshot(state, this);
|
|
2331
|
+
return state.info;
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2168
2336
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2337
|
+
catch (e) {
|
|
2338
|
+
await _commandError(state, e, this);
|
|
2339
|
+
}
|
|
2340
|
+
finally {
|
|
2341
|
+
_commandFinally(state, this);
|
|
2171
2342
|
}
|
|
2172
|
-
return serviceUrl;
|
|
2173
2343
|
}
|
|
2174
2344
|
async visualVerification(text, options = {}, world = null) {
|
|
2175
2345
|
const startTime = Date.now();
|
|
@@ -2185,7 +2355,7 @@ class StableBrowser {
|
|
|
2185
2355
|
throw new Error("TOKEN is not set");
|
|
2186
2356
|
}
|
|
2187
2357
|
try {
|
|
2188
|
-
let serviceUrl =
|
|
2358
|
+
let serviceUrl = _getServerUrl();
|
|
2189
2359
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2190
2360
|
info.screenshotPath = screenshotPath;
|
|
2191
2361
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2221,11 +2391,12 @@ class StableBrowser {
|
|
|
2221
2391
|
info.screenshotPath = screenshotPath;
|
|
2222
2392
|
Object.assign(e, { info: info });
|
|
2223
2393
|
error = e;
|
|
2224
|
-
throw e;
|
|
2394
|
+
// throw e;
|
|
2395
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2225
2396
|
}
|
|
2226
2397
|
finally {
|
|
2227
2398
|
const endTime = Date.now();
|
|
2228
|
-
|
|
2399
|
+
_reportToWorld(world, {
|
|
2229
2400
|
type: Types.VERIFY_VISUAL,
|
|
2230
2401
|
text: "Visual verification",
|
|
2231
2402
|
screenshotId,
|
|
@@ -2273,6 +2444,7 @@ class StableBrowser {
|
|
|
2273
2444
|
let screenshotPath = null;
|
|
2274
2445
|
const info = {};
|
|
2275
2446
|
info.log = "";
|
|
2447
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2276
2448
|
info.operation = "getTableData";
|
|
2277
2449
|
info.selectors = selectors;
|
|
2278
2450
|
try {
|
|
@@ -2288,11 +2460,12 @@ class StableBrowser {
|
|
|
2288
2460
|
info.screenshotPath = screenshotPath;
|
|
2289
2461
|
Object.assign(e, { info: info });
|
|
2290
2462
|
error = e;
|
|
2291
|
-
throw e;
|
|
2463
|
+
// throw e;
|
|
2464
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2292
2465
|
}
|
|
2293
2466
|
finally {
|
|
2294
2467
|
const endTime = Date.now();
|
|
2295
|
-
|
|
2468
|
+
_reportToWorld(world, {
|
|
2296
2469
|
element_name: selectors.element_name,
|
|
2297
2470
|
type: Types.GET_TABLE_DATA,
|
|
2298
2471
|
text: "Get table data",
|
|
@@ -2347,7 +2520,7 @@ class StableBrowser {
|
|
|
2347
2520
|
info.operation = "analyzeTable";
|
|
2348
2521
|
info.selectors = selectors;
|
|
2349
2522
|
info.query = query;
|
|
2350
|
-
query =
|
|
2523
|
+
query = _fixUsingParams(query, _params);
|
|
2351
2524
|
info.query_fixed = query;
|
|
2352
2525
|
info.operator = operator;
|
|
2353
2526
|
info.value = value;
|
|
@@ -2453,11 +2626,12 @@ class StableBrowser {
|
|
|
2453
2626
|
info.screenshotPath = screenshotPath;
|
|
2454
2627
|
Object.assign(e, { info: info });
|
|
2455
2628
|
error = e;
|
|
2456
|
-
throw e;
|
|
2629
|
+
// throw e;
|
|
2630
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2457
2631
|
}
|
|
2458
2632
|
finally {
|
|
2459
2633
|
const endTime = Date.now();
|
|
2460
|
-
|
|
2634
|
+
_reportToWorld(world, {
|
|
2461
2635
|
element_name: selectors.element_name,
|
|
2462
2636
|
type: Types.ANALYZE_TABLE,
|
|
2463
2637
|
text: "Analyze table",
|
|
@@ -2530,7 +2704,7 @@ class StableBrowser {
|
|
|
2530
2704
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2531
2705
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2532
2706
|
const endTime = Date.now();
|
|
2533
|
-
|
|
2707
|
+
_reportToWorld(world, {
|
|
2534
2708
|
type: Types.GET_PAGE_STATUS,
|
|
2535
2709
|
text: "Wait for page load",
|
|
2536
2710
|
screenshotId,
|
|
@@ -2550,41 +2724,35 @@ class StableBrowser {
|
|
|
2550
2724
|
}
|
|
2551
2725
|
}
|
|
2552
2726
|
async closePage(options = {}, world = null) {
|
|
2553
|
-
const
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2727
|
+
const state = {
|
|
2728
|
+
options,
|
|
2729
|
+
world,
|
|
2730
|
+
locate: false,
|
|
2731
|
+
scroll: false,
|
|
2732
|
+
highlight: false,
|
|
2733
|
+
type: Types.CLOSE_PAGE,
|
|
2734
|
+
text: `Close page`,
|
|
2735
|
+
operation: "closePage",
|
|
2736
|
+
log: "***** close page *****\n",
|
|
2737
|
+
throwError: false,
|
|
2738
|
+
};
|
|
2558
2739
|
try {
|
|
2740
|
+
await _preCommand(state, this);
|
|
2559
2741
|
await this.page.close();
|
|
2560
2742
|
}
|
|
2561
2743
|
catch (e) {
|
|
2562
2744
|
console.log(".");
|
|
2745
|
+
await _commandError(state, e, this);
|
|
2563
2746
|
}
|
|
2564
2747
|
finally {
|
|
2565
|
-
|
|
2566
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2567
|
-
const endTime = Date.now();
|
|
2568
|
-
this._reportToWorld(world, {
|
|
2569
|
-
type: Types.CLOSE_PAGE,
|
|
2570
|
-
text: "close page",
|
|
2571
|
-
screenshotId,
|
|
2572
|
-
result: error
|
|
2573
|
-
? {
|
|
2574
|
-
status: "FAILED",
|
|
2575
|
-
startTime,
|
|
2576
|
-
endTime,
|
|
2577
|
-
message: error?.message,
|
|
2578
|
-
}
|
|
2579
|
-
: {
|
|
2580
|
-
status: "PASSED",
|
|
2581
|
-
startTime,
|
|
2582
|
-
endTime,
|
|
2583
|
-
},
|
|
2584
|
-
info: info,
|
|
2585
|
-
});
|
|
2748
|
+
_commandFinally(state, this);
|
|
2586
2749
|
}
|
|
2587
2750
|
}
|
|
2751
|
+
saveTestDataAsGlobal(options, world) {
|
|
2752
|
+
const dataFile = this._getDataFile(world);
|
|
2753
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2754
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2755
|
+
}
|
|
2588
2756
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2589
2757
|
const startTime = Date.now();
|
|
2590
2758
|
let error = null;
|
|
@@ -2602,12 +2770,13 @@ class StableBrowser {
|
|
|
2602
2770
|
}
|
|
2603
2771
|
catch (e) {
|
|
2604
2772
|
console.log(".");
|
|
2773
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2605
2774
|
}
|
|
2606
2775
|
finally {
|
|
2607
2776
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2608
2777
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2609
2778
|
const endTime = Date.now();
|
|
2610
|
-
|
|
2779
|
+
_reportToWorld(world, {
|
|
2611
2780
|
type: Types.SET_VIEWPORT,
|
|
2612
2781
|
text: "set viewport size to " + width + "x" + hight,
|
|
2613
2782
|
screenshotId,
|
|
@@ -2638,12 +2807,13 @@ class StableBrowser {
|
|
|
2638
2807
|
}
|
|
2639
2808
|
catch (e) {
|
|
2640
2809
|
console.log(".");
|
|
2810
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2641
2811
|
}
|
|
2642
2812
|
finally {
|
|
2643
2813
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2644
2814
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2645
2815
|
const endTime = Date.now();
|
|
2646
|
-
|
|
2816
|
+
_reportToWorld(world, {
|
|
2647
2817
|
type: Types.GET_PAGE_STATUS,
|
|
2648
2818
|
text: "page relaod",
|
|
2649
2819
|
screenshotId,
|
|
@@ -2679,11 +2849,45 @@ class StableBrowser {
|
|
|
2679
2849
|
console.log("#-#");
|
|
2680
2850
|
}
|
|
2681
2851
|
}
|
|
2682
|
-
|
|
2683
|
-
if (
|
|
2684
|
-
|
|
2852
|
+
async beforeStep(world, step) {
|
|
2853
|
+
if (this.stepIndex === undefined) {
|
|
2854
|
+
this.stepIndex = 0;
|
|
2855
|
+
}
|
|
2856
|
+
else {
|
|
2857
|
+
this.stepIndex++;
|
|
2858
|
+
}
|
|
2859
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
2860
|
+
this.stepName = step.pickleStep.text;
|
|
2861
|
+
this.logger.info("step: " + this.stepName);
|
|
2862
|
+
}
|
|
2863
|
+
else if (step && step.text) {
|
|
2864
|
+
this.stepName = step.text;
|
|
2865
|
+
}
|
|
2866
|
+
else {
|
|
2867
|
+
this.stepName = "step " + this.stepIndex;
|
|
2868
|
+
}
|
|
2869
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2870
|
+
if (this.context.browserObject.context) {
|
|
2871
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
2875
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
2876
|
+
// check if @global_test_data tag is present
|
|
2877
|
+
if (this.tags.includes("@global_test_data")) {
|
|
2878
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
async afterStep(world, step) {
|
|
2883
|
+
this.stepName = null;
|
|
2884
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2885
|
+
if (this.context.browserObject.context) {
|
|
2886
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
2887
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2685
2890
|
}
|
|
2686
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2687
2891
|
}
|
|
2688
2892
|
}
|
|
2689
2893
|
function createTimedPromise(promise, label) {
|
|
@@ -2691,156 +2895,5 @@ function createTimedPromise(promise, label) {
|
|
|
2691
2895
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2692
2896
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2693
2897
|
}
|
|
2694
|
-
const KEYBOARD_EVENTS = [
|
|
2695
|
-
"ALT",
|
|
2696
|
-
"AltGraph",
|
|
2697
|
-
"CapsLock",
|
|
2698
|
-
"Control",
|
|
2699
|
-
"Fn",
|
|
2700
|
-
"FnLock",
|
|
2701
|
-
"Hyper",
|
|
2702
|
-
"Meta",
|
|
2703
|
-
"NumLock",
|
|
2704
|
-
"ScrollLock",
|
|
2705
|
-
"Shift",
|
|
2706
|
-
"Super",
|
|
2707
|
-
"Symbol",
|
|
2708
|
-
"SymbolLock",
|
|
2709
|
-
"Enter",
|
|
2710
|
-
"Tab",
|
|
2711
|
-
"ArrowDown",
|
|
2712
|
-
"ArrowLeft",
|
|
2713
|
-
"ArrowRight",
|
|
2714
|
-
"ArrowUp",
|
|
2715
|
-
"End",
|
|
2716
|
-
"Home",
|
|
2717
|
-
"PageDown",
|
|
2718
|
-
"PageUp",
|
|
2719
|
-
"Backspace",
|
|
2720
|
-
"Clear",
|
|
2721
|
-
"Copy",
|
|
2722
|
-
"CrSel",
|
|
2723
|
-
"Cut",
|
|
2724
|
-
"Delete",
|
|
2725
|
-
"EraseEof",
|
|
2726
|
-
"ExSel",
|
|
2727
|
-
"Insert",
|
|
2728
|
-
"Paste",
|
|
2729
|
-
"Redo",
|
|
2730
|
-
"Undo",
|
|
2731
|
-
"Accept",
|
|
2732
|
-
"Again",
|
|
2733
|
-
"Attn",
|
|
2734
|
-
"Cancel",
|
|
2735
|
-
"ContextMenu",
|
|
2736
|
-
"Escape",
|
|
2737
|
-
"Execute",
|
|
2738
|
-
"Find",
|
|
2739
|
-
"Finish",
|
|
2740
|
-
"Help",
|
|
2741
|
-
"Pause",
|
|
2742
|
-
"Play",
|
|
2743
|
-
"Props",
|
|
2744
|
-
"Select",
|
|
2745
|
-
"ZoomIn",
|
|
2746
|
-
"ZoomOut",
|
|
2747
|
-
"BrightnessDown",
|
|
2748
|
-
"BrightnessUp",
|
|
2749
|
-
"Eject",
|
|
2750
|
-
"LogOff",
|
|
2751
|
-
"Power",
|
|
2752
|
-
"PowerOff",
|
|
2753
|
-
"PrintScreen",
|
|
2754
|
-
"Hibernate",
|
|
2755
|
-
"Standby",
|
|
2756
|
-
"WakeUp",
|
|
2757
|
-
"AllCandidates",
|
|
2758
|
-
"Alphanumeric",
|
|
2759
|
-
"CodeInput",
|
|
2760
|
-
"Compose",
|
|
2761
|
-
"Convert",
|
|
2762
|
-
"Dead",
|
|
2763
|
-
"FinalMode",
|
|
2764
|
-
"GroupFirst",
|
|
2765
|
-
"GroupLast",
|
|
2766
|
-
"GroupNext",
|
|
2767
|
-
"GroupPrevious",
|
|
2768
|
-
"ModeChange",
|
|
2769
|
-
"NextCandidate",
|
|
2770
|
-
"NonConvert",
|
|
2771
|
-
"PreviousCandidate",
|
|
2772
|
-
"Process",
|
|
2773
|
-
"SingleCandidate",
|
|
2774
|
-
"HangulMode",
|
|
2775
|
-
"HanjaMode",
|
|
2776
|
-
"JunjaMode",
|
|
2777
|
-
"Eisu",
|
|
2778
|
-
"Hankaku",
|
|
2779
|
-
"Hiragana",
|
|
2780
|
-
"HiraganaKatakana",
|
|
2781
|
-
"KanaMode",
|
|
2782
|
-
"KanjiMode",
|
|
2783
|
-
"Katakana",
|
|
2784
|
-
"Romaji",
|
|
2785
|
-
"Zenkaku",
|
|
2786
|
-
"ZenkakuHanaku",
|
|
2787
|
-
"F1",
|
|
2788
|
-
"F2",
|
|
2789
|
-
"F3",
|
|
2790
|
-
"F4",
|
|
2791
|
-
"F5",
|
|
2792
|
-
"F6",
|
|
2793
|
-
"F7",
|
|
2794
|
-
"F8",
|
|
2795
|
-
"F9",
|
|
2796
|
-
"F10",
|
|
2797
|
-
"F11",
|
|
2798
|
-
"F12",
|
|
2799
|
-
"Soft1",
|
|
2800
|
-
"Soft2",
|
|
2801
|
-
"Soft3",
|
|
2802
|
-
"Soft4",
|
|
2803
|
-
"ChannelDown",
|
|
2804
|
-
"ChannelUp",
|
|
2805
|
-
"Close",
|
|
2806
|
-
"MailForward",
|
|
2807
|
-
"MailReply",
|
|
2808
|
-
"MailSend",
|
|
2809
|
-
"MediaFastForward",
|
|
2810
|
-
"MediaPause",
|
|
2811
|
-
"MediaPlay",
|
|
2812
|
-
"MediaPlayPause",
|
|
2813
|
-
"MediaRecord",
|
|
2814
|
-
"MediaRewind",
|
|
2815
|
-
"MediaStop",
|
|
2816
|
-
"MediaTrackNext",
|
|
2817
|
-
"MediaTrackPrevious",
|
|
2818
|
-
"AudioBalanceLeft",
|
|
2819
|
-
"AudioBalanceRight",
|
|
2820
|
-
"AudioBassBoostDown",
|
|
2821
|
-
"AudioBassBoostToggle",
|
|
2822
|
-
"AudioBassBoostUp",
|
|
2823
|
-
"AudioFaderFront",
|
|
2824
|
-
"AudioFaderRear",
|
|
2825
|
-
"AudioSurroundModeNext",
|
|
2826
|
-
"AudioTrebleDown",
|
|
2827
|
-
"AudioTrebleUp",
|
|
2828
|
-
"AudioVolumeDown",
|
|
2829
|
-
"AudioVolumeMute",
|
|
2830
|
-
"AudioVolumeUp",
|
|
2831
|
-
"MicrophoneToggle",
|
|
2832
|
-
"MicrophoneVolumeDown",
|
|
2833
|
-
"MicrophoneVolumeMute",
|
|
2834
|
-
"MicrophoneVolumeUp",
|
|
2835
|
-
"TV",
|
|
2836
|
-
"TV3DMode",
|
|
2837
|
-
"TVAntennaCable",
|
|
2838
|
-
"TVAudioDescription",
|
|
2839
|
-
];
|
|
2840
|
-
function unEscapeString(str) {
|
|
2841
|
-
const placeholder = "__NEWLINE__";
|
|
2842
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2843
|
-
return str;
|
|
2844
|
-
}
|
|
2845
2898
|
export { StableBrowser };
|
|
2846
2899
|
//# sourceMappingURL=stable_browser.js.map
|