automation_model 1.0.490-dev → 1.0.490-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 +29 -9
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +6 -3
- package/lib/browser_manager.js +110 -16
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -0
- package/lib/command_common.js +55 -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 +31 -4
- 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.d.ts +2 -1
- package/lib/network.js +33 -1
- package/lib/network.js.map +1 -1
- package/lib/scripts/find_text.js +126 -0
- package/lib/stable_browser.d.ts +49 -17
- package/lib/stable_browser.js +614 -574
- 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 +236 -4
- 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 {
|
|
20
|
-
import {
|
|
21
|
-
|
|
19
|
+
import { randomUUID } from "crypto";
|
|
20
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
|
+
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
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,25 +95,32 @@ 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);
|
|
104
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
96
105
|
}
|
|
97
106
|
registerEventListeners(context) {
|
|
98
107
|
this.registerConsoleLogListener(this.page, context);
|
|
99
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
108
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
100
109
|
if (!context.pageLoading) {
|
|
101
110
|
context.pageLoading = { status: false };
|
|
102
111
|
}
|
|
103
112
|
context.playContext.on("page", async function (page) {
|
|
113
|
+
if (this.configuration && this.configuration.closePopups === true) {
|
|
114
|
+
console.log("close unexpected popups");
|
|
115
|
+
await page.close();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
104
118
|
context.pageLoading.status = true;
|
|
105
119
|
this.page = page;
|
|
106
120
|
context.page = page;
|
|
107
121
|
context.pages.push(page);
|
|
108
122
|
registerNetworkEvents(this.world, this, context, this.page);
|
|
123
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
109
124
|
page.on("close", async () => {
|
|
110
125
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
111
126
|
this.context.pages.pop();
|
|
@@ -135,9 +150,9 @@ class StableBrowser {
|
|
|
135
150
|
if (this.appName === appName) {
|
|
136
151
|
return;
|
|
137
152
|
}
|
|
138
|
-
let
|
|
153
|
+
let navigate = false;
|
|
139
154
|
if (!apps[appName]) {
|
|
140
|
-
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);
|
|
141
156
|
newContextCreated = true;
|
|
142
157
|
apps[appName] = {
|
|
143
158
|
context: newContext,
|
|
@@ -146,32 +161,15 @@ class StableBrowser {
|
|
|
146
161
|
};
|
|
147
162
|
}
|
|
148
163
|
const tempContext = {};
|
|
149
|
-
|
|
150
|
-
|
|
164
|
+
_copyContext(this, tempContext);
|
|
165
|
+
_copyContext(apps[appName], this);
|
|
151
166
|
apps[this.appName] = tempContext;
|
|
152
167
|
this.appName = appName;
|
|
153
|
-
if (
|
|
154
|
-
this.registerEventListeners(this.context);
|
|
168
|
+
if (navigate) {
|
|
155
169
|
await this.goto(this.context.environment.baseUrl);
|
|
156
170
|
await this.waitForPageLoad();
|
|
157
171
|
}
|
|
158
172
|
}
|
|
159
|
-
_copyContext(from, to) {
|
|
160
|
-
to.browser = from.browser;
|
|
161
|
-
to.page = from.page;
|
|
162
|
-
to.context = from.context;
|
|
163
|
-
}
|
|
164
|
-
getWebLogFile(logFolder) {
|
|
165
|
-
if (!fs.existsSync(logFolder)) {
|
|
166
|
-
fs.mkdirSync(logFolder, { recursive: true });
|
|
167
|
-
}
|
|
168
|
-
let nextIndex = 1;
|
|
169
|
-
while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
|
|
170
|
-
nextIndex++;
|
|
171
|
-
}
|
|
172
|
-
const fileName = nextIndex + ".json";
|
|
173
|
-
return path.join(logFolder, fileName);
|
|
174
|
-
}
|
|
175
173
|
registerConsoleLogListener(page, context) {
|
|
176
174
|
if (!this.context.webLogger) {
|
|
177
175
|
this.context.webLogger = [];
|
|
@@ -221,7 +219,7 @@ class StableBrowser {
|
|
|
221
219
|
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
222
220
|
}
|
|
223
221
|
catch (error) {
|
|
224
|
-
console.error("Error in request listener", error);
|
|
222
|
+
// console.error("Error in request listener", error);
|
|
225
223
|
context.networkLogger.push({
|
|
226
224
|
error: "not able to listen",
|
|
227
225
|
message: error.message,
|
|
@@ -235,55 +233,40 @@ class StableBrowser {
|
|
|
235
233
|
// async closeUnexpectedPopups() {
|
|
236
234
|
// await closeUnexpectedPopups(this.page);
|
|
237
235
|
// }
|
|
238
|
-
async goto(url) {
|
|
236
|
+
async goto(url, world = null) {
|
|
239
237
|
if (!url.startsWith("http")) {
|
|
240
238
|
url = "https://" + url;
|
|
241
239
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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);
|
|
257
259
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// check if not null
|
|
262
|
-
if (!locator) {
|
|
263
|
-
return locator;
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.error("Error on goto", error);
|
|
262
|
+
_commandError(state, error, this);
|
|
264
263
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
this.scanAndManipulate(locator, _params);
|
|
268
|
-
return locator;
|
|
269
|
-
}
|
|
270
|
-
_isObject(value) {
|
|
271
|
-
return value && typeof value === "object" && value.constructor === Object;
|
|
272
|
-
}
|
|
273
|
-
scanAndManipulate(currentObj, _params) {
|
|
274
|
-
for (const key in currentObj) {
|
|
275
|
-
if (typeof currentObj[key] === "string") {
|
|
276
|
-
// Perform string manipulation
|
|
277
|
-
currentObj[key] = this._fixUsingParams(currentObj[key], _params);
|
|
278
|
-
}
|
|
279
|
-
else if (this._isObject(currentObj[key])) {
|
|
280
|
-
// Recursively scan nested objects
|
|
281
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
282
|
-
}
|
|
264
|
+
finally {
|
|
265
|
+
_commandFinally(state, this);
|
|
283
266
|
}
|
|
284
267
|
}
|
|
285
268
|
_getLocator(locator, scope, _params) {
|
|
286
|
-
locator =
|
|
269
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
287
270
|
let locatorReturn;
|
|
288
271
|
if (locator.role) {
|
|
289
272
|
if (locator.role[1].nameReg) {
|
|
@@ -291,7 +274,7 @@ class StableBrowser {
|
|
|
291
274
|
delete locator.role[1].nameReg;
|
|
292
275
|
}
|
|
293
276
|
// if (locator.role[1].name) {
|
|
294
|
-
// locator.role[1].name =
|
|
277
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
295
278
|
// }
|
|
296
279
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
297
280
|
}
|
|
@@ -334,7 +317,7 @@ class StableBrowser {
|
|
|
334
317
|
if (css && css.locator) {
|
|
335
318
|
css = css.locator;
|
|
336
319
|
}
|
|
337
|
-
let result = await this._locateElementByText(scope,
|
|
320
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
338
321
|
if (result.elementCount === 0) {
|
|
339
322
|
return;
|
|
340
323
|
}
|
|
@@ -350,116 +333,42 @@ class StableBrowser {
|
|
|
350
333
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
351
334
|
//const stringifyText = JSON.stringify(text);
|
|
352
335
|
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (currentNode === parent) {
|
|
357
|
-
return true;
|
|
358
|
-
}
|
|
359
|
-
currentNode = currentNode.parentNode;
|
|
360
|
-
}
|
|
361
|
-
return false;
|
|
362
|
-
}
|
|
363
|
-
document.isParent = isParent;
|
|
364
|
-
function getRegex(str) {
|
|
365
|
-
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
366
|
-
if (!match) {
|
|
367
|
-
return null;
|
|
368
|
-
}
|
|
369
|
-
let [_, pattern, flags] = match;
|
|
370
|
-
return new RegExp(pattern, flags);
|
|
371
|
-
}
|
|
372
|
-
document.getRegex = getRegex;
|
|
373
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
374
|
-
// Check and add the element if it has a shadow root
|
|
375
|
-
if (element.shadowRoot) {
|
|
376
|
-
result.push(element);
|
|
377
|
-
// Also search within the shadow root
|
|
378
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
379
|
-
}
|
|
380
|
-
// Iterate over child nodes
|
|
381
|
-
element.childNodes.forEach((child) => {
|
|
382
|
-
// Recursively call the function for each child node
|
|
383
|
-
document.collectAllShadowDomElements(child, result);
|
|
384
|
-
});
|
|
385
|
-
return result;
|
|
386
|
-
}
|
|
387
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
388
|
-
if (!tag) {
|
|
389
|
-
tag = "*";
|
|
390
|
-
}
|
|
391
|
-
let regexpSearch = document.getRegex(text);
|
|
392
|
-
if (regexpSearch) {
|
|
393
|
-
regex = true;
|
|
394
|
-
}
|
|
395
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
396
|
-
let shadowHosts = [];
|
|
397
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
398
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
399
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
400
|
-
if (!shadowElement) {
|
|
401
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
405
|
-
elements = elements.concat(shadowElements);
|
|
406
|
-
}
|
|
407
|
-
let randomToken = null;
|
|
408
|
-
const foundElements = [];
|
|
336
|
+
const options = {
|
|
337
|
+
innerText: true,
|
|
338
|
+
};
|
|
409
339
|
if (regex) {
|
|
410
|
-
|
|
411
|
-
regexpSearch = new RegExp(text, "im");
|
|
412
|
-
}
|
|
413
|
-
for (let i = 0; i < elements.length; i++) {
|
|
414
|
-
const element = elements[i];
|
|
415
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
416
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
417
|
-
foundElements.push(element);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
340
|
+
options.singleRegex = true;
|
|
420
341
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
for (let i = 0; i < elements.length; i++) {
|
|
424
|
-
const element = elements[i];
|
|
425
|
-
if (partial) {
|
|
426
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
427
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
428
|
-
foundElements.push(element);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
else {
|
|
432
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
433
|
-
(element.value && element.value === text)) {
|
|
434
|
-
foundElements.push(element);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
342
|
+
if (tag) {
|
|
343
|
+
options.tag = tag;
|
|
438
344
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
if (isParent(element, foundElements[j])) {
|
|
448
|
-
hasChild = true;
|
|
449
|
-
break;
|
|
450
|
-
}
|
|
345
|
+
if (!(partial === true)) {
|
|
346
|
+
options.exactMatch = true;
|
|
347
|
+
}
|
|
348
|
+
if (text.startsWith("/") && text.endsWith("/")) {
|
|
349
|
+
if (text.length < 3) {
|
|
350
|
+
throw new Error("Invalid regex pattern: empty pattern");
|
|
451
351
|
}
|
|
452
|
-
|
|
453
|
-
|
|
352
|
+
try {
|
|
353
|
+
const pattern = text.slice(1, -1);
|
|
354
|
+
new RegExp(pattern); // Validate pattern
|
|
355
|
+
text = pattern;
|
|
356
|
+
options.singleRegex = true;
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
throw new Error(`Invalid regex pattern: ${e.message}`);
|
|
454
360
|
}
|
|
455
361
|
}
|
|
362
|
+
const elements = window.findMatchingElements(text, options);
|
|
363
|
+
let randomToken = null;
|
|
364
|
+
const foundElements = [];
|
|
456
365
|
let elementCount = 0;
|
|
457
|
-
if (
|
|
458
|
-
for (let i = 0; i <
|
|
366
|
+
if (elements.length > 0) {
|
|
367
|
+
for (let i = 0; i < elements.length; i++) {
|
|
459
368
|
if (randomToken === null) {
|
|
460
369
|
randomToken = Math.random().toString(36).substring(7);
|
|
461
370
|
}
|
|
462
|
-
let element =
|
|
371
|
+
let element = elements[i];
|
|
463
372
|
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
464
373
|
elementCount++;
|
|
465
374
|
}
|
|
@@ -468,9 +377,21 @@ class StableBrowser {
|
|
|
468
377
|
}, [text1, tag1, regex1, partial1]);
|
|
469
378
|
}
|
|
470
379
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
380
|
+
if (!info) {
|
|
381
|
+
info = {};
|
|
382
|
+
}
|
|
383
|
+
if (!info.failCause) {
|
|
384
|
+
info.failCause = {};
|
|
385
|
+
}
|
|
386
|
+
if (!info.log) {
|
|
387
|
+
info.log = "";
|
|
388
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
389
|
+
}
|
|
471
390
|
let locatorSearch = selectorHierarchy[index];
|
|
391
|
+
let originalLocatorSearch = "";
|
|
472
392
|
try {
|
|
473
|
-
|
|
393
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
394
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
474
395
|
}
|
|
475
396
|
catch (e) {
|
|
476
397
|
console.error(e);
|
|
@@ -480,13 +401,18 @@ class StableBrowser {
|
|
|
480
401
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
481
402
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
482
403
|
if (!locatorString) {
|
|
404
|
+
info.failCause.textNotFound = true;
|
|
405
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
483
406
|
return;
|
|
484
407
|
}
|
|
485
408
|
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
486
409
|
}
|
|
487
410
|
else if (locatorSearch.text) {
|
|
488
|
-
let
|
|
411
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
412
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
489
413
|
if (result.elementCount === 0) {
|
|
414
|
+
info.failCause.textNotFound = true;
|
|
415
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
490
416
|
return;
|
|
491
417
|
}
|
|
492
418
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
@@ -503,13 +429,22 @@ class StableBrowser {
|
|
|
503
429
|
// cssHref = true;
|
|
504
430
|
// }
|
|
505
431
|
let count = await locator.count();
|
|
432
|
+
if (count > 0 && !info.failCause.count) {
|
|
433
|
+
info.failCause.count = count;
|
|
434
|
+
}
|
|
506
435
|
//info.log += "total elements found " + count + "\n";
|
|
507
436
|
//let visibleCount = 0;
|
|
508
437
|
let visibleLocator = null;
|
|
509
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
438
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
510
439
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
440
|
+
if (info.locatorLog) {
|
|
441
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
442
|
+
}
|
|
511
443
|
return;
|
|
512
444
|
}
|
|
445
|
+
if (info.locatorLog && count === 0) {
|
|
446
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
447
|
+
}
|
|
513
448
|
for (let j = 0; j < count; j++) {
|
|
514
449
|
let visible = await locator.nth(j).isVisible();
|
|
515
450
|
const enabled = await locator.nth(j).isEnabled();
|
|
@@ -518,24 +453,40 @@ class StableBrowser {
|
|
|
518
453
|
}
|
|
519
454
|
if (visible && enabled) {
|
|
520
455
|
foundLocators.push(locator.nth(j));
|
|
456
|
+
if (info.locatorLog) {
|
|
457
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
458
|
+
}
|
|
521
459
|
}
|
|
522
460
|
else {
|
|
461
|
+
info.failCause.visible = visible;
|
|
462
|
+
info.failCause.enabled = enabled;
|
|
523
463
|
if (!info.printMessages) {
|
|
524
464
|
info.printMessages = {};
|
|
525
465
|
}
|
|
466
|
+
if (info.locatorLog && !visible) {
|
|
467
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
468
|
+
}
|
|
469
|
+
if (info.locatorLog && !enabled) {
|
|
470
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
471
|
+
}
|
|
526
472
|
if (!info.printMessages[j.toString()]) {
|
|
527
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
473
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
528
474
|
info.printMessages[j.toString()] = true;
|
|
529
475
|
}
|
|
530
476
|
}
|
|
531
477
|
}
|
|
532
478
|
}
|
|
533
479
|
async closeUnexpectedPopups(info, _params) {
|
|
480
|
+
if (!info) {
|
|
481
|
+
info = {};
|
|
482
|
+
info.failCause = {};
|
|
483
|
+
info.log = "";
|
|
484
|
+
}
|
|
534
485
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
535
486
|
if (!info) {
|
|
536
487
|
info = {};
|
|
537
488
|
}
|
|
538
|
-
info.log += "scan for popup handlers" + "\n";
|
|
489
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
539
490
|
const handlerGroup = [];
|
|
540
491
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
541
492
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -562,9 +513,21 @@ class StableBrowser {
|
|
|
562
513
|
}
|
|
563
514
|
if (result.foundElements.length > 0) {
|
|
564
515
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
516
|
+
try {
|
|
517
|
+
await scope?.evaluate(() => {
|
|
518
|
+
window.__isClosingPopups = true;
|
|
519
|
+
});
|
|
520
|
+
await dialogCloseLocator.click();
|
|
521
|
+
// wait for the dialog to close
|
|
522
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
523
|
+
}
|
|
524
|
+
catch (e) {
|
|
525
|
+
}
|
|
526
|
+
finally {
|
|
527
|
+
await scope?.evaluate(() => {
|
|
528
|
+
window.__isClosingPopups = false;
|
|
529
|
+
});
|
|
530
|
+
}
|
|
568
531
|
return { rerun: true };
|
|
569
532
|
}
|
|
570
533
|
}
|
|
@@ -588,7 +551,13 @@ class StableBrowser {
|
|
|
588
551
|
}
|
|
589
552
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
590
553
|
}
|
|
591
|
-
async _findFrameScope(selectors, timeout = 30000) {
|
|
554
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
555
|
+
if (!info) {
|
|
556
|
+
info = {};
|
|
557
|
+
info.failCause = {};
|
|
558
|
+
info.log = "";
|
|
559
|
+
}
|
|
560
|
+
let startTime = Date.now();
|
|
592
561
|
let scope = this.page;
|
|
593
562
|
if (selectors.frame) {
|
|
594
563
|
return selectors.frame;
|
|
@@ -619,9 +588,11 @@ class StableBrowser {
|
|
|
619
588
|
}
|
|
620
589
|
return framescope;
|
|
621
590
|
};
|
|
591
|
+
let fLocator = null;
|
|
622
592
|
while (true) {
|
|
623
593
|
let frameFound = false;
|
|
624
594
|
if (selectors.nestFrmLoc) {
|
|
595
|
+
fLocator = selectors.nestFrmLoc;
|
|
625
596
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
626
597
|
frameFound = true;
|
|
627
598
|
break;
|
|
@@ -630,6 +601,7 @@ class StableBrowser {
|
|
|
630
601
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
631
602
|
let frameLocator = selectors.frameLocators[i];
|
|
632
603
|
if (frameLocator.css) {
|
|
604
|
+
fLocator = frameLocator.css;
|
|
633
605
|
scope = scope.frameLocator(frameLocator.css);
|
|
634
606
|
frameFound = true;
|
|
635
607
|
break;
|
|
@@ -637,16 +609,25 @@ class StableBrowser {
|
|
|
637
609
|
}
|
|
638
610
|
}
|
|
639
611
|
if (!frameFound && selectors.iframe_src) {
|
|
612
|
+
fLocator = selectors.iframe_src;
|
|
640
613
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
641
614
|
}
|
|
642
615
|
if (!scope) {
|
|
643
|
-
info
|
|
644
|
-
|
|
616
|
+
if (info && info.locatorLog) {
|
|
617
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
618
|
+
}
|
|
619
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
620
|
+
if (Date.now() - startTime > timeout) {
|
|
621
|
+
info.failCause.iframeNotFound = true;
|
|
622
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
645
623
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
646
624
|
}
|
|
647
625
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
648
626
|
}
|
|
649
627
|
else {
|
|
628
|
+
if (info && info.locatorLog) {
|
|
629
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
630
|
+
}
|
|
650
631
|
break;
|
|
651
632
|
}
|
|
652
633
|
}
|
|
@@ -656,20 +637,27 @@ class StableBrowser {
|
|
|
656
637
|
}
|
|
657
638
|
return scope;
|
|
658
639
|
}
|
|
659
|
-
async _getDocumentBody(selectors, timeout = 30000) {
|
|
660
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
640
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
641
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
661
642
|
return scope.evaluate(() => {
|
|
662
643
|
var bodyContent = document.body.innerHTML;
|
|
663
644
|
return bodyContent;
|
|
664
645
|
});
|
|
665
646
|
}
|
|
666
647
|
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
648
|
+
if (!info) {
|
|
649
|
+
info = {};
|
|
650
|
+
info.failCause = {};
|
|
651
|
+
info.log = "";
|
|
652
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
653
|
+
}
|
|
667
654
|
let highPriorityTimeout = 5000;
|
|
668
655
|
let visibleOnlyTimeout = 6000;
|
|
669
|
-
let startTime =
|
|
656
|
+
let startTime = Date.now();
|
|
670
657
|
let locatorsCount = 0;
|
|
658
|
+
let lazy_scroll = false;
|
|
671
659
|
//let arrayMode = Array.isArray(selectors);
|
|
672
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
660
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
673
661
|
let selectorsLocators = null;
|
|
674
662
|
selectorsLocators = selectors.locators;
|
|
675
663
|
// group selectors by priority
|
|
@@ -756,21 +744,33 @@ class StableBrowser {
|
|
|
756
744
|
return maxCountElement.locator;
|
|
757
745
|
}
|
|
758
746
|
}
|
|
759
|
-
if (
|
|
747
|
+
if (Date.now() - startTime > timeout) {
|
|
760
748
|
break;
|
|
761
749
|
}
|
|
762
|
-
if (
|
|
750
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
763
751
|
info.log += "high priority timeout, will try all elements" + "\n";
|
|
764
752
|
highPriorityOnly = false;
|
|
753
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
754
|
+
lazy_scroll = true;
|
|
755
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
756
|
+
}
|
|
765
757
|
}
|
|
766
|
-
if (
|
|
758
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
767
759
|
info.log += "visible only timeout, will try all elements" + "\n";
|
|
768
760
|
visibleOnly = false;
|
|
769
761
|
}
|
|
770
762
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
771
763
|
}
|
|
772
764
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
773
|
-
info.
|
|
765
|
+
// if (info.locatorLog) {
|
|
766
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
767
|
+
// for (let line of lines) {
|
|
768
|
+
// this.logger.debug(line);
|
|
769
|
+
// }
|
|
770
|
+
// }
|
|
771
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
772
|
+
info.failCause.locatorNotFound = true;
|
|
773
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
774
774
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
775
775
|
}
|
|
776
776
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -784,8 +784,9 @@ class StableBrowser {
|
|
|
784
784
|
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
785
785
|
}
|
|
786
786
|
catch (e) {
|
|
787
|
-
this
|
|
788
|
-
this.logger.debug(
|
|
787
|
+
// this call can fail it the browser is navigating
|
|
788
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
789
|
+
// this.logger.debug(e);
|
|
789
790
|
foundLocators = [];
|
|
790
791
|
try {
|
|
791
792
|
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
@@ -802,10 +803,29 @@ class StableBrowser {
|
|
|
802
803
|
});
|
|
803
804
|
result.locatorIndex = i;
|
|
804
805
|
}
|
|
806
|
+
if (foundLocators.length > 1) {
|
|
807
|
+
info.failCause.foundMultiple = true;
|
|
808
|
+
if (info.locatorLog) {
|
|
809
|
+
info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
|
|
810
|
+
}
|
|
811
|
+
}
|
|
805
812
|
}
|
|
806
813
|
return result;
|
|
807
814
|
}
|
|
808
815
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
816
|
+
const state = {
|
|
817
|
+
locate: false,
|
|
818
|
+
scroll: false,
|
|
819
|
+
highlight: false,
|
|
820
|
+
_params,
|
|
821
|
+
options,
|
|
822
|
+
world,
|
|
823
|
+
type: Types.CLICK,
|
|
824
|
+
text: "Click element",
|
|
825
|
+
operation: "simpleClick",
|
|
826
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
827
|
+
};
|
|
828
|
+
_preCommand(state, this);
|
|
809
829
|
const startTime = Date.now();
|
|
810
830
|
let timeout = 30000;
|
|
811
831
|
if (options && options.timeout) {
|
|
@@ -829,13 +849,32 @@ class StableBrowser {
|
|
|
829
849
|
}
|
|
830
850
|
catch (e) {
|
|
831
851
|
if (performance.now() - startTime > timeout) {
|
|
832
|
-
throw e;
|
|
852
|
+
// throw e;
|
|
853
|
+
try {
|
|
854
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
855
|
+
}
|
|
856
|
+
finally {
|
|
857
|
+
_commandFinally(state, this);
|
|
858
|
+
}
|
|
833
859
|
}
|
|
834
860
|
}
|
|
835
861
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
836
862
|
}
|
|
837
863
|
}
|
|
838
864
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
865
|
+
const state = {
|
|
866
|
+
locate: false,
|
|
867
|
+
scroll: false,
|
|
868
|
+
highlight: false,
|
|
869
|
+
_params,
|
|
870
|
+
options,
|
|
871
|
+
world,
|
|
872
|
+
type: Types.FILL,
|
|
873
|
+
text: "Fill element",
|
|
874
|
+
operation: "simpleClickType",
|
|
875
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
876
|
+
};
|
|
877
|
+
_preCommand(state, this);
|
|
839
878
|
const startTime = Date.now();
|
|
840
879
|
let timeout = 30000;
|
|
841
880
|
if (options && options.timeout) {
|
|
@@ -859,7 +898,13 @@ class StableBrowser {
|
|
|
859
898
|
}
|
|
860
899
|
catch (e) {
|
|
861
900
|
if (performance.now() - startTime > timeout) {
|
|
862
|
-
throw e;
|
|
901
|
+
// throw e;
|
|
902
|
+
try {
|
|
903
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
904
|
+
}
|
|
905
|
+
finally {
|
|
906
|
+
_commandFinally(state, this);
|
|
907
|
+
}
|
|
863
908
|
}
|
|
864
909
|
}
|
|
865
910
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -883,13 +928,13 @@ class StableBrowser {
|
|
|
883
928
|
}
|
|
884
929
|
try {
|
|
885
930
|
await state.element.click();
|
|
886
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
931
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
887
932
|
}
|
|
888
933
|
catch (e) {
|
|
889
934
|
// await this.closeUnexpectedPopups();
|
|
890
935
|
state.element = await this._locate(selectors, state.info, _params);
|
|
891
936
|
await state.element.dispatchEvent("click");
|
|
892
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
937
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
893
938
|
}
|
|
894
939
|
await this.waitForPageLoad();
|
|
895
940
|
return state.info;
|
|
@@ -968,7 +1013,6 @@ class StableBrowser {
|
|
|
968
1013
|
await state.element.hover({ timeout: 10000 });
|
|
969
1014
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
970
1015
|
}
|
|
971
|
-
await _screenshot(state, this);
|
|
972
1016
|
await this.waitForPageLoad();
|
|
973
1017
|
return state.info;
|
|
974
1018
|
}
|
|
@@ -1096,33 +1140,30 @@ class StableBrowser {
|
|
|
1096
1140
|
}
|
|
1097
1141
|
}
|
|
1098
1142
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1143
|
+
const state = {
|
|
1144
|
+
selectors,
|
|
1145
|
+
_params,
|
|
1146
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1147
|
+
options,
|
|
1148
|
+
world,
|
|
1149
|
+
type: Types.SET_DATE_TIME,
|
|
1150
|
+
text: `Set date time value: ${value}`,
|
|
1151
|
+
operation: "setDateTime",
|
|
1152
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1153
|
+
throwError: false,
|
|
1154
|
+
};
|
|
1109
1155
|
try {
|
|
1110
|
-
|
|
1111
|
-
let element = await this._locate(selectors, info, _params);
|
|
1112
|
-
//insert red border around the element
|
|
1113
|
-
await this.scrollIfNeeded(element, info);
|
|
1114
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1115
|
-
await this._highlightElements(element);
|
|
1156
|
+
await _preCommand(state, this);
|
|
1116
1157
|
try {
|
|
1117
|
-
await element.click();
|
|
1158
|
+
await state.element.click();
|
|
1118
1159
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1119
1160
|
if (format) {
|
|
1120
|
-
value = dayjs(value).format(format);
|
|
1121
|
-
await element.fill(value);
|
|
1161
|
+
state.value = dayjs(state.value).format(format);
|
|
1162
|
+
await state.element.fill(state.value);
|
|
1122
1163
|
}
|
|
1123
1164
|
else {
|
|
1124
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1125
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1165
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1166
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1126
1167
|
el.value = ""; // clear input
|
|
1127
1168
|
el.value = dateTimeValue;
|
|
1128
1169
|
}, dateTimeValue);
|
|
@@ -1135,20 +1176,19 @@ class StableBrowser {
|
|
|
1135
1176
|
}
|
|
1136
1177
|
catch (err) {
|
|
1137
1178
|
//await this.closeUnexpectedPopups();
|
|
1138
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1179
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1139
1180
|
this.logger.info("Trying again");
|
|
1140
|
-
|
|
1141
|
-
info.
|
|
1142
|
-
Object.assign(err, { info: info });
|
|
1181
|
+
await _screenshot(state, this);
|
|
1182
|
+
Object.assign(err, { info: state.info });
|
|
1143
1183
|
await element.click();
|
|
1144
1184
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1145
1185
|
if (format) {
|
|
1146
|
-
value = dayjs(value).format(format);
|
|
1147
|
-
await element.fill(value);
|
|
1186
|
+
state.value = dayjs(state.value).format(format);
|
|
1187
|
+
await state.element.fill(state.value);
|
|
1148
1188
|
}
|
|
1149
1189
|
else {
|
|
1150
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1151
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1190
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1191
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1152
1192
|
el.value = ""; // clear input
|
|
1153
1193
|
el.value = dateTimeValue;
|
|
1154
1194
|
}, dateTimeValue);
|
|
@@ -1161,31 +1201,10 @@ class StableBrowser {
|
|
|
1161
1201
|
}
|
|
1162
1202
|
}
|
|
1163
1203
|
catch (e) {
|
|
1164
|
-
|
|
1165
|
-
throw e;
|
|
1204
|
+
await _commandError(state, e, this);
|
|
1166
1205
|
}
|
|
1167
1206
|
finally {
|
|
1168
|
-
|
|
1169
|
-
this._reportToWorld(world, {
|
|
1170
|
-
element_name: selectors.element_name,
|
|
1171
|
-
type: Types.SET_DATE_TIME,
|
|
1172
|
-
screenshotId,
|
|
1173
|
-
value: value,
|
|
1174
|
-
text: `setDateTime input with value: ${value}`,
|
|
1175
|
-
result: error
|
|
1176
|
-
? {
|
|
1177
|
-
status: "FAILED",
|
|
1178
|
-
startTime,
|
|
1179
|
-
endTime,
|
|
1180
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1181
|
-
}
|
|
1182
|
-
: {
|
|
1183
|
-
status: "PASSED",
|
|
1184
|
-
startTime,
|
|
1185
|
-
endTime,
|
|
1186
|
-
},
|
|
1187
|
-
info: info,
|
|
1188
|
-
});
|
|
1207
|
+
_commandFinally(state, this);
|
|
1189
1208
|
}
|
|
1190
1209
|
}
|
|
1191
1210
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1322,6 +1341,7 @@ class StableBrowser {
|
|
|
1322
1341
|
let screenshotPath = null;
|
|
1323
1342
|
if (!info.log) {
|
|
1324
1343
|
info.log = "";
|
|
1344
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1325
1345
|
}
|
|
1326
1346
|
info.operation = "getText";
|
|
1327
1347
|
info.selectors = selectors;
|
|
@@ -1660,11 +1680,9 @@ class StableBrowser {
|
|
|
1660
1680
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1661
1681
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1662
1682
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
}
|
|
1667
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1683
|
+
// to make sure the path doesn't start with -
|
|
1684
|
+
const uuidStr = "id_" + randomUUID();
|
|
1685
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1668
1686
|
try {
|
|
1669
1687
|
await this.takeScreenshot(screenshotPath);
|
|
1670
1688
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1678,7 +1696,7 @@ class StableBrowser {
|
|
|
1678
1696
|
catch (e) {
|
|
1679
1697
|
this.logger.info("unable to take screenshot, ignored");
|
|
1680
1698
|
}
|
|
1681
|
-
result.screenshotId =
|
|
1699
|
+
result.screenshotId = uuidStr;
|
|
1682
1700
|
result.screenshotPath = screenshotPath;
|
|
1683
1701
|
if (info && info.box) {
|
|
1684
1702
|
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
@@ -1776,74 +1794,101 @@ class StableBrowser {
|
|
|
1776
1794
|
}
|
|
1777
1795
|
}
|
|
1778
1796
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1797
|
+
const state = {
|
|
1798
|
+
selectors,
|
|
1799
|
+
_params,
|
|
1800
|
+
attribute,
|
|
1801
|
+
variable,
|
|
1802
|
+
options,
|
|
1803
|
+
world,
|
|
1804
|
+
type: Types.EXTRACT,
|
|
1805
|
+
text: `Extract attribute from element`,
|
|
1806
|
+
operation: "extractAttribute",
|
|
1807
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1808
|
+
};
|
|
1784
1809
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1785
|
-
const info = {};
|
|
1786
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1787
|
-
info.operation = "extract";
|
|
1788
|
-
info.selectors = selectors;
|
|
1789
1810
|
try {
|
|
1790
|
-
|
|
1791
|
-
await this._highlightElements(element);
|
|
1792
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1811
|
+
await _preCommand(state, this);
|
|
1793
1812
|
switch (attribute) {
|
|
1794
1813
|
case "inner_text":
|
|
1795
|
-
|
|
1814
|
+
state.value = await state.element.innerText();
|
|
1796
1815
|
break;
|
|
1797
1816
|
case "href":
|
|
1798
|
-
|
|
1817
|
+
state.value = await state.element.getAttribute("href");
|
|
1818
|
+
break;
|
|
1819
|
+
case "value":
|
|
1820
|
+
state.value = await state.element.inputValue();
|
|
1821
|
+
break;
|
|
1822
|
+
default:
|
|
1823
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1824
|
+
break;
|
|
1825
|
+
}
|
|
1826
|
+
state.info.value = state.value;
|
|
1827
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1828
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1829
|
+
return state.info;
|
|
1830
|
+
}
|
|
1831
|
+
catch (e) {
|
|
1832
|
+
await _commandError(state, e, this);
|
|
1833
|
+
}
|
|
1834
|
+
finally {
|
|
1835
|
+
_commandFinally(state, this);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1839
|
+
const state = {
|
|
1840
|
+
selectors,
|
|
1841
|
+
_params,
|
|
1842
|
+
attribute,
|
|
1843
|
+
value,
|
|
1844
|
+
options,
|
|
1845
|
+
world,
|
|
1846
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1847
|
+
text: `Verify element attribute`,
|
|
1848
|
+
operation: "verifyAttribute",
|
|
1849
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1850
|
+
};
|
|
1851
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1852
|
+
let val;
|
|
1853
|
+
try {
|
|
1854
|
+
await _preCommand(state, this);
|
|
1855
|
+
switch (attribute) {
|
|
1856
|
+
case "innerText":
|
|
1857
|
+
val = String(await state.element.innerText());
|
|
1799
1858
|
break;
|
|
1800
1859
|
case "value":
|
|
1801
|
-
|
|
1860
|
+
val = String(await state.element.inputValue());
|
|
1861
|
+
break;
|
|
1862
|
+
case "checked":
|
|
1863
|
+
val = String(await state.element.isChecked());
|
|
1864
|
+
break;
|
|
1865
|
+
case "disabled":
|
|
1866
|
+
val = String(await state.element.isDisabled());
|
|
1802
1867
|
break;
|
|
1803
1868
|
default:
|
|
1804
|
-
|
|
1869
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1805
1870
|
break;
|
|
1806
1871
|
}
|
|
1807
|
-
|
|
1808
|
-
if (
|
|
1809
|
-
|
|
1872
|
+
let regex;
|
|
1873
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1874
|
+
const patternBody = value.slice(1, -1);
|
|
1875
|
+
regex = new RegExp(patternBody, "g");
|
|
1810
1876
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1877
|
+
else {
|
|
1878
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1879
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1880
|
+
}
|
|
1881
|
+
if (!val.match(regex)) {
|
|
1882
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
1883
|
+
}
|
|
1884
|
+
state.info.value = val;
|
|
1885
|
+
return state.info;
|
|
1814
1886
|
}
|
|
1815
1887
|
catch (e) {
|
|
1816
|
-
|
|
1817
|
-
this.logger.error("extract failed " + info.log);
|
|
1818
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1819
|
-
info.screenshotPath = screenshotPath;
|
|
1820
|
-
Object.assign(e, { info: info });
|
|
1821
|
-
error = e;
|
|
1822
|
-
throw e;
|
|
1888
|
+
await _commandError(state, e, this);
|
|
1823
1889
|
}
|
|
1824
1890
|
finally {
|
|
1825
|
-
|
|
1826
|
-
this._reportToWorld(world, {
|
|
1827
|
-
element_name: selectors.element_name,
|
|
1828
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1829
|
-
variable: variable,
|
|
1830
|
-
value: info.value,
|
|
1831
|
-
text: "Extract attribute from element",
|
|
1832
|
-
screenshotId,
|
|
1833
|
-
result: error
|
|
1834
|
-
? {
|
|
1835
|
-
status: "FAILED",
|
|
1836
|
-
startTime,
|
|
1837
|
-
endTime,
|
|
1838
|
-
message: error?.message,
|
|
1839
|
-
}
|
|
1840
|
-
: {
|
|
1841
|
-
status: "PASSED",
|
|
1842
|
-
startTime,
|
|
1843
|
-
endTime,
|
|
1844
|
-
},
|
|
1845
|
-
info: info,
|
|
1846
|
-
});
|
|
1891
|
+
_commandFinally(state, this);
|
|
1847
1892
|
}
|
|
1848
1893
|
}
|
|
1849
1894
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1864,7 +1909,7 @@ class StableBrowser {
|
|
|
1864
1909
|
if (options && options.timeout) {
|
|
1865
1910
|
timeout = options.timeout;
|
|
1866
1911
|
}
|
|
1867
|
-
const serviceUrl =
|
|
1912
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1868
1913
|
const request = {
|
|
1869
1914
|
method: "POST",
|
|
1870
1915
|
url: serviceUrl,
|
|
@@ -1920,7 +1965,8 @@ class StableBrowser {
|
|
|
1920
1965
|
catch (e) {
|
|
1921
1966
|
errorCount++;
|
|
1922
1967
|
if (errorCount > 3) {
|
|
1923
|
-
throw e;
|
|
1968
|
+
// throw e;
|
|
1969
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1924
1970
|
}
|
|
1925
1971
|
// ignore
|
|
1926
1972
|
}
|
|
@@ -2031,11 +2077,12 @@ class StableBrowser {
|
|
|
2031
2077
|
info.screenshotPath = screenshotPath;
|
|
2032
2078
|
Object.assign(e, { info: info });
|
|
2033
2079
|
error = e;
|
|
2034
|
-
throw e;
|
|
2080
|
+
// throw e;
|
|
2081
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2035
2082
|
}
|
|
2036
2083
|
finally {
|
|
2037
2084
|
const endTime = Date.now();
|
|
2038
|
-
|
|
2085
|
+
_reportToWorld(world, {
|
|
2039
2086
|
type: Types.VERIFY_PAGE_PATH,
|
|
2040
2087
|
text: "Verify page path",
|
|
2041
2088
|
screenshotId,
|
|
@@ -2055,54 +2102,64 @@ class StableBrowser {
|
|
|
2055
2102
|
});
|
|
2056
2103
|
}
|
|
2057
2104
|
}
|
|
2105
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2106
|
+
const frames = this.page.frames();
|
|
2107
|
+
let results = [];
|
|
2108
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2109
|
+
if (dateAlternatives.date) {
|
|
2110
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2111
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
|
|
2112
|
+
result.frame = frames[i];
|
|
2113
|
+
results.push(result);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
else if (numberAlternatives.number) {
|
|
2117
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2118
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
|
|
2119
|
+
result.frame = frames[i];
|
|
2120
|
+
results.push(result);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
else {
|
|
2124
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
|
|
2125
|
+
result.frame = frames[i];
|
|
2126
|
+
results.push(result);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
state.info.results = results;
|
|
2130
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2131
|
+
return resultWithElementsFound;
|
|
2132
|
+
}
|
|
2058
2133
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2059
2134
|
text = unEscapeString(text);
|
|
2060
|
-
const
|
|
2135
|
+
const state = {
|
|
2136
|
+
text_search: text,
|
|
2137
|
+
options,
|
|
2138
|
+
world,
|
|
2139
|
+
locate: false,
|
|
2140
|
+
scroll: false,
|
|
2141
|
+
highlight: false,
|
|
2142
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2143
|
+
text: `Verify text exists in page`,
|
|
2144
|
+
operation: "verifyTextExistInPage",
|
|
2145
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2146
|
+
};
|
|
2061
2147
|
const timeout = this._getLoadTimeout(options);
|
|
2062
|
-
let error = null;
|
|
2063
|
-
let screenshotId = null;
|
|
2064
|
-
let screenshotPath = null;
|
|
2065
2148
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2066
|
-
const info = {};
|
|
2067
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2068
|
-
info.operation = "verifyTextExistInPage";
|
|
2069
2149
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2070
2150
|
if (newValue !== text) {
|
|
2071
2151
|
this.logger.info(text + "=" + newValue);
|
|
2072
2152
|
text = newValue;
|
|
2073
2153
|
}
|
|
2074
|
-
info.text = text;
|
|
2075
2154
|
let dateAlternatives = findDateAlternatives(text);
|
|
2076
2155
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2077
2156
|
try {
|
|
2157
|
+
await _preCommand(state, this);
|
|
2158
|
+
state.info.text = text;
|
|
2078
2159
|
while (true) {
|
|
2079
|
-
const
|
|
2080
|
-
let results = [];
|
|
2081
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2082
|
-
if (dateAlternatives.date) {
|
|
2083
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2084
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
|
|
2085
|
-
result.frame = frames[i];
|
|
2086
|
-
results.push(result);
|
|
2087
|
-
}
|
|
2088
|
-
}
|
|
2089
|
-
else if (numberAlternatives.number) {
|
|
2090
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2091
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
|
|
2092
|
-
result.frame = frames[i];
|
|
2093
|
-
results.push(result);
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
else {
|
|
2097
|
-
const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
|
|
2098
|
-
result.frame = frames[i];
|
|
2099
|
-
results.push(result);
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
info.results = results;
|
|
2103
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2160
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2104
2161
|
if (resultWithElementsFound.length === 0) {
|
|
2105
|
-
if (Date.now() - startTime > timeout) {
|
|
2162
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2106
2163
|
throw new Error(`Text ${text} not found in page`);
|
|
2107
2164
|
}
|
|
2108
2165
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2114,55 +2171,163 @@ class StableBrowser {
|
|
|
2114
2171
|
await this._highlightElements(frame, dataAttribute);
|
|
2115
2172
|
const element = await frame.$(dataAttribute);
|
|
2116
2173
|
if (element) {
|
|
2117
|
-
await this.scrollIfNeeded(element, info);
|
|
2174
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2118
2175
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2119
2176
|
}
|
|
2120
2177
|
}
|
|
2121
|
-
|
|
2122
|
-
return info;
|
|
2178
|
+
await _screenshot(state, this);
|
|
2179
|
+
return state.info;
|
|
2123
2180
|
}
|
|
2124
2181
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2125
2182
|
}
|
|
2126
2183
|
catch (e) {
|
|
2127
|
-
|
|
2128
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2129
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2130
|
-
info.screenshotPath = screenshotPath;
|
|
2131
|
-
Object.assign(e, { info: info });
|
|
2132
|
-
error = e;
|
|
2133
|
-
throw e;
|
|
2184
|
+
await _commandError(state, e, this);
|
|
2134
2185
|
}
|
|
2135
2186
|
finally {
|
|
2136
|
-
|
|
2137
|
-
this._reportToWorld(world, {
|
|
2138
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
2139
|
-
text: "Verify text exists in page",
|
|
2140
|
-
screenshotId,
|
|
2141
|
-
result: error
|
|
2142
|
-
? {
|
|
2143
|
-
status: "FAILED",
|
|
2144
|
-
startTime,
|
|
2145
|
-
endTime,
|
|
2146
|
-
message: error?.message,
|
|
2147
|
-
}
|
|
2148
|
-
: {
|
|
2149
|
-
status: "PASSED",
|
|
2150
|
-
startTime,
|
|
2151
|
-
endTime,
|
|
2152
|
-
},
|
|
2153
|
-
info: info,
|
|
2154
|
-
});
|
|
2187
|
+
_commandFinally(state, this);
|
|
2155
2188
|
}
|
|
2156
2189
|
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2190
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2191
|
+
text = unEscapeString(text);
|
|
2192
|
+
const state = {
|
|
2193
|
+
text_search: text,
|
|
2194
|
+
options,
|
|
2195
|
+
world,
|
|
2196
|
+
locate: false,
|
|
2197
|
+
scroll: false,
|
|
2198
|
+
highlight: false,
|
|
2199
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2200
|
+
text: `Verify text does not exist in page`,
|
|
2201
|
+
operation: "verifyTextNotExistInPage",
|
|
2202
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2203
|
+
};
|
|
2204
|
+
const timeout = this._getLoadTimeout(options);
|
|
2205
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2206
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2207
|
+
if (newValue !== text) {
|
|
2208
|
+
this.logger.info(text + "=" + newValue);
|
|
2209
|
+
text = newValue;
|
|
2210
|
+
}
|
|
2211
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2212
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2213
|
+
try {
|
|
2214
|
+
await _preCommand(state, this);
|
|
2215
|
+
state.info.text = text;
|
|
2216
|
+
while (true) {
|
|
2217
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2218
|
+
if (resultWithElementsFound.length === 0) {
|
|
2219
|
+
await _screenshot(state, this);
|
|
2220
|
+
return state.info;
|
|
2221
|
+
}
|
|
2222
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2223
|
+
throw new Error(`Text ${text} found in page`);
|
|
2224
|
+
}
|
|
2225
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
catch (e) {
|
|
2229
|
+
await _commandError(state, e, this);
|
|
2230
|
+
}
|
|
2231
|
+
finally {
|
|
2232
|
+
_commandFinally(state, this);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2236
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2237
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2238
|
+
const state = {
|
|
2239
|
+
text_search: textToVerify,
|
|
2240
|
+
options,
|
|
2241
|
+
world,
|
|
2242
|
+
locate: false,
|
|
2243
|
+
scroll: false,
|
|
2244
|
+
highlight: false,
|
|
2245
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2246
|
+
text: `Verify text with relation to another text`,
|
|
2247
|
+
operation: "verify_text_with_relation",
|
|
2248
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2249
|
+
};
|
|
2250
|
+
const timeout = this._getLoadTimeout(options);
|
|
2251
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2252
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2253
|
+
if (newValue !== textAnchor) {
|
|
2254
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2255
|
+
textAnchor = newValue;
|
|
2256
|
+
}
|
|
2257
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2258
|
+
if (newValue !== textToVerify) {
|
|
2259
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2260
|
+
textToVerify = newValue;
|
|
2261
|
+
}
|
|
2262
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2263
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2264
|
+
let foundAncore = false;
|
|
2265
|
+
try {
|
|
2266
|
+
await _preCommand(state, this);
|
|
2267
|
+
state.info.text = textToVerify;
|
|
2268
|
+
while (true) {
|
|
2269
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2270
|
+
if (resultWithElementsFound.length === 0) {
|
|
2271
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2272
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2273
|
+
}
|
|
2274
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2275
|
+
continue;
|
|
2276
|
+
}
|
|
2277
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2278
|
+
foundAncore = true;
|
|
2279
|
+
const result = resultWithElementsFound[i];
|
|
2280
|
+
const token = result.randomToken;
|
|
2281
|
+
const frame = result.frame;
|
|
2282
|
+
const css = `[data-blinq-id="blinq-id-${token}"]`;
|
|
2283
|
+
const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
|
|
2284
|
+
const elements = Array.from(document.querySelectorAll(css));
|
|
2285
|
+
for (let i = 0; i < elements.length; i++) {
|
|
2286
|
+
const element = elements[i];
|
|
2287
|
+
let climbParent = element;
|
|
2288
|
+
for (let j = 0; j < climb; j++) {
|
|
2289
|
+
climbParent = climbParent.parentElement;
|
|
2290
|
+
if (!climbParent) {
|
|
2291
|
+
break;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
if (!climbParent) {
|
|
2295
|
+
continue;
|
|
2296
|
+
}
|
|
2297
|
+
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2298
|
+
if (foundElements.length > 0) {
|
|
2299
|
+
// set the container element attribute
|
|
2300
|
+
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2301
|
+
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2302
|
+
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2303
|
+
return { found: true };
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
return { found: false };
|
|
2307
|
+
}, [css, climb, textToVerify, result.randomToken]);
|
|
2308
|
+
if (findResult.found === true) {
|
|
2309
|
+
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2310
|
+
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2311
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2312
|
+
await this._highlightElements(frame, cssAnchor);
|
|
2313
|
+
const element = await frame.$(dataAttribute);
|
|
2314
|
+
if (element) {
|
|
2315
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2316
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2317
|
+
}
|
|
2318
|
+
await _screenshot(state, this);
|
|
2319
|
+
return state.info;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2324
|
+
}
|
|
2325
|
+
catch (e) {
|
|
2326
|
+
await _commandError(state, e, this);
|
|
2161
2327
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2328
|
+
finally {
|
|
2329
|
+
_commandFinally(state, this);
|
|
2164
2330
|
}
|
|
2165
|
-
return serviceUrl;
|
|
2166
2331
|
}
|
|
2167
2332
|
async visualVerification(text, options = {}, world = null) {
|
|
2168
2333
|
const startTime = Date.now();
|
|
@@ -2178,7 +2343,7 @@ class StableBrowser {
|
|
|
2178
2343
|
throw new Error("TOKEN is not set");
|
|
2179
2344
|
}
|
|
2180
2345
|
try {
|
|
2181
|
-
let serviceUrl =
|
|
2346
|
+
let serviceUrl = _getServerUrl();
|
|
2182
2347
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2183
2348
|
info.screenshotPath = screenshotPath;
|
|
2184
2349
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2214,11 +2379,12 @@ class StableBrowser {
|
|
|
2214
2379
|
info.screenshotPath = screenshotPath;
|
|
2215
2380
|
Object.assign(e, { info: info });
|
|
2216
2381
|
error = e;
|
|
2217
|
-
throw e;
|
|
2382
|
+
// throw e;
|
|
2383
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2218
2384
|
}
|
|
2219
2385
|
finally {
|
|
2220
2386
|
const endTime = Date.now();
|
|
2221
|
-
|
|
2387
|
+
_reportToWorld(world, {
|
|
2222
2388
|
type: Types.VERIFY_VISUAL,
|
|
2223
2389
|
text: "Visual verification",
|
|
2224
2390
|
screenshotId,
|
|
@@ -2266,6 +2432,7 @@ class StableBrowser {
|
|
|
2266
2432
|
let screenshotPath = null;
|
|
2267
2433
|
const info = {};
|
|
2268
2434
|
info.log = "";
|
|
2435
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2269
2436
|
info.operation = "getTableData";
|
|
2270
2437
|
info.selectors = selectors;
|
|
2271
2438
|
try {
|
|
@@ -2281,11 +2448,12 @@ class StableBrowser {
|
|
|
2281
2448
|
info.screenshotPath = screenshotPath;
|
|
2282
2449
|
Object.assign(e, { info: info });
|
|
2283
2450
|
error = e;
|
|
2284
|
-
throw e;
|
|
2451
|
+
// throw e;
|
|
2452
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2285
2453
|
}
|
|
2286
2454
|
finally {
|
|
2287
2455
|
const endTime = Date.now();
|
|
2288
|
-
|
|
2456
|
+
_reportToWorld(world, {
|
|
2289
2457
|
element_name: selectors.element_name,
|
|
2290
2458
|
type: Types.GET_TABLE_DATA,
|
|
2291
2459
|
text: "Get table data",
|
|
@@ -2340,7 +2508,7 @@ class StableBrowser {
|
|
|
2340
2508
|
info.operation = "analyzeTable";
|
|
2341
2509
|
info.selectors = selectors;
|
|
2342
2510
|
info.query = query;
|
|
2343
|
-
query =
|
|
2511
|
+
query = _fixUsingParams(query, _params);
|
|
2344
2512
|
info.query_fixed = query;
|
|
2345
2513
|
info.operator = operator;
|
|
2346
2514
|
info.value = value;
|
|
@@ -2446,11 +2614,12 @@ class StableBrowser {
|
|
|
2446
2614
|
info.screenshotPath = screenshotPath;
|
|
2447
2615
|
Object.assign(e, { info: info });
|
|
2448
2616
|
error = e;
|
|
2449
|
-
throw e;
|
|
2617
|
+
// throw e;
|
|
2618
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2450
2619
|
}
|
|
2451
2620
|
finally {
|
|
2452
2621
|
const endTime = Date.now();
|
|
2453
|
-
|
|
2622
|
+
_reportToWorld(world, {
|
|
2454
2623
|
element_name: selectors.element_name,
|
|
2455
2624
|
type: Types.ANALYZE_TABLE,
|
|
2456
2625
|
text: "Analyze table",
|
|
@@ -2523,7 +2692,7 @@ class StableBrowser {
|
|
|
2523
2692
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2524
2693
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2525
2694
|
const endTime = Date.now();
|
|
2526
|
-
|
|
2695
|
+
_reportToWorld(world, {
|
|
2527
2696
|
type: Types.GET_PAGE_STATUS,
|
|
2528
2697
|
text: "Wait for page load",
|
|
2529
2698
|
screenshotId,
|
|
@@ -2543,41 +2712,35 @@ class StableBrowser {
|
|
|
2543
2712
|
}
|
|
2544
2713
|
}
|
|
2545
2714
|
async closePage(options = {}, world = null) {
|
|
2546
|
-
const
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2715
|
+
const state = {
|
|
2716
|
+
options,
|
|
2717
|
+
world,
|
|
2718
|
+
locate: false,
|
|
2719
|
+
scroll: false,
|
|
2720
|
+
highlight: false,
|
|
2721
|
+
type: Types.CLOSE_PAGE,
|
|
2722
|
+
text: `Close page`,
|
|
2723
|
+
operation: "closePage",
|
|
2724
|
+
log: "***** close page *****\n",
|
|
2725
|
+
throwError: false,
|
|
2726
|
+
};
|
|
2551
2727
|
try {
|
|
2728
|
+
await _preCommand(state, this);
|
|
2552
2729
|
await this.page.close();
|
|
2553
2730
|
}
|
|
2554
2731
|
catch (e) {
|
|
2555
2732
|
console.log(".");
|
|
2733
|
+
await _commandError(state, e, this);
|
|
2556
2734
|
}
|
|
2557
2735
|
finally {
|
|
2558
|
-
|
|
2559
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2560
|
-
const endTime = Date.now();
|
|
2561
|
-
this._reportToWorld(world, {
|
|
2562
|
-
type: Types.CLOSE_PAGE,
|
|
2563
|
-
text: "close page",
|
|
2564
|
-
screenshotId,
|
|
2565
|
-
result: error
|
|
2566
|
-
? {
|
|
2567
|
-
status: "FAILED",
|
|
2568
|
-
startTime,
|
|
2569
|
-
endTime,
|
|
2570
|
-
message: error?.message,
|
|
2571
|
-
}
|
|
2572
|
-
: {
|
|
2573
|
-
status: "PASSED",
|
|
2574
|
-
startTime,
|
|
2575
|
-
endTime,
|
|
2576
|
-
},
|
|
2577
|
-
info: info,
|
|
2578
|
-
});
|
|
2736
|
+
_commandFinally(state, this);
|
|
2579
2737
|
}
|
|
2580
2738
|
}
|
|
2739
|
+
saveTestDataAsGlobal(options, world) {
|
|
2740
|
+
const dataFile = this._getDataFile(world);
|
|
2741
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2742
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2743
|
+
}
|
|
2581
2744
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2582
2745
|
const startTime = Date.now();
|
|
2583
2746
|
let error = null;
|
|
@@ -2595,12 +2758,13 @@ class StableBrowser {
|
|
|
2595
2758
|
}
|
|
2596
2759
|
catch (e) {
|
|
2597
2760
|
console.log(".");
|
|
2761
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2598
2762
|
}
|
|
2599
2763
|
finally {
|
|
2600
2764
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2601
2765
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2602
2766
|
const endTime = Date.now();
|
|
2603
|
-
|
|
2767
|
+
_reportToWorld(world, {
|
|
2604
2768
|
type: Types.SET_VIEWPORT,
|
|
2605
2769
|
text: "set viewport size to " + width + "x" + hight,
|
|
2606
2770
|
screenshotId,
|
|
@@ -2631,12 +2795,13 @@ class StableBrowser {
|
|
|
2631
2795
|
}
|
|
2632
2796
|
catch (e) {
|
|
2633
2797
|
console.log(".");
|
|
2798
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2634
2799
|
}
|
|
2635
2800
|
finally {
|
|
2636
2801
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2637
2802
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2638
2803
|
const endTime = Date.now();
|
|
2639
|
-
|
|
2804
|
+
_reportToWorld(world, {
|
|
2640
2805
|
type: Types.GET_PAGE_STATUS,
|
|
2641
2806
|
text: "page relaod",
|
|
2642
2807
|
screenshotId,
|
|
@@ -2672,11 +2837,37 @@ class StableBrowser {
|
|
|
2672
2837
|
console.log("#-#");
|
|
2673
2838
|
}
|
|
2674
2839
|
}
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2840
|
+
async beforeStep(world, step) {
|
|
2841
|
+
this.stepName = step.pickleStep.text;
|
|
2842
|
+
this.logger.info("step: " + this.stepName);
|
|
2843
|
+
if (this.stepIndex === undefined) {
|
|
2844
|
+
this.stepIndex = 0;
|
|
2845
|
+
}
|
|
2846
|
+
else {
|
|
2847
|
+
this.stepIndex++;
|
|
2848
|
+
}
|
|
2849
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2850
|
+
if (this.context.browserObject.context) {
|
|
2851
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
2855
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
2856
|
+
// check if @global_test_data tag is present
|
|
2857
|
+
if (this.tags.includes("@global_test_data")) {
|
|
2858
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
async afterStep(world, step) {
|
|
2863
|
+
this.stepName = null;
|
|
2864
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2865
|
+
if (this.context.browserObject.context) {
|
|
2866
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
2867
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2868
|
+
});
|
|
2869
|
+
}
|
|
2678
2870
|
}
|
|
2679
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2680
2871
|
}
|
|
2681
2872
|
}
|
|
2682
2873
|
function createTimedPromise(promise, label) {
|
|
@@ -2684,156 +2875,5 @@ function createTimedPromise(promise, label) {
|
|
|
2684
2875
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2685
2876
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2686
2877
|
}
|
|
2687
|
-
const KEYBOARD_EVENTS = [
|
|
2688
|
-
"ALT",
|
|
2689
|
-
"AltGraph",
|
|
2690
|
-
"CapsLock",
|
|
2691
|
-
"Control",
|
|
2692
|
-
"Fn",
|
|
2693
|
-
"FnLock",
|
|
2694
|
-
"Hyper",
|
|
2695
|
-
"Meta",
|
|
2696
|
-
"NumLock",
|
|
2697
|
-
"ScrollLock",
|
|
2698
|
-
"Shift",
|
|
2699
|
-
"Super",
|
|
2700
|
-
"Symbol",
|
|
2701
|
-
"SymbolLock",
|
|
2702
|
-
"Enter",
|
|
2703
|
-
"Tab",
|
|
2704
|
-
"ArrowDown",
|
|
2705
|
-
"ArrowLeft",
|
|
2706
|
-
"ArrowRight",
|
|
2707
|
-
"ArrowUp",
|
|
2708
|
-
"End",
|
|
2709
|
-
"Home",
|
|
2710
|
-
"PageDown",
|
|
2711
|
-
"PageUp",
|
|
2712
|
-
"Backspace",
|
|
2713
|
-
"Clear",
|
|
2714
|
-
"Copy",
|
|
2715
|
-
"CrSel",
|
|
2716
|
-
"Cut",
|
|
2717
|
-
"Delete",
|
|
2718
|
-
"EraseEof",
|
|
2719
|
-
"ExSel",
|
|
2720
|
-
"Insert",
|
|
2721
|
-
"Paste",
|
|
2722
|
-
"Redo",
|
|
2723
|
-
"Undo",
|
|
2724
|
-
"Accept",
|
|
2725
|
-
"Again",
|
|
2726
|
-
"Attn",
|
|
2727
|
-
"Cancel",
|
|
2728
|
-
"ContextMenu",
|
|
2729
|
-
"Escape",
|
|
2730
|
-
"Execute",
|
|
2731
|
-
"Find",
|
|
2732
|
-
"Finish",
|
|
2733
|
-
"Help",
|
|
2734
|
-
"Pause",
|
|
2735
|
-
"Play",
|
|
2736
|
-
"Props",
|
|
2737
|
-
"Select",
|
|
2738
|
-
"ZoomIn",
|
|
2739
|
-
"ZoomOut",
|
|
2740
|
-
"BrightnessDown",
|
|
2741
|
-
"BrightnessUp",
|
|
2742
|
-
"Eject",
|
|
2743
|
-
"LogOff",
|
|
2744
|
-
"Power",
|
|
2745
|
-
"PowerOff",
|
|
2746
|
-
"PrintScreen",
|
|
2747
|
-
"Hibernate",
|
|
2748
|
-
"Standby",
|
|
2749
|
-
"WakeUp",
|
|
2750
|
-
"AllCandidates",
|
|
2751
|
-
"Alphanumeric",
|
|
2752
|
-
"CodeInput",
|
|
2753
|
-
"Compose",
|
|
2754
|
-
"Convert",
|
|
2755
|
-
"Dead",
|
|
2756
|
-
"FinalMode",
|
|
2757
|
-
"GroupFirst",
|
|
2758
|
-
"GroupLast",
|
|
2759
|
-
"GroupNext",
|
|
2760
|
-
"GroupPrevious",
|
|
2761
|
-
"ModeChange",
|
|
2762
|
-
"NextCandidate",
|
|
2763
|
-
"NonConvert",
|
|
2764
|
-
"PreviousCandidate",
|
|
2765
|
-
"Process",
|
|
2766
|
-
"SingleCandidate",
|
|
2767
|
-
"HangulMode",
|
|
2768
|
-
"HanjaMode",
|
|
2769
|
-
"JunjaMode",
|
|
2770
|
-
"Eisu",
|
|
2771
|
-
"Hankaku",
|
|
2772
|
-
"Hiragana",
|
|
2773
|
-
"HiraganaKatakana",
|
|
2774
|
-
"KanaMode",
|
|
2775
|
-
"KanjiMode",
|
|
2776
|
-
"Katakana",
|
|
2777
|
-
"Romaji",
|
|
2778
|
-
"Zenkaku",
|
|
2779
|
-
"ZenkakuHanaku",
|
|
2780
|
-
"F1",
|
|
2781
|
-
"F2",
|
|
2782
|
-
"F3",
|
|
2783
|
-
"F4",
|
|
2784
|
-
"F5",
|
|
2785
|
-
"F6",
|
|
2786
|
-
"F7",
|
|
2787
|
-
"F8",
|
|
2788
|
-
"F9",
|
|
2789
|
-
"F10",
|
|
2790
|
-
"F11",
|
|
2791
|
-
"F12",
|
|
2792
|
-
"Soft1",
|
|
2793
|
-
"Soft2",
|
|
2794
|
-
"Soft3",
|
|
2795
|
-
"Soft4",
|
|
2796
|
-
"ChannelDown",
|
|
2797
|
-
"ChannelUp",
|
|
2798
|
-
"Close",
|
|
2799
|
-
"MailForward",
|
|
2800
|
-
"MailReply",
|
|
2801
|
-
"MailSend",
|
|
2802
|
-
"MediaFastForward",
|
|
2803
|
-
"MediaPause",
|
|
2804
|
-
"MediaPlay",
|
|
2805
|
-
"MediaPlayPause",
|
|
2806
|
-
"MediaRecord",
|
|
2807
|
-
"MediaRewind",
|
|
2808
|
-
"MediaStop",
|
|
2809
|
-
"MediaTrackNext",
|
|
2810
|
-
"MediaTrackPrevious",
|
|
2811
|
-
"AudioBalanceLeft",
|
|
2812
|
-
"AudioBalanceRight",
|
|
2813
|
-
"AudioBassBoostDown",
|
|
2814
|
-
"AudioBassBoostToggle",
|
|
2815
|
-
"AudioBassBoostUp",
|
|
2816
|
-
"AudioFaderFront",
|
|
2817
|
-
"AudioFaderRear",
|
|
2818
|
-
"AudioSurroundModeNext",
|
|
2819
|
-
"AudioTrebleDown",
|
|
2820
|
-
"AudioTrebleUp",
|
|
2821
|
-
"AudioVolumeDown",
|
|
2822
|
-
"AudioVolumeMute",
|
|
2823
|
-
"AudioVolumeUp",
|
|
2824
|
-
"MicrophoneToggle",
|
|
2825
|
-
"MicrophoneVolumeDown",
|
|
2826
|
-
"MicrophoneVolumeMute",
|
|
2827
|
-
"MicrophoneVolumeUp",
|
|
2828
|
-
"TV",
|
|
2829
|
-
"TV3DMode",
|
|
2830
|
-
"TVAntennaCable",
|
|
2831
|
-
"TVAudioDescription",
|
|
2832
|
-
];
|
|
2833
|
-
function unEscapeString(str) {
|
|
2834
|
-
const placeholder = "__NEWLINE__";
|
|
2835
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2836
|
-
return str;
|
|
2837
|
-
}
|
|
2838
2878
|
export { StableBrowser };
|
|
2839
2879
|
//# sourceMappingURL=stable_browser.js.map
|