automation_model 1.0.480-dev → 1.0.480-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 +2 -1
  2. package/lib/api.js +92 -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 +58 -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 +482 -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;
@@ -1093,33 +1259,30 @@ class StableBrowser {
1093
1259
  }
1094
1260
  }
1095
1261
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1096
- _validateSelectors(selectors);
1097
- const startTime = Date.now();
1098
- let error = null;
1099
- let screenshotId = null;
1100
- let screenshotPath = null;
1101
- const info = {};
1102
- info.log = "";
1103
- info.operation = Types.SET_DATE_TIME;
1104
- info.selectors = selectors;
1105
- 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
+ };
1106
1274
  try {
1107
- value = await this._replaceWithLocalData(value, this);
1108
- let element = await this._locate(selectors, info, _params);
1109
- //insert red border around the element
1110
- await this.scrollIfNeeded(element, info);
1111
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1112
- await this._highlightElements(element);
1275
+ await _preCommand(state, this);
1113
1276
  try {
1114
- await element.click();
1277
+ await state.element.click();
1115
1278
  await new Promise((resolve) => setTimeout(resolve, 500));
1116
1279
  if (format) {
1117
- value = dayjs(value).format(format);
1118
- await element.fill(value);
1280
+ state.value = dayjs(state.value).format(format);
1281
+ await state.element.fill(state.value);
1119
1282
  }
1120
1283
  else {
1121
- const dateTimeValue = await getDateTimeValue({ value, element });
1122
- await element.evaluateHandle((el, dateTimeValue) => {
1284
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1285
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1123
1286
  el.value = ""; // clear input
1124
1287
  el.value = dateTimeValue;
1125
1288
  }, dateTimeValue);
@@ -1132,20 +1295,19 @@ class StableBrowser {
1132
1295
  }
1133
1296
  catch (err) {
1134
1297
  //await this.closeUnexpectedPopups();
1135
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1298
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1136
1299
  this.logger.info("Trying again");
1137
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1138
- info.screenshotPath = screenshotPath;
1139
- Object.assign(err, { info: info });
1300
+ await _screenshot(state, this);
1301
+ Object.assign(err, { info: state.info });
1140
1302
  await element.click();
1141
1303
  await new Promise((resolve) => setTimeout(resolve, 500));
1142
1304
  if (format) {
1143
- value = dayjs(value).format(format);
1144
- await element.fill(value);
1305
+ state.value = dayjs(state.value).format(format);
1306
+ await state.element.fill(state.value);
1145
1307
  }
1146
1308
  else {
1147
- const dateTimeValue = await getDateTimeValue({ value, element });
1148
- await element.evaluateHandle((el, dateTimeValue) => {
1309
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1310
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1149
1311
  el.value = ""; // clear input
1150
1312
  el.value = dateTimeValue;
1151
1313
  }, dateTimeValue);
@@ -1158,50 +1320,30 @@ class StableBrowser {
1158
1320
  }
1159
1321
  }
1160
1322
  catch (e) {
1161
- error = e;
1162
- throw e;
1323
+ await _commandError(state, e, this);
1163
1324
  }
1164
1325
  finally {
1165
- const endTime = Date.now();
1166
- this._reportToWorld(world, {
1167
- element_name: selectors.element_name,
1168
- type: Types.SET_DATE_TIME,
1169
- screenshotId,
1170
- value: value,
1171
- text: `setDateTime input with value: ${value}`,
1172
- result: error
1173
- ? {
1174
- status: "FAILED",
1175
- startTime,
1176
- endTime,
1177
- message: error === null || error === void 0 ? void 0 : error.message,
1178
- }
1179
- : {
1180
- status: "PASSED",
1181
- startTime,
1182
- endTime,
1183
- },
1184
- info: info,
1185
- });
1326
+ _commandFinally(state, this);
1186
1327
  }
1187
1328
  }
1188
1329
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1330
+ _value = unEscapeString(_value);
1331
+ const newValue = await this._replaceWithLocalData(_value, world);
1189
1332
  const state = {
1190
1333
  selectors,
1191
1334
  _params,
1192
- value: unEscapeString(_value),
1335
+ value: newValue,
1336
+ originalValue: _value,
1193
1337
  options,
1194
1338
  world,
1195
1339
  type: Types.FILL,
1196
1340
  text: `Click type input with value: ${_value}`,
1197
1341
  operation: "clickType",
1198
- log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
1342
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1199
1343
  };
1200
- const newValue = await this._replaceWithLocalData(state.value, world);
1201
1344
  if (newValue !== _value) {
1202
1345
  //this.logger.info(_value + "=" + newValue);
1203
1346
  _value = newValue;
1204
- state.value = newValue;
1205
1347
  }
1206
1348
  try {
1207
1349
  await _preCommand(state, this);
@@ -1210,7 +1352,7 @@ class StableBrowser {
1210
1352
  try {
1211
1353
  let currentValue = await state.element.inputValue();
1212
1354
  if (currentValue) {
1213
- await element.fill("");
1355
+ await state.element.fill("");
1214
1356
  }
1215
1357
  }
1216
1358
  catch (e) {
@@ -1318,6 +1460,7 @@ class StableBrowser {
1318
1460
  let screenshotPath = null;
1319
1461
  if (!info.log) {
1320
1462
  info.log = "";
1463
+ info.locatorLog = new LocatorLog(selectors);
1321
1464
  }
1322
1465
  info.operation = "getText";
1323
1466
  info.selectors = selectors;
@@ -1656,11 +1799,9 @@ class StableBrowser {
1656
1799
  if (!fs.existsSync(world.screenshotPath)) {
1657
1800
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1658
1801
  }
1659
- let nextIndex = 1;
1660
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1661
- nextIndex++;
1662
- }
1663
- 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");
1664
1805
  try {
1665
1806
  await this.takeScreenshot(screenshotPath);
1666
1807
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1674,7 +1815,7 @@ class StableBrowser {
1674
1815
  catch (e) {
1675
1816
  this.logger.info("unable to take screenshot, ignored");
1676
1817
  }
1677
- result.screenshotId = nextIndex;
1818
+ result.screenshotId = uuidStr;
1678
1819
  result.screenshotPath = screenshotPath;
1679
1820
  if (info && info.box) {
1680
1821
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1772,74 +1913,101 @@ class StableBrowser {
1772
1913
  }
1773
1914
  }
1774
1915
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1775
- _validateSelectors(selectors);
1776
- const startTime = Date.now();
1777
- let error = null;
1778
- let screenshotId = null;
1779
- 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
+ };
1780
1928
  await new Promise((resolve) => setTimeout(resolve, 2000));
1781
- const info = {};
1782
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1783
- info.operation = "extract";
1784
- info.selectors = selectors;
1785
1929
  try {
1786
- const element = await this._locate(selectors, info, _params);
1787
- await this._highlightElements(element);
1788
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1930
+ await _preCommand(state, this);
1789
1931
  switch (attribute) {
1790
1932
  case "inner_text":
1791
- info.value = await element.innerText();
1933
+ state.value = await state.element.innerText();
1792
1934
  break;
1793
1935
  case "href":
1794
- info.value = await element.getAttribute("href");
1936
+ state.value = await state.element.getAttribute("href");
1795
1937
  break;
1796
1938
  case "value":
1797
- info.value = await element.inputValue();
1939
+ state.value = await state.element.inputValue();
1798
1940
  break;
1799
1941
  default:
1800
- info.value = await element.getAttribute(attribute);
1942
+ state.value = await state.element.getAttribute(attribute);
1801
1943
  break;
1802
1944
  }
1803
- this[variable] = info.value;
1804
- if (world) {
1805
- 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;
1806
1990
  }
1807
- this.setTestData({ [variable]: info.value }, world);
1808
- this.logger.info("set test data: " + variable + "=" + info.value);
1809
- 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;
1810
2005
  }
1811
2006
  catch (e) {
1812
- //await this.closeUnexpectedPopups();
1813
- this.logger.error("extract failed " + info.log);
1814
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1815
- info.screenshotPath = screenshotPath;
1816
- Object.assign(e, { info: info });
1817
- error = e;
1818
- throw e;
2007
+ await _commandError(state, e, this);
1819
2008
  }
1820
2009
  finally {
1821
- const endTime = Date.now();
1822
- this._reportToWorld(world, {
1823
- element_name: selectors.element_name,
1824
- type: Types.EXTRACT_ATTRIBUTE,
1825
- variable: variable,
1826
- value: info.value,
1827
- text: "Extract attribute from element",
1828
- screenshotId,
1829
- result: error
1830
- ? {
1831
- status: "FAILED",
1832
- startTime,
1833
- endTime,
1834
- message: error?.message,
1835
- }
1836
- : {
1837
- status: "PASSED",
1838
- startTime,
1839
- endTime,
1840
- },
1841
- info: info,
1842
- });
2010
+ _commandFinally(state, this);
1843
2011
  }
1844
2012
  }
1845
2013
  async extractEmailData(emailAddress, options, world) {
@@ -1916,7 +2084,8 @@ class StableBrowser {
1916
2084
  catch (e) {
1917
2085
  errorCount++;
1918
2086
  if (errorCount > 3) {
1919
- throw e;
2087
+ // throw e;
2088
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1920
2089
  }
1921
2090
  // ignore
1922
2091
  }
@@ -2027,11 +2196,12 @@ class StableBrowser {
2027
2196
  info.screenshotPath = screenshotPath;
2028
2197
  Object.assign(e, { info: info });
2029
2198
  error = e;
2030
- throw e;
2199
+ // throw e;
2200
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2031
2201
  }
2032
2202
  finally {
2033
2203
  const endTime = Date.now();
2034
- this._reportToWorld(world, {
2204
+ _reportToWorld(world, {
2035
2205
  type: Types.VERIFY_PAGE_PATH,
2036
2206
  text: "Verify page path",
2037
2207
  screenshotId,
@@ -2053,52 +2223,58 @@ class StableBrowser {
2053
2223
  }
2054
2224
  async verifyTextExistInPage(text, options = {}, world = null) {
2055
2225
  text = unEscapeString(text);
2056
- 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
+ };
2057
2238
  const timeout = this._getLoadTimeout(options);
2058
- let error = null;
2059
- let screenshotId = null;
2060
- let screenshotPath = null;
2061
2239
  await new Promise((resolve) => setTimeout(resolve, 2000));
2062
- const info = {};
2063
- info.log = "***** verify text " + text + " exists in page *****\n";
2064
- info.operation = "verifyTextExistInPage";
2065
2240
  const newValue = await this._replaceWithLocalData(text, world);
2066
2241
  if (newValue !== text) {
2067
2242
  this.logger.info(text + "=" + newValue);
2068
2243
  text = newValue;
2069
2244
  }
2070
- info.text = text;
2071
2245
  let dateAlternatives = findDateAlternatives(text);
2072
2246
  let numberAlternatives = findNumberAlternatives(text);
2073
2247
  try {
2248
+ await _preCommand(state, this);
2249
+ state.info.text = text;
2074
2250
  while (true) {
2075
2251
  const frames = this.page.frames();
2076
2252
  let results = [];
2077
2253
  for (let i = 0; i < frames.length; i++) {
2078
2254
  if (dateAlternatives.date) {
2079
2255
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2080
- 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, {});
2081
2257
  result.frame = frames[i];
2082
2258
  results.push(result);
2083
2259
  }
2084
2260
  }
2085
2261
  else if (numberAlternatives.number) {
2086
2262
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2087
- 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, {});
2088
2264
  result.frame = frames[i];
2089
2265
  results.push(result);
2090
2266
  }
2091
2267
  }
2092
2268
  else {
2093
- 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, {});
2094
2270
  result.frame = frames[i];
2095
2271
  results.push(result);
2096
2272
  }
2097
2273
  }
2098
- info.results = results;
2274
+ state.info.results = results;
2099
2275
  const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2100
2276
  if (resultWithElementsFound.length === 0) {
2101
- if (Date.now() - startTime > timeout) {
2277
+ if (Date.now() - state.startTime > timeout) {
2102
2278
  throw new Error(`Text ${text} not found in page`);
2103
2279
  }
2104
2280
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2110,44 +2286,89 @@ class StableBrowser {
2110
2286
  await this._highlightElements(frame, dataAttribute);
2111
2287
  const element = await frame.$(dataAttribute);
2112
2288
  if (element) {
2113
- await this.scrollIfNeeded(element, info);
2289
+ await this.scrollIfNeeded(element, state.info);
2114
2290
  await element.dispatchEvent("bvt_verify_page_contains_text");
2115
2291
  }
2116
2292
  }
2117
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2118
- return info;
2293
+ await _screenshot(state, this);
2294
+ return state.info;
2119
2295
  }
2120
2296
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2121
2297
  }
2122
2298
  catch (e) {
2123
- //await this.closeUnexpectedPopups();
2124
- this.logger.error("verify text exist in page failed " + info.log);
2125
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2126
- info.screenshotPath = screenshotPath;
2127
- Object.assign(e, { info: info });
2128
- error = e;
2129
- throw e;
2299
+ await _commandError(state, e, this);
2130
2300
  }
2131
2301
  finally {
2132
- const endTime = Date.now();
2133
- this._reportToWorld(world, {
2134
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2135
- text: "Verify text exists in page",
2136
- screenshotId,
2137
- result: error
2138
- ? {
2139
- status: "FAILED",
2140
- startTime,
2141
- endTime,
2142
- 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
+ }
2143
2341
  }
2144
- : {
2145
- status: "PASSED",
2146
- startTime,
2147
- endTime,
2148
- },
2149
- info: info,
2150
- });
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);
2151
2372
  }
2152
2373
  }
2153
2374
  _getServerUrl() {
@@ -2210,11 +2431,12 @@ class StableBrowser {
2210
2431
  info.screenshotPath = screenshotPath;
2211
2432
  Object.assign(e, { info: info });
2212
2433
  error = e;
2213
- throw e;
2434
+ // throw e;
2435
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2214
2436
  }
2215
2437
  finally {
2216
2438
  const endTime = Date.now();
2217
- this._reportToWorld(world, {
2439
+ _reportToWorld(world, {
2218
2440
  type: Types.VERIFY_VISUAL,
2219
2441
  text: "Visual verification",
2220
2442
  screenshotId,
@@ -2262,6 +2484,7 @@ class StableBrowser {
2262
2484
  let screenshotPath = null;
2263
2485
  const info = {};
2264
2486
  info.log = "";
2487
+ info.locatorLog = new LocatorLog(selectors);
2265
2488
  info.operation = "getTableData";
2266
2489
  info.selectors = selectors;
2267
2490
  try {
@@ -2277,11 +2500,12 @@ class StableBrowser {
2277
2500
  info.screenshotPath = screenshotPath;
2278
2501
  Object.assign(e, { info: info });
2279
2502
  error = e;
2280
- throw e;
2503
+ // throw e;
2504
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2281
2505
  }
2282
2506
  finally {
2283
2507
  const endTime = Date.now();
2284
- this._reportToWorld(world, {
2508
+ _reportToWorld(world, {
2285
2509
  element_name: selectors.element_name,
2286
2510
  type: Types.GET_TABLE_DATA,
2287
2511
  text: "Get table data",
@@ -2442,11 +2666,12 @@ class StableBrowser {
2442
2666
  info.screenshotPath = screenshotPath;
2443
2667
  Object.assign(e, { info: info });
2444
2668
  error = e;
2445
- throw e;
2669
+ // throw e;
2670
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2446
2671
  }
2447
2672
  finally {
2448
2673
  const endTime = Date.now();
2449
- this._reportToWorld(world, {
2674
+ _reportToWorld(world, {
2450
2675
  element_name: selectors.element_name,
2451
2676
  type: Types.ANALYZE_TABLE,
2452
2677
  text: "Analyze table",
@@ -2468,27 +2693,7 @@ class StableBrowser {
2468
2693
  }
2469
2694
  }
2470
2695
  async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
2471
- if (!value) {
2472
- return value;
2473
- }
2474
- // find all the accurance of {{(.*?)}} and replace with the value
2475
- let regex = /{{(.*?)}}/g;
2476
- let matches = value.match(regex);
2477
- if (matches) {
2478
- const testData = this.getTestData(world);
2479
- for (let i = 0; i < matches.length; i++) {
2480
- let match = matches[i];
2481
- let key = match.substring(2, match.length - 2);
2482
- let newValue = objectPath.get(testData, key, null);
2483
- if (newValue !== null) {
2484
- value = value.replace(match, newValue);
2485
- }
2486
- }
2487
- }
2488
- if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
2489
- return await decrypt(value, null, totpWait);
2490
- }
2491
- return value;
2696
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2492
2697
  }
2493
2698
  _getLoadTimeout(options) {
2494
2699
  let timeout = 15000;
@@ -2539,7 +2744,7 @@ class StableBrowser {
2539
2744
  await new Promise((resolve) => setTimeout(resolve, 2000));
2540
2745
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2541
2746
  const endTime = Date.now();
2542
- this._reportToWorld(world, {
2747
+ _reportToWorld(world, {
2543
2748
  type: Types.GET_PAGE_STATUS,
2544
2749
  text: "Wait for page load",
2545
2750
  screenshotId,
@@ -2559,41 +2764,35 @@ class StableBrowser {
2559
2764
  }
2560
2765
  }
2561
2766
  async closePage(options = {}, world = null) {
2562
- const startTime = Date.now();
2563
- let error = null;
2564
- let screenshotId = null;
2565
- let screenshotPath = null;
2566
- 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
+ };
2567
2779
  try {
2780
+ await _preCommand(state, this);
2568
2781
  await this.page.close();
2569
2782
  }
2570
2783
  catch (e) {
2571
2784
  console.log(".");
2785
+ await _commandError(state, e, this);
2572
2786
  }
2573
2787
  finally {
2574
- await new Promise((resolve) => setTimeout(resolve, 2000));
2575
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2576
- const endTime = Date.now();
2577
- this._reportToWorld(world, {
2578
- type: Types.CLOSE_PAGE,
2579
- text: "close page",
2580
- screenshotId,
2581
- result: error
2582
- ? {
2583
- status: "FAILED",
2584
- startTime,
2585
- endTime,
2586
- message: error?.message,
2587
- }
2588
- : {
2589
- status: "PASSED",
2590
- startTime,
2591
- endTime,
2592
- },
2593
- info: info,
2594
- });
2788
+ _commandFinally(state, this);
2595
2789
  }
2596
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
+ }
2597
2796
  async setViewportSize(width, hight, options = {}, world = null) {
2598
2797
  const startTime = Date.now();
2599
2798
  let error = null;
@@ -2611,12 +2810,13 @@ class StableBrowser {
2611
2810
  }
2612
2811
  catch (e) {
2613
2812
  console.log(".");
2813
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2614
2814
  }
2615
2815
  finally {
2616
2816
  await new Promise((resolve) => setTimeout(resolve, 2000));
2617
2817
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2618
2818
  const endTime = Date.now();
2619
- this._reportToWorld(world, {
2819
+ _reportToWorld(world, {
2620
2820
  type: Types.SET_VIEWPORT,
2621
2821
  text: "set viewport size to " + width + "x" + hight,
2622
2822
  screenshotId,
@@ -2647,12 +2847,13 @@ class StableBrowser {
2647
2847
  }
2648
2848
  catch (e) {
2649
2849
  console.log(".");
2850
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2650
2851
  }
2651
2852
  finally {
2652
2853
  await new Promise((resolve) => setTimeout(resolve, 2000));
2653
2854
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2654
2855
  const endTime = Date.now();
2655
- this._reportToWorld(world, {
2856
+ _reportToWorld(world, {
2656
2857
  type: Types.GET_PAGE_STATUS,
2657
2858
  text: "page relaod",
2658
2859
  screenshotId,
@@ -2688,11 +2889,37 @@ class StableBrowser {
2688
2889
  console.log("#-#");
2689
2890
  }
2690
2891
  }
2691
- _reportToWorld(world, properties) {
2692
- if (!world || !world.attach) {
2693
- 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
+ }
2694
2922
  }
2695
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2696
2923
  }
2697
2924
  }
2698
2925
  function createTimedPromise(promise, label) {