automation_model 1.0.494-dev → 1.0.494-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 -21
- 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 +26 -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.d.ts +2 -1
- package/lib/network.js +37 -3
- 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 +618 -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 +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 {
|
|
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,105 @@ 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");
|
|
1799
1818
|
break;
|
|
1800
1819
|
case "value":
|
|
1801
|
-
|
|
1820
|
+
state.value = await state.element.inputValue();
|
|
1802
1821
|
break;
|
|
1803
1822
|
default:
|
|
1804
|
-
|
|
1823
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1805
1824
|
break;
|
|
1806
1825
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
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());
|
|
1858
|
+
break;
|
|
1859
|
+
case "value":
|
|
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());
|
|
1867
|
+
break;
|
|
1868
|
+
case "readOnly":
|
|
1869
|
+
const isEditable = await state.element.isEditable();
|
|
1870
|
+
val = String(!isEditable);
|
|
1871
|
+
break;
|
|
1872
|
+
default:
|
|
1873
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1874
|
+
break;
|
|
1810
1875
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1876
|
+
state.info.expectedValue = val;
|
|
1877
|
+
let regex;
|
|
1878
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1879
|
+
const patternBody = value.slice(1, -1);
|
|
1880
|
+
regex = new RegExp(patternBody, "g");
|
|
1881
|
+
}
|
|
1882
|
+
else {
|
|
1883
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1884
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1885
|
+
}
|
|
1886
|
+
if (!val.match(regex)) {
|
|
1887
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
1888
|
+
}
|
|
1889
|
+
return state.info;
|
|
1814
1890
|
}
|
|
1815
1891
|
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;
|
|
1892
|
+
await _commandError(state, e, this);
|
|
1823
1893
|
}
|
|
1824
1894
|
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
|
-
});
|
|
1895
|
+
_commandFinally(state, this);
|
|
1847
1896
|
}
|
|
1848
1897
|
}
|
|
1849
1898
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1864,7 +1913,7 @@ class StableBrowser {
|
|
|
1864
1913
|
if (options && options.timeout) {
|
|
1865
1914
|
timeout = options.timeout;
|
|
1866
1915
|
}
|
|
1867
|
-
const serviceUrl =
|
|
1916
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1868
1917
|
const request = {
|
|
1869
1918
|
method: "POST",
|
|
1870
1919
|
url: serviceUrl,
|
|
@@ -1920,7 +1969,8 @@ class StableBrowser {
|
|
|
1920
1969
|
catch (e) {
|
|
1921
1970
|
errorCount++;
|
|
1922
1971
|
if (errorCount > 3) {
|
|
1923
|
-
throw e;
|
|
1972
|
+
// throw e;
|
|
1973
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1924
1974
|
}
|
|
1925
1975
|
// ignore
|
|
1926
1976
|
}
|
|
@@ -2031,11 +2081,12 @@ class StableBrowser {
|
|
|
2031
2081
|
info.screenshotPath = screenshotPath;
|
|
2032
2082
|
Object.assign(e, { info: info });
|
|
2033
2083
|
error = e;
|
|
2034
|
-
throw e;
|
|
2084
|
+
// throw e;
|
|
2085
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2035
2086
|
}
|
|
2036
2087
|
finally {
|
|
2037
2088
|
const endTime = Date.now();
|
|
2038
|
-
|
|
2089
|
+
_reportToWorld(world, {
|
|
2039
2090
|
type: Types.VERIFY_PAGE_PATH,
|
|
2040
2091
|
text: "Verify page path",
|
|
2041
2092
|
screenshotId,
|
|
@@ -2055,54 +2106,64 @@ class StableBrowser {
|
|
|
2055
2106
|
});
|
|
2056
2107
|
}
|
|
2057
2108
|
}
|
|
2109
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2110
|
+
const frames = this.page.frames();
|
|
2111
|
+
let results = [];
|
|
2112
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2113
|
+
if (dateAlternatives.date) {
|
|
2114
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2115
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
|
|
2116
|
+
result.frame = frames[i];
|
|
2117
|
+
results.push(result);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
else if (numberAlternatives.number) {
|
|
2121
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2122
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
|
|
2123
|
+
result.frame = frames[i];
|
|
2124
|
+
results.push(result);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
else {
|
|
2128
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
|
|
2129
|
+
result.frame = frames[i];
|
|
2130
|
+
results.push(result);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
state.info.results = results;
|
|
2134
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2135
|
+
return resultWithElementsFound;
|
|
2136
|
+
}
|
|
2058
2137
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2059
2138
|
text = unEscapeString(text);
|
|
2060
|
-
const
|
|
2139
|
+
const state = {
|
|
2140
|
+
text_search: text,
|
|
2141
|
+
options,
|
|
2142
|
+
world,
|
|
2143
|
+
locate: false,
|
|
2144
|
+
scroll: false,
|
|
2145
|
+
highlight: false,
|
|
2146
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2147
|
+
text: `Verify text exists in page`,
|
|
2148
|
+
operation: "verifyTextExistInPage",
|
|
2149
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2150
|
+
};
|
|
2061
2151
|
const timeout = this._getLoadTimeout(options);
|
|
2062
|
-
let error = null;
|
|
2063
|
-
let screenshotId = null;
|
|
2064
|
-
let screenshotPath = null;
|
|
2065
2152
|
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
2153
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2070
2154
|
if (newValue !== text) {
|
|
2071
2155
|
this.logger.info(text + "=" + newValue);
|
|
2072
2156
|
text = newValue;
|
|
2073
2157
|
}
|
|
2074
|
-
info.text = text;
|
|
2075
2158
|
let dateAlternatives = findDateAlternatives(text);
|
|
2076
2159
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2077
2160
|
try {
|
|
2161
|
+
await _preCommand(state, this);
|
|
2162
|
+
state.info.text = text;
|
|
2078
2163
|
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);
|
|
2164
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2104
2165
|
if (resultWithElementsFound.length === 0) {
|
|
2105
|
-
if (Date.now() - startTime > timeout) {
|
|
2166
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2106
2167
|
throw new Error(`Text ${text} not found in page`);
|
|
2107
2168
|
}
|
|
2108
2169
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2114,55 +2175,163 @@ class StableBrowser {
|
|
|
2114
2175
|
await this._highlightElements(frame, dataAttribute);
|
|
2115
2176
|
const element = await frame.$(dataAttribute);
|
|
2116
2177
|
if (element) {
|
|
2117
|
-
await this.scrollIfNeeded(element, info);
|
|
2178
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2118
2179
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2119
2180
|
}
|
|
2120
2181
|
}
|
|
2121
|
-
|
|
2122
|
-
return info;
|
|
2182
|
+
await _screenshot(state, this);
|
|
2183
|
+
return state.info;
|
|
2123
2184
|
}
|
|
2124
2185
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2125
2186
|
}
|
|
2126
2187
|
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;
|
|
2188
|
+
await _commandError(state, e, this);
|
|
2134
2189
|
}
|
|
2135
2190
|
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
|
-
});
|
|
2191
|
+
_commandFinally(state, this);
|
|
2155
2192
|
}
|
|
2156
2193
|
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2194
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2195
|
+
text = unEscapeString(text);
|
|
2196
|
+
const state = {
|
|
2197
|
+
text_search: text,
|
|
2198
|
+
options,
|
|
2199
|
+
world,
|
|
2200
|
+
locate: false,
|
|
2201
|
+
scroll: false,
|
|
2202
|
+
highlight: false,
|
|
2203
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2204
|
+
text: `Verify text does not exist in page`,
|
|
2205
|
+
operation: "verifyTextNotExistInPage",
|
|
2206
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2207
|
+
};
|
|
2208
|
+
const timeout = this._getLoadTimeout(options);
|
|
2209
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2210
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2211
|
+
if (newValue !== text) {
|
|
2212
|
+
this.logger.info(text + "=" + newValue);
|
|
2213
|
+
text = newValue;
|
|
2214
|
+
}
|
|
2215
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2216
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2217
|
+
try {
|
|
2218
|
+
await _preCommand(state, this);
|
|
2219
|
+
state.info.text = text;
|
|
2220
|
+
while (true) {
|
|
2221
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2222
|
+
if (resultWithElementsFound.length === 0) {
|
|
2223
|
+
await _screenshot(state, this);
|
|
2224
|
+
return state.info;
|
|
2225
|
+
}
|
|
2226
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2227
|
+
throw new Error(`Text ${text} found in page`);
|
|
2228
|
+
}
|
|
2229
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
catch (e) {
|
|
2233
|
+
await _commandError(state, e, this);
|
|
2234
|
+
}
|
|
2235
|
+
finally {
|
|
2236
|
+
_commandFinally(state, this);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2240
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2241
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2242
|
+
const state = {
|
|
2243
|
+
text_search: textToVerify,
|
|
2244
|
+
options,
|
|
2245
|
+
world,
|
|
2246
|
+
locate: false,
|
|
2247
|
+
scroll: false,
|
|
2248
|
+
highlight: false,
|
|
2249
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2250
|
+
text: `Verify text with relation to another text`,
|
|
2251
|
+
operation: "verify_text_with_relation",
|
|
2252
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2253
|
+
};
|
|
2254
|
+
const timeout = this._getLoadTimeout(options);
|
|
2255
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2256
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2257
|
+
if (newValue !== textAnchor) {
|
|
2258
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2259
|
+
textAnchor = newValue;
|
|
2260
|
+
}
|
|
2261
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2262
|
+
if (newValue !== textToVerify) {
|
|
2263
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2264
|
+
textToVerify = newValue;
|
|
2265
|
+
}
|
|
2266
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2267
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2268
|
+
let foundAncore = false;
|
|
2269
|
+
try {
|
|
2270
|
+
await _preCommand(state, this);
|
|
2271
|
+
state.info.text = textToVerify;
|
|
2272
|
+
while (true) {
|
|
2273
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2274
|
+
if (resultWithElementsFound.length === 0) {
|
|
2275
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2276
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2277
|
+
}
|
|
2278
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2279
|
+
continue;
|
|
2280
|
+
}
|
|
2281
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2282
|
+
foundAncore = true;
|
|
2283
|
+
const result = resultWithElementsFound[i];
|
|
2284
|
+
const token = result.randomToken;
|
|
2285
|
+
const frame = result.frame;
|
|
2286
|
+
const css = `[data-blinq-id="blinq-id-${token}"]`;
|
|
2287
|
+
const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
|
|
2288
|
+
const elements = Array.from(document.querySelectorAll(css));
|
|
2289
|
+
for (let i = 0; i < elements.length; i++) {
|
|
2290
|
+
const element = elements[i];
|
|
2291
|
+
let climbParent = element;
|
|
2292
|
+
for (let j = 0; j < climb; j++) {
|
|
2293
|
+
climbParent = climbParent.parentElement;
|
|
2294
|
+
if (!climbParent) {
|
|
2295
|
+
break;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
if (!climbParent) {
|
|
2299
|
+
continue;
|
|
2300
|
+
}
|
|
2301
|
+
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2302
|
+
if (foundElements.length > 0) {
|
|
2303
|
+
// set the container element attribute
|
|
2304
|
+
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2305
|
+
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2306
|
+
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2307
|
+
return { found: true };
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
return { found: false };
|
|
2311
|
+
}, [css, climb, textToVerify, result.randomToken]);
|
|
2312
|
+
if (findResult.found === true) {
|
|
2313
|
+
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2314
|
+
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2315
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2316
|
+
await this._highlightElements(frame, cssAnchor);
|
|
2317
|
+
const element = await frame.$(dataAttribute);
|
|
2318
|
+
if (element) {
|
|
2319
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2320
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2321
|
+
}
|
|
2322
|
+
await _screenshot(state, this);
|
|
2323
|
+
return state.info;
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2328
|
+
}
|
|
2329
|
+
catch (e) {
|
|
2330
|
+
await _commandError(state, e, this);
|
|
2161
2331
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2332
|
+
finally {
|
|
2333
|
+
_commandFinally(state, this);
|
|
2164
2334
|
}
|
|
2165
|
-
return serviceUrl;
|
|
2166
2335
|
}
|
|
2167
2336
|
async visualVerification(text, options = {}, world = null) {
|
|
2168
2337
|
const startTime = Date.now();
|
|
@@ -2178,7 +2347,7 @@ class StableBrowser {
|
|
|
2178
2347
|
throw new Error("TOKEN is not set");
|
|
2179
2348
|
}
|
|
2180
2349
|
try {
|
|
2181
|
-
let serviceUrl =
|
|
2350
|
+
let serviceUrl = _getServerUrl();
|
|
2182
2351
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2183
2352
|
info.screenshotPath = screenshotPath;
|
|
2184
2353
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2214,11 +2383,12 @@ class StableBrowser {
|
|
|
2214
2383
|
info.screenshotPath = screenshotPath;
|
|
2215
2384
|
Object.assign(e, { info: info });
|
|
2216
2385
|
error = e;
|
|
2217
|
-
throw e;
|
|
2386
|
+
// throw e;
|
|
2387
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2218
2388
|
}
|
|
2219
2389
|
finally {
|
|
2220
2390
|
const endTime = Date.now();
|
|
2221
|
-
|
|
2391
|
+
_reportToWorld(world, {
|
|
2222
2392
|
type: Types.VERIFY_VISUAL,
|
|
2223
2393
|
text: "Visual verification",
|
|
2224
2394
|
screenshotId,
|
|
@@ -2266,6 +2436,7 @@ class StableBrowser {
|
|
|
2266
2436
|
let screenshotPath = null;
|
|
2267
2437
|
const info = {};
|
|
2268
2438
|
info.log = "";
|
|
2439
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2269
2440
|
info.operation = "getTableData";
|
|
2270
2441
|
info.selectors = selectors;
|
|
2271
2442
|
try {
|
|
@@ -2281,11 +2452,12 @@ class StableBrowser {
|
|
|
2281
2452
|
info.screenshotPath = screenshotPath;
|
|
2282
2453
|
Object.assign(e, { info: info });
|
|
2283
2454
|
error = e;
|
|
2284
|
-
throw e;
|
|
2455
|
+
// throw e;
|
|
2456
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2285
2457
|
}
|
|
2286
2458
|
finally {
|
|
2287
2459
|
const endTime = Date.now();
|
|
2288
|
-
|
|
2460
|
+
_reportToWorld(world, {
|
|
2289
2461
|
element_name: selectors.element_name,
|
|
2290
2462
|
type: Types.GET_TABLE_DATA,
|
|
2291
2463
|
text: "Get table data",
|
|
@@ -2340,7 +2512,7 @@ class StableBrowser {
|
|
|
2340
2512
|
info.operation = "analyzeTable";
|
|
2341
2513
|
info.selectors = selectors;
|
|
2342
2514
|
info.query = query;
|
|
2343
|
-
query =
|
|
2515
|
+
query = _fixUsingParams(query, _params);
|
|
2344
2516
|
info.query_fixed = query;
|
|
2345
2517
|
info.operator = operator;
|
|
2346
2518
|
info.value = value;
|
|
@@ -2446,11 +2618,12 @@ class StableBrowser {
|
|
|
2446
2618
|
info.screenshotPath = screenshotPath;
|
|
2447
2619
|
Object.assign(e, { info: info });
|
|
2448
2620
|
error = e;
|
|
2449
|
-
throw e;
|
|
2621
|
+
// throw e;
|
|
2622
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2450
2623
|
}
|
|
2451
2624
|
finally {
|
|
2452
2625
|
const endTime = Date.now();
|
|
2453
|
-
|
|
2626
|
+
_reportToWorld(world, {
|
|
2454
2627
|
element_name: selectors.element_name,
|
|
2455
2628
|
type: Types.ANALYZE_TABLE,
|
|
2456
2629
|
text: "Analyze table",
|
|
@@ -2523,7 +2696,7 @@ class StableBrowser {
|
|
|
2523
2696
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2524
2697
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2525
2698
|
const endTime = Date.now();
|
|
2526
|
-
|
|
2699
|
+
_reportToWorld(world, {
|
|
2527
2700
|
type: Types.GET_PAGE_STATUS,
|
|
2528
2701
|
text: "Wait for page load",
|
|
2529
2702
|
screenshotId,
|
|
@@ -2543,41 +2716,35 @@ class StableBrowser {
|
|
|
2543
2716
|
}
|
|
2544
2717
|
}
|
|
2545
2718
|
async closePage(options = {}, world = null) {
|
|
2546
|
-
const
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2719
|
+
const state = {
|
|
2720
|
+
options,
|
|
2721
|
+
world,
|
|
2722
|
+
locate: false,
|
|
2723
|
+
scroll: false,
|
|
2724
|
+
highlight: false,
|
|
2725
|
+
type: Types.CLOSE_PAGE,
|
|
2726
|
+
text: `Close page`,
|
|
2727
|
+
operation: "closePage",
|
|
2728
|
+
log: "***** close page *****\n",
|
|
2729
|
+
throwError: false,
|
|
2730
|
+
};
|
|
2551
2731
|
try {
|
|
2732
|
+
await _preCommand(state, this);
|
|
2552
2733
|
await this.page.close();
|
|
2553
2734
|
}
|
|
2554
2735
|
catch (e) {
|
|
2555
2736
|
console.log(".");
|
|
2737
|
+
await _commandError(state, e, this);
|
|
2556
2738
|
}
|
|
2557
2739
|
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
|
-
});
|
|
2740
|
+
_commandFinally(state, this);
|
|
2579
2741
|
}
|
|
2580
2742
|
}
|
|
2743
|
+
saveTestDataAsGlobal(options, world) {
|
|
2744
|
+
const dataFile = this._getDataFile(world);
|
|
2745
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2746
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2747
|
+
}
|
|
2581
2748
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2582
2749
|
const startTime = Date.now();
|
|
2583
2750
|
let error = null;
|
|
@@ -2595,12 +2762,13 @@ class StableBrowser {
|
|
|
2595
2762
|
}
|
|
2596
2763
|
catch (e) {
|
|
2597
2764
|
console.log(".");
|
|
2765
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2598
2766
|
}
|
|
2599
2767
|
finally {
|
|
2600
2768
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2601
2769
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2602
2770
|
const endTime = Date.now();
|
|
2603
|
-
|
|
2771
|
+
_reportToWorld(world, {
|
|
2604
2772
|
type: Types.SET_VIEWPORT,
|
|
2605
2773
|
text: "set viewport size to " + width + "x" + hight,
|
|
2606
2774
|
screenshotId,
|
|
@@ -2631,12 +2799,13 @@ class StableBrowser {
|
|
|
2631
2799
|
}
|
|
2632
2800
|
catch (e) {
|
|
2633
2801
|
console.log(".");
|
|
2802
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2634
2803
|
}
|
|
2635
2804
|
finally {
|
|
2636
2805
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2637
2806
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2638
2807
|
const endTime = Date.now();
|
|
2639
|
-
|
|
2808
|
+
_reportToWorld(world, {
|
|
2640
2809
|
type: Types.GET_PAGE_STATUS,
|
|
2641
2810
|
text: "page relaod",
|
|
2642
2811
|
screenshotId,
|
|
@@ -2672,11 +2841,37 @@ class StableBrowser {
|
|
|
2672
2841
|
console.log("#-#");
|
|
2673
2842
|
}
|
|
2674
2843
|
}
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2844
|
+
async beforeStep(world, step) {
|
|
2845
|
+
this.stepName = step.pickleStep.text;
|
|
2846
|
+
this.logger.info("step: " + this.stepName);
|
|
2847
|
+
if (this.stepIndex === undefined) {
|
|
2848
|
+
this.stepIndex = 0;
|
|
2849
|
+
}
|
|
2850
|
+
else {
|
|
2851
|
+
this.stepIndex++;
|
|
2852
|
+
}
|
|
2853
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2854
|
+
if (this.context.browserObject.context) {
|
|
2855
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
2859
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
2860
|
+
// check if @global_test_data tag is present
|
|
2861
|
+
if (this.tags.includes("@global_test_data")) {
|
|
2862
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
async afterStep(world, step) {
|
|
2867
|
+
this.stepName = null;
|
|
2868
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2869
|
+
if (this.context.browserObject.context) {
|
|
2870
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
2871
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2678
2874
|
}
|
|
2679
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2680
2875
|
}
|
|
2681
2876
|
}
|
|
2682
2877
|
function createTimedPromise(promise, label) {
|
|
@@ -2684,156 +2879,5 @@ function createTimedPromise(promise, label) {
|
|
|
2684
2879
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2685
2880
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2686
2881
|
}
|
|
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
2882
|
export { StableBrowser };
|
|
2839
2883
|
//# sourceMappingURL=stable_browser.js.map
|