automation_model 1.0.549-dev → 1.0.549-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.
Files changed (42) hide show
  1. package/README.md +130 -0
  2. package/lib/api.d.ts +1 -0
  3. package/lib/api.js +8 -7
  4. package/lib/api.js.map +1 -1
  5. package/lib/auto_page.d.ts +5 -2
  6. package/lib/auto_page.js +101 -5
  7. package/lib/auto_page.js.map +1 -1
  8. package/lib/browser_manager.d.ts +3 -2
  9. package/lib/browser_manager.js +85 -39
  10. package/lib/browser_manager.js.map +1 -1
  11. package/lib/command_common.d.ts +1 -1
  12. package/lib/command_common.js +47 -13
  13. package/lib/command_common.js.map +1 -1
  14. package/lib/error-messages.js +18 -0
  15. package/lib/error-messages.js.map +1 -1
  16. package/lib/generation_scripts.d.ts +4 -0
  17. package/lib/generation_scripts.js +2 -0
  18. package/lib/generation_scripts.js.map +1 -0
  19. package/lib/init_browser.d.ts +4 -2
  20. package/lib/init_browser.js +52 -5
  21. package/lib/init_browser.js.map +1 -1
  22. package/lib/locate_element.js +10 -10
  23. package/lib/locate_element.js.map +1 -1
  24. package/lib/locator_log.d.ts +26 -0
  25. package/lib/locator_log.js +69 -0
  26. package/lib/locator_log.js.map +1 -0
  27. package/lib/network.js +51 -12
  28. package/lib/network.js.map +1 -1
  29. package/lib/stable_browser.d.ts +57 -17
  30. package/lib/stable_browser.js +777 -527
  31. package/lib/stable_browser.js.map +1 -1
  32. package/lib/table_helper.d.ts +19 -0
  33. package/lib/table_helper.js +95 -0
  34. package/lib/table_helper.js.map +1 -0
  35. package/lib/test_context.d.ts +2 -0
  36. package/lib/test_context.js +1 -0
  37. package/lib/test_context.js.map +1 -1
  38. package/lib/utils.d.ts +16 -1
  39. package/lib/utils.js +416 -6
  40. package/lib/utils.js.map +1 -1
  41. package/package.json +8 -8
  42. /package/lib/{axe → scripts}/axe.mini.js +0 -0
@@ -10,17 +10,21 @@ 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, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, } from "./utils.js";
14
14
  import csv from "csv-parser";
15
15
  import { Readable } from "node:stream";
16
16
  import readline from "readline";
17
- import { getContext } from "./init_browser.js";
17
+ import { getContext, refreshBrowser } from "./init_browser.js";
18
18
  import { locate_element } from "./locate_element.js";
19
19
  import { randomUUID } from "crypto";
20
20
  import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
21
21
  import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
22
- const Types = {
22
+ import { LocatorLog } from "./locator_log.js";
23
+ import axios from "axios";
24
+ import { _findCellArea, findElementsInArea } from "./table_helper.js";
25
+ export const Types = {
23
26
  CLICK: "click_element",
27
+ WAIT_ELEMENT: "wait_element",
24
28
  NAVIGATE: "navigate",
25
29
  FILL: "fill_element",
26
30
  EXECUTE: "execute_page_method",
@@ -30,6 +34,8 @@ const Types = {
30
34
  GET_PAGE_STATUS: "get_page_status",
31
35
  CLICK_ROW_ACTION: "click_row_action",
32
36
  VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
37
+ VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
38
+ VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
33
39
  ANALYZE_TABLE: "analyze_table",
34
40
  SELECT: "select_combobox",
35
41
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -40,6 +46,7 @@ const Types = {
40
46
  UNCHECK: "uncheck_element",
41
47
  EXTRACT: "extract_attribute",
42
48
  CLOSE_PAGE: "close_page",
49
+ TABLE_OPERATION: "table_operation",
43
50
  SET_DATE_TIME: "set_date_time",
44
51
  SET_VIEWPORT: "set_viewport",
45
52
  VERIFY_VISUAL: "verify_visual",
@@ -47,8 +54,12 @@ const Types = {
47
54
  SET_INPUT: "set_input",
48
55
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
49
56
  VERIFY_ATTRIBUTE: "verify_element_attribute",
57
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
50
58
  };
51
59
  export const apps = {};
60
+ const formatElementName = (elementName) => {
61
+ return elementName ? JSON.stringify(elementName) : "element";
62
+ };
52
63
  class StableBrowser {
53
64
  browser;
54
65
  page;
@@ -62,6 +73,7 @@ class StableBrowser {
62
73
  appName = "main";
63
74
  tags = null;
64
75
  isRecording = false;
76
+ initSnapshotTaken = false;
65
77
  constructor(browser, page, logger = null, context = null, world = null) {
66
78
  this.browser = browser;
67
79
  this.page = page;
@@ -100,27 +112,9 @@ class StableBrowser {
100
112
  registerNetworkEvents(this.world, this, this.context, this.page);
101
113
  registerDownloadEvent(this.page, this.world, this.context);
102
114
  }
103
- async scrollPageToLoadLazyElements() {
104
- let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
105
- let retry = 0;
106
- while (true) {
107
- await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
108
- await new Promise((resolve) => setTimeout(resolve, 1000));
109
- let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
110
- if (newHeight === lastHeight) {
111
- break;
112
- }
113
- lastHeight = newHeight;
114
- retry++;
115
- if (retry > 10) {
116
- break;
117
- }
118
- }
119
- await this.page.evaluate(() => window.scrollTo(0, 0));
120
- }
121
115
  registerEventListeners(context) {
122
116
  this.registerConsoleLogListener(this.page, context);
123
- this.registerRequestListener(this.page, context, this.webLogFile);
117
+ // this.registerRequestListener(this.page, context, this.webLogFile);
124
118
  if (!context.pageLoading) {
125
119
  context.pageLoading = { status: false };
126
120
  }
@@ -176,8 +170,8 @@ class StableBrowser {
176
170
  };
177
171
  }
178
172
  const tempContext = {};
179
- this._copyContext(this, tempContext);
180
- this._copyContext(apps[appName], this);
173
+ _copyContext(this, tempContext);
174
+ _copyContext(apps[appName], this);
181
175
  apps[this.appName] = tempContext;
182
176
  this.appName = appName;
183
177
  if (newContextCreated) {
@@ -186,22 +180,6 @@ class StableBrowser {
186
180
  await this.waitForPageLoad();
187
181
  }
188
182
  }
189
- _copyContext(from, to) {
190
- to.browser = from.browser;
191
- to.page = from.page;
192
- to.context = from.context;
193
- }
194
- getWebLogFile(logFolder) {
195
- if (!fs.existsSync(logFolder)) {
196
- fs.mkdirSync(logFolder, { recursive: true });
197
- }
198
- let nextIndex = 1;
199
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
200
- nextIndex++;
201
- }
202
- const fileName = nextIndex + ".json";
203
- return path.join(logFolder, fileName);
204
- }
205
183
  registerConsoleLogListener(page, context) {
206
184
  if (!this.context.webLogger) {
207
185
  this.context.webLogger = [];
@@ -265,55 +243,51 @@ class StableBrowser {
265
243
  // async closeUnexpectedPopups() {
266
244
  // await closeUnexpectedPopups(this.page);
267
245
  // }
268
- async goto(url) {
246
+ async goto(url, world = null) {
247
+ if (!url) {
248
+ throw new Error("url is null, verify that the environment file is correct");
249
+ }
269
250
  if (!url.startsWith("http")) {
270
251
  url = "https://" + url;
271
252
  }
272
- await this.page.goto(url, {
273
- timeout: 60000,
274
- });
275
- }
276
- _fixUsingParams(text, _params) {
277
- if (!_params || typeof text !== "string") {
278
- return text;
253
+ const state = {
254
+ value: url,
255
+ world: world,
256
+ type: Types.NAVIGATE,
257
+ text: `Navigate Page to: ${url}`,
258
+ operation: "goto",
259
+ log: "***** navigate page to " + url + " *****\n",
260
+ info: {},
261
+ locate: false,
262
+ scroll: false,
263
+ screenshot: false,
264
+ highlight: false,
265
+ };
266
+ try {
267
+ await _preCommand(state, this);
268
+ await this.page.goto(url, {
269
+ timeout: 60000,
270
+ });
271
+ await _screenshot(state, this);
279
272
  }
280
- for (let key in _params) {
281
- let regValue = key;
282
- if (key.startsWith("_")) {
283
- // remove the _ prefix
284
- regValue = key.substring(1);
285
- }
286
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
273
+ catch (error) {
274
+ console.error("Error on goto", error);
275
+ _commandError(state, error, this);
287
276
  }
288
- return text;
289
- }
290
- _fixLocatorUsingParams(locator, _params) {
291
- // check if not null
292
- if (!locator) {
293
- return locator;
277
+ finally {
278
+ _commandFinally(state, this);
294
279
  }
295
- // clone the locator
296
- locator = JSON.parse(JSON.stringify(locator));
297
- this.scanAndManipulate(locator, _params);
298
- return locator;
299
- }
300
- _isObject(value) {
301
- return value && typeof value === "object" && value.constructor === Object;
302
280
  }
303
- scanAndManipulate(currentObj, _params) {
304
- for (const key in currentObj) {
305
- if (typeof currentObj[key] === "string") {
306
- // Perform string manipulation
307
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
308
- }
309
- else if (this._isObject(currentObj[key])) {
310
- // Recursively scan nested objects
311
- this.scanAndManipulate(currentObj[key], _params);
281
+ async _getLocator(locator, scope, _params) {
282
+ locator = _fixLocatorUsingParams(locator, _params);
283
+ // locator = await this._replaceWithLocalData(locator);
284
+ for (let key in locator) {
285
+ if (typeof locator[key] !== "string")
286
+ continue;
287
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
288
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
312
289
  }
313
290
  }
314
- }
315
- _getLocator(locator, scope, _params) {
316
- locator = this._fixLocatorUsingParams(locator, _params);
317
291
  let locatorReturn;
318
292
  if (locator.role) {
319
293
  if (locator.role[1].nameReg) {
@@ -321,7 +295,7 @@ class StableBrowser {
321
295
  delete locator.role[1].nameReg;
322
296
  }
323
297
  // if (locator.role[1].name) {
324
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
298
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
325
299
  // }
326
300
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
327
301
  }
@@ -364,140 +338,60 @@ class StableBrowser {
364
338
  if (css && css.locator) {
365
339
  css = css.locator;
366
340
  }
367
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
341
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
368
342
  if (result.elementCount === 0) {
369
343
  return;
370
344
  }
371
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
345
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
372
346
  // css climb to parent element
373
347
  const climbArray = [];
374
348
  for (let i = 0; i < climb; i++) {
375
349
  climbArray.push("..");
376
350
  }
377
351
  let climbXpath = "xpath=" + climbArray.join("/");
378
- return textElementCss + " >> " + climbXpath + " >> " + css;
379
- }
380
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
381
- //const stringifyText = JSON.stringify(text);
382
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
383
- function isParent(parent, child) {
384
- let currentNode = child.parentNode;
385
- while (currentNode !== null) {
386
- if (currentNode === parent) {
387
- return true;
388
- }
389
- currentNode = currentNode.parentNode;
390
- }
391
- return false;
392
- }
393
- document.isParent = isParent;
394
- function getRegex(str) {
395
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
396
- if (!match) {
397
- return null;
398
- }
399
- let [_, pattern, flags] = match;
400
- return new RegExp(pattern, flags);
401
- }
402
- document.getRegex = getRegex;
403
- function collectAllShadowDomElements(element, result = []) {
404
- // Check and add the element if it has a shadow root
405
- if (element.shadowRoot) {
406
- result.push(element);
407
- // Also search within the shadow root
408
- document.collectAllShadowDomElements(element.shadowRoot, result);
409
- }
410
- // Iterate over child nodes
411
- element.childNodes.forEach((child) => {
412
- // Recursively call the function for each child node
413
- document.collectAllShadowDomElements(child, result);
414
- });
415
- return result;
416
- }
417
- document.collectAllShadowDomElements = collectAllShadowDomElements;
418
- if (!tag) {
419
- tag = "*:not(script, style, head)";
420
- }
421
- let regexpSearch = document.getRegex(text);
422
- if (regexpSearch) {
423
- regex = true;
424
- }
425
- let elements = Array.from(document.querySelectorAll(tag));
426
- let shadowHosts = [];
427
- document.collectAllShadowDomElements(document, shadowHosts);
428
- for (let i = 0; i < shadowHosts.length; i++) {
429
- let shadowElement = shadowHosts[i].shadowRoot;
430
- if (!shadowElement) {
431
- console.log("shadowElement is null, for host " + shadowHosts[i]);
432
- continue;
433
- }
434
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
435
- elements = elements.concat(shadowElements);
436
- }
437
- let randomToken = null;
438
- const foundElements = [];
439
- if (regex) {
440
- if (!regexpSearch) {
441
- regexpSearch = new RegExp(text, "im");
442
- }
443
- for (let i = 0; i < elements.length; i++) {
444
- const element = elements[i];
445
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
446
- (element.value && regexpSearch.test(element.value))) {
447
- foundElements.push(element);
448
- }
449
- }
450
- }
451
- else {
452
- text = text.trim();
453
- for (let i = 0; i < elements.length; i++) {
454
- const element = elements[i];
455
- if (partial) {
456
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
457
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
458
- foundElements.push(element);
459
- }
352
+ let resultCss = textElementCss + " >> " + climbXpath;
353
+ if (css) {
354
+ resultCss = resultCss + " >> " + css;
355
+ }
356
+ return resultCss;
357
+ }
358
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
359
+ const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
360
+ const locator = scope.locator(query);
361
+ const count = await locator.count();
362
+ if (!tag1) {
363
+ tag1 = "*";
364
+ }
365
+ const randomToken = Math.random().toString(36).substring(7);
366
+ let tagCount = 0;
367
+ for (let i = 0; i < count; i++) {
368
+ const element = locator.nth(i);
369
+ // check if the tag matches
370
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
371
+ if (!tag.startsWith("*")) {
372
+ if (el.tagName.toLowerCase() !== tag) {
373
+ return false;
460
374
  }
461
- else {
462
- if ((element.innerText && element.innerText.trim() === text) ||
463
- (element.value && element.value === text)) {
464
- foundElements.push(element);
465
- }
466
- }
467
- }
468
- }
469
- let noChildElements = [];
470
- for (let i = 0; i < foundElements.length; i++) {
471
- let element = foundElements[i];
472
- let hasChild = false;
473
- for (let j = 0; j < foundElements.length; j++) {
474
- if (i === j) {
475
- continue;
476
- }
477
- if (isParent(element, foundElements[j])) {
478
- hasChild = true;
479
- break;
480
- }
481
- }
482
- if (!hasChild) {
483
- noChildElements.push(element);
484
375
  }
485
- }
486
- let elementCount = 0;
487
- if (noChildElements.length > 0) {
488
- for (let i = 0; i < noChildElements.length; i++) {
489
- if (randomToken === null) {
490
- randomToken = Math.random().toString(36).substring(7);
491
- }
492
- let element = noChildElements[i];
493
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
494
- elementCount++;
376
+ if (!el.setAttribute) {
377
+ el = el.parentElement;
495
378
  }
379
+ // remove any attributes start with data-blinq-id
380
+ // for (let i = 0; i < el.attributes.length; i++) {
381
+ // if (el.attributes[i].name.startsWith("data-blinq-id")) {
382
+ // el.removeAttribute(el.attributes[i].name);
383
+ // }
384
+ // }
385
+ el.setAttribute("data-blinq-id-" + randomToken, "");
386
+ return true;
387
+ }, [tag1, randomToken]))) {
388
+ continue;
496
389
  }
497
- return { elementCount: elementCount, randomToken: randomToken };
498
- }, [text1, tag1, regex1, partial1]);
390
+ tagCount++;
391
+ }
392
+ return { elementCount: tagCount, randomToken };
499
393
  }
500
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
394
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
501
395
  if (!info) {
502
396
  info = {};
503
397
  }
@@ -506,10 +400,13 @@ class StableBrowser {
506
400
  }
507
401
  if (!info.log) {
508
402
  info.log = "";
403
+ info.locatorLog = new LocatorLog(selectorHierarchy);
509
404
  }
510
405
  let locatorSearch = selectorHierarchy[index];
406
+ let originalLocatorSearch = "";
511
407
  try {
512
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
408
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
409
+ locatorSearch = JSON.parse(originalLocatorSearch);
513
410
  }
514
411
  catch (e) {
515
412
  console.error(e);
@@ -517,30 +414,31 @@ class StableBrowser {
517
414
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
518
415
  let locator = null;
519
416
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
520
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
417
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
418
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
521
419
  if (!locatorString) {
522
420
  info.failCause.textNotFound = true;
523
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
421
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
524
422
  return;
525
423
  }
526
- locator = this._getLocator({ css: locatorString }, scope, _params);
424
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
527
425
  }
528
426
  else if (locatorSearch.text) {
529
- let text = this._fixUsingParams(locatorSearch.text, _params);
530
- let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
427
+ let text = _fixUsingParams(locatorSearch.text, _params);
428
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
531
429
  if (result.elementCount === 0) {
532
430
  info.failCause.textNotFound = true;
533
- info.failCause.lastError = "failed to locate element by text: " + text;
431
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
534
432
  return;
535
433
  }
536
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
434
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
537
435
  if (locatorSearch.childCss) {
538
436
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
539
437
  }
540
- locator = this._getLocator(locatorSearch, scope, _params);
438
+ locator = await this._getLocator(locatorSearch, scope, _params);
541
439
  }
542
440
  else {
543
- locator = this._getLocator(locatorSearch, scope, _params);
441
+ locator = await this._getLocator(locatorSearch, scope, _params);
544
442
  }
545
443
  // let cssHref = false;
546
444
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -555,16 +453,25 @@ class StableBrowser {
555
453
  let visibleLocator = null;
556
454
  if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
557
455
  foundLocators.push(locator.nth(locatorSearch.index));
456
+ if (info.locatorLog) {
457
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
458
+ }
558
459
  return;
559
460
  }
461
+ if (info.locatorLog && count === 0) {
462
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
463
+ }
560
464
  for (let j = 0; j < count; j++) {
561
465
  let visible = await locator.nth(j).isVisible();
562
466
  const enabled = await locator.nth(j).isEnabled();
563
467
  if (!visibleOnly) {
564
468
  visible = true;
565
469
  }
566
- if (visible && enabled) {
470
+ if (visible && (allowDisabled || enabled)) {
567
471
  foundLocators.push(locator.nth(j));
472
+ if (info.locatorLog) {
473
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
474
+ }
568
475
  }
569
476
  else {
570
477
  info.failCause.visible = visible;
@@ -572,8 +479,16 @@ class StableBrowser {
572
479
  if (!info.printMessages) {
573
480
  info.printMessages = {};
574
481
  }
482
+ if (info.locatorLog && !visible) {
483
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
484
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
485
+ }
486
+ if (info.locatorLog && !enabled) {
487
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
488
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
489
+ }
575
490
  if (!info.printMessages[j.toString()]) {
576
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
491
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
577
492
  info.printMessages[j.toString()] = true;
578
493
  }
579
494
  }
@@ -589,7 +504,7 @@ class StableBrowser {
589
504
  if (!info) {
590
505
  info = {};
591
506
  }
592
- info.log += "scan for popup handlers" + "\n";
507
+ //info.log += "scan for popup handlers" + "\n";
593
508
  const handlerGroup = [];
594
509
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
595
510
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -637,7 +552,7 @@ class StableBrowser {
637
552
  }
638
553
  return { rerun: false };
639
554
  }
640
- async _locate(selectors, info, _params, timeout) {
555
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
641
556
  if (!timeout) {
642
557
  timeout = 30000;
643
558
  }
@@ -647,9 +562,18 @@ class StableBrowser {
647
562
  let selector = selectors.locators[j];
648
563
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
649
564
  }
650
- let element = await this._locate_internal(selectors, info, _params, timeout);
565
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
651
566
  if (!element.rerun) {
652
- return element;
567
+ const randomToken = Math.random().toString(36).substring(7);
568
+ element.evaluate((el, randomToken) => {
569
+ el.setAttribute("data-blinq-id-" + randomToken, "");
570
+ }, randomToken);
571
+ if (element._frame) {
572
+ return element;
573
+ }
574
+ const scope = element.page();
575
+ const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
576
+ return newSelector;
653
577
  }
654
578
  }
655
579
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -691,9 +615,11 @@ class StableBrowser {
691
615
  }
692
616
  return framescope;
693
617
  };
618
+ let fLocator = null;
694
619
  while (true) {
695
620
  let frameFound = false;
696
621
  if (selectors.nestFrmLoc) {
622
+ fLocator = selectors.nestFrmLoc;
697
623
  scope = await findFrame(selectors.nestFrmLoc, scope);
698
624
  frameFound = true;
699
625
  break;
@@ -702,6 +628,7 @@ class StableBrowser {
702
628
  for (let i = 0; i < selectors.frameLocators.length; i++) {
703
629
  let frameLocator = selectors.frameLocators[i];
704
630
  if (frameLocator.css) {
631
+ fLocator = frameLocator.css;
705
632
  scope = scope.frameLocator(frameLocator.css);
706
633
  frameFound = true;
707
634
  break;
@@ -709,18 +636,25 @@ class StableBrowser {
709
636
  }
710
637
  }
711
638
  if (!frameFound && selectors.iframe_src) {
639
+ fLocator = selectors.iframe_src;
712
640
  scope = this.page.frame({ url: selectors.iframe_src });
713
641
  }
714
642
  if (!scope) {
715
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
643
+ if (info && info.locatorLog) {
644
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
645
+ }
646
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
716
647
  if (Date.now() - startTime > timeout) {
717
648
  info.failCause.iframeNotFound = true;
718
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
649
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
719
650
  throw new Error("unable to locate iframe " + selectors.iframe_src);
720
651
  }
721
652
  await new Promise((resolve) => setTimeout(resolve, 1000));
722
653
  }
723
654
  else {
655
+ if (info && info.locatorLog) {
656
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
657
+ }
724
658
  break;
725
659
  }
726
660
  }
@@ -737,11 +671,12 @@ class StableBrowser {
737
671
  return bodyContent;
738
672
  });
739
673
  }
740
- async _locate_internal(selectors, info, _params, timeout = 30000) {
674
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
741
675
  if (!info) {
742
676
  info = {};
743
677
  info.failCause = {};
744
678
  info.log = "";
679
+ info.locatorLog = new LocatorLog(selectors);
745
680
  }
746
681
  let highPriorityTimeout = 5000;
747
682
  let visibleOnlyTimeout = 6000;
@@ -785,17 +720,17 @@ class StableBrowser {
785
720
  }
786
721
  // info.log += "scanning locators in priority 1" + "\n";
787
722
  let onlyPriority3 = selectorsLocators[0].priority === 3;
788
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
723
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
789
724
  if (result.foundElements.length === 0) {
790
725
  // info.log += "scanning locators in priority 2" + "\n";
791
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
726
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
792
727
  }
793
728
  if (result.foundElements.length === 0 && onlyPriority3) {
794
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
729
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
795
730
  }
796
731
  else {
797
732
  if (result.foundElements.length === 0 && !highPriorityOnly) {
798
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
733
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
799
734
  }
800
735
  }
801
736
  let foundElements = result.foundElements;
@@ -840,26 +775,34 @@ class StableBrowser {
840
775
  break;
841
776
  }
842
777
  if (Date.now() - startTime > highPriorityTimeout) {
843
- info.log += "high priority timeout, will try all elements" + "\n";
778
+ //info.log += "high priority timeout, will try all elements" + "\n";
844
779
  highPriorityOnly = false;
845
780
  if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
846
781
  lazy_scroll = true;
847
- await this.scrollPageToLoadLazyElements();
782
+ await scrollPageToLoadLazyElements(this.page);
848
783
  }
849
784
  }
850
785
  if (Date.now() - startTime > visibleOnlyTimeout) {
851
- info.log += "visible only timeout, will try all elements" + "\n";
786
+ //info.log += "visible only timeout, will try all elements" + "\n";
852
787
  visibleOnly = false;
853
788
  }
854
789
  await new Promise((resolve) => setTimeout(resolve, 1000));
855
790
  }
856
791
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
857
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
792
+ // if (info.locatorLog) {
793
+ // const lines = info.locatorLog.toString().split("\n");
794
+ // for (let line of lines) {
795
+ // this.logger.debug(line);
796
+ // }
797
+ // }
798
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
858
799
  info.failCause.locatorNotFound = true;
859
- info.failCause.lastError = "failed to locate unique element";
800
+ if (!info?.failCause?.lastError) {
801
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
802
+ }
860
803
  throw new Error("failed to locate first element no elements found, " + info.log);
861
804
  }
862
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
805
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
863
806
  let foundElements = [];
864
807
  const result = {
865
808
  foundElements: foundElements,
@@ -867,14 +810,15 @@ class StableBrowser {
867
810
  for (let i = 0; i < locatorsGroup.length; i++) {
868
811
  let foundLocators = [];
869
812
  try {
870
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
813
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
871
814
  }
872
815
  catch (e) {
873
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
874
- this.logger.debug(e);
816
+ // this call can fail it the browser is navigating
817
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
818
+ // this.logger.debug(e);
875
819
  foundLocators = [];
876
820
  try {
877
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
821
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
878
822
  }
879
823
  catch (e) {
880
824
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -890,6 +834,9 @@ class StableBrowser {
890
834
  }
891
835
  if (foundLocators.length > 1) {
892
836
  info.failCause.foundMultiple = true;
837
+ if (info.locatorLog) {
838
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
839
+ }
893
840
  }
894
841
  }
895
842
  return result;
@@ -999,15 +946,16 @@ class StableBrowser {
999
946
  options,
1000
947
  world,
1001
948
  text: "Click element",
949
+ _text: "Click on " + selectors.element_name,
1002
950
  type: Types.CLICK,
1003
951
  operation: "click",
1004
952
  log: "***** click on " + selectors.element_name + " *****\n",
1005
953
  };
1006
954
  try {
1007
955
  await _preCommand(state, this);
1008
- if (state.options && state.options.context) {
1009
- state.selectors.locators[0].text = state.options.context;
1010
- }
956
+ // if (state.options && state.options.context) {
957
+ // state.selectors.locators[0].text = state.options.context;
958
+ // }
1011
959
  try {
1012
960
  await state.element.click();
1013
961
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1028,6 +976,38 @@ class StableBrowser {
1028
976
  _commandFinally(state, this);
1029
977
  }
1030
978
  }
979
+ async waitForElement(selectors, _params, options = {}, world = null) {
980
+ const timeout = this._getFindElementTimeout(options);
981
+ const state = {
982
+ selectors,
983
+ _params,
984
+ options,
985
+ world,
986
+ text: "Wait for element",
987
+ _text: "Wait for " + selectors.element_name,
988
+ type: Types.WAIT_ELEMENT,
989
+ operation: "waitForElement",
990
+ log: "***** wait for " + selectors.element_name + " *****\n",
991
+ };
992
+ let found = false;
993
+ try {
994
+ await _preCommand(state, this);
995
+ // if (state.options && state.options.context) {
996
+ // state.selectors.locators[0].text = state.options.context;
997
+ // }
998
+ await state.element.waitFor({ timeout: timeout });
999
+ found = true;
1000
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1001
+ }
1002
+ catch (e) {
1003
+ console.error("Error on waitForElement", e);
1004
+ // await _commandError(state, e, this);
1005
+ }
1006
+ finally {
1007
+ _commandFinally(state, this);
1008
+ }
1009
+ return found;
1010
+ }
1031
1011
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
1032
1012
  const state = {
1033
1013
  selectors,
@@ -1036,6 +1016,7 @@ class StableBrowser {
1036
1016
  world,
1037
1017
  type: checked ? Types.CHECK : Types.UNCHECK,
1038
1018
  text: checked ? `Check element` : `Uncheck element`,
1019
+ _text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
1039
1020
  operation: "setCheck",
1040
1021
  log: "***** check " + selectors.element_name + " *****\n",
1041
1022
  };
@@ -1045,9 +1026,15 @@ class StableBrowser {
1045
1026
  // let element = await this._locate(selectors, info, _params);
1046
1027
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1047
1028
  try {
1048
- // await this._highlightElements(element);
1029
+ // if (world && world.screenshot && !world.screenshotPath) {
1030
+ // console.log(`Highlighting while running from recorder`);
1031
+ await this._highlightElements(element);
1049
1032
  await state.element.setChecked(checked);
1050
1033
  await new Promise((resolve) => setTimeout(resolve, 1000));
1034
+ // await this._unHighlightElements(element);
1035
+ // }
1036
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1037
+ // await this._unHighlightElements(element);
1051
1038
  }
1052
1039
  catch (e) {
1053
1040
  if (e.message && e.message.includes("did not change its state")) {
@@ -1079,6 +1066,7 @@ class StableBrowser {
1079
1066
  world,
1080
1067
  type: Types.HOVER,
1081
1068
  text: `Hover element`,
1069
+ _text: `Hover on ${selectors.element_name}`,
1082
1070
  operation: "hover",
1083
1071
  log: "***** hover " + selectors.element_name + " *****\n",
1084
1072
  };
@@ -1086,6 +1074,7 @@ class StableBrowser {
1086
1074
  await _preCommand(state, this);
1087
1075
  try {
1088
1076
  await state.element.hover();
1077
+ // await _screenshot(state, this);
1089
1078
  await new Promise((resolve) => setTimeout(resolve, 1000));
1090
1079
  }
1091
1080
  catch (e) {
@@ -1093,6 +1082,7 @@ class StableBrowser {
1093
1082
  state.info.log += "hover failed, will try again" + "\n";
1094
1083
  state.element = await this._locate(selectors, state.info, _params);
1095
1084
  await state.element.hover({ timeout: 10000 });
1085
+ // await _screenshot(state, this);
1096
1086
  await new Promise((resolve) => setTimeout(resolve, 1000));
1097
1087
  }
1098
1088
  await _screenshot(state, this);
@@ -1118,6 +1108,7 @@ class StableBrowser {
1118
1108
  value: values.toString(),
1119
1109
  type: Types.SELECT,
1120
1110
  text: `Select option: ${values}`,
1111
+ _text: `Select option: ${values} on ${selectors.element_name}`,
1121
1112
  operation: "selectOption",
1122
1113
  log: "***** select option " + selectors.element_name + " *****\n",
1123
1114
  };
@@ -1152,6 +1143,7 @@ class StableBrowser {
1152
1143
  highlight: false,
1153
1144
  type: Types.TYPE_PRESS,
1154
1145
  text: `Type value: ${_value}`,
1146
+ _text: `Type value: ${_value}`,
1155
1147
  operation: "type",
1156
1148
  log: "",
1157
1149
  };
@@ -1231,6 +1223,7 @@ class StableBrowser {
1231
1223
  world,
1232
1224
  type: Types.SET_DATE_TIME,
1233
1225
  text: `Set date time value: ${value}`,
1226
+ _text: `Set date time value: ${value} on ${selectors.element_name}`,
1234
1227
  operation: "setDateTime",
1235
1228
  log: "***** set date time value " + selectors.element_name + " *****\n",
1236
1229
  throwError: false,
@@ -1302,6 +1295,7 @@ class StableBrowser {
1302
1295
  world,
1303
1296
  type: Types.FILL,
1304
1297
  text: `Click type input with value: ${_value}`,
1298
+ _text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
1305
1299
  operation: "clickType",
1306
1300
  log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1307
1301
  };
@@ -1367,7 +1361,12 @@ class StableBrowser {
1367
1361
  await this.waitForPageLoad();
1368
1362
  }
1369
1363
  else if (enter === false) {
1370
- await state.element.dispatchEvent("change");
1364
+ try {
1365
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1366
+ }
1367
+ catch (e) {
1368
+ // ignore
1369
+ }
1371
1370
  //await this.page.keyboard.press("Tab");
1372
1371
  }
1373
1372
  else {
@@ -1419,15 +1418,17 @@ class StableBrowser {
1419
1418
  return await this._getText(selectors, 0, _params, options, info, world);
1420
1419
  }
1421
1420
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1421
+ const timeout = this._getFindElementTimeout(options);
1422
1422
  _validateSelectors(selectors);
1423
1423
  let screenshotId = null;
1424
1424
  let screenshotPath = null;
1425
1425
  if (!info.log) {
1426
1426
  info.log = "";
1427
+ info.locatorLog = new LocatorLog(selectors);
1427
1428
  }
1428
1429
  info.operation = "getText";
1429
1430
  info.selectors = selectors;
1430
- let element = await this._locate(selectors, info, _params);
1431
+ let element = await this._locate(selectors, info, _params, timeout);
1431
1432
  if (climb > 0) {
1432
1433
  const climbArray = [];
1433
1434
  for (let i = 0; i < climb; i++) {
@@ -1446,6 +1447,18 @@ class StableBrowser {
1446
1447
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1447
1448
  try {
1448
1449
  await this._highlightElements(element);
1450
+ // if (world && world.screenshot && !world.screenshotPath) {
1451
+ // // console.log(`Highlighting for get text while running from recorder`);
1452
+ // this._highlightElements(element)
1453
+ // .then(async () => {
1454
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1455
+ // this._unhighlightElements(element).then(
1456
+ // () => {}
1457
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1458
+ // );
1459
+ // })
1460
+ // .catch(e);
1461
+ // }
1449
1462
  const elementText = await element.innerText();
1450
1463
  return {
1451
1464
  text: elementText,
@@ -1457,7 +1470,7 @@ class StableBrowser {
1457
1470
  }
1458
1471
  catch (e) {
1459
1472
  //await this.closeUnexpectedPopups();
1460
- this.logger.info("no innerText will use textContent");
1473
+ this.logger.info("no innerText, will use textContent");
1461
1474
  const elementText = await element.textContent();
1462
1475
  return { text: elementText, screenshotId, screenshotPath, value: value };
1463
1476
  }
@@ -1482,6 +1495,7 @@ class StableBrowser {
1482
1495
  highlight: false,
1483
1496
  type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1484
1497
  text: `Verify element contains pattern: ${pattern}`,
1498
+ _text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
1485
1499
  operation: "containsPattern",
1486
1500
  log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1487
1501
  };
@@ -1517,6 +1531,8 @@ class StableBrowser {
1517
1531
  }
1518
1532
  }
1519
1533
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1534
+ const timeout = this._getFindElementTimeout(options);
1535
+ const startTime = Date.now();
1520
1536
  const state = {
1521
1537
  selectors,
1522
1538
  _params,
@@ -1543,62 +1559,54 @@ class StableBrowser {
1543
1559
  }
1544
1560
  let foundObj = null;
1545
1561
  try {
1546
- await _preCommand(state, this);
1547
- foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1548
- if (foundObj && foundObj.element) {
1549
- await this.scrollIfNeeded(foundObj.element, state.info);
1550
- }
1551
- await _screenshot(state, this);
1552
- const dateAlternatives = findDateAlternatives(text);
1553
- const numberAlternatives = findNumberAlternatives(text);
1554
- if (dateAlternatives.date) {
1555
- for (let i = 0; i < dateAlternatives.dates.length; i++) {
1556
- if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1557
- foundObj?.value?.includes(dateAlternatives.dates[i])) {
1558
- return state.info;
1562
+ while (Date.now() - startTime < timeout) {
1563
+ try {
1564
+ await _preCommand(state, this);
1565
+ foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
1566
+ if (foundObj && foundObj.element) {
1567
+ await this.scrollIfNeeded(foundObj.element, state.info);
1559
1568
  }
1560
- }
1561
- throw new Error("element doesn't contain text " + text);
1562
- }
1563
- else if (numberAlternatives.number) {
1564
- for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1565
- if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1566
- foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1569
+ await _screenshot(state, this);
1570
+ const dateAlternatives = findDateAlternatives(text);
1571
+ const numberAlternatives = findNumberAlternatives(text);
1572
+ if (dateAlternatives.date) {
1573
+ for (let i = 0; i < dateAlternatives.dates.length; i++) {
1574
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1575
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1576
+ return state.info;
1577
+ }
1578
+ }
1579
+ }
1580
+ else if (numberAlternatives.number) {
1581
+ for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1582
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1583
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1584
+ return state.info;
1585
+ }
1586
+ }
1587
+ }
1588
+ else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
1567
1589
  return state.info;
1568
1590
  }
1569
1591
  }
1570
- throw new Error("element doesn't contain text " + text);
1571
- }
1572
- else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1573
- state.info.foundText = foundObj?.text;
1574
- state.info.value = foundObj?.value;
1575
- throw new Error("element doesn't contain text " + text);
1592
+ catch (e) {
1593
+ // Log error but continue retrying until timeout is reached
1594
+ this.logger.warn("Retrying containsText due to: " + e.message);
1595
+ }
1596
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
1576
1597
  }
1577
- return state.info;
1598
+ state.info.foundText = foundObj?.text;
1599
+ state.info.value = foundObj?.value;
1600
+ throw new Error("element doesn't contain text " + text);
1578
1601
  }
1579
1602
  catch (e) {
1580
1603
  await _commandError(state, e, this);
1604
+ throw e;
1581
1605
  }
1582
1606
  finally {
1583
1607
  _commandFinally(state, this);
1584
1608
  }
1585
1609
  }
1586
- _getDataFile(world = null) {
1587
- let dataFile = null;
1588
- if (world && world.reportFolder) {
1589
- dataFile = path.join(world.reportFolder, "data.json");
1590
- }
1591
- else if (this.reportFolder) {
1592
- dataFile = path.join(this.reportFolder, "data.json");
1593
- }
1594
- else if (this.context && this.context.reportFolder) {
1595
- dataFile = path.join(this.context.reportFolder, "data.json");
1596
- }
1597
- else {
1598
- dataFile = "data.json";
1599
- }
1600
- return dataFile;
1601
- }
1602
1610
  async waitForUserInput(message, world = null) {
1603
1611
  if (!message) {
1604
1612
  message = "# Wait for user input. Press any key to continue";
@@ -1627,7 +1635,7 @@ class StableBrowser {
1627
1635
  return;
1628
1636
  }
1629
1637
  // if data file exists, load it
1630
- const dataFile = this._getDataFile(world);
1638
+ const dataFile = _getDataFile(world, this.context, this);
1631
1639
  let data = this.getTestData(world);
1632
1640
  // merge the testData with the existing data
1633
1641
  Object.assign(data, testData);
@@ -1730,7 +1738,7 @@ class StableBrowser {
1730
1738
  }
1731
1739
  }
1732
1740
  getTestData(world = null) {
1733
- const dataFile = this._getDataFile(world);
1741
+ const dataFile = _getDataFile(world, this.context, this);
1734
1742
  let data = {};
1735
1743
  if (fs.existsSync(dataFile)) {
1736
1744
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1817,6 +1825,15 @@ class StableBrowser {
1817
1825
  document.documentElement.clientWidth,
1818
1826
  ])));
1819
1827
  let screenshotBuffer = null;
1828
+ // if (focusedElement) {
1829
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1830
+ // await this._unhighlightElements(focusedElement);
1831
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1832
+ // console.log(`Unhighlighted previous element`);
1833
+ // }
1834
+ // if (focusedElement) {
1835
+ // await this._highlightElements(focusedElement);
1836
+ // }
1820
1837
  if (this.context.browserName === "chromium") {
1821
1838
  const client = await playContext.newCDPSession(this.page);
1822
1839
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1838,6 +1855,10 @@ class StableBrowser {
1838
1855
  else {
1839
1856
  screenshotBuffer = await this.page.screenshot();
1840
1857
  }
1858
+ // if (focusedElement) {
1859
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1860
+ // await this._unhighlightElements(focusedElement);
1861
+ // }
1841
1862
  let image = await Jimp.read(screenshotBuffer);
1842
1863
  // Get the image dimensions
1843
1864
  const { width, height } = image.bitmap;
@@ -1850,6 +1871,7 @@ class StableBrowser {
1850
1871
  else {
1851
1872
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1852
1873
  }
1874
+ return screenshotBuffer;
1853
1875
  }
1854
1876
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1855
1877
  const state = {
@@ -1885,8 +1907,10 @@ class StableBrowser {
1885
1907
  world,
1886
1908
  type: Types.EXTRACT,
1887
1909
  text: `Extract attribute from element`,
1910
+ _text: `Extract attribute ${attribute} from ${selectors.element_name}`,
1888
1911
  operation: "extractAttribute",
1889
1912
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1913
+ allowDisabled: true,
1890
1914
  };
1891
1915
  await new Promise((resolve) => setTimeout(resolve, 2000));
1892
1916
  try {
@@ -1908,6 +1932,7 @@ class StableBrowser {
1908
1932
  state.info.value = state.value;
1909
1933
  this.setTestData({ [variable]: state.value }, world);
1910
1934
  this.logger.info("set test data: " + variable + "=" + state.value);
1935
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1911
1936
  return state.info;
1912
1937
  }
1913
1938
  catch (e) {
@@ -1926,14 +1951,21 @@ class StableBrowser {
1926
1951
  options,
1927
1952
  world,
1928
1953
  type: Types.VERIFY_ATTRIBUTE,
1954
+ highlight: true,
1955
+ screenshot: true,
1929
1956
  text: `Verify element attribute`,
1957
+ _text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
1930
1958
  operation: "verifyAttribute",
1931
1959
  log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1960
+ allowDisabled: true,
1932
1961
  };
1933
1962
  await new Promise((resolve) => setTimeout(resolve, 2000));
1934
1963
  let val;
1964
+ let expectedValue;
1935
1965
  try {
1936
1966
  await _preCommand(state, this);
1967
+ expectedValue = state.value;
1968
+ state.info.expectedValue = expectedValue;
1937
1969
  switch (attribute) {
1938
1970
  case "innerText":
1939
1971
  val = String(await state.element.innerText());
@@ -1947,23 +1979,30 @@ class StableBrowser {
1947
1979
  case "disabled":
1948
1980
  val = String(await state.element.isDisabled());
1949
1981
  break;
1982
+ case "readOnly":
1983
+ const isEditable = await state.element.isEditable();
1984
+ val = String(!isEditable);
1985
+ break;
1950
1986
  default:
1951
1987
  val = String(await state.element.getAttribute(attribute));
1952
1988
  break;
1953
1989
  }
1990
+ state.info.value = val;
1954
1991
  let regex;
1955
- if (value.startsWith("/") && value.endsWith("/")) {
1956
- const patternBody = value.slice(1, -1);
1992
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
1993
+ const patternBody = expectedValue.slice(1, -1);
1957
1994
  regex = new RegExp(patternBody, "g");
1958
1995
  }
1959
1996
  else {
1960
- const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1997
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1961
1998
  regex = new RegExp(escapedPattern, "g");
1962
1999
  }
1963
2000
  if (!val.match(regex)) {
1964
- throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
2001
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
2002
+ state.info.failCause.assertionFailed = true;
2003
+ state.info.failCause.lastError = errorMessage;
2004
+ throw new Error(errorMessage);
1965
2005
  }
1966
- state.info.value = val;
1967
2006
  return state.info;
1968
2007
  }
1969
2008
  catch (e) {
@@ -1991,7 +2030,7 @@ class StableBrowser {
1991
2030
  if (options && options.timeout) {
1992
2031
  timeout = options.timeout;
1993
2032
  }
1994
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
2033
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1995
2034
  const request = {
1996
2035
  method: "POST",
1997
2036
  url: serviceUrl,
@@ -2062,27 +2101,32 @@ class StableBrowser {
2062
2101
  async _highlightElements(scope, css) {
2063
2102
  try {
2064
2103
  if (!scope) {
2104
+ // console.log(`Scope is not defined`);
2065
2105
  return;
2066
2106
  }
2067
2107
  if (!css) {
2068
2108
  scope
2069
2109
  .evaluate((node) => {
2070
2110
  if (node && node.style) {
2071
- let originalBorder = node.style.border;
2072
- node.style.border = "2px solid red";
2111
+ let originalOutline = node.style.outline;
2112
+ // console.log(`Original outline was: ${originalOutline}`);
2113
+ // node.__previousOutline = originalOutline;
2114
+ node.style.outline = "2px solid red";
2115
+ // console.log(`New outline is: ${node.style.outline}`);
2073
2116
  if (window) {
2074
2117
  window.addEventListener("beforeunload", function (e) {
2075
- node.style.border = originalBorder;
2118
+ node.style.outline = originalOutline;
2076
2119
  });
2077
2120
  }
2078
2121
  setTimeout(function () {
2079
- node.style.border = originalBorder;
2122
+ node.style.outline = originalOutline;
2080
2123
  }, 2000);
2081
2124
  }
2082
2125
  })
2083
2126
  .then(() => { })
2084
2127
  .catch((e) => {
2085
2128
  // ignore
2129
+ // console.error(`Could not highlight node : ${e}`);
2086
2130
  });
2087
2131
  }
2088
2132
  else {
@@ -2098,17 +2142,18 @@ class StableBrowser {
2098
2142
  if (!element.style) {
2099
2143
  return;
2100
2144
  }
2101
- var originalBorder = element.style.border;
2145
+ let originalOutline = element.style.outline;
2146
+ element.__previousOutline = originalOutline;
2102
2147
  // Set the new border to be red and 2px solid
2103
- element.style.border = "2px solid red";
2148
+ element.style.outline = "2px solid red";
2104
2149
  if (window) {
2105
2150
  window.addEventListener("beforeunload", function (e) {
2106
- element.style.border = originalBorder;
2151
+ element.style.outline = originalOutline;
2107
2152
  });
2108
2153
  }
2109
2154
  // Set a timeout to revert to the original border after 2 seconds
2110
2155
  setTimeout(function () {
2111
- element.style.border = originalBorder;
2156
+ element.style.outline = originalOutline;
2112
2157
  }, 2000);
2113
2158
  }
2114
2159
  return;
@@ -2116,6 +2161,7 @@ class StableBrowser {
2116
2161
  .then(() => { })
2117
2162
  .catch((e) => {
2118
2163
  // ignore
2164
+ // console.error(`Could not highlight css: ${e}`);
2119
2165
  });
2120
2166
  }
2121
2167
  }
@@ -2123,6 +2169,54 @@ class StableBrowser {
2123
2169
  console.debug(error);
2124
2170
  }
2125
2171
  }
2172
+ // async _unhighlightElements(scope, css) {
2173
+ // try {
2174
+ // if (!scope) {
2175
+ // return;
2176
+ // }
2177
+ // if (!css) {
2178
+ // scope
2179
+ // .evaluate((node) => {
2180
+ // if (node && node.style) {
2181
+ // if (!node.__previousOutline) {
2182
+ // node.style.outline = "";
2183
+ // } else {
2184
+ // node.style.outline = node.__previousOutline;
2185
+ // }
2186
+ // }
2187
+ // })
2188
+ // .then(() => {})
2189
+ // .catch((e) => {
2190
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2191
+ // });
2192
+ // } else {
2193
+ // scope
2194
+ // .evaluate(([css]) => {
2195
+ // if (!css) {
2196
+ // return;
2197
+ // }
2198
+ // let elements = Array.from(document.querySelectorAll(css));
2199
+ // for (i = 0; i < elements.length; i++) {
2200
+ // let element = elements[i];
2201
+ // if (!element.style) {
2202
+ // return;
2203
+ // }
2204
+ // if (!element.__previousOutline) {
2205
+ // element.style.outline = "";
2206
+ // } else {
2207
+ // element.style.outline = element.__previousOutline;
2208
+ // }
2209
+ // }
2210
+ // })
2211
+ // .then(() => {})
2212
+ // .catch((e) => {
2213
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2214
+ // });
2215
+ // }
2216
+ // } catch (error) {
2217
+ // // console.debug(error);
2218
+ // }
2219
+ // }
2126
2220
  async verifyPagePath(pathPart, options = {}, world = null) {
2127
2221
  const startTime = Date.now();
2128
2222
  let error = null;
@@ -2167,6 +2261,7 @@ class StableBrowser {
2167
2261
  _reportToWorld(world, {
2168
2262
  type: Types.VERIFY_PAGE_PATH,
2169
2263
  text: "Verify page path",
2264
+ _text: "Verify the page path contains " + pathPart,
2170
2265
  screenshotId,
2171
2266
  result: error
2172
2267
  ? {
@@ -2184,6 +2279,35 @@ class StableBrowser {
2184
2279
  });
2185
2280
  }
2186
2281
  }
2282
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2283
+ const frames = this.page.frames();
2284
+ let results = [];
2285
+ let ignoreCase = false;
2286
+ for (let i = 0; i < frames.length; i++) {
2287
+ if (dateAlternatives.date) {
2288
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2289
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2290
+ result.frame = frames[i];
2291
+ results.push(result);
2292
+ }
2293
+ }
2294
+ else if (numberAlternatives.number) {
2295
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2296
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2297
+ result.frame = frames[i];
2298
+ results.push(result);
2299
+ }
2300
+ }
2301
+ else {
2302
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2303
+ result.frame = frames[i];
2304
+ results.push(result);
2305
+ }
2306
+ }
2307
+ state.info.results = results;
2308
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2309
+ return resultWithElementsFound;
2310
+ }
2187
2311
  async verifyTextExistInPage(text, options = {}, world = null) {
2188
2312
  text = unEscapeString(text);
2189
2313
  const state = {
@@ -2193,12 +2317,16 @@ class StableBrowser {
2193
2317
  locate: false,
2194
2318
  scroll: false,
2195
2319
  highlight: false,
2196
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2320
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2197
2321
  text: `Verify text exists in page`,
2322
+ _text: `Verify the text '${text}' exists in page`,
2198
2323
  operation: "verifyTextExistInPage",
2199
2324
  log: "***** verify text " + text + " exists in page *****\n",
2200
2325
  };
2201
- const timeout = this._getLoadTimeout(options);
2326
+ if (testForRegex(text)) {
2327
+ text = text.replace(/\\"/g, '"');
2328
+ }
2329
+ const timeout = this._getFindElementTimeout(options);
2202
2330
  await new Promise((resolve) => setTimeout(resolve, 2000));
2203
2331
  const newValue = await this._replaceWithLocalData(text, world);
2204
2332
  if (newValue !== text) {
@@ -2211,31 +2339,15 @@ class StableBrowser {
2211
2339
  await _preCommand(state, this);
2212
2340
  state.info.text = text;
2213
2341
  while (true) {
2214
- const frames = this.page.frames();
2215
- let results = [];
2216
- for (let i = 0; i < frames.length; i++) {
2217
- if (dateAlternatives.date) {
2218
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2219
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2220
- result.frame = frames[i];
2221
- results.push(result);
2222
- }
2223
- }
2224
- else if (numberAlternatives.number) {
2225
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2226
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2227
- result.frame = frames[i];
2228
- results.push(result);
2229
- }
2230
- }
2231
- else {
2232
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2233
- result.frame = frames[i];
2234
- results.push(result);
2235
- }
2342
+ let resultWithElementsFound = {
2343
+ length: 0,
2344
+ };
2345
+ try {
2346
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2347
+ }
2348
+ catch (error) {
2349
+ // ignore
2236
2350
  }
2237
- state.info.results = results;
2238
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2239
2351
  if (resultWithElementsFound.length === 0) {
2240
2352
  if (Date.now() - state.startTime > timeout) {
2241
2353
  throw new Error(`Text ${text} not found in page`);
@@ -2243,18 +2355,40 @@ class StableBrowser {
2243
2355
  await new Promise((resolve) => setTimeout(resolve, 1000));
2244
2356
  continue;
2245
2357
  }
2246
- if (resultWithElementsFound[0].randomToken) {
2247
- const frame = resultWithElementsFound[0].frame;
2248
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2249
- await this._highlightElements(frame, dataAttribute);
2250
- const element = await frame.$(dataAttribute);
2251
- if (element) {
2252
- await this.scrollIfNeeded(element, state.info);
2253
- await element.dispatchEvent("bvt_verify_page_contains_text");
2358
+ try {
2359
+ if (resultWithElementsFound[0].randomToken) {
2360
+ const frame = resultWithElementsFound[0].frame;
2361
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2362
+ await this._highlightElements(frame, dataAttribute);
2363
+ // if (world && world.screenshot && !world.screenshotPath) {
2364
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2365
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2366
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2367
+ // this._unhighlightElements(frame, dataAttribute)
2368
+ // .then(async () => {
2369
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2370
+ // })
2371
+ // .catch(
2372
+ // (e) => {}
2373
+ // console.error(e)
2374
+ // );
2375
+ // });
2376
+ // }
2377
+ const element = await frame.locator(dataAttribute).first();
2378
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2379
+ // await this._unhighlightElements(frame, dataAttribute);
2380
+ if (element) {
2381
+ await this.scrollIfNeeded(element, state.info);
2382
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2383
+ // await _screenshot(state, this, element);
2384
+ }
2254
2385
  }
2386
+ await _screenshot(state, this);
2387
+ return state.info;
2388
+ }
2389
+ catch (error) {
2390
+ console.error(error);
2255
2391
  }
2256
- await _screenshot(state, this);
2257
- return state.info;
2258
2392
  }
2259
2393
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2260
2394
  }
@@ -2276,10 +2410,14 @@ class StableBrowser {
2276
2410
  highlight: false,
2277
2411
  type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2278
2412
  text: `Verify text does not exist in page`,
2413
+ _text: `Verify the text '${text}' does not exist in page`,
2279
2414
  operation: "verifyTextNotExistInPage",
2280
2415
  log: "***** verify text " + text + " does not exist in page *****\n",
2281
2416
  };
2282
- const timeout = this._getLoadTimeout(options);
2417
+ if (testForRegex(text)) {
2418
+ text = text.replace(/\\"/g, '"');
2419
+ }
2420
+ const timeout = this._getFindElementTimeout(options);
2283
2421
  await new Promise((resolve) => setTimeout(resolve, 2000));
2284
2422
  const newValue = await this._replaceWithLocalData(text, world);
2285
2423
  if (newValue !== text) {
@@ -2291,32 +2429,16 @@ class StableBrowser {
2291
2429
  try {
2292
2430
  await _preCommand(state, this);
2293
2431
  state.info.text = text;
2432
+ let resultWithElementsFound = {
2433
+ length: null, // initial cannot be 0
2434
+ };
2294
2435
  while (true) {
2295
- const frames = this.page.frames();
2296
- let results = [];
2297
- for (let i = 0; i < frames.length; i++) {
2298
- if (dateAlternatives.date) {
2299
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2300
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2301
- result.frame = frames[i];
2302
- results.push(result);
2303
- }
2304
- }
2305
- else if (numberAlternatives.number) {
2306
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2307
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2308
- result.frame = frames[i];
2309
- results.push(result);
2310
- }
2311
- }
2312
- else {
2313
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2314
- result.frame = frames[i];
2315
- results.push(result);
2316
- }
2436
+ try {
2437
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2438
+ }
2439
+ catch (error) {
2440
+ // ignore
2317
2441
  }
2318
- state.info.results = results;
2319
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2320
2442
  if (resultWithElementsFound.length === 0) {
2321
2443
  await _screenshot(state, this);
2322
2444
  return state.info;
@@ -2334,15 +2456,116 @@ class StableBrowser {
2334
2456
  _commandFinally(state, this);
2335
2457
  }
2336
2458
  }
2337
- _getServerUrl() {
2338
- let serviceUrl = "https://api.blinq.io";
2339
- if (process.env.NODE_ENV_BLINQ === "dev") {
2340
- serviceUrl = "https://dev.api.blinq.io";
2459
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2460
+ textAnchor = unEscapeString(textAnchor);
2461
+ textToVerify = unEscapeString(textToVerify);
2462
+ const state = {
2463
+ text_search: textToVerify,
2464
+ options,
2465
+ world,
2466
+ locate: false,
2467
+ scroll: false,
2468
+ highlight: false,
2469
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2470
+ text: `Verify text with relation to another text`,
2471
+ _text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
2472
+ operation: "verify_text_with_relation",
2473
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2474
+ };
2475
+ const timeout = this._getFindElementTimeout(options);
2476
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2477
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2478
+ if (newValue !== textAnchor) {
2479
+ this.logger.info(textAnchor + "=" + newValue);
2480
+ textAnchor = newValue;
2481
+ }
2482
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2483
+ if (newValue !== textToVerify) {
2484
+ this.logger.info(textToVerify + "=" + newValue);
2485
+ textToVerify = newValue;
2486
+ }
2487
+ let dateAlternatives = findDateAlternatives(textToVerify);
2488
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2489
+ let foundAncore = false;
2490
+ try {
2491
+ await _preCommand(state, this);
2492
+ state.info.text = textToVerify;
2493
+ let resultWithElementsFound = {
2494
+ length: 0,
2495
+ };
2496
+ while (true) {
2497
+ try {
2498
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2499
+ }
2500
+ catch (error) {
2501
+ // ignore
2502
+ }
2503
+ if (resultWithElementsFound.length === 0) {
2504
+ if (Date.now() - state.startTime > timeout) {
2505
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2506
+ }
2507
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2508
+ continue;
2509
+ }
2510
+ try {
2511
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2512
+ foundAncore = true;
2513
+ const result = resultWithElementsFound[i];
2514
+ const token = result.randomToken;
2515
+ const frame = result.frame;
2516
+ let css = `[data-blinq-id-${token}]`;
2517
+ const climbArray1 = [];
2518
+ for (let i = 0; i < climb; i++) {
2519
+ climbArray1.push("..");
2520
+ }
2521
+ let climbXpath = "xpath=" + climbArray1.join("/");
2522
+ css = css + " >> " + climbXpath;
2523
+ const count = await frame.locator(css).count();
2524
+ for (let j = 0; j < count; j++) {
2525
+ const continer = await frame.locator(css).nth(j);
2526
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2527
+ if (result.elementCount > 0) {
2528
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2529
+ await this._highlightElements(frame, dataAttribute);
2530
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2531
+ // if (world && world.screenshot && !world.screenshotPath) {
2532
+ // console.log(`Highlighting for vtrt while running from recorder`);
2533
+ // this._highlightElements(frame, dataAttribute)
2534
+ // .then(async () => {
2535
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2536
+ // this._unhighlightElements(frame, dataAttribute).then(
2537
+ // () => {}
2538
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2539
+ // );
2540
+ // })
2541
+ // .catch(e);
2542
+ // }
2543
+ //await this._highlightElements(frame, cssAnchor);
2544
+ const element = await frame.locator(dataAttribute).first();
2545
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2546
+ // await this._unhighlightElements(frame, dataAttribute);
2547
+ if (element) {
2548
+ await this.scrollIfNeeded(element, state.info);
2549
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2550
+ }
2551
+ await _screenshot(state, this);
2552
+ return state.info;
2553
+ }
2554
+ }
2555
+ }
2556
+ }
2557
+ catch (error) {
2558
+ console.error(error);
2559
+ }
2560
+ }
2561
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2562
+ }
2563
+ catch (e) {
2564
+ await _commandError(state, e, this);
2341
2565
  }
2342
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2343
- serviceUrl = "https://stage.api.blinq.io";
2566
+ finally {
2567
+ _commandFinally(state, this);
2344
2568
  }
2345
- return serviceUrl;
2346
2569
  }
2347
2570
  async visualVerification(text, options = {}, world = null) {
2348
2571
  const startTime = Date.now();
@@ -2358,14 +2581,17 @@ class StableBrowser {
2358
2581
  throw new Error("TOKEN is not set");
2359
2582
  }
2360
2583
  try {
2361
- let serviceUrl = this._getServerUrl();
2584
+ let serviceUrl = _getServerUrl();
2362
2585
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2363
2586
  info.screenshotPath = screenshotPath;
2364
2587
  const screenshot = await this.takeScreenshot();
2365
- const request = {
2366
- method: "POST",
2588
+ let request = {
2589
+ method: "post",
2590
+ maxBodyLength: Infinity,
2367
2591
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2368
2592
  headers: {
2593
+ "x-bvt-project-id": path.basename(this.project_path),
2594
+ "x-source": "aaa",
2369
2595
  "Content-Type": "application/json",
2370
2596
  Authorization: `Bearer ${process.env.TOKEN}`,
2371
2597
  },
@@ -2374,7 +2600,7 @@ class StableBrowser {
2374
2600
  screenshot: screenshot,
2375
2601
  }),
2376
2602
  };
2377
- let result = await this.context.api.request(request);
2603
+ const result = await axios.request(request);
2378
2604
  if (result.data.status !== true) {
2379
2605
  throw new Error("Visual validation failed");
2380
2606
  }
@@ -2402,6 +2628,7 @@ class StableBrowser {
2402
2628
  _reportToWorld(world, {
2403
2629
  type: Types.VERIFY_VISUAL,
2404
2630
  text: "Visual verification",
2631
+ _text: "Visual verification of " + text,
2405
2632
  screenshotId,
2406
2633
  result: error
2407
2634
  ? {
@@ -2447,6 +2674,7 @@ class StableBrowser {
2447
2674
  let screenshotPath = null;
2448
2675
  const info = {};
2449
2676
  info.log = "";
2677
+ info.locatorLog = new LocatorLog(selectors);
2450
2678
  info.operation = "getTableData";
2451
2679
  info.selectors = selectors;
2452
2680
  try {
@@ -2522,7 +2750,7 @@ class StableBrowser {
2522
2750
  info.operation = "analyzeTable";
2523
2751
  info.selectors = selectors;
2524
2752
  info.query = query;
2525
- query = this._fixUsingParams(query, _params);
2753
+ query = _fixUsingParams(query, _params);
2526
2754
  info.query_fixed = query;
2527
2755
  info.operator = operator;
2528
2756
  info.value = value;
@@ -2667,6 +2895,32 @@ class StableBrowser {
2667
2895
  }
2668
2896
  return timeout;
2669
2897
  }
2898
+ _getFindElementTimeout(options) {
2899
+ if (options && options.timeout) {
2900
+ return options.timeout;
2901
+ }
2902
+ if (this.configuration.find_element_timeout) {
2903
+ return this.configuration.find_element_timeout;
2904
+ }
2905
+ return 30000;
2906
+ }
2907
+ async saveStoreState(path = null, world = null) {
2908
+ const storageState = await this.page.context().storageState();
2909
+ //const testDataFile = _getDataFile(world, this.context, this);
2910
+ if (path) {
2911
+ // save { storageState: storageState } into the path
2912
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
2913
+ }
2914
+ else {
2915
+ await this.setTestData({ storageState: storageState }, world);
2916
+ }
2917
+ }
2918
+ async restoreSaveState(path = null, world = null) {
2919
+ await refreshBrowser(this, path, world);
2920
+ this.registerEventListeners(this.context);
2921
+ registerNetworkEvents(this.world, this, this.context, this.page);
2922
+ registerDownloadEvent(this.page, this.world, this.context);
2923
+ }
2670
2924
  async waitForPageLoad(options = {}, world = null) {
2671
2925
  let timeout = this._getLoadTimeout(options);
2672
2926
  const promiseArray = [];
@@ -2734,6 +2988,7 @@ class StableBrowser {
2734
2988
  highlight: false,
2735
2989
  type: Types.CLOSE_PAGE,
2736
2990
  text: `Close page`,
2991
+ _text: `Close the page`,
2737
2992
  operation: "closePage",
2738
2993
  log: "***** close page *****\n",
2739
2994
  throwError: false,
@@ -2750,8 +3005,97 @@ class StableBrowser {
2750
3005
  _commandFinally(state, this);
2751
3006
  }
2752
3007
  }
3008
+ async tableCellOperation(headerText, rowText, options, world = null) {
3009
+ let operation = null;
3010
+ if (!options || !options.operation) {
3011
+ throw new Error("operation is not defined");
3012
+ }
3013
+ operation = options.operation;
3014
+ // validate operation is one of the supported operations
3015
+ if (operation != "click" && operation != "hover+click") {
3016
+ throw new Error("operation is not supported");
3017
+ }
3018
+ const state = {
3019
+ options,
3020
+ world,
3021
+ locate: false,
3022
+ scroll: false,
3023
+ highlight: false,
3024
+ type: Types.TABLE_OPERATION,
3025
+ text: `Table operation`,
3026
+ _text: `Table ${operation} operation`,
3027
+ operation: operation,
3028
+ log: "***** Table operation *****\n",
3029
+ };
3030
+ const timeout = this._getFindElementTimeout(options);
3031
+ try {
3032
+ await _preCommand(state, this);
3033
+ const start = Date.now();
3034
+ let cellArea = null;
3035
+ while (true) {
3036
+ try {
3037
+ cellArea = await _findCellArea(headerText, rowText, this, state);
3038
+ if (cellArea) {
3039
+ break;
3040
+ }
3041
+ }
3042
+ catch (e) {
3043
+ // ignore
3044
+ }
3045
+ if (Date.now() - start > timeout) {
3046
+ throw new Error(`Cell not found in table`);
3047
+ }
3048
+ await new Promise((resolve) => setTimeout(resolve, 1000));
3049
+ }
3050
+ switch (operation) {
3051
+ case "click":
3052
+ if (!options.css) {
3053
+ // will click in the center of the cell
3054
+ let xOffset = 0;
3055
+ let yOffset = 0;
3056
+ if (options.xOffset) {
3057
+ xOffset = options.xOffset;
3058
+ }
3059
+ if (options.yOffset) {
3060
+ yOffset = options.yOffset;
3061
+ }
3062
+ await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
3063
+ }
3064
+ else {
3065
+ const results = await findElementsInArea(options.css, cellArea, this.page, this, options);
3066
+ if (results.length === 0) {
3067
+ throw new Error(`Element not found in cell area`);
3068
+ }
3069
+ state.element = results[0];
3070
+ await results[0].click();
3071
+ }
3072
+ break;
3073
+ case "hover+click":
3074
+ if (!options.css) {
3075
+ throw new Error("css is not defined");
3076
+ }
3077
+ const results = await findElementsInArea(options.css, cellArea, this.page, this, options);
3078
+ if (results.length === 0) {
3079
+ throw new Error(`Element not found in cell area`);
3080
+ }
3081
+ state.element = results[0];
3082
+ await results[0].hover();
3083
+ await new Promise((resolve) => setTimeout(resolve, 1000));
3084
+ await results[0].click();
3085
+ break;
3086
+ default:
3087
+ throw new Error("operation is not supported");
3088
+ }
3089
+ }
3090
+ catch (e) {
3091
+ await _commandError(state, e, this);
3092
+ }
3093
+ finally {
3094
+ _commandFinally(state, this);
3095
+ }
3096
+ }
2753
3097
  saveTestDataAsGlobal(options, world) {
2754
- const dataFile = this._getDataFile(world);
3098
+ const dataFile = _getDataFile(world, this.context, this);
2755
3099
  process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2756
3100
  this.logger.info("Save the scenario test data as global for the following scenarios.");
2757
3101
  }
@@ -2781,6 +3125,7 @@ class StableBrowser {
2781
3125
  _reportToWorld(world, {
2782
3126
  type: Types.SET_VIEWPORT,
2783
3127
  text: "set viewport size to " + width + "x" + hight,
3128
+ _text: "Set the viewport size to " + width + "x" + hight,
2784
3129
  screenshotId,
2785
3130
  result: error
2786
3131
  ? {
@@ -2852,14 +3197,25 @@ class StableBrowser {
2852
3197
  }
2853
3198
  }
2854
3199
  async beforeStep(world, step) {
2855
- this.stepName = step.pickleStep.text;
2856
- this.logger.info("step: " + this.stepName);
2857
3200
  if (this.stepIndex === undefined) {
2858
3201
  this.stepIndex = 0;
2859
3202
  }
2860
3203
  else {
2861
3204
  this.stepIndex++;
2862
3205
  }
3206
+ if (step && step.pickleStep && step.pickleStep.text) {
3207
+ this.stepName = step.pickleStep.text;
3208
+ this.logger.info("step: " + this.stepName);
3209
+ }
3210
+ else if (step && step.text) {
3211
+ this.stepName = step.text;
3212
+ }
3213
+ else {
3214
+ this.stepName = "step " + this.stepIndex;
3215
+ }
3216
+ if (this.context) {
3217
+ this.context.examplesRow = extractStepExampleParameters(step);
3218
+ }
2863
3219
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2864
3220
  if (this.context.browserObject.context) {
2865
3221
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2872,6 +3228,41 @@ class StableBrowser {
2872
3228
  this.saveTestDataAsGlobal({}, world);
2873
3229
  }
2874
3230
  }
3231
+ if (this.initSnapshotTaken === false) {
3232
+ this.initSnapshotTaken = true;
3233
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3234
+ const snapshot = await this.getAriaSnapshot();
3235
+ if (snapshot) {
3236
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
3237
+ }
3238
+ }
3239
+ }
3240
+ }
3241
+ async getAriaSnapshot() {
3242
+ try {
3243
+ // find the page url
3244
+ const url = await this.page.url();
3245
+ // extract the path from the url
3246
+ const path = new URL(url).pathname;
3247
+ // get the page title
3248
+ const title = await this.page.title();
3249
+ // go over other frams
3250
+ const frames = this.page.frames();
3251
+ const snapshots = [];
3252
+ const content = [`- path: ${path}`, `- title: ${title}`];
3253
+ const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
3254
+ for (let i = 0; i < frames.length; i++) {
3255
+ content.push(`- frame: ${i}`);
3256
+ const frame = frames[i];
3257
+ const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
3258
+ content.push(snapshot);
3259
+ }
3260
+ return content.join("\n");
3261
+ }
3262
+ catch (e) {
3263
+ console.error(e);
3264
+ }
3265
+ return null;
2875
3266
  }
2876
3267
  async afterStep(world, step) {
2877
3268
  this.stepName = null;
@@ -2882,6 +3273,16 @@ class StableBrowser {
2882
3273
  });
2883
3274
  }
2884
3275
  }
3276
+ if (this.context) {
3277
+ this.context.examplesRow = null;
3278
+ }
3279
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3280
+ const snapshot = await this.getAriaSnapshot();
3281
+ if (snapshot) {
3282
+ const obj = {};
3283
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
3284
+ }
3285
+ }
2885
3286
  }
2886
3287
  }
2887
3288
  function createTimedPromise(promise, label) {
@@ -2889,156 +3290,5 @@ function createTimedPromise(promise, label) {
2889
3290
  .then((result) => ({ status: "fulfilled", label, result }))
2890
3291
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2891
3292
  }
2892
- const KEYBOARD_EVENTS = [
2893
- "ALT",
2894
- "AltGraph",
2895
- "CapsLock",
2896
- "Control",
2897
- "Fn",
2898
- "FnLock",
2899
- "Hyper",
2900
- "Meta",
2901
- "NumLock",
2902
- "ScrollLock",
2903
- "Shift",
2904
- "Super",
2905
- "Symbol",
2906
- "SymbolLock",
2907
- "Enter",
2908
- "Tab",
2909
- "ArrowDown",
2910
- "ArrowLeft",
2911
- "ArrowRight",
2912
- "ArrowUp",
2913
- "End",
2914
- "Home",
2915
- "PageDown",
2916
- "PageUp",
2917
- "Backspace",
2918
- "Clear",
2919
- "Copy",
2920
- "CrSel",
2921
- "Cut",
2922
- "Delete",
2923
- "EraseEof",
2924
- "ExSel",
2925
- "Insert",
2926
- "Paste",
2927
- "Redo",
2928
- "Undo",
2929
- "Accept",
2930
- "Again",
2931
- "Attn",
2932
- "Cancel",
2933
- "ContextMenu",
2934
- "Escape",
2935
- "Execute",
2936
- "Find",
2937
- "Finish",
2938
- "Help",
2939
- "Pause",
2940
- "Play",
2941
- "Props",
2942
- "Select",
2943
- "ZoomIn",
2944
- "ZoomOut",
2945
- "BrightnessDown",
2946
- "BrightnessUp",
2947
- "Eject",
2948
- "LogOff",
2949
- "Power",
2950
- "PowerOff",
2951
- "PrintScreen",
2952
- "Hibernate",
2953
- "Standby",
2954
- "WakeUp",
2955
- "AllCandidates",
2956
- "Alphanumeric",
2957
- "CodeInput",
2958
- "Compose",
2959
- "Convert",
2960
- "Dead",
2961
- "FinalMode",
2962
- "GroupFirst",
2963
- "GroupLast",
2964
- "GroupNext",
2965
- "GroupPrevious",
2966
- "ModeChange",
2967
- "NextCandidate",
2968
- "NonConvert",
2969
- "PreviousCandidate",
2970
- "Process",
2971
- "SingleCandidate",
2972
- "HangulMode",
2973
- "HanjaMode",
2974
- "JunjaMode",
2975
- "Eisu",
2976
- "Hankaku",
2977
- "Hiragana",
2978
- "HiraganaKatakana",
2979
- "KanaMode",
2980
- "KanjiMode",
2981
- "Katakana",
2982
- "Romaji",
2983
- "Zenkaku",
2984
- "ZenkakuHanaku",
2985
- "F1",
2986
- "F2",
2987
- "F3",
2988
- "F4",
2989
- "F5",
2990
- "F6",
2991
- "F7",
2992
- "F8",
2993
- "F9",
2994
- "F10",
2995
- "F11",
2996
- "F12",
2997
- "Soft1",
2998
- "Soft2",
2999
- "Soft3",
3000
- "Soft4",
3001
- "ChannelDown",
3002
- "ChannelUp",
3003
- "Close",
3004
- "MailForward",
3005
- "MailReply",
3006
- "MailSend",
3007
- "MediaFastForward",
3008
- "MediaPause",
3009
- "MediaPlay",
3010
- "MediaPlayPause",
3011
- "MediaRecord",
3012
- "MediaRewind",
3013
- "MediaStop",
3014
- "MediaTrackNext",
3015
- "MediaTrackPrevious",
3016
- "AudioBalanceLeft",
3017
- "AudioBalanceRight",
3018
- "AudioBassBoostDown",
3019
- "AudioBassBoostToggle",
3020
- "AudioBassBoostUp",
3021
- "AudioFaderFront",
3022
- "AudioFaderRear",
3023
- "AudioSurroundModeNext",
3024
- "AudioTrebleDown",
3025
- "AudioTrebleUp",
3026
- "AudioVolumeDown",
3027
- "AudioVolumeMute",
3028
- "AudioVolumeUp",
3029
- "MicrophoneToggle",
3030
- "MicrophoneVolumeDown",
3031
- "MicrophoneVolumeMute",
3032
- "MicrophoneVolumeUp",
3033
- "TV",
3034
- "TV3DMode",
3035
- "TVAntennaCable",
3036
- "TVAudioDescription",
3037
- ];
3038
- function unEscapeString(str) {
3039
- const placeholder = "__NEWLINE__";
3040
- str = str.replace(new RegExp(placeholder, "g"), "\n");
3041
- return str;
3042
- }
3043
3293
  export { StableBrowser };
3044
3294
  //# sourceMappingURL=stable_browser.js.map