automation_model 1.0.502-dev → 1.0.502-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 +98 -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 +90 -32
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -0
- package/lib/command_common.js +47 -7
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.d.ts +6 -0
- package/lib/error-messages.js +188 -0
- package/lib/error-messages.js.map +1 -0
- package/lib/generation_scripts.d.ts +4 -0
- package/lib/generation_scripts.js +2 -0
- package/lib/generation_scripts.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +20 -6
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +15 -13
- package/lib/locate_element.js.map +1 -1
- package/lib/locator.d.ts +36 -0
- package/lib/locator.js +165 -0
- package/lib/locator.js.map +1 -1
- package/lib/locator_log.d.ts +26 -0
- package/lib/locator_log.js +69 -0
- package/lib/locator_log.js.map +1 -0
- package/lib/network.js +16 -3
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +55 -17
- package/lib/stable_browser.js +591 -595
- 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 +13 -1
- package/lib/utils.js +273 -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 { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
17
|
import { getContext } from "./init_browser.js";
|
|
18
18
|
import { locate_element } from "./locate_element.js";
|
|
19
|
-
import {
|
|
19
|
+
import { randomUUID } from "crypto";
|
|
20
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
20
21
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
21
|
-
|
|
22
|
+
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
export const Types = {
|
|
22
24
|
CLICK: "click_element",
|
|
23
25
|
NAVIGATE: "navigate",
|
|
24
26
|
FILL: "fill_element",
|
|
@@ -29,6 +31,8 @@ const Types = {
|
|
|
29
31
|
GET_PAGE_STATUS: "get_page_status",
|
|
30
32
|
CLICK_ROW_ACTION: "click_row_action",
|
|
31
33
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
34
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
35
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
32
36
|
ANALYZE_TABLE: "analyze_table",
|
|
33
37
|
SELECT: "select_combobox",
|
|
34
38
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -44,6 +48,9 @@ const Types = {
|
|
|
44
48
|
VERIFY_VISUAL: "verify_visual",
|
|
45
49
|
LOAD_DATA: "load_data",
|
|
46
50
|
SET_INPUT: "set_input",
|
|
51
|
+
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
52
|
+
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
53
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
47
54
|
};
|
|
48
55
|
export const apps = {};
|
|
49
56
|
class StableBrowser {
|
|
@@ -57,6 +64,7 @@ class StableBrowser {
|
|
|
57
64
|
networkLogger = null;
|
|
58
65
|
configuration = null;
|
|
59
66
|
appName = "main";
|
|
67
|
+
tags = null;
|
|
60
68
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
61
69
|
this.browser = browser;
|
|
62
70
|
this.page = page;
|
|
@@ -87,17 +95,17 @@ class StableBrowser {
|
|
|
87
95
|
catch (e) {
|
|
88
96
|
this.logger.error("unable to read ai_config.json");
|
|
89
97
|
}
|
|
90
|
-
context.pageLoading = { status: false };
|
|
91
|
-
context.pages = [this.page];
|
|
92
98
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
93
99
|
this.world = world;
|
|
100
|
+
context.pages = [this.page];
|
|
101
|
+
context.pageLoading = { status: false };
|
|
94
102
|
this.registerEventListeners(this.context);
|
|
95
103
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
96
104
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
97
105
|
}
|
|
98
106
|
registerEventListeners(context) {
|
|
99
107
|
this.registerConsoleLogListener(this.page, context);
|
|
100
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
108
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
101
109
|
if (!context.pageLoading) {
|
|
102
110
|
context.pageLoading = { status: false };
|
|
103
111
|
}
|
|
@@ -142,9 +150,9 @@ class StableBrowser {
|
|
|
142
150
|
if (this.appName === appName) {
|
|
143
151
|
return;
|
|
144
152
|
}
|
|
145
|
-
let
|
|
153
|
+
let navigate = false;
|
|
146
154
|
if (!apps[appName]) {
|
|
147
|
-
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this);
|
|
155
|
+
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
148
156
|
newContextCreated = true;
|
|
149
157
|
apps[appName] = {
|
|
150
158
|
context: newContext,
|
|
@@ -153,32 +161,15 @@ class StableBrowser {
|
|
|
153
161
|
};
|
|
154
162
|
}
|
|
155
163
|
const tempContext = {};
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
_copyContext(this, tempContext);
|
|
165
|
+
_copyContext(apps[appName], this);
|
|
158
166
|
apps[this.appName] = tempContext;
|
|
159
167
|
this.appName = appName;
|
|
160
|
-
if (
|
|
161
|
-
this.registerEventListeners(this.context);
|
|
168
|
+
if (navigate) {
|
|
162
169
|
await this.goto(this.context.environment.baseUrl);
|
|
163
170
|
await this.waitForPageLoad();
|
|
164
171
|
}
|
|
165
172
|
}
|
|
166
|
-
_copyContext(from, to) {
|
|
167
|
-
to.browser = from.browser;
|
|
168
|
-
to.page = from.page;
|
|
169
|
-
to.context = from.context;
|
|
170
|
-
}
|
|
171
|
-
getWebLogFile(logFolder) {
|
|
172
|
-
if (!fs.existsSync(logFolder)) {
|
|
173
|
-
fs.mkdirSync(logFolder, { recursive: true });
|
|
174
|
-
}
|
|
175
|
-
let nextIndex = 1;
|
|
176
|
-
while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
|
|
177
|
-
nextIndex++;
|
|
178
|
-
}
|
|
179
|
-
const fileName = nextIndex + ".json";
|
|
180
|
-
return path.join(logFolder, fileName);
|
|
181
|
-
}
|
|
182
173
|
registerConsoleLogListener(page, context) {
|
|
183
174
|
if (!this.context.webLogger) {
|
|
184
175
|
this.context.webLogger = [];
|
|
@@ -228,7 +219,7 @@ class StableBrowser {
|
|
|
228
219
|
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
229
220
|
}
|
|
230
221
|
catch (error) {
|
|
231
|
-
console.error("Error in request listener", error);
|
|
222
|
+
// console.error("Error in request listener", error);
|
|
232
223
|
context.networkLogger.push({
|
|
233
224
|
error: "not able to listen",
|
|
234
225
|
message: error.message,
|
|
@@ -242,55 +233,48 @@ class StableBrowser {
|
|
|
242
233
|
// async closeUnexpectedPopups() {
|
|
243
234
|
// await closeUnexpectedPopups(this.page);
|
|
244
235
|
// }
|
|
245
|
-
async goto(url) {
|
|
236
|
+
async goto(url, world = null) {
|
|
246
237
|
if (!url.startsWith("http")) {
|
|
247
238
|
url = "https://" + url;
|
|
248
239
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
240
|
+
const state = {
|
|
241
|
+
value: url,
|
|
242
|
+
world: world,
|
|
243
|
+
type: Types.NAVIGATE,
|
|
244
|
+
text: `Navigate Page to: ${url}`,
|
|
245
|
+
operation: "goto",
|
|
246
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
247
|
+
info: {},
|
|
248
|
+
locate: false,
|
|
249
|
+
scroll: false,
|
|
250
|
+
screenshot: false,
|
|
251
|
+
highlight: false,
|
|
252
|
+
};
|
|
253
|
+
try {
|
|
254
|
+
await _preCommand(state, this);
|
|
255
|
+
await this.page.goto(url, {
|
|
256
|
+
timeout: 60000,
|
|
257
|
+
});
|
|
258
|
+
await _screenshot(state, this);
|
|
256
259
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// remove the _ prefix
|
|
261
|
-
regValue = key.substring(1);
|
|
262
|
-
}
|
|
263
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.error("Error on goto", error);
|
|
262
|
+
_commandError(state, error, this);
|
|
264
263
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
268
|
-
// check if not null
|
|
269
|
-
if (!locator) {
|
|
270
|
-
return locator;
|
|
264
|
+
finally {
|
|
265
|
+
_commandFinally(state, this);
|
|
271
266
|
}
|
|
272
|
-
// clone the locator
|
|
273
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
274
|
-
this.scanAndManipulate(locator, _params);
|
|
275
|
-
return locator;
|
|
276
267
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
else if (this._isObject(currentObj[key])) {
|
|
287
|
-
// Recursively scan nested objects
|
|
288
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
268
|
+
async _getLocator(locator, scope, _params) {
|
|
269
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
270
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
271
|
+
for (let key in locator) {
|
|
272
|
+
if (typeof locator[key] !== "string")
|
|
273
|
+
continue;
|
|
274
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
275
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
289
276
|
}
|
|
290
277
|
}
|
|
291
|
-
}
|
|
292
|
-
_getLocator(locator, scope, _params) {
|
|
293
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
294
278
|
let locatorReturn;
|
|
295
279
|
if (locator.role) {
|
|
296
280
|
if (locator.role[1].nameReg) {
|
|
@@ -298,7 +282,7 @@ class StableBrowser {
|
|
|
298
282
|
delete locator.role[1].nameReg;
|
|
299
283
|
}
|
|
300
284
|
// if (locator.role[1].name) {
|
|
301
|
-
// locator.role[1].name =
|
|
285
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
302
286
|
// }
|
|
303
287
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
304
288
|
}
|
|
@@ -341,7 +325,7 @@ class StableBrowser {
|
|
|
341
325
|
if (css && css.locator) {
|
|
342
326
|
css = css.locator;
|
|
343
327
|
}
|
|
344
|
-
let result = await this._locateElementByText(scope,
|
|
328
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
345
329
|
if (result.elementCount === 0) {
|
|
346
330
|
return;
|
|
347
331
|
}
|
|
@@ -352,132 +336,58 @@ class StableBrowser {
|
|
|
352
336
|
climbArray.push("..");
|
|
353
337
|
}
|
|
354
338
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
return
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return new RegExp(pattern, flags);
|
|
378
|
-
}
|
|
379
|
-
document.getRegex = getRegex;
|
|
380
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
381
|
-
// Check and add the element if it has a shadow root
|
|
382
|
-
if (element.shadowRoot) {
|
|
383
|
-
result.push(element);
|
|
384
|
-
// Also search within the shadow root
|
|
385
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
386
|
-
}
|
|
387
|
-
// Iterate over child nodes
|
|
388
|
-
element.childNodes.forEach((child) => {
|
|
389
|
-
// Recursively call the function for each child node
|
|
390
|
-
document.collectAllShadowDomElements(child, result);
|
|
391
|
-
});
|
|
392
|
-
return result;
|
|
393
|
-
}
|
|
394
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
395
|
-
if (!tag) {
|
|
396
|
-
tag = "*:not(script, style, head)";
|
|
397
|
-
}
|
|
398
|
-
let regexpSearch = document.getRegex(text);
|
|
399
|
-
if (regexpSearch) {
|
|
400
|
-
regex = true;
|
|
401
|
-
}
|
|
402
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
403
|
-
let shadowHosts = [];
|
|
404
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
405
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
406
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
407
|
-
if (!shadowElement) {
|
|
408
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
412
|
-
elements = elements.concat(shadowElements);
|
|
413
|
-
}
|
|
414
|
-
let randomToken = null;
|
|
415
|
-
const foundElements = [];
|
|
416
|
-
if (regex) {
|
|
417
|
-
if (!regexpSearch) {
|
|
418
|
-
regexpSearch = new RegExp(text, "im");
|
|
419
|
-
}
|
|
420
|
-
for (let i = 0; i < elements.length; i++) {
|
|
421
|
-
const element = elements[i];
|
|
422
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
423
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
424
|
-
foundElements.push(element);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
else {
|
|
429
|
-
text = text.trim();
|
|
430
|
-
for (let i = 0; i < elements.length; i++) {
|
|
431
|
-
const element = elements[i];
|
|
432
|
-
if (partial) {
|
|
433
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
434
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
435
|
-
foundElements.push(element);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
440
|
-
(element.value && element.value === text)) {
|
|
441
|
-
foundElements.push(element);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
let noChildElements = [];
|
|
447
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
448
|
-
let element = foundElements[i];
|
|
449
|
-
let hasChild = false;
|
|
450
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
451
|
-
if (i === j) {
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
if (isParent(element, foundElements[j])) {
|
|
455
|
-
hasChild = true;
|
|
456
|
-
break;
|
|
339
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
340
|
+
if (css) {
|
|
341
|
+
resultCss = resultCss + " >> " + css;
|
|
342
|
+
}
|
|
343
|
+
return resultCss;
|
|
344
|
+
}
|
|
345
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
346
|
+
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
|
|
347
|
+
const locator = scope.locator(query);
|
|
348
|
+
const count = await locator.count();
|
|
349
|
+
if (!tag1) {
|
|
350
|
+
tag1 = "*";
|
|
351
|
+
}
|
|
352
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
353
|
+
let tagCount = 0;
|
|
354
|
+
for (let i = 0; i < count; i++) {
|
|
355
|
+
const element = locator.nth(i);
|
|
356
|
+
// check if the tag matches
|
|
357
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
358
|
+
if (!tag.startsWith("*")) {
|
|
359
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
360
|
+
return false;
|
|
457
361
|
}
|
|
458
362
|
}
|
|
459
|
-
if (!
|
|
460
|
-
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
let elementCount = 0;
|
|
464
|
-
if (noChildElements.length > 0) {
|
|
465
|
-
for (let i = 0; i < noChildElements.length; i++) {
|
|
466
|
-
if (randomToken === null) {
|
|
467
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
468
|
-
}
|
|
469
|
-
let element = noChildElements[i];
|
|
470
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
471
|
-
elementCount++;
|
|
363
|
+
if (!el.setAttribute) {
|
|
364
|
+
el = el.parentElement;
|
|
472
365
|
}
|
|
366
|
+
el.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
367
|
+
return true;
|
|
368
|
+
}, [tag1, randomToken]))) {
|
|
369
|
+
continue;
|
|
473
370
|
}
|
|
474
|
-
|
|
475
|
-
}
|
|
371
|
+
tagCount++;
|
|
372
|
+
}
|
|
373
|
+
return { elementCount: tagCount, randomToken };
|
|
476
374
|
}
|
|
477
375
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
376
|
+
if (!info) {
|
|
377
|
+
info = {};
|
|
378
|
+
}
|
|
379
|
+
if (!info.failCause) {
|
|
380
|
+
info.failCause = {};
|
|
381
|
+
}
|
|
382
|
+
if (!info.log) {
|
|
383
|
+
info.log = "";
|
|
384
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
385
|
+
}
|
|
478
386
|
let locatorSearch = selectorHierarchy[index];
|
|
387
|
+
let originalLocatorSearch = "";
|
|
479
388
|
try {
|
|
480
|
-
|
|
389
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
390
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
481
391
|
}
|
|
482
392
|
catch (e) {
|
|
483
393
|
console.error(e);
|
|
@@ -491,11 +401,11 @@ class StableBrowser {
|
|
|
491
401
|
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
492
402
|
return;
|
|
493
403
|
}
|
|
494
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
404
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
495
405
|
}
|
|
496
406
|
else if (locatorSearch.text) {
|
|
497
|
-
let text =
|
|
498
|
-
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
407
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
408
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
499
409
|
if (result.elementCount === 0) {
|
|
500
410
|
info.failCause.textNotFound = true;
|
|
501
411
|
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
@@ -505,10 +415,10 @@ class StableBrowser {
|
|
|
505
415
|
if (locatorSearch.childCss) {
|
|
506
416
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
507
417
|
}
|
|
508
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
418
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
509
419
|
}
|
|
510
420
|
else {
|
|
511
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
421
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
512
422
|
}
|
|
513
423
|
// let cssHref = false;
|
|
514
424
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -521,10 +431,16 @@ class StableBrowser {
|
|
|
521
431
|
//info.log += "total elements found " + count + "\n";
|
|
522
432
|
//let visibleCount = 0;
|
|
523
433
|
let visibleLocator = null;
|
|
524
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
434
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
525
435
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
436
|
+
if (info.locatorLog) {
|
|
437
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
438
|
+
}
|
|
526
439
|
return;
|
|
527
440
|
}
|
|
441
|
+
if (info.locatorLog && count === 0) {
|
|
442
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
443
|
+
}
|
|
528
444
|
for (let j = 0; j < count; j++) {
|
|
529
445
|
let visible = await locator.nth(j).isVisible();
|
|
530
446
|
const enabled = await locator.nth(j).isEnabled();
|
|
@@ -533,6 +449,9 @@ class StableBrowser {
|
|
|
533
449
|
}
|
|
534
450
|
if (visible && enabled) {
|
|
535
451
|
foundLocators.push(locator.nth(j));
|
|
452
|
+
if (info.locatorLog) {
|
|
453
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
454
|
+
}
|
|
536
455
|
}
|
|
537
456
|
else {
|
|
538
457
|
info.failCause.visible = visible;
|
|
@@ -540,8 +459,14 @@ class StableBrowser {
|
|
|
540
459
|
if (!info.printMessages) {
|
|
541
460
|
info.printMessages = {};
|
|
542
461
|
}
|
|
462
|
+
if (info.locatorLog && !visible) {
|
|
463
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
464
|
+
}
|
|
465
|
+
if (info.locatorLog && !enabled) {
|
|
466
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
467
|
+
}
|
|
543
468
|
if (!info.printMessages[j.toString()]) {
|
|
544
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
469
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
545
470
|
info.printMessages[j.toString()] = true;
|
|
546
471
|
}
|
|
547
472
|
}
|
|
@@ -557,7 +482,7 @@ class StableBrowser {
|
|
|
557
482
|
if (!info) {
|
|
558
483
|
info = {};
|
|
559
484
|
}
|
|
560
|
-
info.log += "scan for popup handlers" + "\n";
|
|
485
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
561
486
|
const handlerGroup = [];
|
|
562
487
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
563
488
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -584,9 +509,21 @@ class StableBrowser {
|
|
|
584
509
|
}
|
|
585
510
|
if (result.foundElements.length > 0) {
|
|
586
511
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
512
|
+
try {
|
|
513
|
+
await scope?.evaluate(() => {
|
|
514
|
+
window.__isClosingPopups = true;
|
|
515
|
+
});
|
|
516
|
+
await dialogCloseLocator.click();
|
|
517
|
+
// wait for the dialog to close
|
|
518
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
519
|
+
}
|
|
520
|
+
catch (e) {
|
|
521
|
+
}
|
|
522
|
+
finally {
|
|
523
|
+
await scope?.evaluate(() => {
|
|
524
|
+
window.__isClosingPopups = false;
|
|
525
|
+
});
|
|
526
|
+
}
|
|
590
527
|
return { rerun: true };
|
|
591
528
|
}
|
|
592
529
|
}
|
|
@@ -616,6 +553,7 @@ class StableBrowser {
|
|
|
616
553
|
info.failCause = {};
|
|
617
554
|
info.log = "";
|
|
618
555
|
}
|
|
556
|
+
let startTime = Date.now();
|
|
619
557
|
let scope = this.page;
|
|
620
558
|
if (selectors.frame) {
|
|
621
559
|
return selectors.frame;
|
|
@@ -646,9 +584,11 @@ class StableBrowser {
|
|
|
646
584
|
}
|
|
647
585
|
return framescope;
|
|
648
586
|
};
|
|
587
|
+
let fLocator = null;
|
|
649
588
|
while (true) {
|
|
650
589
|
let frameFound = false;
|
|
651
590
|
if (selectors.nestFrmLoc) {
|
|
591
|
+
fLocator = selectors.nestFrmLoc;
|
|
652
592
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
653
593
|
frameFound = true;
|
|
654
594
|
break;
|
|
@@ -657,6 +597,7 @@ class StableBrowser {
|
|
|
657
597
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
658
598
|
let frameLocator = selectors.frameLocators[i];
|
|
659
599
|
if (frameLocator.css) {
|
|
600
|
+
fLocator = frameLocator.css;
|
|
660
601
|
scope = scope.frameLocator(frameLocator.css);
|
|
661
602
|
frameFound = true;
|
|
662
603
|
break;
|
|
@@ -664,11 +605,15 @@ class StableBrowser {
|
|
|
664
605
|
}
|
|
665
606
|
}
|
|
666
607
|
if (!frameFound && selectors.iframe_src) {
|
|
608
|
+
fLocator = selectors.iframe_src;
|
|
667
609
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
668
610
|
}
|
|
669
611
|
if (!scope) {
|
|
670
|
-
info
|
|
671
|
-
|
|
612
|
+
if (info && info.locatorLog) {
|
|
613
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
614
|
+
}
|
|
615
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
616
|
+
if (Date.now() - startTime > timeout) {
|
|
672
617
|
info.failCause.iframeNotFound = true;
|
|
673
618
|
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
674
619
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
@@ -676,6 +621,9 @@ class StableBrowser {
|
|
|
676
621
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
677
622
|
}
|
|
678
623
|
else {
|
|
624
|
+
if (info && info.locatorLog) {
|
|
625
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
626
|
+
}
|
|
679
627
|
break;
|
|
680
628
|
}
|
|
681
629
|
}
|
|
@@ -697,11 +645,13 @@ class StableBrowser {
|
|
|
697
645
|
info = {};
|
|
698
646
|
info.failCause = {};
|
|
699
647
|
info.log = "";
|
|
648
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
700
649
|
}
|
|
701
650
|
let highPriorityTimeout = 5000;
|
|
702
651
|
let visibleOnlyTimeout = 6000;
|
|
703
|
-
let startTime =
|
|
652
|
+
let startTime = Date.now();
|
|
704
653
|
let locatorsCount = 0;
|
|
654
|
+
let lazy_scroll = false;
|
|
705
655
|
//let arrayMode = Array.isArray(selectors);
|
|
706
656
|
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
707
657
|
let selectorsLocators = null;
|
|
@@ -790,21 +740,31 @@ class StableBrowser {
|
|
|
790
740
|
return maxCountElement.locator;
|
|
791
741
|
}
|
|
792
742
|
}
|
|
793
|
-
if (
|
|
743
|
+
if (Date.now() - startTime > timeout) {
|
|
794
744
|
break;
|
|
795
745
|
}
|
|
796
|
-
if (
|
|
797
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
746
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
747
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
798
748
|
highPriorityOnly = false;
|
|
749
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
750
|
+
lazy_scroll = true;
|
|
751
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
752
|
+
}
|
|
799
753
|
}
|
|
800
|
-
if (
|
|
801
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
754
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
755
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
802
756
|
visibleOnly = false;
|
|
803
757
|
}
|
|
804
758
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
805
759
|
}
|
|
806
760
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
807
|
-
info.
|
|
761
|
+
// if (info.locatorLog) {
|
|
762
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
763
|
+
// for (let line of lines) {
|
|
764
|
+
// this.logger.debug(line);
|
|
765
|
+
// }
|
|
766
|
+
// }
|
|
767
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
808
768
|
info.failCause.locatorNotFound = true;
|
|
809
769
|
info.failCause.lastError = "failed to locate unique element";
|
|
810
770
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
@@ -820,8 +780,9 @@ class StableBrowser {
|
|
|
820
780
|
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
821
781
|
}
|
|
822
782
|
catch (e) {
|
|
823
|
-
this
|
|
824
|
-
this.logger.debug(
|
|
783
|
+
// this call can fail it the browser is navigating
|
|
784
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
785
|
+
// this.logger.debug(e);
|
|
825
786
|
foundLocators = [];
|
|
826
787
|
try {
|
|
827
788
|
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
@@ -840,11 +801,27 @@ class StableBrowser {
|
|
|
840
801
|
}
|
|
841
802
|
if (foundLocators.length > 1) {
|
|
842
803
|
info.failCause.foundMultiple = true;
|
|
804
|
+
if (info.locatorLog) {
|
|
805
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
806
|
+
}
|
|
843
807
|
}
|
|
844
808
|
}
|
|
845
809
|
return result;
|
|
846
810
|
}
|
|
847
811
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
812
|
+
const state = {
|
|
813
|
+
locate: false,
|
|
814
|
+
scroll: false,
|
|
815
|
+
highlight: false,
|
|
816
|
+
_params,
|
|
817
|
+
options,
|
|
818
|
+
world,
|
|
819
|
+
type: Types.CLICK,
|
|
820
|
+
text: "Click element",
|
|
821
|
+
operation: "simpleClick",
|
|
822
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
823
|
+
};
|
|
824
|
+
_preCommand(state, this);
|
|
848
825
|
const startTime = Date.now();
|
|
849
826
|
let timeout = 30000;
|
|
850
827
|
if (options && options.timeout) {
|
|
@@ -868,13 +845,32 @@ class StableBrowser {
|
|
|
868
845
|
}
|
|
869
846
|
catch (e) {
|
|
870
847
|
if (performance.now() - startTime > timeout) {
|
|
871
|
-
throw e;
|
|
848
|
+
// throw e;
|
|
849
|
+
try {
|
|
850
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
851
|
+
}
|
|
852
|
+
finally {
|
|
853
|
+
_commandFinally(state, this);
|
|
854
|
+
}
|
|
872
855
|
}
|
|
873
856
|
}
|
|
874
857
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
875
858
|
}
|
|
876
859
|
}
|
|
877
860
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
861
|
+
const state = {
|
|
862
|
+
locate: false,
|
|
863
|
+
scroll: false,
|
|
864
|
+
highlight: false,
|
|
865
|
+
_params,
|
|
866
|
+
options,
|
|
867
|
+
world,
|
|
868
|
+
type: Types.FILL,
|
|
869
|
+
text: "Fill element",
|
|
870
|
+
operation: "simpleClickType",
|
|
871
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
872
|
+
};
|
|
873
|
+
_preCommand(state, this);
|
|
878
874
|
const startTime = Date.now();
|
|
879
875
|
let timeout = 30000;
|
|
880
876
|
if (options && options.timeout) {
|
|
@@ -898,7 +894,13 @@ class StableBrowser {
|
|
|
898
894
|
}
|
|
899
895
|
catch (e) {
|
|
900
896
|
if (performance.now() - startTime > timeout) {
|
|
901
|
-
throw e;
|
|
897
|
+
// throw e;
|
|
898
|
+
try {
|
|
899
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
900
|
+
}
|
|
901
|
+
finally {
|
|
902
|
+
_commandFinally(state, this);
|
|
903
|
+
}
|
|
902
904
|
}
|
|
903
905
|
}
|
|
904
906
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -917,18 +919,18 @@ class StableBrowser {
|
|
|
917
919
|
};
|
|
918
920
|
try {
|
|
919
921
|
await _preCommand(state, this);
|
|
920
|
-
if (state.options && state.options.context) {
|
|
921
|
-
|
|
922
|
-
}
|
|
922
|
+
// if (state.options && state.options.context) {
|
|
923
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
924
|
+
// }
|
|
923
925
|
try {
|
|
924
926
|
await state.element.click();
|
|
925
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
927
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
926
928
|
}
|
|
927
929
|
catch (e) {
|
|
928
930
|
// await this.closeUnexpectedPopups();
|
|
929
931
|
state.element = await this._locate(selectors, state.info, _params);
|
|
930
932
|
await state.element.dispatchEvent("click");
|
|
931
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
933
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
932
934
|
}
|
|
933
935
|
await this.waitForPageLoad();
|
|
934
936
|
return state.info;
|
|
@@ -1007,7 +1009,6 @@ class StableBrowser {
|
|
|
1007
1009
|
await state.element.hover({ timeout: 10000 });
|
|
1008
1010
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1009
1011
|
}
|
|
1010
|
-
await _screenshot(state, this);
|
|
1011
1012
|
await this.waitForPageLoad();
|
|
1012
1013
|
return state.info;
|
|
1013
1014
|
}
|
|
@@ -1135,33 +1136,30 @@ class StableBrowser {
|
|
|
1135
1136
|
}
|
|
1136
1137
|
}
|
|
1137
1138
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1139
|
+
const state = {
|
|
1140
|
+
selectors,
|
|
1141
|
+
_params,
|
|
1142
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1143
|
+
options,
|
|
1144
|
+
world,
|
|
1145
|
+
type: Types.SET_DATE_TIME,
|
|
1146
|
+
text: `Set date time value: ${value}`,
|
|
1147
|
+
operation: "setDateTime",
|
|
1148
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1149
|
+
throwError: false,
|
|
1150
|
+
};
|
|
1148
1151
|
try {
|
|
1149
|
-
|
|
1150
|
-
let element = await this._locate(selectors, info, _params);
|
|
1151
|
-
//insert red border around the element
|
|
1152
|
-
await this.scrollIfNeeded(element, info);
|
|
1153
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1154
|
-
await this._highlightElements(element);
|
|
1152
|
+
await _preCommand(state, this);
|
|
1155
1153
|
try {
|
|
1156
|
-
await element.click();
|
|
1154
|
+
await state.element.click();
|
|
1157
1155
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1158
1156
|
if (format) {
|
|
1159
|
-
value = dayjs(value).format(format);
|
|
1160
|
-
await element.fill(value);
|
|
1157
|
+
state.value = dayjs(state.value).format(format);
|
|
1158
|
+
await state.element.fill(state.value);
|
|
1161
1159
|
}
|
|
1162
1160
|
else {
|
|
1163
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1164
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1161
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1162
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1165
1163
|
el.value = ""; // clear input
|
|
1166
1164
|
el.value = dateTimeValue;
|
|
1167
1165
|
}, dateTimeValue);
|
|
@@ -1174,20 +1172,19 @@ class StableBrowser {
|
|
|
1174
1172
|
}
|
|
1175
1173
|
catch (err) {
|
|
1176
1174
|
//await this.closeUnexpectedPopups();
|
|
1177
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1175
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1178
1176
|
this.logger.info("Trying again");
|
|
1179
|
-
|
|
1180
|
-
info.
|
|
1181
|
-
Object.assign(err, { info: info });
|
|
1177
|
+
await _screenshot(state, this);
|
|
1178
|
+
Object.assign(err, { info: state.info });
|
|
1182
1179
|
await element.click();
|
|
1183
1180
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1184
1181
|
if (format) {
|
|
1185
|
-
value = dayjs(value).format(format);
|
|
1186
|
-
await element.fill(value);
|
|
1182
|
+
state.value = dayjs(state.value).format(format);
|
|
1183
|
+
await state.element.fill(state.value);
|
|
1187
1184
|
}
|
|
1188
1185
|
else {
|
|
1189
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1190
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1186
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1187
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1191
1188
|
el.value = ""; // clear input
|
|
1192
1189
|
el.value = dateTimeValue;
|
|
1193
1190
|
}, dateTimeValue);
|
|
@@ -1200,31 +1197,10 @@ class StableBrowser {
|
|
|
1200
1197
|
}
|
|
1201
1198
|
}
|
|
1202
1199
|
catch (e) {
|
|
1203
|
-
|
|
1204
|
-
throw e;
|
|
1200
|
+
await _commandError(state, e, this);
|
|
1205
1201
|
}
|
|
1206
1202
|
finally {
|
|
1207
|
-
|
|
1208
|
-
this._reportToWorld(world, {
|
|
1209
|
-
element_name: selectors.element_name,
|
|
1210
|
-
type: Types.SET_DATE_TIME,
|
|
1211
|
-
screenshotId,
|
|
1212
|
-
value: value,
|
|
1213
|
-
text: `setDateTime input with value: ${value}`,
|
|
1214
|
-
result: error
|
|
1215
|
-
? {
|
|
1216
|
-
status: "FAILED",
|
|
1217
|
-
startTime,
|
|
1218
|
-
endTime,
|
|
1219
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1220
|
-
}
|
|
1221
|
-
: {
|
|
1222
|
-
status: "PASSED",
|
|
1223
|
-
startTime,
|
|
1224
|
-
endTime,
|
|
1225
|
-
},
|
|
1226
|
-
info: info,
|
|
1227
|
-
});
|
|
1203
|
+
_commandFinally(state, this);
|
|
1228
1204
|
}
|
|
1229
1205
|
}
|
|
1230
1206
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1361,6 +1337,7 @@ class StableBrowser {
|
|
|
1361
1337
|
let screenshotPath = null;
|
|
1362
1338
|
if (!info.log) {
|
|
1363
1339
|
info.log = "";
|
|
1340
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1364
1341
|
}
|
|
1365
1342
|
info.operation = "getText";
|
|
1366
1343
|
info.selectors = selectors;
|
|
@@ -1699,11 +1676,9 @@ class StableBrowser {
|
|
|
1699
1676
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1700
1677
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1701
1678
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
}
|
|
1706
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1679
|
+
// to make sure the path doesn't start with -
|
|
1680
|
+
const uuidStr = "id_" + randomUUID();
|
|
1681
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1707
1682
|
try {
|
|
1708
1683
|
await this.takeScreenshot(screenshotPath);
|
|
1709
1684
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1717,7 +1692,7 @@ class StableBrowser {
|
|
|
1717
1692
|
catch (e) {
|
|
1718
1693
|
this.logger.info("unable to take screenshot, ignored");
|
|
1719
1694
|
}
|
|
1720
|
-
result.screenshotId =
|
|
1695
|
+
result.screenshotId = uuidStr;
|
|
1721
1696
|
result.screenshotPath = screenshotPath;
|
|
1722
1697
|
if (info && info.box) {
|
|
1723
1698
|
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
@@ -1815,74 +1790,105 @@ class StableBrowser {
|
|
|
1815
1790
|
}
|
|
1816
1791
|
}
|
|
1817
1792
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1793
|
+
const state = {
|
|
1794
|
+
selectors,
|
|
1795
|
+
_params,
|
|
1796
|
+
attribute,
|
|
1797
|
+
variable,
|
|
1798
|
+
options,
|
|
1799
|
+
world,
|
|
1800
|
+
type: Types.EXTRACT,
|
|
1801
|
+
text: `Extract attribute from element`,
|
|
1802
|
+
operation: "extractAttribute",
|
|
1803
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1804
|
+
};
|
|
1823
1805
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1824
|
-
const info = {};
|
|
1825
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1826
|
-
info.operation = "extract";
|
|
1827
|
-
info.selectors = selectors;
|
|
1828
1806
|
try {
|
|
1829
|
-
|
|
1830
|
-
await this._highlightElements(element);
|
|
1831
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1807
|
+
await _preCommand(state, this);
|
|
1832
1808
|
switch (attribute) {
|
|
1833
1809
|
case "inner_text":
|
|
1834
|
-
|
|
1810
|
+
state.value = await state.element.innerText();
|
|
1835
1811
|
break;
|
|
1836
1812
|
case "href":
|
|
1837
|
-
|
|
1813
|
+
state.value = await state.element.getAttribute("href");
|
|
1838
1814
|
break;
|
|
1839
1815
|
case "value":
|
|
1840
|
-
|
|
1816
|
+
state.value = await state.element.inputValue();
|
|
1841
1817
|
break;
|
|
1842
1818
|
default:
|
|
1843
|
-
|
|
1819
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1844
1820
|
break;
|
|
1845
1821
|
}
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1822
|
+
state.info.value = state.value;
|
|
1823
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1824
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1825
|
+
return state.info;
|
|
1826
|
+
}
|
|
1827
|
+
catch (e) {
|
|
1828
|
+
await _commandError(state, e, this);
|
|
1829
|
+
}
|
|
1830
|
+
finally {
|
|
1831
|
+
_commandFinally(state, this);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1835
|
+
const state = {
|
|
1836
|
+
selectors,
|
|
1837
|
+
_params,
|
|
1838
|
+
attribute,
|
|
1839
|
+
value,
|
|
1840
|
+
options,
|
|
1841
|
+
world,
|
|
1842
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1843
|
+
text: `Verify element attribute`,
|
|
1844
|
+
operation: "verifyAttribute",
|
|
1845
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1846
|
+
};
|
|
1847
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1848
|
+
let val;
|
|
1849
|
+
try {
|
|
1850
|
+
await _preCommand(state, this);
|
|
1851
|
+
switch (attribute) {
|
|
1852
|
+
case "innerText":
|
|
1853
|
+
val = String(await state.element.innerText());
|
|
1854
|
+
break;
|
|
1855
|
+
case "value":
|
|
1856
|
+
val = String(await state.element.inputValue());
|
|
1857
|
+
break;
|
|
1858
|
+
case "checked":
|
|
1859
|
+
val = String(await state.element.isChecked());
|
|
1860
|
+
break;
|
|
1861
|
+
case "disabled":
|
|
1862
|
+
val = String(await state.element.isDisabled());
|
|
1863
|
+
break;
|
|
1864
|
+
case "readOnly":
|
|
1865
|
+
const isEditable = await state.element.isEditable();
|
|
1866
|
+
val = String(!isEditable);
|
|
1867
|
+
break;
|
|
1868
|
+
default:
|
|
1869
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1870
|
+
break;
|
|
1849
1871
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1872
|
+
state.info.expectedValue = val;
|
|
1873
|
+
let regex;
|
|
1874
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1875
|
+
const patternBody = value.slice(1, -1);
|
|
1876
|
+
regex = new RegExp(patternBody, "g");
|
|
1877
|
+
}
|
|
1878
|
+
else {
|
|
1879
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1880
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1881
|
+
}
|
|
1882
|
+
if (!val.match(regex)) {
|
|
1883
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
1884
|
+
}
|
|
1885
|
+
return state.info;
|
|
1853
1886
|
}
|
|
1854
1887
|
catch (e) {
|
|
1855
|
-
|
|
1856
|
-
this.logger.error("extract failed " + info.log);
|
|
1857
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1858
|
-
info.screenshotPath = screenshotPath;
|
|
1859
|
-
Object.assign(e, { info: info });
|
|
1860
|
-
error = e;
|
|
1861
|
-
throw e;
|
|
1888
|
+
await _commandError(state, e, this);
|
|
1862
1889
|
}
|
|
1863
1890
|
finally {
|
|
1864
|
-
|
|
1865
|
-
this._reportToWorld(world, {
|
|
1866
|
-
element_name: selectors.element_name,
|
|
1867
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1868
|
-
variable: variable,
|
|
1869
|
-
value: info.value,
|
|
1870
|
-
text: "Extract attribute from element",
|
|
1871
|
-
screenshotId,
|
|
1872
|
-
result: error
|
|
1873
|
-
? {
|
|
1874
|
-
status: "FAILED",
|
|
1875
|
-
startTime,
|
|
1876
|
-
endTime,
|
|
1877
|
-
message: error?.message,
|
|
1878
|
-
}
|
|
1879
|
-
: {
|
|
1880
|
-
status: "PASSED",
|
|
1881
|
-
startTime,
|
|
1882
|
-
endTime,
|
|
1883
|
-
},
|
|
1884
|
-
info: info,
|
|
1885
|
-
});
|
|
1891
|
+
_commandFinally(state, this);
|
|
1886
1892
|
}
|
|
1887
1893
|
}
|
|
1888
1894
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1903,7 +1909,7 @@ class StableBrowser {
|
|
|
1903
1909
|
if (options && options.timeout) {
|
|
1904
1910
|
timeout = options.timeout;
|
|
1905
1911
|
}
|
|
1906
|
-
const serviceUrl =
|
|
1912
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1907
1913
|
const request = {
|
|
1908
1914
|
method: "POST",
|
|
1909
1915
|
url: serviceUrl,
|
|
@@ -1959,7 +1965,8 @@ class StableBrowser {
|
|
|
1959
1965
|
catch (e) {
|
|
1960
1966
|
errorCount++;
|
|
1961
1967
|
if (errorCount > 3) {
|
|
1962
|
-
throw e;
|
|
1968
|
+
// throw e;
|
|
1969
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1963
1970
|
}
|
|
1964
1971
|
// ignore
|
|
1965
1972
|
}
|
|
@@ -2070,11 +2077,12 @@ class StableBrowser {
|
|
|
2070
2077
|
info.screenshotPath = screenshotPath;
|
|
2071
2078
|
Object.assign(e, { info: info });
|
|
2072
2079
|
error = e;
|
|
2073
|
-
throw e;
|
|
2080
|
+
// throw e;
|
|
2081
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2074
2082
|
}
|
|
2075
2083
|
finally {
|
|
2076
2084
|
const endTime = Date.now();
|
|
2077
|
-
|
|
2085
|
+
_reportToWorld(world, {
|
|
2078
2086
|
type: Types.VERIFY_PAGE_PATH,
|
|
2079
2087
|
text: "Verify page path",
|
|
2080
2088
|
screenshotId,
|
|
@@ -2094,54 +2102,65 @@ class StableBrowser {
|
|
|
2094
2102
|
});
|
|
2095
2103
|
}
|
|
2096
2104
|
}
|
|
2105
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2106
|
+
const frames = this.page.frames();
|
|
2107
|
+
let results = [];
|
|
2108
|
+
let ignoreCase = !(text.startsWith("/") && text.endsWith("/"));
|
|
2109
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2110
|
+
if (dateAlternatives.date) {
|
|
2111
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2112
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2113
|
+
result.frame = frames[i];
|
|
2114
|
+
results.push(result);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
else if (numberAlternatives.number) {
|
|
2118
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2119
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2120
|
+
result.frame = frames[i];
|
|
2121
|
+
results.push(result);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
else {
|
|
2125
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2126
|
+
result.frame = frames[i];
|
|
2127
|
+
results.push(result);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
state.info.results = results;
|
|
2131
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2132
|
+
return resultWithElementsFound;
|
|
2133
|
+
}
|
|
2097
2134
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2098
2135
|
text = unEscapeString(text);
|
|
2099
|
-
const
|
|
2136
|
+
const state = {
|
|
2137
|
+
text_search: text,
|
|
2138
|
+
options,
|
|
2139
|
+
world,
|
|
2140
|
+
locate: false,
|
|
2141
|
+
scroll: false,
|
|
2142
|
+
highlight: false,
|
|
2143
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2144
|
+
text: `Verify text exists in page`,
|
|
2145
|
+
operation: "verifyTextExistInPage",
|
|
2146
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2147
|
+
};
|
|
2100
2148
|
const timeout = this._getLoadTimeout(options);
|
|
2101
|
-
let error = null;
|
|
2102
|
-
let screenshotId = null;
|
|
2103
|
-
let screenshotPath = null;
|
|
2104
2149
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2105
|
-
const info = {};
|
|
2106
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2107
|
-
info.operation = "verifyTextExistInPage";
|
|
2108
2150
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2109
2151
|
if (newValue !== text) {
|
|
2110
2152
|
this.logger.info(text + "=" + newValue);
|
|
2111
2153
|
text = newValue;
|
|
2112
2154
|
}
|
|
2113
|
-
info.text = text;
|
|
2114
2155
|
let dateAlternatives = findDateAlternatives(text);
|
|
2115
2156
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2116
2157
|
try {
|
|
2158
|
+
await _preCommand(state, this);
|
|
2159
|
+
state.info.text = text;
|
|
2117
2160
|
while (true) {
|
|
2118
|
-
const
|
|
2119
|
-
let results = [];
|
|
2120
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2121
|
-
if (dateAlternatives.date) {
|
|
2122
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2123
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2124
|
-
result.frame = frames[i];
|
|
2125
|
-
results.push(result);
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
else if (numberAlternatives.number) {
|
|
2129
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2130
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2131
|
-
result.frame = frames[i];
|
|
2132
|
-
results.push(result);
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
else {
|
|
2136
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2137
|
-
result.frame = frames[i];
|
|
2138
|
-
results.push(result);
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
info.results = results;
|
|
2142
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2161
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2143
2162
|
if (resultWithElementsFound.length === 0) {
|
|
2144
|
-
if (Date.now() - startTime > timeout) {
|
|
2163
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2145
2164
|
throw new Error(`Text ${text} not found in page`);
|
|
2146
2165
|
}
|
|
2147
2166
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2151,57 +2170,151 @@ class StableBrowser {
|
|
|
2151
2170
|
const frame = resultWithElementsFound[0].frame;
|
|
2152
2171
|
const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
|
|
2153
2172
|
await this._highlightElements(frame, dataAttribute);
|
|
2154
|
-
const element = await frame
|
|
2173
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2155
2174
|
if (element) {
|
|
2156
|
-
await this.scrollIfNeeded(element, info);
|
|
2175
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2157
2176
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2158
2177
|
}
|
|
2159
2178
|
}
|
|
2160
|
-
|
|
2161
|
-
return info;
|
|
2179
|
+
await _screenshot(state, this);
|
|
2180
|
+
return state.info;
|
|
2162
2181
|
}
|
|
2163
2182
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2164
2183
|
}
|
|
2165
2184
|
catch (e) {
|
|
2166
|
-
|
|
2167
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2168
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2169
|
-
info.screenshotPath = screenshotPath;
|
|
2170
|
-
Object.assign(e, { info: info });
|
|
2171
|
-
error = e;
|
|
2172
|
-
throw e;
|
|
2185
|
+
await _commandError(state, e, this);
|
|
2173
2186
|
}
|
|
2174
2187
|
finally {
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2188
|
+
_commandFinally(state, this);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2192
|
+
text = unEscapeString(text);
|
|
2193
|
+
const state = {
|
|
2194
|
+
text_search: text,
|
|
2195
|
+
options,
|
|
2196
|
+
world,
|
|
2197
|
+
locate: false,
|
|
2198
|
+
scroll: false,
|
|
2199
|
+
highlight: false,
|
|
2200
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2201
|
+
text: `Verify text does not exist in page`,
|
|
2202
|
+
operation: "verifyTextNotExistInPage",
|
|
2203
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2204
|
+
};
|
|
2205
|
+
const timeout = this._getLoadTimeout(options);
|
|
2206
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2207
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2208
|
+
if (newValue !== text) {
|
|
2209
|
+
this.logger.info(text + "=" + newValue);
|
|
2210
|
+
text = newValue;
|
|
2211
|
+
}
|
|
2212
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2213
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2214
|
+
try {
|
|
2215
|
+
await _preCommand(state, this);
|
|
2216
|
+
state.info.text = text;
|
|
2217
|
+
while (true) {
|
|
2218
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2219
|
+
if (resultWithElementsFound.length === 0) {
|
|
2220
|
+
await _screenshot(state, this);
|
|
2221
|
+
return state.info;
|
|
2222
|
+
}
|
|
2223
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2224
|
+
throw new Error(`Text ${text} found in page`);
|
|
2225
|
+
}
|
|
2226
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
catch (e) {
|
|
2230
|
+
await _commandError(state, e, this);
|
|
2231
|
+
}
|
|
2232
|
+
finally {
|
|
2233
|
+
_commandFinally(state, this);
|
|
2194
2234
|
}
|
|
2195
2235
|
}
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2236
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2237
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2238
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2239
|
+
const state = {
|
|
2240
|
+
text_search: textToVerify,
|
|
2241
|
+
options,
|
|
2242
|
+
world,
|
|
2243
|
+
locate: false,
|
|
2244
|
+
scroll: false,
|
|
2245
|
+
highlight: false,
|
|
2246
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2247
|
+
text: `Verify text with relation to another text`,
|
|
2248
|
+
operation: "verify_text_with_relation",
|
|
2249
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2250
|
+
};
|
|
2251
|
+
const timeout = this._getLoadTimeout(options);
|
|
2252
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2253
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2254
|
+
if (newValue !== textAnchor) {
|
|
2255
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2256
|
+
textAnchor = newValue;
|
|
2257
|
+
}
|
|
2258
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2259
|
+
if (newValue !== textToVerify) {
|
|
2260
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2261
|
+
textToVerify = newValue;
|
|
2262
|
+
}
|
|
2263
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2264
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2265
|
+
let foundAncore = false;
|
|
2266
|
+
try {
|
|
2267
|
+
await _preCommand(state, this);
|
|
2268
|
+
state.info.text = textToVerify;
|
|
2269
|
+
while (true) {
|
|
2270
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2271
|
+
if (resultWithElementsFound.length === 0) {
|
|
2272
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2273
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2274
|
+
}
|
|
2275
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2279
|
+
foundAncore = true;
|
|
2280
|
+
const result = resultWithElementsFound[i];
|
|
2281
|
+
const token = result.randomToken;
|
|
2282
|
+
const frame = result.frame;
|
|
2283
|
+
let css = `[data-blinq-id="blinq-id-${token}"]`;
|
|
2284
|
+
const climbArray1 = [];
|
|
2285
|
+
for (let i = 0; i < climb; i++) {
|
|
2286
|
+
climbArray1.push("..");
|
|
2287
|
+
}
|
|
2288
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2289
|
+
css = css + " >> " + climbXpath;
|
|
2290
|
+
const count = await frame.locator(css).count();
|
|
2291
|
+
for (let j = 0; j < count; j++) {
|
|
2292
|
+
const continer = await frame.locator(css).nth(j);
|
|
2293
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2294
|
+
if (result.elementCount > 0) {
|
|
2295
|
+
const dataAttribute = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
2296
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2297
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2298
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2299
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2300
|
+
if (element) {
|
|
2301
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2302
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2303
|
+
}
|
|
2304
|
+
await _screenshot(state, this);
|
|
2305
|
+
return state.info;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2311
|
+
}
|
|
2312
|
+
catch (e) {
|
|
2313
|
+
await _commandError(state, e, this);
|
|
2200
2314
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2315
|
+
finally {
|
|
2316
|
+
_commandFinally(state, this);
|
|
2203
2317
|
}
|
|
2204
|
-
return serviceUrl;
|
|
2205
2318
|
}
|
|
2206
2319
|
async visualVerification(text, options = {}, world = null) {
|
|
2207
2320
|
const startTime = Date.now();
|
|
@@ -2217,7 +2330,7 @@ class StableBrowser {
|
|
|
2217
2330
|
throw new Error("TOKEN is not set");
|
|
2218
2331
|
}
|
|
2219
2332
|
try {
|
|
2220
|
-
let serviceUrl =
|
|
2333
|
+
let serviceUrl = _getServerUrl();
|
|
2221
2334
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2222
2335
|
info.screenshotPath = screenshotPath;
|
|
2223
2336
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2253,11 +2366,12 @@ class StableBrowser {
|
|
|
2253
2366
|
info.screenshotPath = screenshotPath;
|
|
2254
2367
|
Object.assign(e, { info: info });
|
|
2255
2368
|
error = e;
|
|
2256
|
-
throw e;
|
|
2369
|
+
// throw e;
|
|
2370
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2257
2371
|
}
|
|
2258
2372
|
finally {
|
|
2259
2373
|
const endTime = Date.now();
|
|
2260
|
-
|
|
2374
|
+
_reportToWorld(world, {
|
|
2261
2375
|
type: Types.VERIFY_VISUAL,
|
|
2262
2376
|
text: "Visual verification",
|
|
2263
2377
|
screenshotId,
|
|
@@ -2305,6 +2419,7 @@ class StableBrowser {
|
|
|
2305
2419
|
let screenshotPath = null;
|
|
2306
2420
|
const info = {};
|
|
2307
2421
|
info.log = "";
|
|
2422
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2308
2423
|
info.operation = "getTableData";
|
|
2309
2424
|
info.selectors = selectors;
|
|
2310
2425
|
try {
|
|
@@ -2320,11 +2435,12 @@ class StableBrowser {
|
|
|
2320
2435
|
info.screenshotPath = screenshotPath;
|
|
2321
2436
|
Object.assign(e, { info: info });
|
|
2322
2437
|
error = e;
|
|
2323
|
-
throw e;
|
|
2438
|
+
// throw e;
|
|
2439
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2324
2440
|
}
|
|
2325
2441
|
finally {
|
|
2326
2442
|
const endTime = Date.now();
|
|
2327
|
-
|
|
2443
|
+
_reportToWorld(world, {
|
|
2328
2444
|
element_name: selectors.element_name,
|
|
2329
2445
|
type: Types.GET_TABLE_DATA,
|
|
2330
2446
|
text: "Get table data",
|
|
@@ -2379,7 +2495,7 @@ class StableBrowser {
|
|
|
2379
2495
|
info.operation = "analyzeTable";
|
|
2380
2496
|
info.selectors = selectors;
|
|
2381
2497
|
info.query = query;
|
|
2382
|
-
query =
|
|
2498
|
+
query = _fixUsingParams(query, _params);
|
|
2383
2499
|
info.query_fixed = query;
|
|
2384
2500
|
info.operator = operator;
|
|
2385
2501
|
info.value = value;
|
|
@@ -2485,11 +2601,12 @@ class StableBrowser {
|
|
|
2485
2601
|
info.screenshotPath = screenshotPath;
|
|
2486
2602
|
Object.assign(e, { info: info });
|
|
2487
2603
|
error = e;
|
|
2488
|
-
throw e;
|
|
2604
|
+
// throw e;
|
|
2605
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2489
2606
|
}
|
|
2490
2607
|
finally {
|
|
2491
2608
|
const endTime = Date.now();
|
|
2492
|
-
|
|
2609
|
+
_reportToWorld(world, {
|
|
2493
2610
|
element_name: selectors.element_name,
|
|
2494
2611
|
type: Types.ANALYZE_TABLE,
|
|
2495
2612
|
text: "Analyze table",
|
|
@@ -2562,7 +2679,7 @@ class StableBrowser {
|
|
|
2562
2679
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2563
2680
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2564
2681
|
const endTime = Date.now();
|
|
2565
|
-
|
|
2682
|
+
_reportToWorld(world, {
|
|
2566
2683
|
type: Types.GET_PAGE_STATUS,
|
|
2567
2684
|
text: "Wait for page load",
|
|
2568
2685
|
screenshotId,
|
|
@@ -2582,41 +2699,35 @@ class StableBrowser {
|
|
|
2582
2699
|
}
|
|
2583
2700
|
}
|
|
2584
2701
|
async closePage(options = {}, world = null) {
|
|
2585
|
-
const
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2702
|
+
const state = {
|
|
2703
|
+
options,
|
|
2704
|
+
world,
|
|
2705
|
+
locate: false,
|
|
2706
|
+
scroll: false,
|
|
2707
|
+
highlight: false,
|
|
2708
|
+
type: Types.CLOSE_PAGE,
|
|
2709
|
+
text: `Close page`,
|
|
2710
|
+
operation: "closePage",
|
|
2711
|
+
log: "***** close page *****\n",
|
|
2712
|
+
throwError: false,
|
|
2713
|
+
};
|
|
2590
2714
|
try {
|
|
2715
|
+
await _preCommand(state, this);
|
|
2591
2716
|
await this.page.close();
|
|
2592
2717
|
}
|
|
2593
2718
|
catch (e) {
|
|
2594
2719
|
console.log(".");
|
|
2720
|
+
await _commandError(state, e, this);
|
|
2595
2721
|
}
|
|
2596
2722
|
finally {
|
|
2597
|
-
|
|
2598
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2599
|
-
const endTime = Date.now();
|
|
2600
|
-
this._reportToWorld(world, {
|
|
2601
|
-
type: Types.CLOSE_PAGE,
|
|
2602
|
-
text: "close page",
|
|
2603
|
-
screenshotId,
|
|
2604
|
-
result: error
|
|
2605
|
-
? {
|
|
2606
|
-
status: "FAILED",
|
|
2607
|
-
startTime,
|
|
2608
|
-
endTime,
|
|
2609
|
-
message: error?.message,
|
|
2610
|
-
}
|
|
2611
|
-
: {
|
|
2612
|
-
status: "PASSED",
|
|
2613
|
-
startTime,
|
|
2614
|
-
endTime,
|
|
2615
|
-
},
|
|
2616
|
-
info: info,
|
|
2617
|
-
});
|
|
2723
|
+
_commandFinally(state, this);
|
|
2618
2724
|
}
|
|
2619
2725
|
}
|
|
2726
|
+
saveTestDataAsGlobal(options, world) {
|
|
2727
|
+
const dataFile = this._getDataFile(world);
|
|
2728
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2729
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2730
|
+
}
|
|
2620
2731
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2621
2732
|
const startTime = Date.now();
|
|
2622
2733
|
let error = null;
|
|
@@ -2634,12 +2745,13 @@ class StableBrowser {
|
|
|
2634
2745
|
}
|
|
2635
2746
|
catch (e) {
|
|
2636
2747
|
console.log(".");
|
|
2748
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2637
2749
|
}
|
|
2638
2750
|
finally {
|
|
2639
2751
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2640
2752
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2641
2753
|
const endTime = Date.now();
|
|
2642
|
-
|
|
2754
|
+
_reportToWorld(world, {
|
|
2643
2755
|
type: Types.SET_VIEWPORT,
|
|
2644
2756
|
text: "set viewport size to " + width + "x" + hight,
|
|
2645
2757
|
screenshotId,
|
|
@@ -2670,12 +2782,13 @@ class StableBrowser {
|
|
|
2670
2782
|
}
|
|
2671
2783
|
catch (e) {
|
|
2672
2784
|
console.log(".");
|
|
2785
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2673
2786
|
}
|
|
2674
2787
|
finally {
|
|
2675
2788
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2676
2789
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2677
2790
|
const endTime = Date.now();
|
|
2678
|
-
|
|
2791
|
+
_reportToWorld(world, {
|
|
2679
2792
|
type: Types.GET_PAGE_STATUS,
|
|
2680
2793
|
text: "page relaod",
|
|
2681
2794
|
screenshotId,
|
|
@@ -2711,11 +2824,45 @@ class StableBrowser {
|
|
|
2711
2824
|
console.log("#-#");
|
|
2712
2825
|
}
|
|
2713
2826
|
}
|
|
2714
|
-
|
|
2715
|
-
if (
|
|
2716
|
-
|
|
2827
|
+
async beforeStep(world, step) {
|
|
2828
|
+
if (this.stepIndex === undefined) {
|
|
2829
|
+
this.stepIndex = 0;
|
|
2830
|
+
}
|
|
2831
|
+
else {
|
|
2832
|
+
this.stepIndex++;
|
|
2833
|
+
}
|
|
2834
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
2835
|
+
this.stepName = step.pickleStep.text;
|
|
2836
|
+
this.logger.info("step: " + this.stepName);
|
|
2837
|
+
}
|
|
2838
|
+
else if (step && step.text) {
|
|
2839
|
+
this.stepName = step.text;
|
|
2840
|
+
}
|
|
2841
|
+
else {
|
|
2842
|
+
this.stepName = "step " + this.stepIndex;
|
|
2843
|
+
}
|
|
2844
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2845
|
+
if (this.context.browserObject.context) {
|
|
2846
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
2850
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
2851
|
+
// check if @global_test_data tag is present
|
|
2852
|
+
if (this.tags.includes("@global_test_data")) {
|
|
2853
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
async afterStep(world, step) {
|
|
2858
|
+
this.stepName = null;
|
|
2859
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2860
|
+
if (this.context.browserObject.context) {
|
|
2861
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
2862
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2717
2865
|
}
|
|
2718
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2719
2866
|
}
|
|
2720
2867
|
}
|
|
2721
2868
|
function createTimedPromise(promise, label) {
|
|
@@ -2723,156 +2870,5 @@ function createTimedPromise(promise, label) {
|
|
|
2723
2870
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2724
2871
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2725
2872
|
}
|
|
2726
|
-
const KEYBOARD_EVENTS = [
|
|
2727
|
-
"ALT",
|
|
2728
|
-
"AltGraph",
|
|
2729
|
-
"CapsLock",
|
|
2730
|
-
"Control",
|
|
2731
|
-
"Fn",
|
|
2732
|
-
"FnLock",
|
|
2733
|
-
"Hyper",
|
|
2734
|
-
"Meta",
|
|
2735
|
-
"NumLock",
|
|
2736
|
-
"ScrollLock",
|
|
2737
|
-
"Shift",
|
|
2738
|
-
"Super",
|
|
2739
|
-
"Symbol",
|
|
2740
|
-
"SymbolLock",
|
|
2741
|
-
"Enter",
|
|
2742
|
-
"Tab",
|
|
2743
|
-
"ArrowDown",
|
|
2744
|
-
"ArrowLeft",
|
|
2745
|
-
"ArrowRight",
|
|
2746
|
-
"ArrowUp",
|
|
2747
|
-
"End",
|
|
2748
|
-
"Home",
|
|
2749
|
-
"PageDown",
|
|
2750
|
-
"PageUp",
|
|
2751
|
-
"Backspace",
|
|
2752
|
-
"Clear",
|
|
2753
|
-
"Copy",
|
|
2754
|
-
"CrSel",
|
|
2755
|
-
"Cut",
|
|
2756
|
-
"Delete",
|
|
2757
|
-
"EraseEof",
|
|
2758
|
-
"ExSel",
|
|
2759
|
-
"Insert",
|
|
2760
|
-
"Paste",
|
|
2761
|
-
"Redo",
|
|
2762
|
-
"Undo",
|
|
2763
|
-
"Accept",
|
|
2764
|
-
"Again",
|
|
2765
|
-
"Attn",
|
|
2766
|
-
"Cancel",
|
|
2767
|
-
"ContextMenu",
|
|
2768
|
-
"Escape",
|
|
2769
|
-
"Execute",
|
|
2770
|
-
"Find",
|
|
2771
|
-
"Finish",
|
|
2772
|
-
"Help",
|
|
2773
|
-
"Pause",
|
|
2774
|
-
"Play",
|
|
2775
|
-
"Props",
|
|
2776
|
-
"Select",
|
|
2777
|
-
"ZoomIn",
|
|
2778
|
-
"ZoomOut",
|
|
2779
|
-
"BrightnessDown",
|
|
2780
|
-
"BrightnessUp",
|
|
2781
|
-
"Eject",
|
|
2782
|
-
"LogOff",
|
|
2783
|
-
"Power",
|
|
2784
|
-
"PowerOff",
|
|
2785
|
-
"PrintScreen",
|
|
2786
|
-
"Hibernate",
|
|
2787
|
-
"Standby",
|
|
2788
|
-
"WakeUp",
|
|
2789
|
-
"AllCandidates",
|
|
2790
|
-
"Alphanumeric",
|
|
2791
|
-
"CodeInput",
|
|
2792
|
-
"Compose",
|
|
2793
|
-
"Convert",
|
|
2794
|
-
"Dead",
|
|
2795
|
-
"FinalMode",
|
|
2796
|
-
"GroupFirst",
|
|
2797
|
-
"GroupLast",
|
|
2798
|
-
"GroupNext",
|
|
2799
|
-
"GroupPrevious",
|
|
2800
|
-
"ModeChange",
|
|
2801
|
-
"NextCandidate",
|
|
2802
|
-
"NonConvert",
|
|
2803
|
-
"PreviousCandidate",
|
|
2804
|
-
"Process",
|
|
2805
|
-
"SingleCandidate",
|
|
2806
|
-
"HangulMode",
|
|
2807
|
-
"HanjaMode",
|
|
2808
|
-
"JunjaMode",
|
|
2809
|
-
"Eisu",
|
|
2810
|
-
"Hankaku",
|
|
2811
|
-
"Hiragana",
|
|
2812
|
-
"HiraganaKatakana",
|
|
2813
|
-
"KanaMode",
|
|
2814
|
-
"KanjiMode",
|
|
2815
|
-
"Katakana",
|
|
2816
|
-
"Romaji",
|
|
2817
|
-
"Zenkaku",
|
|
2818
|
-
"ZenkakuHanaku",
|
|
2819
|
-
"F1",
|
|
2820
|
-
"F2",
|
|
2821
|
-
"F3",
|
|
2822
|
-
"F4",
|
|
2823
|
-
"F5",
|
|
2824
|
-
"F6",
|
|
2825
|
-
"F7",
|
|
2826
|
-
"F8",
|
|
2827
|
-
"F9",
|
|
2828
|
-
"F10",
|
|
2829
|
-
"F11",
|
|
2830
|
-
"F12",
|
|
2831
|
-
"Soft1",
|
|
2832
|
-
"Soft2",
|
|
2833
|
-
"Soft3",
|
|
2834
|
-
"Soft4",
|
|
2835
|
-
"ChannelDown",
|
|
2836
|
-
"ChannelUp",
|
|
2837
|
-
"Close",
|
|
2838
|
-
"MailForward",
|
|
2839
|
-
"MailReply",
|
|
2840
|
-
"MailSend",
|
|
2841
|
-
"MediaFastForward",
|
|
2842
|
-
"MediaPause",
|
|
2843
|
-
"MediaPlay",
|
|
2844
|
-
"MediaPlayPause",
|
|
2845
|
-
"MediaRecord",
|
|
2846
|
-
"MediaRewind",
|
|
2847
|
-
"MediaStop",
|
|
2848
|
-
"MediaTrackNext",
|
|
2849
|
-
"MediaTrackPrevious",
|
|
2850
|
-
"AudioBalanceLeft",
|
|
2851
|
-
"AudioBalanceRight",
|
|
2852
|
-
"AudioBassBoostDown",
|
|
2853
|
-
"AudioBassBoostToggle",
|
|
2854
|
-
"AudioBassBoostUp",
|
|
2855
|
-
"AudioFaderFront",
|
|
2856
|
-
"AudioFaderRear",
|
|
2857
|
-
"AudioSurroundModeNext",
|
|
2858
|
-
"AudioTrebleDown",
|
|
2859
|
-
"AudioTrebleUp",
|
|
2860
|
-
"AudioVolumeDown",
|
|
2861
|
-
"AudioVolumeMute",
|
|
2862
|
-
"AudioVolumeUp",
|
|
2863
|
-
"MicrophoneToggle",
|
|
2864
|
-
"MicrophoneVolumeDown",
|
|
2865
|
-
"MicrophoneVolumeMute",
|
|
2866
|
-
"MicrophoneVolumeUp",
|
|
2867
|
-
"TV",
|
|
2868
|
-
"TV3DMode",
|
|
2869
|
-
"TVAntennaCable",
|
|
2870
|
-
"TVAudioDescription",
|
|
2871
|
-
];
|
|
2872
|
-
function unEscapeString(str) {
|
|
2873
|
-
const placeholder = "__NEWLINE__";
|
|
2874
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2875
|
-
return str;
|
|
2876
|
-
}
|
|
2877
2873
|
export { StableBrowser };
|
|
2878
2874
|
//# sourceMappingURL=stable_browser.js.map
|