automation_model 1.0.560-dev → 1.0.560-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.
@@ -10,18 +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 { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
13
+ import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } 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
22
  import { LocatorLog } from "./locator_log.js";
23
+ import axios from "axios";
24
+ import { _findCellArea, findElementsInArea } from "./table_helper.js";
23
25
  export const Types = {
24
26
  CLICK: "click_element",
27
+ WAIT_ELEMENT: "wait_element",
25
28
  NAVIGATE: "navigate",
26
29
  FILL: "fill_element",
27
30
  EXECUTE: "execute_page_method",
@@ -43,6 +46,7 @@ export const Types = {
43
46
  UNCHECK: "uncheck_element",
44
47
  EXTRACT: "extract_attribute",
45
48
  CLOSE_PAGE: "close_page",
49
+ TABLE_OPERATION: "table_operation",
46
50
  SET_DATE_TIME: "set_date_time",
47
51
  SET_VIEWPORT: "set_viewport",
48
52
  VERIFY_VISUAL: "verify_visual",
@@ -53,6 +57,9 @@ export const Types = {
53
57
  VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
54
58
  };
55
59
  export const apps = {};
60
+ const formatElementName = (elementName) => {
61
+ return elementName ? JSON.stringify(elementName) : "element";
62
+ };
56
63
  class StableBrowser {
57
64
  browser;
58
65
  page;
@@ -66,6 +73,7 @@ class StableBrowser {
66
73
  appName = "main";
67
74
  tags = null;
68
75
  isRecording = false;
76
+ initSnapshotTaken = false;
69
77
  constructor(browser, page, logger = null, context = null, world = null) {
70
78
  this.browser = browser;
71
79
  this.page = page;
@@ -235,16 +243,51 @@ class StableBrowser {
235
243
  // async closeUnexpectedPopups() {
236
244
  // await closeUnexpectedPopups(this.page);
237
245
  // }
238
- 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
+ }
239
250
  if (!url.startsWith("http")) {
240
251
  url = "https://" + url;
241
252
  }
242
- await this.page.goto(url, {
243
- timeout: 60000,
244
- });
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);
272
+ }
273
+ catch (error) {
274
+ console.error("Error on goto", error);
275
+ _commandError(state, error, this);
276
+ }
277
+ finally {
278
+ _commandFinally(state, this);
279
+ }
245
280
  }
246
- _getLocator(locator, scope, _params) {
281
+ async _getLocator(locator, scope, _params) {
247
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);
289
+ }
290
+ }
248
291
  let locatorReturn;
249
292
  if (locator.role) {
250
293
  if (locator.role[1].nameReg) {
@@ -295,52 +338,60 @@ class StableBrowser {
295
338
  if (css && css.locator) {
296
339
  css = css.locator;
297
340
  }
298
- let result = await this._locateElementByText(scope, _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);
299
342
  if (result.elementCount === 0) {
300
343
  return;
301
344
  }
302
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
345
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
303
346
  // css climb to parent element
304
347
  const climbArray = [];
305
348
  for (let i = 0; i < climb; i++) {
306
349
  climbArray.push("..");
307
350
  }
308
351
  let climbXpath = "xpath=" + climbArray.join("/");
309
- return textElementCss + " >> " + climbXpath + " >> " + css;
310
- }
311
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
312
- //const stringifyText = JSON.stringify(text);
313
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
314
- const options = {
315
- innerText: true,
316
- };
317
- if (regex) {
318
- options.singleRegex = true;
319
- }
320
- if (tag) {
321
- options.tag = tag;
322
- }
323
- if (!(partial === true)) {
324
- options.exactMatch = true;
325
- }
326
- const elements = window.findMatchingElements(text, options);
327
- let randomToken = null;
328
- const foundElements = [];
329
- let elementCount = 0;
330
- if (elements.length > 0) {
331
- for (let i = 0; i < elements.length; i++) {
332
- if (randomToken === null) {
333
- randomToken = Math.random().toString(36).substring(7);
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;
334
374
  }
335
- let element = elements[i];
336
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
337
- elementCount++;
338
375
  }
376
+ if (!el.setAttribute) {
377
+ el = el.parentElement;
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;
339
389
  }
340
- return { elementCount: elementCount, randomToken: randomToken };
341
- }, [text1, tag1, regex1, partial1]);
390
+ tagCount++;
391
+ }
392
+ return { elementCount: tagCount, randomToken };
342
393
  }
343
- 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) {
344
395
  if (!info) {
345
396
  info = {};
346
397
  }
@@ -363,30 +414,31 @@ class StableBrowser {
363
414
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
364
415
  let locator = null;
365
416
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
366
- 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);
367
419
  if (!locatorString) {
368
420
  info.failCause.textNotFound = true;
369
- 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}`;
370
422
  return;
371
423
  }
372
- locator = this._getLocator({ css: locatorString }, scope, _params);
424
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
373
425
  }
374
426
  else if (locatorSearch.text) {
375
427
  let text = _fixUsingParams(locatorSearch.text, _params);
376
- let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
428
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
377
429
  if (result.elementCount === 0) {
378
430
  info.failCause.textNotFound = true;
379
- info.failCause.lastError = "failed to locate element by text: " + text;
431
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
380
432
  return;
381
433
  }
382
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
434
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
383
435
  if (locatorSearch.childCss) {
384
436
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
385
437
  }
386
- locator = this._getLocator(locatorSearch, scope, _params);
438
+ locator = await this._getLocator(locatorSearch, scope, _params);
387
439
  }
388
440
  else {
389
- locator = this._getLocator(locatorSearch, scope, _params);
441
+ locator = await this._getLocator(locatorSearch, scope, _params);
390
442
  }
391
443
  // let cssHref = false;
392
444
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -415,7 +467,7 @@ class StableBrowser {
415
467
  if (!visibleOnly) {
416
468
  visible = true;
417
469
  }
418
- if (visible && enabled) {
470
+ if (visible && (allowDisabled || enabled)) {
419
471
  foundLocators.push(locator.nth(j));
420
472
  if (info.locatorLog) {
421
473
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
@@ -428,9 +480,11 @@ class StableBrowser {
428
480
  info.printMessages = {};
429
481
  }
430
482
  if (info.locatorLog && !visible) {
483
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
431
484
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
432
485
  }
433
486
  if (info.locatorLog && !enabled) {
487
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
434
488
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
435
489
  }
436
490
  if (!info.printMessages[j.toString()]) {
@@ -498,7 +552,7 @@ class StableBrowser {
498
552
  }
499
553
  return { rerun: false };
500
554
  }
501
- async _locate(selectors, info, _params, timeout) {
555
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
502
556
  if (!timeout) {
503
557
  timeout = 30000;
504
558
  }
@@ -508,9 +562,18 @@ class StableBrowser {
508
562
  let selector = selectors.locators[j];
509
563
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
510
564
  }
511
- let element = await this._locate_internal(selectors, info, _params, timeout);
565
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
512
566
  if (!element.rerun) {
513
- 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;
514
577
  }
515
578
  }
516
579
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -578,19 +641,19 @@ class StableBrowser {
578
641
  }
579
642
  if (!scope) {
580
643
  if (info && info.locatorLog) {
581
- info.locatorLog.setFrameSearchStatus("frame-" + fLocator, "NOT_FOUND");
644
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
582
645
  }
583
646
  //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
584
647
  if (Date.now() - startTime > timeout) {
585
648
  info.failCause.iframeNotFound = true;
586
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
649
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
587
650
  throw new Error("unable to locate iframe " + selectors.iframe_src);
588
651
  }
589
652
  await new Promise((resolve) => setTimeout(resolve, 1000));
590
653
  }
591
654
  else {
592
655
  if (info && info.locatorLog) {
593
- info.locatorLog.setFrameSearchStatus("frame-" + fLocator, "FOUND");
656
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
594
657
  }
595
658
  break;
596
659
  }
@@ -608,7 +671,7 @@ class StableBrowser {
608
671
  return bodyContent;
609
672
  });
610
673
  }
611
- async _locate_internal(selectors, info, _params, timeout = 30000) {
674
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
612
675
  if (!info) {
613
676
  info = {};
614
677
  info.failCause = {};
@@ -657,17 +720,17 @@ class StableBrowser {
657
720
  }
658
721
  // info.log += "scanning locators in priority 1" + "\n";
659
722
  let onlyPriority3 = selectorsLocators[0].priority === 3;
660
- 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);
661
724
  if (result.foundElements.length === 0) {
662
725
  // info.log += "scanning locators in priority 2" + "\n";
663
- 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);
664
727
  }
665
728
  if (result.foundElements.length === 0 && onlyPriority3) {
666
- 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);
667
730
  }
668
731
  else {
669
732
  if (result.foundElements.length === 0 && !highPriorityOnly) {
670
- 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);
671
734
  }
672
735
  }
673
736
  let foundElements = result.foundElements;
@@ -712,7 +775,7 @@ class StableBrowser {
712
775
  break;
713
776
  }
714
777
  if (Date.now() - startTime > highPriorityTimeout) {
715
- info.log += "high priority timeout, will try all elements" + "\n";
778
+ //info.log += "high priority timeout, will try all elements" + "\n";
716
779
  highPriorityOnly = false;
717
780
  if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
718
781
  lazy_scroll = true;
@@ -720,7 +783,7 @@ class StableBrowser {
720
783
  }
721
784
  }
722
785
  if (Date.now() - startTime > visibleOnlyTimeout) {
723
- info.log += "visible only timeout, will try all elements" + "\n";
786
+ //info.log += "visible only timeout, will try all elements" + "\n";
724
787
  visibleOnly = false;
725
788
  }
726
789
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -734,10 +797,12 @@ class StableBrowser {
734
797
  // }
735
798
  //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
736
799
  info.failCause.locatorNotFound = true;
737
- 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
+ }
738
803
  throw new Error("failed to locate first element no elements found, " + info.log);
739
804
  }
740
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
805
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
741
806
  let foundElements = [];
742
807
  const result = {
743
808
  foundElements: foundElements,
@@ -745,14 +810,15 @@ class StableBrowser {
745
810
  for (let i = 0; i < locatorsGroup.length; i++) {
746
811
  let foundLocators = [];
747
812
  try {
748
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
813
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
749
814
  }
750
815
  catch (e) {
751
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
752
- 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);
753
819
  foundLocators = [];
754
820
  try {
755
- 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);
756
822
  }
757
823
  catch (e) {
758
824
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -767,9 +833,40 @@ class StableBrowser {
767
833
  result.locatorIndex = i;
768
834
  }
769
835
  if (foundLocators.length > 1) {
770
- info.failCause.foundMultiple = true;
771
- if (info.locatorLog) {
772
- info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
836
+ // remove elements that consume the same space with 10 pixels tolerance
837
+ const boxes = [];
838
+ for (let j = 0; j < foundLocators.length; j++) {
839
+ boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
840
+ }
841
+ for (let j = 0; j < boxes.length; j++) {
842
+ for (let k = 0; k < boxes.length; k++) {
843
+ if (j === k) {
844
+ continue;
845
+ }
846
+ // check if x, y, width, height are the same with 10 pixels tolerance
847
+ if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
848
+ Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
849
+ Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
850
+ Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
851
+ // as the element is not unique, will remove it
852
+ boxes.splice(k, 1);
853
+ k--;
854
+ }
855
+ }
856
+ }
857
+ if (boxes.length === 1) {
858
+ result.foundElements.push({
859
+ locator: boxes[0].locator.first(),
860
+ box: boxes[0].box,
861
+ unique: true,
862
+ });
863
+ result.locatorIndex = i;
864
+ }
865
+ else {
866
+ info.failCause.foundMultiple = true;
867
+ if (info.locatorLog) {
868
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
869
+ }
773
870
  }
774
871
  }
775
872
  }
@@ -880,25 +977,14 @@ class StableBrowser {
880
977
  options,
881
978
  world,
882
979
  text: "Click element",
980
+ _text: "Click on " + selectors.element_name,
883
981
  type: Types.CLICK,
884
982
  operation: "click",
885
983
  log: "***** click on " + selectors.element_name + " *****\n",
886
984
  };
887
985
  try {
888
986
  await _preCommand(state, this);
889
- if (state.options && state.options.context) {
890
- state.selectors.locators[0].text = state.options.context;
891
- }
892
- try {
893
- await state.element.click();
894
- // await new Promise((resolve) => setTimeout(resolve, 1000));
895
- }
896
- catch (e) {
897
- // await this.closeUnexpectedPopups();
898
- state.element = await this._locate(selectors, state.info, _params);
899
- await state.element.dispatchEvent("click");
900
- // await new Promise((resolve) => setTimeout(resolve, 1000));
901
- }
987
+ await performAction("click", state.element, options, this, state, _params);
902
988
  await this.waitForPageLoad();
903
989
  return state.info;
904
990
  }
@@ -909,6 +995,38 @@ class StableBrowser {
909
995
  _commandFinally(state, this);
910
996
  }
911
997
  }
998
+ async waitForElement(selectors, _params, options = {}, world = null) {
999
+ const timeout = this._getFindElementTimeout(options);
1000
+ const state = {
1001
+ selectors,
1002
+ _params,
1003
+ options,
1004
+ world,
1005
+ text: "Wait for element",
1006
+ _text: "Wait for " + selectors.element_name,
1007
+ type: Types.WAIT_ELEMENT,
1008
+ operation: "waitForElement",
1009
+ log: "***** wait for " + selectors.element_name + " *****\n",
1010
+ };
1011
+ let found = false;
1012
+ try {
1013
+ await _preCommand(state, this);
1014
+ // if (state.options && state.options.context) {
1015
+ // state.selectors.locators[0].text = state.options.context;
1016
+ // }
1017
+ await state.element.waitFor({ timeout: timeout });
1018
+ found = true;
1019
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1020
+ }
1021
+ catch (e) {
1022
+ console.error("Error on waitForElement", e);
1023
+ // await _commandError(state, e, this);
1024
+ }
1025
+ finally {
1026
+ _commandFinally(state, this);
1027
+ }
1028
+ return found;
1029
+ }
912
1030
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
913
1031
  const state = {
914
1032
  selectors,
@@ -917,6 +1035,7 @@ class StableBrowser {
917
1035
  world,
918
1036
  type: checked ? Types.CHECK : Types.UNCHECK,
919
1037
  text: checked ? `Check element` : `Uncheck element`,
1038
+ _text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
920
1039
  operation: "setCheck",
921
1040
  log: "***** check " + selectors.element_name + " *****\n",
922
1041
  };
@@ -926,9 +1045,15 @@ class StableBrowser {
926
1045
  // let element = await this._locate(selectors, info, _params);
927
1046
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
928
1047
  try {
929
- // await this._highlightElements(element);
1048
+ // if (world && world.screenshot && !world.screenshotPath) {
1049
+ // console.log(`Highlighting while running from recorder`);
1050
+ await this._highlightElements(element);
930
1051
  await state.element.setChecked(checked);
931
1052
  await new Promise((resolve) => setTimeout(resolve, 1000));
1053
+ // await this._unHighlightElements(element);
1054
+ // }
1055
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1056
+ // await this._unHighlightElements(element);
932
1057
  }
933
1058
  catch (e) {
934
1059
  if (e.message && e.message.includes("did not change its state")) {
@@ -960,22 +1085,13 @@ class StableBrowser {
960
1085
  world,
961
1086
  type: Types.HOVER,
962
1087
  text: `Hover element`,
1088
+ _text: `Hover on ${selectors.element_name}`,
963
1089
  operation: "hover",
964
1090
  log: "***** hover " + selectors.element_name + " *****\n",
965
1091
  };
966
1092
  try {
967
1093
  await _preCommand(state, this);
968
- try {
969
- await state.element.hover();
970
- await new Promise((resolve) => setTimeout(resolve, 1000));
971
- }
972
- catch (e) {
973
- //await this.closeUnexpectedPopups();
974
- state.info.log += "hover failed, will try again" + "\n";
975
- state.element = await this._locate(selectors, state.info, _params);
976
- await state.element.hover({ timeout: 10000 });
977
- await new Promise((resolve) => setTimeout(resolve, 1000));
978
- }
1094
+ await performAction("hover", state.element, options, this, state, _params);
979
1095
  await _screenshot(state, this);
980
1096
  await this.waitForPageLoad();
981
1097
  return state.info;
@@ -999,6 +1115,7 @@ class StableBrowser {
999
1115
  value: values.toString(),
1000
1116
  type: Types.SELECT,
1001
1117
  text: `Select option: ${values}`,
1118
+ _text: `Select option: ${values} on ${selectors.element_name}`,
1002
1119
  operation: "selectOption",
1003
1120
  log: "***** select option " + selectors.element_name + " *****\n",
1004
1121
  };
@@ -1033,6 +1150,7 @@ class StableBrowser {
1033
1150
  highlight: false,
1034
1151
  type: Types.TYPE_PRESS,
1035
1152
  text: `Type value: ${_value}`,
1153
+ _text: `Type value: ${_value}`,
1036
1154
  operation: "type",
1037
1155
  log: "",
1038
1156
  };
@@ -1112,6 +1230,7 @@ class StableBrowser {
1112
1230
  world,
1113
1231
  type: Types.SET_DATE_TIME,
1114
1232
  text: `Set date time value: ${value}`,
1233
+ _text: `Set date time value: ${value} on ${selectors.element_name}`,
1115
1234
  operation: "setDateTime",
1116
1235
  log: "***** set date time value " + selectors.element_name + " *****\n",
1117
1236
  throwError: false,
@@ -1119,7 +1238,7 @@ class StableBrowser {
1119
1238
  try {
1120
1239
  await _preCommand(state, this);
1121
1240
  try {
1122
- await state.element.click();
1241
+ await performAction("click", state.element, options, this, state, _params);
1123
1242
  await new Promise((resolve) => setTimeout(resolve, 500));
1124
1243
  if (format) {
1125
1244
  state.value = dayjs(state.value).format(format);
@@ -1183,9 +1302,13 @@ class StableBrowser {
1183
1302
  world,
1184
1303
  type: Types.FILL,
1185
1304
  text: `Click type input with value: ${_value}`,
1305
+ _text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
1186
1306
  operation: "clickType",
1187
1307
  log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1188
1308
  };
1309
+ if (!options) {
1310
+ options = {};
1311
+ }
1189
1312
  if (newValue !== _value) {
1190
1313
  //this.logger.info(_value + "=" + newValue);
1191
1314
  _value = newValue;
@@ -1193,7 +1316,7 @@ class StableBrowser {
1193
1316
  try {
1194
1317
  await _preCommand(state, this);
1195
1318
  state.info.value = _value;
1196
- if (options === null || options === undefined || !options.press) {
1319
+ if (!options.press) {
1197
1320
  try {
1198
1321
  let currentValue = await state.element.inputValue();
1199
1322
  if (currentValue) {
@@ -1204,13 +1327,9 @@ class StableBrowser {
1204
1327
  this.logger.info("unable to clear input value");
1205
1328
  }
1206
1329
  }
1207
- if (options === null || options === undefined || options.press) {
1208
- try {
1209
- await state.element.click({ timeout: 5000 });
1210
- }
1211
- catch (e) {
1212
- await state.element.dispatchEvent("click");
1213
- }
1330
+ if (options.press) {
1331
+ options.timeout = 5000;
1332
+ await performAction("click", state.element, options, this, state, _params);
1214
1333
  }
1215
1334
  else {
1216
1335
  try {
@@ -1248,7 +1367,12 @@ class StableBrowser {
1248
1367
  await this.waitForPageLoad();
1249
1368
  }
1250
1369
  else if (enter === false) {
1251
- await state.element.dispatchEvent("change");
1370
+ try {
1371
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1372
+ }
1373
+ catch (e) {
1374
+ // ignore
1375
+ }
1252
1376
  //await this.page.keyboard.press("Tab");
1253
1377
  }
1254
1378
  else {
@@ -1300,6 +1424,7 @@ class StableBrowser {
1300
1424
  return await this._getText(selectors, 0, _params, options, info, world);
1301
1425
  }
1302
1426
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1427
+ const timeout = this._getFindElementTimeout(options);
1303
1428
  _validateSelectors(selectors);
1304
1429
  let screenshotId = null;
1305
1430
  let screenshotPath = null;
@@ -1309,7 +1434,7 @@ class StableBrowser {
1309
1434
  }
1310
1435
  info.operation = "getText";
1311
1436
  info.selectors = selectors;
1312
- let element = await this._locate(selectors, info, _params);
1437
+ let element = await this._locate(selectors, info, _params, timeout);
1313
1438
  if (climb > 0) {
1314
1439
  const climbArray = [];
1315
1440
  for (let i = 0; i < climb; i++) {
@@ -1328,6 +1453,18 @@ class StableBrowser {
1328
1453
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1329
1454
  try {
1330
1455
  await this._highlightElements(element);
1456
+ // if (world && world.screenshot && !world.screenshotPath) {
1457
+ // // console.log(`Highlighting for get text while running from recorder`);
1458
+ // this._highlightElements(element)
1459
+ // .then(async () => {
1460
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1461
+ // this._unhighlightElements(element).then(
1462
+ // () => {}
1463
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1464
+ // );
1465
+ // })
1466
+ // .catch(e);
1467
+ // }
1331
1468
  const elementText = await element.innerText();
1332
1469
  return {
1333
1470
  text: elementText,
@@ -1339,7 +1476,7 @@ class StableBrowser {
1339
1476
  }
1340
1477
  catch (e) {
1341
1478
  //await this.closeUnexpectedPopups();
1342
- this.logger.info("no innerText will use textContent");
1479
+ this.logger.info("no innerText, will use textContent");
1343
1480
  const elementText = await element.textContent();
1344
1481
  return { text: elementText, screenshotId, screenshotPath, value: value };
1345
1482
  }
@@ -1364,6 +1501,7 @@ class StableBrowser {
1364
1501
  highlight: false,
1365
1502
  type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1366
1503
  text: `Verify element contains pattern: ${pattern}`,
1504
+ _text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
1367
1505
  operation: "containsPattern",
1368
1506
  log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1369
1507
  };
@@ -1399,6 +1537,8 @@ class StableBrowser {
1399
1537
  }
1400
1538
  }
1401
1539
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1540
+ const timeout = this._getFindElementTimeout(options);
1541
+ const startTime = Date.now();
1402
1542
  const state = {
1403
1543
  selectors,
1404
1544
  _params,
@@ -1425,62 +1565,54 @@ class StableBrowser {
1425
1565
  }
1426
1566
  let foundObj = null;
1427
1567
  try {
1428
- await _preCommand(state, this);
1429
- foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1430
- if (foundObj && foundObj.element) {
1431
- await this.scrollIfNeeded(foundObj.element, state.info);
1432
- }
1433
- await _screenshot(state, this);
1434
- const dateAlternatives = findDateAlternatives(text);
1435
- const numberAlternatives = findNumberAlternatives(text);
1436
- if (dateAlternatives.date) {
1437
- for (let i = 0; i < dateAlternatives.dates.length; i++) {
1438
- if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1439
- foundObj?.value?.includes(dateAlternatives.dates[i])) {
1440
- return state.info;
1568
+ while (Date.now() - startTime < timeout) {
1569
+ try {
1570
+ await _preCommand(state, this);
1571
+ foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
1572
+ if (foundObj && foundObj.element) {
1573
+ await this.scrollIfNeeded(foundObj.element, state.info);
1441
1574
  }
1442
- }
1443
- throw new Error("element doesn't contain text " + text);
1444
- }
1445
- else if (numberAlternatives.number) {
1446
- for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1447
- if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1448
- foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1575
+ await _screenshot(state, this);
1576
+ const dateAlternatives = findDateAlternatives(text);
1577
+ const numberAlternatives = findNumberAlternatives(text);
1578
+ if (dateAlternatives.date) {
1579
+ for (let i = 0; i < dateAlternatives.dates.length; i++) {
1580
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1581
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1582
+ return state.info;
1583
+ }
1584
+ }
1585
+ }
1586
+ else if (numberAlternatives.number) {
1587
+ for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1588
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1589
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1590
+ return state.info;
1591
+ }
1592
+ }
1593
+ }
1594
+ else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
1449
1595
  return state.info;
1450
1596
  }
1451
1597
  }
1452
- throw new Error("element doesn't contain text " + text);
1453
- }
1454
- else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1455
- state.info.foundText = foundObj?.text;
1456
- state.info.value = foundObj?.value;
1457
- throw new Error("element doesn't contain text " + text);
1598
+ catch (e) {
1599
+ // Log error but continue retrying until timeout is reached
1600
+ this.logger.warn("Retrying containsText due to: " + e.message);
1601
+ }
1602
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
1458
1603
  }
1459
- return state.info;
1604
+ state.info.foundText = foundObj?.text;
1605
+ state.info.value = foundObj?.value;
1606
+ throw new Error("element doesn't contain text " + text);
1460
1607
  }
1461
1608
  catch (e) {
1462
1609
  await _commandError(state, e, this);
1610
+ throw e;
1463
1611
  }
1464
1612
  finally {
1465
1613
  _commandFinally(state, this);
1466
1614
  }
1467
1615
  }
1468
- _getDataFile(world = null) {
1469
- let dataFile = null;
1470
- if (world && world.reportFolder) {
1471
- dataFile = path.join(world.reportFolder, "data.json");
1472
- }
1473
- else if (this.reportFolder) {
1474
- dataFile = path.join(this.reportFolder, "data.json");
1475
- }
1476
- else if (this.context && this.context.reportFolder) {
1477
- dataFile = path.join(this.context.reportFolder, "data.json");
1478
- }
1479
- else {
1480
- dataFile = "data.json";
1481
- }
1482
- return dataFile;
1483
- }
1484
1616
  async waitForUserInput(message, world = null) {
1485
1617
  if (!message) {
1486
1618
  message = "# Wait for user input. Press any key to continue";
@@ -1509,7 +1641,7 @@ class StableBrowser {
1509
1641
  return;
1510
1642
  }
1511
1643
  // if data file exists, load it
1512
- const dataFile = this._getDataFile(world);
1644
+ const dataFile = _getDataFile(world, this.context, this);
1513
1645
  let data = this.getTestData(world);
1514
1646
  // merge the testData with the existing data
1515
1647
  Object.assign(data, testData);
@@ -1612,7 +1744,7 @@ class StableBrowser {
1612
1744
  }
1613
1745
  }
1614
1746
  getTestData(world = null) {
1615
- const dataFile = this._getDataFile(world);
1747
+ const dataFile = _getDataFile(world, this.context, this);
1616
1748
  let data = {};
1617
1749
  if (fs.existsSync(dataFile)) {
1618
1750
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1699,6 +1831,15 @@ class StableBrowser {
1699
1831
  document.documentElement.clientWidth,
1700
1832
  ])));
1701
1833
  let screenshotBuffer = null;
1834
+ // if (focusedElement) {
1835
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1836
+ // await this._unhighlightElements(focusedElement);
1837
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1838
+ // console.log(`Unhighlighted previous element`);
1839
+ // }
1840
+ // if (focusedElement) {
1841
+ // await this._highlightElements(focusedElement);
1842
+ // }
1702
1843
  if (this.context.browserName === "chromium") {
1703
1844
  const client = await playContext.newCDPSession(this.page);
1704
1845
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1720,6 +1861,10 @@ class StableBrowser {
1720
1861
  else {
1721
1862
  screenshotBuffer = await this.page.screenshot();
1722
1863
  }
1864
+ // if (focusedElement) {
1865
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1866
+ // await this._unhighlightElements(focusedElement);
1867
+ // }
1723
1868
  let image = await Jimp.read(screenshotBuffer);
1724
1869
  // Get the image dimensions
1725
1870
  const { width, height } = image.bitmap;
@@ -1732,6 +1877,7 @@ class StableBrowser {
1732
1877
  else {
1733
1878
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1734
1879
  }
1880
+ return screenshotBuffer;
1735
1881
  }
1736
1882
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1737
1883
  const state = {
@@ -1767,8 +1913,10 @@ class StableBrowser {
1767
1913
  world,
1768
1914
  type: Types.EXTRACT,
1769
1915
  text: `Extract attribute from element`,
1916
+ _text: `Extract attribute ${attribute} from ${selectors.element_name}`,
1770
1917
  operation: "extractAttribute",
1771
1918
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1919
+ allowDisabled: true,
1772
1920
  };
1773
1921
  await new Promise((resolve) => setTimeout(resolve, 2000));
1774
1922
  try {
@@ -1790,6 +1938,7 @@ class StableBrowser {
1790
1938
  state.info.value = state.value;
1791
1939
  this.setTestData({ [variable]: state.value }, world);
1792
1940
  this.logger.info("set test data: " + variable + "=" + state.value);
1941
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1793
1942
  return state.info;
1794
1943
  }
1795
1944
  catch (e) {
@@ -1808,14 +1957,21 @@ class StableBrowser {
1808
1957
  options,
1809
1958
  world,
1810
1959
  type: Types.VERIFY_ATTRIBUTE,
1960
+ highlight: true,
1961
+ screenshot: true,
1811
1962
  text: `Verify element attribute`,
1963
+ _text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
1812
1964
  operation: "verifyAttribute",
1813
1965
  log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1966
+ allowDisabled: true,
1814
1967
  };
1815
1968
  await new Promise((resolve) => setTimeout(resolve, 2000));
1816
1969
  let val;
1970
+ let expectedValue;
1817
1971
  try {
1818
1972
  await _preCommand(state, this);
1973
+ expectedValue = state.value;
1974
+ state.info.expectedValue = expectedValue;
1819
1975
  switch (attribute) {
1820
1976
  case "innerText":
1821
1977
  val = String(await state.element.innerText());
@@ -1829,23 +1985,30 @@ class StableBrowser {
1829
1985
  case "disabled":
1830
1986
  val = String(await state.element.isDisabled());
1831
1987
  break;
1988
+ case "readOnly":
1989
+ const isEditable = await state.element.isEditable();
1990
+ val = String(!isEditable);
1991
+ break;
1832
1992
  default:
1833
1993
  val = String(await state.element.getAttribute(attribute));
1834
1994
  break;
1835
1995
  }
1996
+ state.info.value = val;
1836
1997
  let regex;
1837
- if (value.startsWith("/") && value.endsWith("/")) {
1838
- const patternBody = value.slice(1, -1);
1998
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
1999
+ const patternBody = expectedValue.slice(1, -1);
1839
2000
  regex = new RegExp(patternBody, "g");
1840
2001
  }
1841
2002
  else {
1842
- const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2003
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1843
2004
  regex = new RegExp(escapedPattern, "g");
1844
2005
  }
1845
2006
  if (!val.match(regex)) {
1846
- throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
2007
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
2008
+ state.info.failCause.assertionFailed = true;
2009
+ state.info.failCause.lastError = errorMessage;
2010
+ throw new Error(errorMessage);
1847
2011
  }
1848
- state.info.value = val;
1849
2012
  return state.info;
1850
2013
  }
1851
2014
  catch (e) {
@@ -1944,27 +2107,32 @@ class StableBrowser {
1944
2107
  async _highlightElements(scope, css) {
1945
2108
  try {
1946
2109
  if (!scope) {
2110
+ // console.log(`Scope is not defined`);
1947
2111
  return;
1948
2112
  }
1949
2113
  if (!css) {
1950
2114
  scope
1951
2115
  .evaluate((node) => {
1952
2116
  if (node && node.style) {
1953
- let originalBorder = node.style.border;
1954
- node.style.border = "2px solid red";
2117
+ let originalOutline = node.style.outline;
2118
+ // console.log(`Original outline was: ${originalOutline}`);
2119
+ // node.__previousOutline = originalOutline;
2120
+ node.style.outline = "2px solid red";
2121
+ // console.log(`New outline is: ${node.style.outline}`);
1955
2122
  if (window) {
1956
2123
  window.addEventListener("beforeunload", function (e) {
1957
- node.style.border = originalBorder;
2124
+ node.style.outline = originalOutline;
1958
2125
  });
1959
2126
  }
1960
2127
  setTimeout(function () {
1961
- node.style.border = originalBorder;
2128
+ node.style.outline = originalOutline;
1962
2129
  }, 2000);
1963
2130
  }
1964
2131
  })
1965
2132
  .then(() => { })
1966
2133
  .catch((e) => {
1967
2134
  // ignore
2135
+ // console.error(`Could not highlight node : ${e}`);
1968
2136
  });
1969
2137
  }
1970
2138
  else {
@@ -1980,17 +2148,18 @@ class StableBrowser {
1980
2148
  if (!element.style) {
1981
2149
  return;
1982
2150
  }
1983
- var originalBorder = element.style.border;
2151
+ let originalOutline = element.style.outline;
2152
+ element.__previousOutline = originalOutline;
1984
2153
  // Set the new border to be red and 2px solid
1985
- element.style.border = "2px solid red";
2154
+ element.style.outline = "2px solid red";
1986
2155
  if (window) {
1987
2156
  window.addEventListener("beforeunload", function (e) {
1988
- element.style.border = originalBorder;
2157
+ element.style.outline = originalOutline;
1989
2158
  });
1990
2159
  }
1991
2160
  // Set a timeout to revert to the original border after 2 seconds
1992
2161
  setTimeout(function () {
1993
- element.style.border = originalBorder;
2162
+ element.style.outline = originalOutline;
1994
2163
  }, 2000);
1995
2164
  }
1996
2165
  return;
@@ -1998,6 +2167,7 @@ class StableBrowser {
1998
2167
  .then(() => { })
1999
2168
  .catch((e) => {
2000
2169
  // ignore
2170
+ // console.error(`Could not highlight css: ${e}`);
2001
2171
  });
2002
2172
  }
2003
2173
  }
@@ -2005,6 +2175,54 @@ class StableBrowser {
2005
2175
  console.debug(error);
2006
2176
  }
2007
2177
  }
2178
+ // async _unhighlightElements(scope, css) {
2179
+ // try {
2180
+ // if (!scope) {
2181
+ // return;
2182
+ // }
2183
+ // if (!css) {
2184
+ // scope
2185
+ // .evaluate((node) => {
2186
+ // if (node && node.style) {
2187
+ // if (!node.__previousOutline) {
2188
+ // node.style.outline = "";
2189
+ // } else {
2190
+ // node.style.outline = node.__previousOutline;
2191
+ // }
2192
+ // }
2193
+ // })
2194
+ // .then(() => {})
2195
+ // .catch((e) => {
2196
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2197
+ // });
2198
+ // } else {
2199
+ // scope
2200
+ // .evaluate(([css]) => {
2201
+ // if (!css) {
2202
+ // return;
2203
+ // }
2204
+ // let elements = Array.from(document.querySelectorAll(css));
2205
+ // for (i = 0; i < elements.length; i++) {
2206
+ // let element = elements[i];
2207
+ // if (!element.style) {
2208
+ // return;
2209
+ // }
2210
+ // if (!element.__previousOutline) {
2211
+ // element.style.outline = "";
2212
+ // } else {
2213
+ // element.style.outline = element.__previousOutline;
2214
+ // }
2215
+ // }
2216
+ // })
2217
+ // .then(() => {})
2218
+ // .catch((e) => {
2219
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2220
+ // });
2221
+ // }
2222
+ // } catch (error) {
2223
+ // // console.debug(error);
2224
+ // }
2225
+ // }
2008
2226
  async verifyPagePath(pathPart, options = {}, world = null) {
2009
2227
  const startTime = Date.now();
2010
2228
  let error = null;
@@ -2049,6 +2267,7 @@ class StableBrowser {
2049
2267
  _reportToWorld(world, {
2050
2268
  type: Types.VERIFY_PAGE_PATH,
2051
2269
  text: "Verify page path",
2270
+ _text: "Verify the page path contains " + pathPart,
2052
2271
  screenshotId,
2053
2272
  result: error
2054
2273
  ? {
@@ -2066,26 +2285,27 @@ class StableBrowser {
2066
2285
  });
2067
2286
  }
2068
2287
  }
2069
- async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2288
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
2070
2289
  const frames = this.page.frames();
2071
2290
  let results = [];
2291
+ // let ignoreCase = false;
2072
2292
  for (let i = 0; i < frames.length; i++) {
2073
2293
  if (dateAlternatives.date) {
2074
2294
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2075
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
2295
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2076
2296
  result.frame = frames[i];
2077
2297
  results.push(result);
2078
2298
  }
2079
2299
  }
2080
2300
  else if (numberAlternatives.number) {
2081
2301
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2082
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
2302
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2083
2303
  result.frame = frames[i];
2084
2304
  results.push(result);
2085
2305
  }
2086
2306
  }
2087
2307
  else {
2088
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
2308
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
2089
2309
  result.frame = frames[i];
2090
2310
  results.push(result);
2091
2311
  }
@@ -2104,11 +2324,15 @@ class StableBrowser {
2104
2324
  scroll: false,
2105
2325
  highlight: false,
2106
2326
  type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2107
- text: `Verify text exists in page`,
2327
+ text: `Verify the text '${text}' exists in page`,
2328
+ _text: `Verify the text '${text}' exists in page`,
2108
2329
  operation: "verifyTextExistInPage",
2109
2330
  log: "***** verify text " + text + " exists in page *****\n",
2110
2331
  };
2111
- const timeout = this._getLoadTimeout(options);
2332
+ if (testForRegex(text)) {
2333
+ text = text.replace(/\\"/g, '"');
2334
+ }
2335
+ const timeout = this._getFindElementTimeout(options);
2112
2336
  await new Promise((resolve) => setTimeout(resolve, 2000));
2113
2337
  const newValue = await this._replaceWithLocalData(text, world);
2114
2338
  if (newValue !== text) {
@@ -2121,7 +2345,15 @@ class StableBrowser {
2121
2345
  await _preCommand(state, this);
2122
2346
  state.info.text = text;
2123
2347
  while (true) {
2124
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2348
+ let resultWithElementsFound = {
2349
+ length: 0,
2350
+ };
2351
+ try {
2352
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2353
+ }
2354
+ catch (error) {
2355
+ // ignore
2356
+ }
2125
2357
  if (resultWithElementsFound.length === 0) {
2126
2358
  if (Date.now() - state.startTime > timeout) {
2127
2359
  throw new Error(`Text ${text} not found in page`);
@@ -2129,18 +2361,40 @@ class StableBrowser {
2129
2361
  await new Promise((resolve) => setTimeout(resolve, 1000));
2130
2362
  continue;
2131
2363
  }
2132
- if (resultWithElementsFound[0].randomToken) {
2133
- const frame = resultWithElementsFound[0].frame;
2134
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2135
- await this._highlightElements(frame, dataAttribute);
2136
- const element = await frame.$(dataAttribute);
2137
- if (element) {
2138
- await this.scrollIfNeeded(element, state.info);
2139
- await element.dispatchEvent("bvt_verify_page_contains_text");
2364
+ try {
2365
+ if (resultWithElementsFound[0].randomToken) {
2366
+ const frame = resultWithElementsFound[0].frame;
2367
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2368
+ await this._highlightElements(frame, dataAttribute);
2369
+ // if (world && world.screenshot && !world.screenshotPath) {
2370
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2371
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2372
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2373
+ // this._unhighlightElements(frame, dataAttribute)
2374
+ // .then(async () => {
2375
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2376
+ // })
2377
+ // .catch(
2378
+ // (e) => {}
2379
+ // console.error(e)
2380
+ // );
2381
+ // });
2382
+ // }
2383
+ const element = await frame.locator(dataAttribute).first();
2384
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2385
+ // await this._unhighlightElements(frame, dataAttribute);
2386
+ if (element) {
2387
+ await this.scrollIfNeeded(element, state.info);
2388
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2389
+ // await _screenshot(state, this, element);
2390
+ }
2140
2391
  }
2392
+ await _screenshot(state, this);
2393
+ return state.info;
2394
+ }
2395
+ catch (error) {
2396
+ console.error(error);
2141
2397
  }
2142
- await _screenshot(state, this);
2143
- return state.info;
2144
2398
  }
2145
2399
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2146
2400
  }
@@ -2161,11 +2415,15 @@ class StableBrowser {
2161
2415
  scroll: false,
2162
2416
  highlight: false,
2163
2417
  type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2164
- text: `Verify text does not exist in page`,
2418
+ text: `Verify the text '${text}' does not exist in page`,
2419
+ _text: `Verify the text '${text}' does not exist in page`,
2165
2420
  operation: "verifyTextNotExistInPage",
2166
2421
  log: "***** verify text " + text + " does not exist in page *****\n",
2167
2422
  };
2168
- const timeout = this._getLoadTimeout(options);
2423
+ if (testForRegex(text)) {
2424
+ text = text.replace(/\\"/g, '"');
2425
+ }
2426
+ const timeout = this._getFindElementTimeout(options);
2169
2427
  await new Promise((resolve) => setTimeout(resolve, 2000));
2170
2428
  const newValue = await this._replaceWithLocalData(text, world);
2171
2429
  if (newValue !== text) {
@@ -2177,8 +2435,16 @@ class StableBrowser {
2177
2435
  try {
2178
2436
  await _preCommand(state, this);
2179
2437
  state.info.text = text;
2438
+ let resultWithElementsFound = {
2439
+ length: null, // initial cannot be 0
2440
+ };
2180
2441
  while (true) {
2181
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2442
+ try {
2443
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2444
+ }
2445
+ catch (error) {
2446
+ // ignore
2447
+ }
2182
2448
  if (resultWithElementsFound.length === 0) {
2183
2449
  await _screenshot(state, this);
2184
2450
  return state.info;
@@ -2208,10 +2474,11 @@ class StableBrowser {
2208
2474
  highlight: false,
2209
2475
  type: Types.VERIFY_TEXT_WITH_RELATION,
2210
2476
  text: `Verify text with relation to another text`,
2477
+ _text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
2211
2478
  operation: "verify_text_with_relation",
2212
2479
  log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2213
2480
  };
2214
- const timeout = this._getLoadTimeout(options);
2481
+ const timeout = this._getFindElementTimeout(options);
2215
2482
  await new Promise((resolve) => setTimeout(resolve, 2000));
2216
2483
  let newValue = await this._replaceWithLocalData(textAnchor, world);
2217
2484
  if (newValue !== textAnchor) {
@@ -2229,8 +2496,16 @@ class StableBrowser {
2229
2496
  try {
2230
2497
  await _preCommand(state, this);
2231
2498
  state.info.text = textToVerify;
2499
+ let resultWithElementsFound = {
2500
+ length: 0,
2501
+ };
2232
2502
  while (true) {
2233
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2503
+ try {
2504
+ resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
2505
+ }
2506
+ catch (error) {
2507
+ // ignore
2508
+ }
2234
2509
  if (resultWithElementsFound.length === 0) {
2235
2510
  if (Date.now() - state.startTime > timeout) {
2236
2511
  throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
@@ -2238,51 +2513,56 @@ class StableBrowser {
2238
2513
  await new Promise((resolve) => setTimeout(resolve, 1000));
2239
2514
  continue;
2240
2515
  }
2241
- for (let i = 0; i < resultWithElementsFound.length; i++) {
2242
- foundAncore = true;
2243
- const result = resultWithElementsFound[i];
2244
- const token = result.randomToken;
2245
- const frame = result.frame;
2246
- const css = `[data-blinq-id="blinq-id-${token}"]`;
2247
- const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
2248
- const elements = Array.from(document.querySelectorAll(css));
2249
- for (let i = 0; i < elements.length; i++) {
2250
- const element = elements[i];
2251
- let climbParent = element;
2252
- for (let j = 0; j < climb; j++) {
2253
- climbParent = climbParent.parentElement;
2254
- if (!climbParent) {
2255
- break;
2516
+ try {
2517
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2518
+ foundAncore = true;
2519
+ const result = resultWithElementsFound[i];
2520
+ const token = result.randomToken;
2521
+ const frame = result.frame;
2522
+ let css = `[data-blinq-id-${token}]`;
2523
+ const climbArray1 = [];
2524
+ for (let i = 0; i < climb; i++) {
2525
+ climbArray1.push("..");
2526
+ }
2527
+ let climbXpath = "xpath=" + climbArray1.join("/");
2528
+ css = css + " >> " + climbXpath;
2529
+ const count = await frame.locator(css).count();
2530
+ for (let j = 0; j < count; j++) {
2531
+ const continer = await frame.locator(css).nth(j);
2532
+ const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, false, true, {});
2533
+ if (result.elementCount > 0) {
2534
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2535
+ await this._highlightElements(frame, dataAttribute);
2536
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2537
+ // if (world && world.screenshot && !world.screenshotPath) {
2538
+ // console.log(`Highlighting for vtrt while running from recorder`);
2539
+ // this._highlightElements(frame, dataAttribute)
2540
+ // .then(async () => {
2541
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2542
+ // this._unhighlightElements(frame, dataAttribute).then(
2543
+ // () => {}
2544
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2545
+ // );
2546
+ // })
2547
+ // .catch(e);
2548
+ // }
2549
+ //await this._highlightElements(frame, cssAnchor);
2550
+ const element = await frame.locator(dataAttribute).first();
2551
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2552
+ // await this._unhighlightElements(frame, dataAttribute);
2553
+ if (element) {
2554
+ await this.scrollIfNeeded(element, state.info);
2555
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2256
2556
  }
2257
- }
2258
- if (!climbParent) {
2259
- continue;
2260
- }
2261
- const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
2262
- if (foundElements.length > 0) {
2263
- // set the container element attribute
2264
- element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
2265
- climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
2266
- foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
2267
- return { found: true };
2557
+ await _screenshot(state, this);
2558
+ return state.info;
2268
2559
  }
2269
2560
  }
2270
- return { found: false };
2271
- }, [css, climb, textToVerify, result.randomToken]);
2272
- if (findResult.found === true) {
2273
- const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
2274
- const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2275
- await this._highlightElements(frame, dataAttribute);
2276
- await this._highlightElements(frame, cssAnchor);
2277
- const element = await frame.$(dataAttribute);
2278
- if (element) {
2279
- await this.scrollIfNeeded(element, state.info);
2280
- await element.dispatchEvent("bvt_verify_page_contains_text");
2281
- }
2282
- await _screenshot(state, this);
2283
- return state.info;
2284
2561
  }
2285
2562
  }
2563
+ catch (error) {
2564
+ console.error(error);
2565
+ }
2286
2566
  }
2287
2567
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2288
2568
  }
@@ -2293,6 +2573,30 @@ class StableBrowser {
2293
2573
  _commandFinally(state, this);
2294
2574
  }
2295
2575
  }
2576
+ async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
2577
+ const frames = this.page.frames();
2578
+ let results = [];
2579
+ let ignoreCase = false;
2580
+ for (let i = 0; i < frames.length; i++) {
2581
+ const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
2582
+ result.frame = frames[i];
2583
+ const climbArray = [];
2584
+ for (let i = 0; i < climb; i++) {
2585
+ climbArray.push("..");
2586
+ }
2587
+ let climbXpath = "xpath=" + climbArray.join("/");
2588
+ const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
2589
+ const count = await frames[i].locator(newLocator).count();
2590
+ if (count > 0) {
2591
+ result.elementCount = count;
2592
+ result.locator = newLocator;
2593
+ results.push(result);
2594
+ }
2595
+ }
2596
+ // state.info.results = results;
2597
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2598
+ return resultWithElementsFound;
2599
+ }
2296
2600
  async visualVerification(text, options = {}, world = null) {
2297
2601
  const startTime = Date.now();
2298
2602
  let error = null;
@@ -2311,10 +2615,13 @@ class StableBrowser {
2311
2615
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2312
2616
  info.screenshotPath = screenshotPath;
2313
2617
  const screenshot = await this.takeScreenshot();
2314
- const request = {
2315
- method: "POST",
2618
+ let request = {
2619
+ method: "post",
2620
+ maxBodyLength: Infinity,
2316
2621
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2317
2622
  headers: {
2623
+ "x-bvt-project-id": path.basename(this.project_path),
2624
+ "x-source": "aaa",
2318
2625
  "Content-Type": "application/json",
2319
2626
  Authorization: `Bearer ${process.env.TOKEN}`,
2320
2627
  },
@@ -2323,7 +2630,7 @@ class StableBrowser {
2323
2630
  screenshot: screenshot,
2324
2631
  }),
2325
2632
  };
2326
- let result = await this.context.api.request(request);
2633
+ const result = await axios.request(request);
2327
2634
  if (result.data.status !== true) {
2328
2635
  throw new Error("Visual validation failed");
2329
2636
  }
@@ -2351,6 +2658,7 @@ class StableBrowser {
2351
2658
  _reportToWorld(world, {
2352
2659
  type: Types.VERIFY_VISUAL,
2353
2660
  text: "Visual verification",
2661
+ _text: "Visual verification of " + text,
2354
2662
  screenshotId,
2355
2663
  result: error
2356
2664
  ? {
@@ -2617,6 +2925,32 @@ class StableBrowser {
2617
2925
  }
2618
2926
  return timeout;
2619
2927
  }
2928
+ _getFindElementTimeout(options) {
2929
+ if (options && options.timeout) {
2930
+ return options.timeout;
2931
+ }
2932
+ if (this.configuration.find_element_timeout) {
2933
+ return this.configuration.find_element_timeout;
2934
+ }
2935
+ return 30000;
2936
+ }
2937
+ async saveStoreState(path = null, world = null) {
2938
+ const storageState = await this.page.context().storageState();
2939
+ //const testDataFile = _getDataFile(world, this.context, this);
2940
+ if (path) {
2941
+ // save { storageState: storageState } into the path
2942
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
2943
+ }
2944
+ else {
2945
+ await this.setTestData({ storageState: storageState }, world);
2946
+ }
2947
+ }
2948
+ async restoreSaveState(path = null, world = null) {
2949
+ await refreshBrowser(this, path, world);
2950
+ this.registerEventListeners(this.context);
2951
+ registerNetworkEvents(this.world, this, this.context, this.page);
2952
+ registerDownloadEvent(this.page, this.world, this.context);
2953
+ }
2620
2954
  async waitForPageLoad(options = {}, world = null) {
2621
2955
  let timeout = this._getLoadTimeout(options);
2622
2956
  const promiseArray = [];
@@ -2684,6 +3018,7 @@ class StableBrowser {
2684
3018
  highlight: false,
2685
3019
  type: Types.CLOSE_PAGE,
2686
3020
  text: `Close page`,
3021
+ _text: `Close the page`,
2687
3022
  operation: "closePage",
2688
3023
  log: "***** close page *****\n",
2689
3024
  throwError: false,
@@ -2700,8 +3035,95 @@ class StableBrowser {
2700
3035
  _commandFinally(state, this);
2701
3036
  }
2702
3037
  }
3038
+ async tableCellOperation(headerText, rowText, options, _params, world = null) {
3039
+ let operation = null;
3040
+ if (!options || !options.operation) {
3041
+ throw new Error("operation is not defined");
3042
+ }
3043
+ operation = options.operation;
3044
+ // validate operation is one of the supported operations
3045
+ if (operation != "click" && operation != "hover+click") {
3046
+ throw new Error("operation is not supported");
3047
+ }
3048
+ const state = {
3049
+ options,
3050
+ world,
3051
+ locate: false,
3052
+ scroll: false,
3053
+ highlight: false,
3054
+ type: Types.TABLE_OPERATION,
3055
+ text: `Table operation`,
3056
+ _text: `Table ${operation} operation`,
3057
+ operation: operation,
3058
+ log: "***** Table operation *****\n",
3059
+ };
3060
+ const timeout = this._getFindElementTimeout(options);
3061
+ try {
3062
+ await _preCommand(state, this);
3063
+ const start = Date.now();
3064
+ let cellArea = null;
3065
+ while (true) {
3066
+ try {
3067
+ cellArea = await _findCellArea(headerText, rowText, this, state);
3068
+ if (cellArea) {
3069
+ break;
3070
+ }
3071
+ }
3072
+ catch (e) {
3073
+ // ignore
3074
+ }
3075
+ if (Date.now() - start > timeout) {
3076
+ throw new Error(`Cell not found in table`);
3077
+ }
3078
+ await new Promise((resolve) => setTimeout(resolve, 1000));
3079
+ }
3080
+ switch (operation) {
3081
+ case "click":
3082
+ if (!options.css) {
3083
+ // will click in the center of the cell
3084
+ let xOffset = 0;
3085
+ let yOffset = 0;
3086
+ if (options.xOffset) {
3087
+ xOffset = options.xOffset;
3088
+ }
3089
+ if (options.yOffset) {
3090
+ yOffset = options.yOffset;
3091
+ }
3092
+ await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
3093
+ }
3094
+ else {
3095
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3096
+ if (results.length === 0) {
3097
+ throw new Error(`Element not found in cell area`);
3098
+ }
3099
+ state.element = results[0];
3100
+ await performAction("click", state.element, options, this, state, _params);
3101
+ }
3102
+ break;
3103
+ case "hover+click":
3104
+ if (!options.css) {
3105
+ throw new Error("css is not defined");
3106
+ }
3107
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3108
+ if (results.length === 0) {
3109
+ throw new Error(`Element not found in cell area`);
3110
+ }
3111
+ state.element = results[0];
3112
+ await performAction("hover+click", state.element, options, this, state, _params);
3113
+ break;
3114
+ default:
3115
+ throw new Error("operation is not supported");
3116
+ }
3117
+ }
3118
+ catch (e) {
3119
+ await _commandError(state, e, this);
3120
+ }
3121
+ finally {
3122
+ _commandFinally(state, this);
3123
+ }
3124
+ }
2703
3125
  saveTestDataAsGlobal(options, world) {
2704
- const dataFile = this._getDataFile(world);
3126
+ const dataFile = _getDataFile(world, this.context, this);
2705
3127
  process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2706
3128
  this.logger.info("Save the scenario test data as global for the following scenarios.");
2707
3129
  }
@@ -2731,6 +3153,7 @@ class StableBrowser {
2731
3153
  _reportToWorld(world, {
2732
3154
  type: Types.SET_VIEWPORT,
2733
3155
  text: "set viewport size to " + width + "x" + hight,
3156
+ _text: "Set the viewport size to " + width + "x" + hight,
2734
3157
  screenshotId,
2735
3158
  result: error
2736
3159
  ? {
@@ -2802,14 +3225,25 @@ class StableBrowser {
2802
3225
  }
2803
3226
  }
2804
3227
  async beforeStep(world, step) {
2805
- this.stepName = step.pickleStep.text;
2806
- this.logger.info("step: " + this.stepName);
2807
3228
  if (this.stepIndex === undefined) {
2808
3229
  this.stepIndex = 0;
2809
3230
  }
2810
3231
  else {
2811
3232
  this.stepIndex++;
2812
3233
  }
3234
+ if (step && step.pickleStep && step.pickleStep.text) {
3235
+ this.stepName = step.pickleStep.text;
3236
+ this.logger.info("step: " + this.stepName);
3237
+ }
3238
+ else if (step && step.text) {
3239
+ this.stepName = step.text;
3240
+ }
3241
+ else {
3242
+ this.stepName = "step " + this.stepIndex;
3243
+ }
3244
+ if (this.context) {
3245
+ this.context.examplesRow = extractStepExampleParameters(step);
3246
+ }
2813
3247
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2814
3248
  if (this.context.browserObject.context) {
2815
3249
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2822,6 +3256,41 @@ class StableBrowser {
2822
3256
  this.saveTestDataAsGlobal({}, world);
2823
3257
  }
2824
3258
  }
3259
+ if (this.initSnapshotTaken === false) {
3260
+ this.initSnapshotTaken = true;
3261
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3262
+ const snapshot = await this.getAriaSnapshot();
3263
+ if (snapshot) {
3264
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
3265
+ }
3266
+ }
3267
+ }
3268
+ }
3269
+ async getAriaSnapshot() {
3270
+ try {
3271
+ // find the page url
3272
+ const url = await this.page.url();
3273
+ // extract the path from the url
3274
+ const path = new URL(url).pathname;
3275
+ // get the page title
3276
+ const title = await this.page.title();
3277
+ // go over other frams
3278
+ const frames = this.page.frames();
3279
+ const snapshots = [];
3280
+ const content = [`- path: ${path}`, `- title: ${title}`];
3281
+ const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
3282
+ for (let i = 0; i < frames.length; i++) {
3283
+ content.push(`- frame: ${i}`);
3284
+ const frame = frames[i];
3285
+ const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
3286
+ content.push(snapshot);
3287
+ }
3288
+ return content.join("\n");
3289
+ }
3290
+ catch (e) {
3291
+ console.error(e);
3292
+ }
3293
+ return null;
2825
3294
  }
2826
3295
  async afterStep(world, step) {
2827
3296
  this.stepName = null;
@@ -2832,6 +3301,16 @@ class StableBrowser {
2832
3301
  });
2833
3302
  }
2834
3303
  }
3304
+ if (this.context) {
3305
+ this.context.examplesRow = null;
3306
+ }
3307
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3308
+ const snapshot = await this.getAriaSnapshot();
3309
+ if (snapshot) {
3310
+ const obj = {};
3311
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
3312
+ }
3313
+ }
2835
3314
  }
2836
3315
  }
2837
3316
  function createTimedPromise(promise, label) {