automation_model 1.0.479-dev → 1.0.479-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 (48) hide show
  1. package/lib/api.d.ts +1 -1
  2. package/lib/api.js +91 -92
  3. package/lib/api.js.map +1 -1
  4. package/lib/auto_page.d.ts +2 -1
  5. package/lib/auto_page.js +39 -17
  6. package/lib/auto_page.js.map +1 -1
  7. package/lib/browser_manager.d.ts +6 -3
  8. package/lib/browser_manager.js +86 -16
  9. package/lib/browser_manager.js.map +1 -1
  10. package/lib/command_common.d.ts +1 -0
  11. package/lib/command_common.js +60 -5
  12. package/lib/command_common.js.map +1 -1
  13. package/lib/error-messages.d.ts +6 -0
  14. package/lib/error-messages.js +188 -0
  15. package/lib/error-messages.js.map +1 -0
  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/index.d.ts +1 -0
  20. package/lib/index.js +1 -0
  21. package/lib/index.js.map +1 -1
  22. package/lib/init_browser.d.ts +2 -1
  23. package/lib/init_browser.js +31 -4
  24. package/lib/init_browser.js.map +1 -1
  25. package/lib/locate_element.js +5 -3
  26. package/lib/locate_element.js.map +1 -1
  27. package/lib/locator.d.ts +36 -0
  28. package/lib/locator.js +165 -0
  29. package/lib/locator.js.map +1 -1
  30. package/lib/locator_log.d.ts +26 -0
  31. package/lib/locator_log.js +69 -0
  32. package/lib/locator_log.js.map +1 -0
  33. package/lib/network.d.ts +3 -0
  34. package/lib/network.js +155 -0
  35. package/lib/network.js.map +1 -0
  36. package/lib/stable_browser.d.ts +44 -9
  37. package/lib/stable_browser.js +484 -255
  38. package/lib/stable_browser.js.map +1 -1
  39. package/lib/table.d.ts +13 -0
  40. package/lib/table.js +187 -0
  41. package/lib/table.js.map +1 -0
  42. package/lib/test_context.d.ts +1 -0
  43. package/lib/test_context.js +1 -0
  44. package/lib/test_context.js.map +1 -1
  45. package/lib/utils.d.ts +3 -1
  46. package/lib/utils.js +68 -1
  47. package/lib/utils.js.map +1 -1
  48. package/package.json +5 -4
@@ -10,15 +10,17 @@ import { getDateTimeValue } from "./date_time.js";
10
10
  import drawRectangle from "./drawRect.js";
11
11
  //import { closeUnexpectedPopups } from "./popups.js";
12
12
  import { getTableCells, getTableData } from "./table_analyze.js";
13
- import objectPath from "object-path";
14
- import { decrypt } from "./utils.js";
13
+ import { maskValue, replaceWithLocalTestData } from "./utils.js";
15
14
  import csv from "csv-parser";
16
15
  import { Readable } from "node:stream";
17
16
  import readline from "readline";
18
17
  import { getContext } from "./init_browser.js";
19
18
  import { locate_element } from "./locate_element.js";
20
- import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot } from "./command_common.js";
21
- const Types = {
19
+ import { randomUUID } from "crypto";
20
+ import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
21
+ import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
22
+ import { LocatorLog } from "./locator_log.js";
23
+ export const Types = {
22
24
  CLICK: "click_element",
23
25
  NAVIGATE: "navigate",
24
26
  FILL: "fill_element",
@@ -44,6 +46,8 @@ const Types = {
44
46
  VERIFY_VISUAL: "verify_visual",
45
47
  LOAD_DATA: "load_data",
46
48
  SET_INPUT: "set_input",
49
+ WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
50
+ VERIFY_ATTRIBUTE: "verify_element_attribute",
47
51
  };
48
52
  export const apps = {};
49
53
  class StableBrowser {
@@ -57,6 +61,7 @@ class StableBrowser {
57
61
  networkLogger = null;
58
62
  configuration = null;
59
63
  appName = "main";
64
+ tags = null;
60
65
  constructor(browser, page, logger = null, context = null, world = null) {
61
66
  this.browser = browser;
62
67
  this.page = page;
@@ -87,23 +92,50 @@ class StableBrowser {
87
92
  catch (e) {
88
93
  this.logger.error("unable to read ai_config.json");
89
94
  }
90
- context.pageLoading = { status: false };
91
- context.pages = [this.page];
92
95
  const logFolder = path.join(this.project_path, "logs", "web");
93
96
  this.world = world;
97
+ context.pages = [this.page];
98
+ context.pageLoading = { status: false };
94
99
  this.registerEventListeners(this.context);
100
+ registerNetworkEvents(this.world, this, this.context, this.page);
101
+ registerDownloadEvent(this.page, this.world, this.context);
102
+ }
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));
95
120
  }
96
121
  registerEventListeners(context) {
97
122
  this.registerConsoleLogListener(this.page, context);
98
- this.registerRequestListener(this.page, context, this.webLogFile);
123
+ // this.registerRequestListener(this.page, context, this.webLogFile);
99
124
  if (!context.pageLoading) {
100
125
  context.pageLoading = { status: false };
101
126
  }
102
127
  context.playContext.on("page", async function (page) {
128
+ if (this.configuration && this.configuration.closePopups === true) {
129
+ console.log("close unexpected popups");
130
+ await page.close();
131
+ return;
132
+ }
103
133
  context.pageLoading.status = true;
104
134
  this.page = page;
105
135
  context.page = page;
106
136
  context.pages.push(page);
137
+ registerNetworkEvents(this.world, this, context, this.page);
138
+ registerDownloadEvent(this.page, this.world, context);
107
139
  page.on("close", async () => {
108
140
  if (this.context && this.context.pages && this.context.pages.length > 1) {
109
141
  this.context.pages.pop();
@@ -133,9 +165,9 @@ class StableBrowser {
133
165
  if (this.appName === appName) {
134
166
  return;
135
167
  }
136
- let newContextCreated = false;
168
+ let navigate = false;
137
169
  if (!apps[appName]) {
138
- let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this);
170
+ let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
139
171
  newContextCreated = true;
140
172
  apps[appName] = {
141
173
  context: newContext,
@@ -148,8 +180,7 @@ class StableBrowser {
148
180
  this._copyContext(apps[appName], this);
149
181
  apps[this.appName] = tempContext;
150
182
  this.appName = appName;
151
- if (newContextCreated) {
152
- this.registerEventListeners(this.context);
183
+ if (navigate) {
153
184
  await this.goto(this.context.environment.baseUrl);
154
185
  await this.waitForPageLoad();
155
186
  }
@@ -219,7 +250,7 @@ class StableBrowser {
219
250
  this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
220
251
  }
221
252
  catch (error) {
222
- console.error("Error in request listener", error);
253
+ // console.error("Error in request listener", error);
223
254
  context.networkLogger.push({
224
255
  error: "not able to listen",
225
256
  message: error.message,
@@ -332,7 +363,7 @@ class StableBrowser {
332
363
  if (css && css.locator) {
333
364
  css = css.locator;
334
365
  }
335
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
366
+ let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
336
367
  if (result.elementCount === 0) {
337
368
  return;
338
369
  }
@@ -384,7 +415,7 @@ class StableBrowser {
384
415
  }
385
416
  document.collectAllShadowDomElements = collectAllShadowDomElements;
386
417
  if (!tag) {
387
- tag = "*";
418
+ tag = "*:not(script, style, head)";
388
419
  }
389
420
  let regexpSearch = document.getRegex(text);
390
421
  if (regexpSearch) {
@@ -466,9 +497,21 @@ class StableBrowser {
466
497
  }, [text1, tag1, regex1, partial1]);
467
498
  }
468
499
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
500
+ if (!info) {
501
+ info = {};
502
+ }
503
+ if (!info.failCause) {
504
+ info.failCause = {};
505
+ }
506
+ if (!info.log) {
507
+ info.log = "";
508
+ info.locatorLog = new LocatorLog(selectorHierarchy);
509
+ }
469
510
  let locatorSearch = selectorHierarchy[index];
511
+ let originalLocatorSearch = "";
470
512
  try {
471
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
513
+ originalLocatorSearch = this._fixUsingParams(JSON.stringify(locatorSearch), _params);
514
+ locatorSearch = JSON.parse(originalLocatorSearch);
472
515
  }
473
516
  catch (e) {
474
517
  console.error(e);
@@ -478,13 +521,18 @@ class StableBrowser {
478
521
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
479
522
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
480
523
  if (!locatorString) {
524
+ info.failCause.textNotFound = true;
525
+ info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
481
526
  return;
482
527
  }
483
528
  locator = this._getLocator({ css: locatorString }, scope, _params);
484
529
  }
485
530
  else if (locatorSearch.text) {
486
- let result = await this._locateElementByText(scope, this._fixUsingParams(locatorSearch.text, _params), locatorSearch.tag, false, locatorSearch.partial === true, _params);
531
+ let text = this._fixUsingParams(locatorSearch.text, _params);
532
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
487
533
  if (result.elementCount === 0) {
534
+ info.failCause.textNotFound = true;
535
+ info.failCause.lastError = "failed to locate element by text: " + text;
488
536
  return;
489
537
  }
490
538
  locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
@@ -501,13 +549,22 @@ class StableBrowser {
501
549
  // cssHref = true;
502
550
  // }
503
551
  let count = await locator.count();
552
+ if (count > 0 && !info.failCause.count) {
553
+ info.failCause.count = count;
554
+ }
504
555
  //info.log += "total elements found " + count + "\n";
505
556
  //let visibleCount = 0;
506
557
  let visibleLocator = null;
507
- if (locatorSearch.index && locatorSearch.index < count) {
558
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
508
559
  foundLocators.push(locator.nth(locatorSearch.index));
560
+ if (info.locatorLog) {
561
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
562
+ }
509
563
  return;
510
564
  }
565
+ if (info.locatorLog && count === 0) {
566
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
567
+ }
511
568
  for (let j = 0; j < count; j++) {
512
569
  let visible = await locator.nth(j).isVisible();
513
570
  const enabled = await locator.nth(j).isEnabled();
@@ -516,24 +573,40 @@ class StableBrowser {
516
573
  }
517
574
  if (visible && enabled) {
518
575
  foundLocators.push(locator.nth(j));
576
+ if (info.locatorLog) {
577
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
578
+ }
519
579
  }
520
580
  else {
581
+ info.failCause.visible = visible;
582
+ info.failCause.enabled = enabled;
521
583
  if (!info.printMessages) {
522
584
  info.printMessages = {};
523
585
  }
586
+ if (info.locatorLog && !visible) {
587
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
588
+ }
589
+ if (info.locatorLog && !enabled) {
590
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
591
+ }
524
592
  if (!info.printMessages[j.toString()]) {
525
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
593
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
526
594
  info.printMessages[j.toString()] = true;
527
595
  }
528
596
  }
529
597
  }
530
598
  }
531
599
  async closeUnexpectedPopups(info, _params) {
600
+ if (!info) {
601
+ info = {};
602
+ info.failCause = {};
603
+ info.log = "";
604
+ }
532
605
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
533
606
  if (!info) {
534
607
  info = {};
535
608
  }
536
- info.log += "scan for popup handlers" + "\n";
609
+ //info.log += "scan for popup handlers" + "\n";
537
610
  const handlerGroup = [];
538
611
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
539
612
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -560,9 +633,21 @@ class StableBrowser {
560
633
  }
561
634
  if (result.foundElements.length > 0) {
562
635
  let dialogCloseLocator = result.foundElements[0].locator;
563
- await dialogCloseLocator.click();
564
- // wait for the dialog to close
565
- await dialogCloseLocator.waitFor({ state: "hidden" });
636
+ try {
637
+ await scope?.evaluate(() => {
638
+ window.__isClosingPopups = true;
639
+ });
640
+ await dialogCloseLocator.click();
641
+ // wait for the dialog to close
642
+ await dialogCloseLocator.waitFor({ state: "hidden" });
643
+ }
644
+ catch (e) {
645
+ }
646
+ finally {
647
+ await scope?.evaluate(() => {
648
+ window.__isClosingPopups = false;
649
+ });
650
+ }
566
651
  return { rerun: true };
567
652
  }
568
653
  }
@@ -586,7 +671,13 @@ class StableBrowser {
586
671
  }
587
672
  throw new Error("unable to locate element " + JSON.stringify(selectors));
588
673
  }
589
- async _findFrameScope(selectors, timeout = 30000) {
674
+ async _findFrameScope(selectors, timeout = 30000, info) {
675
+ if (!info) {
676
+ info = {};
677
+ info.failCause = {};
678
+ info.log = "";
679
+ }
680
+ let startTime = Date.now();
590
681
  let scope = this.page;
591
682
  if (selectors.frame) {
592
683
  return selectors.frame;
@@ -617,9 +708,11 @@ class StableBrowser {
617
708
  }
618
709
  return framescope;
619
710
  };
711
+ let fLocator = null;
620
712
  while (true) {
621
713
  let frameFound = false;
622
714
  if (selectors.nestFrmLoc) {
715
+ fLocator = selectors.nestFrmLoc;
623
716
  scope = await findFrame(selectors.nestFrmLoc, scope);
624
717
  frameFound = true;
625
718
  break;
@@ -628,6 +721,7 @@ class StableBrowser {
628
721
  for (let i = 0; i < selectors.frameLocators.length; i++) {
629
722
  let frameLocator = selectors.frameLocators[i];
630
723
  if (frameLocator.css) {
724
+ fLocator = frameLocator.css;
631
725
  scope = scope.frameLocator(frameLocator.css);
632
726
  frameFound = true;
633
727
  break;
@@ -635,16 +729,25 @@ class StableBrowser {
635
729
  }
636
730
  }
637
731
  if (!frameFound && selectors.iframe_src) {
732
+ fLocator = selectors.iframe_src;
638
733
  scope = this.page.frame({ url: selectors.iframe_src });
639
734
  }
640
735
  if (!scope) {
641
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
642
- if (performance.now() - startTime > timeout) {
736
+ if (info && info.locatorLog) {
737
+ info.locatorLog.setFrameSearchStatus("frame-" + fLocator, "NOT_FOUND");
738
+ }
739
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
740
+ if (Date.now() - startTime > timeout) {
741
+ info.failCause.iframeNotFound = true;
742
+ info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
643
743
  throw new Error("unable to locate iframe " + selectors.iframe_src);
644
744
  }
645
745
  await new Promise((resolve) => setTimeout(resolve, 1000));
646
746
  }
647
747
  else {
748
+ if (info && info.locatorLog) {
749
+ info.locatorLog.setFrameSearchStatus("frame-" + fLocator, "FOUND");
750
+ }
648
751
  break;
649
752
  }
650
753
  }
@@ -654,20 +757,27 @@ class StableBrowser {
654
757
  }
655
758
  return scope;
656
759
  }
657
- async _getDocumentBody(selectors, timeout = 30000) {
658
- let scope = await this._findFrameScope(selectors, timeout);
760
+ async _getDocumentBody(selectors, timeout = 30000, info) {
761
+ let scope = await this._findFrameScope(selectors, timeout, info);
659
762
  return scope.evaluate(() => {
660
763
  var bodyContent = document.body.innerHTML;
661
764
  return bodyContent;
662
765
  });
663
766
  }
664
767
  async _locate_internal(selectors, info, _params, timeout = 30000) {
768
+ if (!info) {
769
+ info = {};
770
+ info.failCause = {};
771
+ info.log = "";
772
+ info.locatorLog = new LocatorLog(selectors);
773
+ }
665
774
  let highPriorityTimeout = 5000;
666
775
  let visibleOnlyTimeout = 6000;
667
- let startTime = performance.now();
776
+ let startTime = Date.now();
668
777
  let locatorsCount = 0;
778
+ let lazy_scroll = false;
669
779
  //let arrayMode = Array.isArray(selectors);
670
- let scope = await this._findFrameScope(selectors, timeout);
780
+ let scope = await this._findFrameScope(selectors, timeout, info);
671
781
  let selectorsLocators = null;
672
782
  selectorsLocators = selectors.locators;
673
783
  // group selectors by priority
@@ -754,21 +864,33 @@ class StableBrowser {
754
864
  return maxCountElement.locator;
755
865
  }
756
866
  }
757
- if (performance.now() - startTime > timeout) {
867
+ if (Date.now() - startTime > timeout) {
758
868
  break;
759
869
  }
760
- if (performance.now() - startTime > highPriorityTimeout) {
870
+ if (Date.now() - startTime > highPriorityTimeout) {
761
871
  info.log += "high priority timeout, will try all elements" + "\n";
762
872
  highPriorityOnly = false;
873
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
874
+ lazy_scroll = true;
875
+ await this.scrollPageToLoadLazyElements();
876
+ }
763
877
  }
764
- if (performance.now() - startTime > visibleOnlyTimeout) {
878
+ if (Date.now() - startTime > visibleOnlyTimeout) {
765
879
  info.log += "visible only timeout, will try all elements" + "\n";
766
880
  visibleOnly = false;
767
881
  }
768
882
  await new Promise((resolve) => setTimeout(resolve, 1000));
769
883
  }
770
884
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
771
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
885
+ // if (info.locatorLog) {
886
+ // const lines = info.locatorLog.toString().split("\n");
887
+ // for (let line of lines) {
888
+ // this.logger.debug(line);
889
+ // }
890
+ // }
891
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
892
+ info.failCause.locatorNotFound = true;
893
+ info.failCause.lastError = "failed to locate unique element";
772
894
  throw new Error("failed to locate first element no elements found, " + info.log);
773
895
  }
774
896
  async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
@@ -800,10 +922,29 @@ class StableBrowser {
800
922
  });
801
923
  result.locatorIndex = i;
802
924
  }
925
+ if (foundLocators.length > 1) {
926
+ info.failCause.foundMultiple = true;
927
+ if (info.locatorLog) {
928
+ info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
929
+ }
930
+ }
803
931
  }
804
932
  return result;
805
933
  }
806
934
  async simpleClick(elementDescription, _params, options = {}, world = null) {
935
+ const state = {
936
+ locate: false,
937
+ scroll: false,
938
+ highlight: false,
939
+ _params,
940
+ options,
941
+ world,
942
+ type: Types.CLICK,
943
+ text: "Click element",
944
+ operation: "simpleClick",
945
+ log: "***** click on " + elementDescription + " *****\n",
946
+ };
947
+ _preCommand(state, this);
807
948
  const startTime = Date.now();
808
949
  let timeout = 30000;
809
950
  if (options && options.timeout) {
@@ -827,13 +968,32 @@ class StableBrowser {
827
968
  }
828
969
  catch (e) {
829
970
  if (performance.now() - startTime > timeout) {
830
- throw e;
971
+ // throw e;
972
+ try {
973
+ await _commandError(state, "timeout looking for " + elementDescription, this);
974
+ }
975
+ finally {
976
+ _commandFinally(state, this);
977
+ }
831
978
  }
832
979
  }
833
980
  await new Promise((resolve) => setTimeout(resolve, 3000));
834
981
  }
835
982
  }
836
983
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
984
+ const state = {
985
+ locate: false,
986
+ scroll: false,
987
+ highlight: false,
988
+ _params,
989
+ options,
990
+ world,
991
+ type: Types.FILL,
992
+ text: "Fill element",
993
+ operation: "simpleClickType",
994
+ log: "***** click type on " + elementDescription + " *****\n",
995
+ };
996
+ _preCommand(state, this);
837
997
  const startTime = Date.now();
838
998
  let timeout = 30000;
839
999
  if (options && options.timeout) {
@@ -857,7 +1017,13 @@ class StableBrowser {
857
1017
  }
858
1018
  catch (e) {
859
1019
  if (performance.now() - startTime > timeout) {
860
- throw e;
1020
+ // throw e;
1021
+ try {
1022
+ await _commandError(state, "timeout looking for " + elementDescription, this);
1023
+ }
1024
+ finally {
1025
+ _commandFinally(state, this);
1026
+ }
861
1027
  }
862
1028
  }
863
1029
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -881,13 +1047,13 @@ class StableBrowser {
881
1047
  }
882
1048
  try {
883
1049
  await state.element.click();
884
- await new Promise((resolve) => setTimeout(resolve, 1000));
1050
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
885
1051
  }
886
1052
  catch (e) {
887
1053
  // await this.closeUnexpectedPopups();
888
1054
  state.element = await this._locate(selectors, state.info, _params);
889
1055
  await state.element.dispatchEvent("click");
890
- await new Promise((resolve) => setTimeout(resolve, 1000));
1056
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
891
1057
  }
892
1058
  await this.waitForPageLoad();
893
1059
  return state.info;
@@ -985,6 +1151,7 @@ class StableBrowser {
985
1151
  _params,
986
1152
  options,
987
1153
  world,
1154
+ value: values.toString(),
988
1155
  type: Types.SELECT,
989
1156
  text: `Select option: ${values}`,
990
1157
  operation: "selectOption",
@@ -1092,33 +1259,30 @@ class StableBrowser {
1092
1259
  }
1093
1260
  }
1094
1261
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1095
- _validateSelectors(selectors);
1096
- const startTime = Date.now();
1097
- let error = null;
1098
- let screenshotId = null;
1099
- let screenshotPath = null;
1100
- const info = {};
1101
- info.log = "";
1102
- info.operation = Types.SET_DATE_TIME;
1103
- info.selectors = selectors;
1104
- info.value = value;
1262
+ const state = {
1263
+ selectors,
1264
+ _params,
1265
+ value: await this._replaceWithLocalData(value, this),
1266
+ options,
1267
+ world,
1268
+ type: Types.SET_DATE_TIME,
1269
+ text: `Set date time value: ${value}`,
1270
+ operation: "setDateTime",
1271
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1272
+ throwError: false,
1273
+ };
1105
1274
  try {
1106
- value = await this._replaceWithLocalData(value, this);
1107
- let element = await this._locate(selectors, info, _params);
1108
- //insert red border around the element
1109
- await this.scrollIfNeeded(element, info);
1110
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1111
- await this._highlightElements(element);
1275
+ await _preCommand(state, this);
1112
1276
  try {
1113
- await element.click();
1277
+ await state.element.click();
1114
1278
  await new Promise((resolve) => setTimeout(resolve, 500));
1115
1279
  if (format) {
1116
- value = dayjs(value).format(format);
1117
- await element.fill(value);
1280
+ state.value = dayjs(state.value).format(format);
1281
+ await state.element.fill(state.value);
1118
1282
  }
1119
1283
  else {
1120
- const dateTimeValue = await getDateTimeValue({ value, element });
1121
- await element.evaluateHandle((el, dateTimeValue) => {
1284
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1285
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1122
1286
  el.value = ""; // clear input
1123
1287
  el.value = dateTimeValue;
1124
1288
  }, dateTimeValue);
@@ -1131,20 +1295,19 @@ class StableBrowser {
1131
1295
  }
1132
1296
  catch (err) {
1133
1297
  //await this.closeUnexpectedPopups();
1134
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1298
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1135
1299
  this.logger.info("Trying again");
1136
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1137
- info.screenshotPath = screenshotPath;
1138
- Object.assign(err, { info: info });
1300
+ await _screenshot(state, this);
1301
+ Object.assign(err, { info: state.info });
1139
1302
  await element.click();
1140
1303
  await new Promise((resolve) => setTimeout(resolve, 500));
1141
1304
  if (format) {
1142
- value = dayjs(value).format(format);
1143
- await element.fill(value);
1305
+ state.value = dayjs(state.value).format(format);
1306
+ await state.element.fill(state.value);
1144
1307
  }
1145
1308
  else {
1146
- const dateTimeValue = await getDateTimeValue({ value, element });
1147
- await element.evaluateHandle((el, dateTimeValue) => {
1309
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1310
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1148
1311
  el.value = ""; // clear input
1149
1312
  el.value = dateTimeValue;
1150
1313
  }, dateTimeValue);
@@ -1157,46 +1320,27 @@ class StableBrowser {
1157
1320
  }
1158
1321
  }
1159
1322
  catch (e) {
1160
- error = e;
1161
- throw e;
1323
+ await _commandError(state, e, this);
1162
1324
  }
1163
1325
  finally {
1164
- const endTime = Date.now();
1165
- this._reportToWorld(world, {
1166
- element_name: selectors.element_name,
1167
- type: Types.SET_DATE_TIME,
1168
- screenshotId,
1169
- value: value,
1170
- text: `setDateTime input with value: ${value}`,
1171
- result: error
1172
- ? {
1173
- status: "FAILED",
1174
- startTime,
1175
- endTime,
1176
- message: error === null || error === void 0 ? void 0 : error.message,
1177
- }
1178
- : {
1179
- status: "PASSED",
1180
- startTime,
1181
- endTime,
1182
- },
1183
- info: info,
1184
- });
1326
+ _commandFinally(state, this);
1185
1327
  }
1186
1328
  }
1187
1329
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1330
+ _value = unEscapeString(_value);
1331
+ const newValue = await this._replaceWithLocalData(_value, world);
1188
1332
  const state = {
1189
1333
  selectors,
1190
1334
  _params,
1191
- value: unEscapeString(_value),
1335
+ value: newValue,
1336
+ originalValue: _value,
1192
1337
  options,
1193
1338
  world,
1194
1339
  type: Types.FILL,
1195
1340
  text: `Click type input with value: ${_value}`,
1196
1341
  operation: "clickType",
1197
- log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
1342
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1198
1343
  };
1199
- const newValue = await this._replaceWithLocalData(state.value, world);
1200
1344
  if (newValue !== _value) {
1201
1345
  //this.logger.info(_value + "=" + newValue);
1202
1346
  _value = newValue;
@@ -1208,7 +1352,7 @@ class StableBrowser {
1208
1352
  try {
1209
1353
  let currentValue = await state.element.inputValue();
1210
1354
  if (currentValue) {
1211
- await element.fill("");
1355
+ await state.element.fill("");
1212
1356
  }
1213
1357
  }
1214
1358
  catch (e) {
@@ -1316,6 +1460,7 @@ class StableBrowser {
1316
1460
  let screenshotPath = null;
1317
1461
  if (!info.log) {
1318
1462
  info.log = "";
1463
+ info.locatorLog = new LocatorLog(selectors);
1319
1464
  }
1320
1465
  info.operation = "getText";
1321
1466
  info.selectors = selectors;
@@ -1365,7 +1510,7 @@ class StableBrowser {
1365
1510
  selectors,
1366
1511
  _params,
1367
1512
  pattern,
1368
- value: text,
1513
+ value: pattern,
1369
1514
  options,
1370
1515
  world,
1371
1516
  locate: false,
@@ -1654,11 +1799,9 @@ class StableBrowser {
1654
1799
  if (!fs.existsSync(world.screenshotPath)) {
1655
1800
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1656
1801
  }
1657
- let nextIndex = 1;
1658
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1659
- nextIndex++;
1660
- }
1661
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1802
+ // to make sure the path doesn't start with -
1803
+ const uuidStr = "id_" + randomUUID();
1804
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1662
1805
  try {
1663
1806
  await this.takeScreenshot(screenshotPath);
1664
1807
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1672,7 +1815,7 @@ class StableBrowser {
1672
1815
  catch (e) {
1673
1816
  this.logger.info("unable to take screenshot, ignored");
1674
1817
  }
1675
- result.screenshotId = nextIndex;
1818
+ result.screenshotId = uuidStr;
1676
1819
  result.screenshotPath = screenshotPath;
1677
1820
  if (info && info.box) {
1678
1821
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1770,74 +1913,101 @@ class StableBrowser {
1770
1913
  }
1771
1914
  }
1772
1915
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1773
- _validateSelectors(selectors);
1774
- const startTime = Date.now();
1775
- let error = null;
1776
- let screenshotId = null;
1777
- let screenshotPath = null;
1916
+ const state = {
1917
+ selectors,
1918
+ _params,
1919
+ attribute,
1920
+ variable,
1921
+ options,
1922
+ world,
1923
+ type: Types.EXTRACT,
1924
+ text: `Extract attribute from element`,
1925
+ operation: "extractAttribute",
1926
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1927
+ };
1778
1928
  await new Promise((resolve) => setTimeout(resolve, 2000));
1779
- const info = {};
1780
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1781
- info.operation = "extract";
1782
- info.selectors = selectors;
1783
1929
  try {
1784
- const element = await this._locate(selectors, info, _params);
1785
- await this._highlightElements(element);
1786
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1930
+ await _preCommand(state, this);
1787
1931
  switch (attribute) {
1788
1932
  case "inner_text":
1789
- info.value = await element.innerText();
1933
+ state.value = await state.element.innerText();
1790
1934
  break;
1791
1935
  case "href":
1792
- info.value = await element.getAttribute("href");
1936
+ state.value = await state.element.getAttribute("href");
1793
1937
  break;
1794
1938
  case "value":
1795
- info.value = await element.inputValue();
1939
+ state.value = await state.element.inputValue();
1796
1940
  break;
1797
1941
  default:
1798
- info.value = await element.getAttribute(attribute);
1942
+ state.value = await state.element.getAttribute(attribute);
1799
1943
  break;
1800
1944
  }
1801
- this[variable] = info.value;
1802
- if (world) {
1803
- world[variable] = info.value;
1945
+ state.info.value = state.value;
1946
+ this.setTestData({ [variable]: state.value }, world);
1947
+ this.logger.info("set test data: " + variable + "=" + state.value);
1948
+ return state.info;
1949
+ }
1950
+ catch (e) {
1951
+ await _commandError(state, e, this);
1952
+ }
1953
+ finally {
1954
+ _commandFinally(state, this);
1955
+ }
1956
+ }
1957
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1958
+ const state = {
1959
+ selectors,
1960
+ _params,
1961
+ attribute,
1962
+ value,
1963
+ options,
1964
+ world,
1965
+ type: Types.VERIFY_ATTRIBUTE,
1966
+ text: `Verify element attribute`,
1967
+ operation: "verifyAttribute",
1968
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1969
+ };
1970
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1971
+ let val;
1972
+ try {
1973
+ await _preCommand(state, this);
1974
+ switch (attribute) {
1975
+ case "innerText":
1976
+ val = String(await state.element.innerText());
1977
+ break;
1978
+ case "value":
1979
+ val = String(await state.element.inputValue());
1980
+ break;
1981
+ case "checked":
1982
+ val = String(await state.element.isChecked());
1983
+ break;
1984
+ case "disabled":
1985
+ val = String(await state.element.isDisabled());
1986
+ break;
1987
+ default:
1988
+ val = String(await state.element.getAttribute(attribute));
1989
+ break;
1804
1990
  }
1805
- this.setTestData({ [variable]: info.value }, world);
1806
- this.logger.info("set test data: " + variable + "=" + info.value);
1807
- return info;
1991
+ let regex;
1992
+ if (value.startsWith("/") && value.endsWith("/")) {
1993
+ const patternBody = value.slice(1, -1);
1994
+ regex = new RegExp(patternBody, "g");
1995
+ }
1996
+ else {
1997
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1998
+ regex = new RegExp(escapedPattern, "g");
1999
+ }
2000
+ if (!val.match(regex)) {
2001
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
2002
+ }
2003
+ state.info.value = val;
2004
+ return state.info;
1808
2005
  }
1809
2006
  catch (e) {
1810
- //await this.closeUnexpectedPopups();
1811
- this.logger.error("extract failed " + info.log);
1812
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1813
- info.screenshotPath = screenshotPath;
1814
- Object.assign(e, { info: info });
1815
- error = e;
1816
- throw e;
2007
+ await _commandError(state, e, this);
1817
2008
  }
1818
2009
  finally {
1819
- const endTime = Date.now();
1820
- this._reportToWorld(world, {
1821
- element_name: selectors.element_name,
1822
- type: Types.EXTRACT_ATTRIBUTE,
1823
- variable: variable,
1824
- value: info.value,
1825
- text: "Extract attribute from element",
1826
- screenshotId,
1827
- result: error
1828
- ? {
1829
- status: "FAILED",
1830
- startTime,
1831
- endTime,
1832
- message: error?.message,
1833
- }
1834
- : {
1835
- status: "PASSED",
1836
- startTime,
1837
- endTime,
1838
- },
1839
- info: info,
1840
- });
2010
+ _commandFinally(state, this);
1841
2011
  }
1842
2012
  }
1843
2013
  async extractEmailData(emailAddress, options, world) {
@@ -1914,7 +2084,8 @@ class StableBrowser {
1914
2084
  catch (e) {
1915
2085
  errorCount++;
1916
2086
  if (errorCount > 3) {
1917
- throw e;
2087
+ // throw e;
2088
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1918
2089
  }
1919
2090
  // ignore
1920
2091
  }
@@ -2025,11 +2196,12 @@ class StableBrowser {
2025
2196
  info.screenshotPath = screenshotPath;
2026
2197
  Object.assign(e, { info: info });
2027
2198
  error = e;
2028
- throw e;
2199
+ // throw e;
2200
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2029
2201
  }
2030
2202
  finally {
2031
2203
  const endTime = Date.now();
2032
- this._reportToWorld(world, {
2204
+ _reportToWorld(world, {
2033
2205
  type: Types.VERIFY_PAGE_PATH,
2034
2206
  text: "Verify page path",
2035
2207
  screenshotId,
@@ -2051,52 +2223,58 @@ class StableBrowser {
2051
2223
  }
2052
2224
  async verifyTextExistInPage(text, options = {}, world = null) {
2053
2225
  text = unEscapeString(text);
2054
- const startTime = Date.now();
2226
+ const state = {
2227
+ text_search: text,
2228
+ options,
2229
+ world,
2230
+ locate: false,
2231
+ scroll: false,
2232
+ highlight: false,
2233
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2234
+ text: `Verify text exists in page`,
2235
+ operation: "verifyTextExistInPage",
2236
+ log: "***** verify text " + text + " exists in page *****\n",
2237
+ };
2055
2238
  const timeout = this._getLoadTimeout(options);
2056
- let error = null;
2057
- let screenshotId = null;
2058
- let screenshotPath = null;
2059
2239
  await new Promise((resolve) => setTimeout(resolve, 2000));
2060
- const info = {};
2061
- info.log = "***** verify text " + text + " exists in page *****\n";
2062
- info.operation = "verifyTextExistInPage";
2063
2240
  const newValue = await this._replaceWithLocalData(text, world);
2064
2241
  if (newValue !== text) {
2065
2242
  this.logger.info(text + "=" + newValue);
2066
2243
  text = newValue;
2067
2244
  }
2068
- info.text = text;
2069
2245
  let dateAlternatives = findDateAlternatives(text);
2070
2246
  let numberAlternatives = findNumberAlternatives(text);
2071
2247
  try {
2248
+ await _preCommand(state, this);
2249
+ state.info.text = text;
2072
2250
  while (true) {
2073
2251
  const frames = this.page.frames();
2074
2252
  let results = [];
2075
2253
  for (let i = 0; i < frames.length; i++) {
2076
2254
  if (dateAlternatives.date) {
2077
2255
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2078
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2256
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2079
2257
  result.frame = frames[i];
2080
2258
  results.push(result);
2081
2259
  }
2082
2260
  }
2083
2261
  else if (numberAlternatives.number) {
2084
2262
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2085
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2263
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2086
2264
  result.frame = frames[i];
2087
2265
  results.push(result);
2088
2266
  }
2089
2267
  }
2090
2268
  else {
2091
- const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2269
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2092
2270
  result.frame = frames[i];
2093
2271
  results.push(result);
2094
2272
  }
2095
2273
  }
2096
- info.results = results;
2274
+ state.info.results = results;
2097
2275
  const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2098
2276
  if (resultWithElementsFound.length === 0) {
2099
- if (Date.now() - startTime > timeout) {
2277
+ if (Date.now() - state.startTime > timeout) {
2100
2278
  throw new Error(`Text ${text} not found in page`);
2101
2279
  }
2102
2280
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2108,44 +2286,89 @@ class StableBrowser {
2108
2286
  await this._highlightElements(frame, dataAttribute);
2109
2287
  const element = await frame.$(dataAttribute);
2110
2288
  if (element) {
2111
- await this.scrollIfNeeded(element, info);
2289
+ await this.scrollIfNeeded(element, state.info);
2112
2290
  await element.dispatchEvent("bvt_verify_page_contains_text");
2113
2291
  }
2114
2292
  }
2115
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2116
- return info;
2293
+ await _screenshot(state, this);
2294
+ return state.info;
2117
2295
  }
2118
2296
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2119
2297
  }
2120
2298
  catch (e) {
2121
- //await this.closeUnexpectedPopups();
2122
- this.logger.error("verify text exist in page failed " + info.log);
2123
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2124
- info.screenshotPath = screenshotPath;
2125
- Object.assign(e, { info: info });
2126
- error = e;
2127
- throw e;
2299
+ await _commandError(state, e, this);
2128
2300
  }
2129
2301
  finally {
2130
- const endTime = Date.now();
2131
- this._reportToWorld(world, {
2132
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2133
- text: "Verify text exists in page",
2134
- screenshotId,
2135
- result: error
2136
- ? {
2137
- status: "FAILED",
2138
- startTime,
2139
- endTime,
2140
- message: error?.message,
2302
+ _commandFinally(state, this);
2303
+ }
2304
+ }
2305
+ async waitForTextToDisappear(text, options = {}, world = null) {
2306
+ text = unEscapeString(text);
2307
+ const state = {
2308
+ text_search: text,
2309
+ options,
2310
+ world,
2311
+ locate: false,
2312
+ scroll: false,
2313
+ highlight: false,
2314
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2315
+ text: `Verify text does not exist in page`,
2316
+ operation: "verifyTextNotExistInPage",
2317
+ log: "***** verify text " + text + " does not exist in page *****\n",
2318
+ };
2319
+ const timeout = this._getLoadTimeout(options);
2320
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2321
+ const newValue = await this._replaceWithLocalData(text, world);
2322
+ if (newValue !== text) {
2323
+ this.logger.info(text + "=" + newValue);
2324
+ text = newValue;
2325
+ }
2326
+ let dateAlternatives = findDateAlternatives(text);
2327
+ let numberAlternatives = findNumberAlternatives(text);
2328
+ try {
2329
+ await _preCommand(state, this);
2330
+ state.info.text = text;
2331
+ while (true) {
2332
+ const frames = this.page.frames();
2333
+ let results = [];
2334
+ for (let i = 0; i < frames.length; i++) {
2335
+ if (dateAlternatives.date) {
2336
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2337
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2338
+ result.frame = frames[i];
2339
+ results.push(result);
2340
+ }
2141
2341
  }
2142
- : {
2143
- status: "PASSED",
2144
- startTime,
2145
- endTime,
2146
- },
2147
- info: info,
2148
- });
2342
+ else if (numberAlternatives.number) {
2343
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2344
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2345
+ result.frame = frames[i];
2346
+ results.push(result);
2347
+ }
2348
+ }
2349
+ else {
2350
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2351
+ result.frame = frames[i];
2352
+ results.push(result);
2353
+ }
2354
+ }
2355
+ state.info.results = results;
2356
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2357
+ if (resultWithElementsFound.length === 0) {
2358
+ await _screenshot(state, this);
2359
+ return state.info;
2360
+ }
2361
+ if (Date.now() - state.startTime > timeout) {
2362
+ throw new Error(`Text ${text} found in page`);
2363
+ }
2364
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2365
+ }
2366
+ }
2367
+ catch (e) {
2368
+ await _commandError(state, e, this);
2369
+ }
2370
+ finally {
2371
+ _commandFinally(state, this);
2149
2372
  }
2150
2373
  }
2151
2374
  _getServerUrl() {
@@ -2208,11 +2431,12 @@ class StableBrowser {
2208
2431
  info.screenshotPath = screenshotPath;
2209
2432
  Object.assign(e, { info: info });
2210
2433
  error = e;
2211
- throw e;
2434
+ // throw e;
2435
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2212
2436
  }
2213
2437
  finally {
2214
2438
  const endTime = Date.now();
2215
- this._reportToWorld(world, {
2439
+ _reportToWorld(world, {
2216
2440
  type: Types.VERIFY_VISUAL,
2217
2441
  text: "Visual verification",
2218
2442
  screenshotId,
@@ -2260,6 +2484,7 @@ class StableBrowser {
2260
2484
  let screenshotPath = null;
2261
2485
  const info = {};
2262
2486
  info.log = "";
2487
+ info.locatorLog = new LocatorLog(selectors);
2263
2488
  info.operation = "getTableData";
2264
2489
  info.selectors = selectors;
2265
2490
  try {
@@ -2275,11 +2500,12 @@ class StableBrowser {
2275
2500
  info.screenshotPath = screenshotPath;
2276
2501
  Object.assign(e, { info: info });
2277
2502
  error = e;
2278
- throw e;
2503
+ // throw e;
2504
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2279
2505
  }
2280
2506
  finally {
2281
2507
  const endTime = Date.now();
2282
- this._reportToWorld(world, {
2508
+ _reportToWorld(world, {
2283
2509
  element_name: selectors.element_name,
2284
2510
  type: Types.GET_TABLE_DATA,
2285
2511
  text: "Get table data",
@@ -2440,11 +2666,12 @@ class StableBrowser {
2440
2666
  info.screenshotPath = screenshotPath;
2441
2667
  Object.assign(e, { info: info });
2442
2668
  error = e;
2443
- throw e;
2669
+ // throw e;
2670
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2444
2671
  }
2445
2672
  finally {
2446
2673
  const endTime = Date.now();
2447
- this._reportToWorld(world, {
2674
+ _reportToWorld(world, {
2448
2675
  element_name: selectors.element_name,
2449
2676
  type: Types.ANALYZE_TABLE,
2450
2677
  text: "Analyze table",
@@ -2466,27 +2693,7 @@ class StableBrowser {
2466
2693
  }
2467
2694
  }
2468
2695
  async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
2469
- if (!value) {
2470
- return value;
2471
- }
2472
- // find all the accurance of {{(.*?)}} and replace with the value
2473
- let regex = /{{(.*?)}}/g;
2474
- let matches = value.match(regex);
2475
- if (matches) {
2476
- const testData = this.getTestData(world);
2477
- for (let i = 0; i < matches.length; i++) {
2478
- let match = matches[i];
2479
- let key = match.substring(2, match.length - 2);
2480
- let newValue = objectPath.get(testData, key, null);
2481
- if (newValue !== null) {
2482
- value = value.replace(match, newValue);
2483
- }
2484
- }
2485
- }
2486
- if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
2487
- return await decrypt(value, null, totpWait);
2488
- }
2489
- return value;
2696
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2490
2697
  }
2491
2698
  _getLoadTimeout(options) {
2492
2699
  let timeout = 15000;
@@ -2537,7 +2744,7 @@ class StableBrowser {
2537
2744
  await new Promise((resolve) => setTimeout(resolve, 2000));
2538
2745
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2539
2746
  const endTime = Date.now();
2540
- this._reportToWorld(world, {
2747
+ _reportToWorld(world, {
2541
2748
  type: Types.GET_PAGE_STATUS,
2542
2749
  text: "Wait for page load",
2543
2750
  screenshotId,
@@ -2557,41 +2764,35 @@ class StableBrowser {
2557
2764
  }
2558
2765
  }
2559
2766
  async closePage(options = {}, world = null) {
2560
- const startTime = Date.now();
2561
- let error = null;
2562
- let screenshotId = null;
2563
- let screenshotPath = null;
2564
- const info = {};
2767
+ const state = {
2768
+ options,
2769
+ world,
2770
+ locate: false,
2771
+ scroll: false,
2772
+ highlight: false,
2773
+ type: Types.CLOSE_PAGE,
2774
+ text: `Close page`,
2775
+ operation: "closePage",
2776
+ log: "***** close page *****\n",
2777
+ throwError: false,
2778
+ };
2565
2779
  try {
2780
+ await _preCommand(state, this);
2566
2781
  await this.page.close();
2567
2782
  }
2568
2783
  catch (e) {
2569
2784
  console.log(".");
2785
+ await _commandError(state, e, this);
2570
2786
  }
2571
2787
  finally {
2572
- await new Promise((resolve) => setTimeout(resolve, 2000));
2573
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2574
- const endTime = Date.now();
2575
- this._reportToWorld(world, {
2576
- type: Types.CLOSE_PAGE,
2577
- text: "close page",
2578
- screenshotId,
2579
- result: error
2580
- ? {
2581
- status: "FAILED",
2582
- startTime,
2583
- endTime,
2584
- message: error?.message,
2585
- }
2586
- : {
2587
- status: "PASSED",
2588
- startTime,
2589
- endTime,
2590
- },
2591
- info: info,
2592
- });
2788
+ _commandFinally(state, this);
2593
2789
  }
2594
2790
  }
2791
+ saveTestDataAsGlobal(options, world) {
2792
+ const dataFile = this._getDataFile(world);
2793
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2794
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2795
+ }
2595
2796
  async setViewportSize(width, hight, options = {}, world = null) {
2596
2797
  const startTime = Date.now();
2597
2798
  let error = null;
@@ -2609,12 +2810,13 @@ class StableBrowser {
2609
2810
  }
2610
2811
  catch (e) {
2611
2812
  console.log(".");
2813
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2612
2814
  }
2613
2815
  finally {
2614
2816
  await new Promise((resolve) => setTimeout(resolve, 2000));
2615
2817
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2616
2818
  const endTime = Date.now();
2617
- this._reportToWorld(world, {
2819
+ _reportToWorld(world, {
2618
2820
  type: Types.SET_VIEWPORT,
2619
2821
  text: "set viewport size to " + width + "x" + hight,
2620
2822
  screenshotId,
@@ -2645,12 +2847,13 @@ class StableBrowser {
2645
2847
  }
2646
2848
  catch (e) {
2647
2849
  console.log(".");
2850
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2648
2851
  }
2649
2852
  finally {
2650
2853
  await new Promise((resolve) => setTimeout(resolve, 2000));
2651
2854
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2652
2855
  const endTime = Date.now();
2653
- this._reportToWorld(world, {
2856
+ _reportToWorld(world, {
2654
2857
  type: Types.GET_PAGE_STATUS,
2655
2858
  text: "page relaod",
2656
2859
  screenshotId,
@@ -2686,11 +2889,37 @@ class StableBrowser {
2686
2889
  console.log("#-#");
2687
2890
  }
2688
2891
  }
2689
- _reportToWorld(world, properties) {
2690
- if (!world || !world.attach) {
2691
- return;
2892
+ async beforeStep(world, step) {
2893
+ this.stepName = step.pickleStep.text;
2894
+ this.logger.info("step: " + this.stepName);
2895
+ if (this.stepIndex === undefined) {
2896
+ this.stepIndex = 0;
2897
+ }
2898
+ else {
2899
+ this.stepIndex++;
2900
+ }
2901
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2902
+ if (this.context.browserObject.context) {
2903
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2904
+ }
2905
+ }
2906
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2907
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2908
+ // check if @global_test_data tag is present
2909
+ if (this.tags.includes("@global_test_data")) {
2910
+ this.saveTestDataAsGlobal({}, world);
2911
+ }
2912
+ }
2913
+ }
2914
+ async afterStep(world, step) {
2915
+ this.stepName = null;
2916
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2917
+ if (this.context.browserObject.context) {
2918
+ await this.context.browserObject.context.tracing.stopChunk({
2919
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2920
+ });
2921
+ }
2692
2922
  }
2693
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2694
2923
  }
2695
2924
  }
2696
2925
  function createTimedPromise(promise, label) {