automation_model 1.0.478-dev → 1.0.478-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 (50) hide show
  1. package/lib/api.d.ts +42 -1
  2. package/lib/api.js +221 -47
  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 +92 -18
  9. package/lib/browser_manager.js.map +1 -1
  10. package/lib/command_common.d.ts +1 -0
  11. package/lib/command_common.js +61 -7
  12. package/lib/command_common.js.map +1 -1
  13. package/lib/environment.js +5 -3
  14. package/lib/environment.js.map +1 -1
  15. package/lib/error-messages.d.ts +6 -0
  16. package/lib/error-messages.js +188 -0
  17. package/lib/error-messages.js.map +1 -0
  18. package/lib/generation_scripts.d.ts +4 -0
  19. package/lib/generation_scripts.js +2 -0
  20. package/lib/generation_scripts.js.map +1 -0
  21. package/lib/index.d.ts +1 -0
  22. package/lib/index.js +1 -0
  23. package/lib/index.js.map +1 -1
  24. package/lib/init_browser.d.ts +2 -1
  25. package/lib/init_browser.js +31 -4
  26. package/lib/init_browser.js.map +1 -1
  27. package/lib/locate_element.js +5 -3
  28. package/lib/locate_element.js.map +1 -1
  29. package/lib/locator.d.ts +36 -0
  30. package/lib/locator.js +165 -0
  31. package/lib/locator.js.map +1 -1
  32. package/lib/locator_log.d.ts +26 -0
  33. package/lib/locator_log.js +69 -0
  34. package/lib/locator_log.js.map +1 -0
  35. package/lib/network.d.ts +3 -0
  36. package/lib/network.js +155 -0
  37. package/lib/network.js.map +1 -0
  38. package/lib/stable_browser.d.ts +44 -9
  39. package/lib/stable_browser.js +520 -290
  40. package/lib/stable_browser.js.map +1 -1
  41. package/lib/table.d.ts +13 -0
  42. package/lib/table.js +187 -0
  43. package/lib/table.js.map +1 -0
  44. package/lib/test_context.d.ts +1 -0
  45. package/lib/test_context.js +12 -12
  46. package/lib/test_context.js.map +1 -1
  47. package/lib/utils.d.ts +3 -1
  48. package/lib/utils.js +68 -1
  49. package/lib/utils.js.map +1 -1
  50. 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,20 +46,28 @@ 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 {
54
+ browser;
55
+ page;
56
+ logger;
57
+ context;
58
+ world;
59
+ project_path = null;
60
+ webLogFile = null;
61
+ networkLogger = null;
62
+ configuration = null;
63
+ appName = "main";
64
+ tags = null;
50
65
  constructor(browser, page, logger = null, context = null, world = null) {
51
66
  this.browser = browser;
52
67
  this.page = page;
53
68
  this.logger = logger;
54
69
  this.context = context;
55
70
  this.world = world;
56
- this.project_path = null;
57
- this.webLogFile = null;
58
- this.networkLogger = null;
59
- this.configuration = null;
60
- this.appName = "main";
61
71
  if (!this.logger) {
62
72
  this.logger = console;
63
73
  }
@@ -82,23 +92,50 @@ class StableBrowser {
82
92
  catch (e) {
83
93
  this.logger.error("unable to read ai_config.json");
84
94
  }
85
- context.pageLoading = { status: false };
86
- context.pages = [this.page];
87
95
  const logFolder = path.join(this.project_path, "logs", "web");
88
96
  this.world = world;
97
+ context.pages = [this.page];
98
+ context.pageLoading = { status: false };
89
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));
90
120
  }
91
121
  registerEventListeners(context) {
92
122
  this.registerConsoleLogListener(this.page, context);
93
- this.registerRequestListener(this.page, context, this.webLogFile);
123
+ // this.registerRequestListener(this.page, context, this.webLogFile);
94
124
  if (!context.pageLoading) {
95
125
  context.pageLoading = { status: false };
96
126
  }
97
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
+ }
98
133
  context.pageLoading.status = true;
99
134
  this.page = page;
100
135
  context.page = page;
101
136
  context.pages.push(page);
137
+ registerNetworkEvents(this.world, this, context, this.page);
138
+ registerDownloadEvent(this.page, this.world, context);
102
139
  page.on("close", async () => {
103
140
  if (this.context && this.context.pages && this.context.pages.length > 1) {
104
141
  this.context.pages.pop();
@@ -128,9 +165,9 @@ class StableBrowser {
128
165
  if (this.appName === appName) {
129
166
  return;
130
167
  }
131
- let newContextCreated = false;
168
+ let navigate = false;
132
169
  if (!apps[appName]) {
133
- 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);
134
171
  newContextCreated = true;
135
172
  apps[appName] = {
136
173
  context: newContext,
@@ -143,8 +180,7 @@ class StableBrowser {
143
180
  this._copyContext(apps[appName], this);
144
181
  apps[this.appName] = tempContext;
145
182
  this.appName = appName;
146
- if (newContextCreated) {
147
- this.registerEventListeners(this.context);
183
+ if (navigate) {
148
184
  await this.goto(this.context.environment.baseUrl);
149
185
  await this.waitForPageLoad();
150
186
  }
@@ -170,7 +206,6 @@ class StableBrowser {
170
206
  this.context.webLogger = [];
171
207
  }
172
208
  page.on("console", async (msg) => {
173
- var _a;
174
209
  const obj = {
175
210
  type: msg.type(),
176
211
  text: msg.text(),
@@ -179,7 +214,7 @@ class StableBrowser {
179
214
  };
180
215
  this.context.webLogger.push(obj);
181
216
  if (msg.type() === "error") {
182
- (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
217
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
183
218
  }
184
219
  });
185
220
  }
@@ -188,7 +223,6 @@ class StableBrowser {
188
223
  this.context.networkLogger = [];
189
224
  }
190
225
  page.on("request", async (data) => {
191
- var _a;
192
226
  const startTime = new Date().getTime();
193
227
  try {
194
228
  const pageUrl = new URL(page.url());
@@ -213,10 +247,10 @@ class StableBrowser {
213
247
  startTime,
214
248
  };
215
249
  context.networkLogger.push(obj);
216
- (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
250
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
217
251
  }
218
252
  catch (error) {
219
- console.error("Error in request listener", error);
253
+ // console.error("Error in request listener", error);
220
254
  context.networkLogger.push({
221
255
  error: "not able to listen",
222
256
  message: error.message,
@@ -305,7 +339,7 @@ class StableBrowser {
305
339
  locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
306
340
  }
307
341
  }
308
- if (locator === null || locator === void 0 ? void 0 : locator.engine) {
342
+ if (locator?.engine) {
309
343
  if (locator.engine === "css") {
310
344
  locatorReturn = scope.locator(locator.selector);
311
345
  }
@@ -329,7 +363,7 @@ class StableBrowser {
329
363
  if (css && css.locator) {
330
364
  css = css.locator;
331
365
  }
332
- 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);
333
367
  if (result.elementCount === 0) {
334
368
  return;
335
369
  }
@@ -381,7 +415,7 @@ class StableBrowser {
381
415
  }
382
416
  document.collectAllShadowDomElements = collectAllShadowDomElements;
383
417
  if (!tag) {
384
- tag = "*";
418
+ tag = "*:not(script, style, head)";
385
419
  }
386
420
  let regexpSearch = document.getRegex(text);
387
421
  if (regexpSearch) {
@@ -463,9 +497,21 @@ class StableBrowser {
463
497
  }, [text1, tag1, regex1, partial1]);
464
498
  }
465
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
+ }
466
510
  let locatorSearch = selectorHierarchy[index];
511
+ let originalLocatorSearch = "";
467
512
  try {
468
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
513
+ originalLocatorSearch = this._fixUsingParams(JSON.stringify(locatorSearch), _params);
514
+ locatorSearch = JSON.parse(originalLocatorSearch);
469
515
  }
470
516
  catch (e) {
471
517
  console.error(e);
@@ -475,13 +521,18 @@ class StableBrowser {
475
521
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
476
522
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
477
523
  if (!locatorString) {
524
+ info.failCause.textNotFound = true;
525
+ info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
478
526
  return;
479
527
  }
480
528
  locator = this._getLocator({ css: locatorString }, scope, _params);
481
529
  }
482
530
  else if (locatorSearch.text) {
483
- 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);
484
533
  if (result.elementCount === 0) {
534
+ info.failCause.textNotFound = true;
535
+ info.failCause.lastError = "failed to locate element by text: " + text;
485
536
  return;
486
537
  }
487
538
  locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
@@ -498,13 +549,22 @@ class StableBrowser {
498
549
  // cssHref = true;
499
550
  // }
500
551
  let count = await locator.count();
552
+ if (count > 0 && !info.failCause.count) {
553
+ info.failCause.count = count;
554
+ }
501
555
  //info.log += "total elements found " + count + "\n";
502
556
  //let visibleCount = 0;
503
557
  let visibleLocator = null;
504
- if (locatorSearch.index && locatorSearch.index < count) {
558
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
505
559
  foundLocators.push(locator.nth(locatorSearch.index));
560
+ if (info.locatorLog) {
561
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
562
+ }
506
563
  return;
507
564
  }
565
+ if (info.locatorLog && count === 0) {
566
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
567
+ }
508
568
  for (let j = 0; j < count; j++) {
509
569
  let visible = await locator.nth(j).isVisible();
510
570
  const enabled = await locator.nth(j).isEnabled();
@@ -513,24 +573,40 @@ class StableBrowser {
513
573
  }
514
574
  if (visible && enabled) {
515
575
  foundLocators.push(locator.nth(j));
576
+ if (info.locatorLog) {
577
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
578
+ }
516
579
  }
517
580
  else {
581
+ info.failCause.visible = visible;
582
+ info.failCause.enabled = enabled;
518
583
  if (!info.printMessages) {
519
584
  info.printMessages = {};
520
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
+ }
521
592
  if (!info.printMessages[j.toString()]) {
522
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
593
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
523
594
  info.printMessages[j.toString()] = true;
524
595
  }
525
596
  }
526
597
  }
527
598
  }
528
599
  async closeUnexpectedPopups(info, _params) {
600
+ if (!info) {
601
+ info = {};
602
+ info.failCause = {};
603
+ info.log = "";
604
+ }
529
605
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
530
606
  if (!info) {
531
607
  info = {};
532
608
  }
533
- info.log += "scan for popup handlers" + "\n";
609
+ //info.log += "scan for popup handlers" + "\n";
534
610
  const handlerGroup = [];
535
611
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
536
612
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -557,9 +633,21 @@ class StableBrowser {
557
633
  }
558
634
  if (result.foundElements.length > 0) {
559
635
  let dialogCloseLocator = result.foundElements[0].locator;
560
- await dialogCloseLocator.click();
561
- // wait for the dialog to close
562
- 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
+ }
563
651
  return { rerun: true };
564
652
  }
565
653
  }
@@ -583,7 +671,13 @@ class StableBrowser {
583
671
  }
584
672
  throw new Error("unable to locate element " + JSON.stringify(selectors));
585
673
  }
586
- 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();
587
681
  let scope = this.page;
588
682
  if (selectors.frame) {
589
683
  return selectors.frame;
@@ -614,9 +708,11 @@ class StableBrowser {
614
708
  }
615
709
  return framescope;
616
710
  };
711
+ let fLocator = null;
617
712
  while (true) {
618
713
  let frameFound = false;
619
714
  if (selectors.nestFrmLoc) {
715
+ fLocator = selectors.nestFrmLoc;
620
716
  scope = await findFrame(selectors.nestFrmLoc, scope);
621
717
  frameFound = true;
622
718
  break;
@@ -625,6 +721,7 @@ class StableBrowser {
625
721
  for (let i = 0; i < selectors.frameLocators.length; i++) {
626
722
  let frameLocator = selectors.frameLocators[i];
627
723
  if (frameLocator.css) {
724
+ fLocator = frameLocator.css;
628
725
  scope = scope.frameLocator(frameLocator.css);
629
726
  frameFound = true;
630
727
  break;
@@ -632,16 +729,25 @@ class StableBrowser {
632
729
  }
633
730
  }
634
731
  if (!frameFound && selectors.iframe_src) {
732
+ fLocator = selectors.iframe_src;
635
733
  scope = this.page.frame({ url: selectors.iframe_src });
636
734
  }
637
735
  if (!scope) {
638
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
639
- 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;
640
743
  throw new Error("unable to locate iframe " + selectors.iframe_src);
641
744
  }
642
745
  await new Promise((resolve) => setTimeout(resolve, 1000));
643
746
  }
644
747
  else {
748
+ if (info && info.locatorLog) {
749
+ info.locatorLog.setFrameSearchStatus("frame-" + fLocator, "FOUND");
750
+ }
645
751
  break;
646
752
  }
647
753
  }
@@ -651,20 +757,27 @@ class StableBrowser {
651
757
  }
652
758
  return scope;
653
759
  }
654
- async _getDocumentBody(selectors, timeout = 30000) {
655
- let scope = await this._findFrameScope(selectors, timeout);
760
+ async _getDocumentBody(selectors, timeout = 30000, info) {
761
+ let scope = await this._findFrameScope(selectors, timeout, info);
656
762
  return scope.evaluate(() => {
657
763
  var bodyContent = document.body.innerHTML;
658
764
  return bodyContent;
659
765
  });
660
766
  }
661
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
+ }
662
774
  let highPriorityTimeout = 5000;
663
775
  let visibleOnlyTimeout = 6000;
664
- let startTime = performance.now();
776
+ let startTime = Date.now();
665
777
  let locatorsCount = 0;
778
+ let lazy_scroll = false;
666
779
  //let arrayMode = Array.isArray(selectors);
667
- let scope = await this._findFrameScope(selectors, timeout);
780
+ let scope = await this._findFrameScope(selectors, timeout, info);
668
781
  let selectorsLocators = null;
669
782
  selectorsLocators = selectors.locators;
670
783
  // group selectors by priority
@@ -751,21 +864,33 @@ class StableBrowser {
751
864
  return maxCountElement.locator;
752
865
  }
753
866
  }
754
- if (performance.now() - startTime > timeout) {
867
+ if (Date.now() - startTime > timeout) {
755
868
  break;
756
869
  }
757
- if (performance.now() - startTime > highPriorityTimeout) {
870
+ if (Date.now() - startTime > highPriorityTimeout) {
758
871
  info.log += "high priority timeout, will try all elements" + "\n";
759
872
  highPriorityOnly = false;
873
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
874
+ lazy_scroll = true;
875
+ await this.scrollPageToLoadLazyElements();
876
+ }
760
877
  }
761
- if (performance.now() - startTime > visibleOnlyTimeout) {
878
+ if (Date.now() - startTime > visibleOnlyTimeout) {
762
879
  info.log += "visible only timeout, will try all elements" + "\n";
763
880
  visibleOnly = false;
764
881
  }
765
882
  await new Promise((resolve) => setTimeout(resolve, 1000));
766
883
  }
767
884
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
768
- 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";
769
894
  throw new Error("failed to locate first element no elements found, " + info.log);
770
895
  }
771
896
  async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
@@ -797,10 +922,29 @@ class StableBrowser {
797
922
  });
798
923
  result.locatorIndex = i;
799
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
+ }
800
931
  }
801
932
  return result;
802
933
  }
803
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);
804
948
  const startTime = Date.now();
805
949
  let timeout = 30000;
806
950
  if (options && options.timeout) {
@@ -809,12 +953,12 @@ class StableBrowser {
809
953
  while (true) {
810
954
  try {
811
955
  const result = await locate_element(this.context, elementDescription, "click");
812
- if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
956
+ if (result?.elementNumber >= 0) {
813
957
  const selectors = {
814
- frame: result === null || result === void 0 ? void 0 : result.frame,
958
+ frame: result?.frame,
815
959
  locators: [
816
960
  {
817
- css: result === null || result === void 0 ? void 0 : result.css,
961
+ css: result?.css,
818
962
  },
819
963
  ],
820
964
  };
@@ -824,13 +968,32 @@ class StableBrowser {
824
968
  }
825
969
  catch (e) {
826
970
  if (performance.now() - startTime > timeout) {
827
- throw e;
971
+ // throw e;
972
+ try {
973
+ await _commandError(state, "timeout looking for " + elementDescription, this);
974
+ }
975
+ finally {
976
+ _commandFinally(state, this);
977
+ }
828
978
  }
829
979
  }
830
980
  await new Promise((resolve) => setTimeout(resolve, 3000));
831
981
  }
832
982
  }
833
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);
834
997
  const startTime = Date.now();
835
998
  let timeout = 30000;
836
999
  if (options && options.timeout) {
@@ -839,12 +1002,12 @@ class StableBrowser {
839
1002
  while (true) {
840
1003
  try {
841
1004
  const result = await locate_element(this.context, elementDescription, "fill", value);
842
- if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
1005
+ if (result?.elementNumber >= 0) {
843
1006
  const selectors = {
844
- frame: result === null || result === void 0 ? void 0 : result.frame,
1007
+ frame: result?.frame,
845
1008
  locators: [
846
1009
  {
847
- css: result === null || result === void 0 ? void 0 : result.css,
1010
+ css: result?.css,
848
1011
  },
849
1012
  ],
850
1013
  };
@@ -854,7 +1017,13 @@ class StableBrowser {
854
1017
  }
855
1018
  catch (e) {
856
1019
  if (performance.now() - startTime > timeout) {
857
- throw e;
1020
+ // throw e;
1021
+ try {
1022
+ await _commandError(state, "timeout looking for " + elementDescription, this);
1023
+ }
1024
+ finally {
1025
+ _commandFinally(state, this);
1026
+ }
858
1027
  }
859
1028
  }
860
1029
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -878,13 +1047,13 @@ class StableBrowser {
878
1047
  }
879
1048
  try {
880
1049
  await state.element.click();
881
- await new Promise((resolve) => setTimeout(resolve, 1000));
1050
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
882
1051
  }
883
1052
  catch (e) {
884
1053
  // await this.closeUnexpectedPopups();
885
1054
  state.element = await this._locate(selectors, state.info, _params);
886
1055
  await state.element.dispatchEvent("click");
887
- await new Promise((resolve) => setTimeout(resolve, 1000));
1056
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
888
1057
  }
889
1058
  await this.waitForPageLoad();
890
1059
  return state.info;
@@ -982,6 +1151,7 @@ class StableBrowser {
982
1151
  _params,
983
1152
  options,
984
1153
  world,
1154
+ value: values.toString(),
985
1155
  type: Types.SELECT,
986
1156
  text: `Select option: ${values}`,
987
1157
  operation: "selectOption",
@@ -1089,33 +1259,30 @@ class StableBrowser {
1089
1259
  }
1090
1260
  }
1091
1261
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1092
- _validateSelectors(selectors);
1093
- const startTime = Date.now();
1094
- let error = null;
1095
- let screenshotId = null;
1096
- let screenshotPath = null;
1097
- const info = {};
1098
- info.log = "";
1099
- info.operation = Types.SET_DATE_TIME;
1100
- info.selectors = selectors;
1101
- 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
+ };
1102
1274
  try {
1103
- value = await this._replaceWithLocalData(value, this);
1104
- let element = await this._locate(selectors, info, _params);
1105
- //insert red border around the element
1106
- await this.scrollIfNeeded(element, info);
1107
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1108
- await this._highlightElements(element);
1275
+ await _preCommand(state, this);
1109
1276
  try {
1110
- await element.click();
1277
+ await state.element.click();
1111
1278
  await new Promise((resolve) => setTimeout(resolve, 500));
1112
1279
  if (format) {
1113
- value = dayjs(value).format(format);
1114
- await element.fill(value);
1280
+ state.value = dayjs(state.value).format(format);
1281
+ await state.element.fill(state.value);
1115
1282
  }
1116
1283
  else {
1117
- const dateTimeValue = await getDateTimeValue({ value, element });
1118
- await element.evaluateHandle((el, dateTimeValue) => {
1284
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1285
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1119
1286
  el.value = ""; // clear input
1120
1287
  el.value = dateTimeValue;
1121
1288
  }, dateTimeValue);
@@ -1128,20 +1295,19 @@ class StableBrowser {
1128
1295
  }
1129
1296
  catch (err) {
1130
1297
  //await this.closeUnexpectedPopups();
1131
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1298
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1132
1299
  this.logger.info("Trying again");
1133
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1134
- info.screenshotPath = screenshotPath;
1135
- Object.assign(err, { info: info });
1300
+ await _screenshot(state, this);
1301
+ Object.assign(err, { info: state.info });
1136
1302
  await element.click();
1137
1303
  await new Promise((resolve) => setTimeout(resolve, 500));
1138
1304
  if (format) {
1139
- value = dayjs(value).format(format);
1140
- await element.fill(value);
1305
+ state.value = dayjs(state.value).format(format);
1306
+ await state.element.fill(state.value);
1141
1307
  }
1142
1308
  else {
1143
- const dateTimeValue = await getDateTimeValue({ value, element });
1144
- await element.evaluateHandle((el, dateTimeValue) => {
1309
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1310
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1145
1311
  el.value = ""; // clear input
1146
1312
  el.value = dateTimeValue;
1147
1313
  }, dateTimeValue);
@@ -1154,46 +1320,27 @@ class StableBrowser {
1154
1320
  }
1155
1321
  }
1156
1322
  catch (e) {
1157
- error = e;
1158
- throw e;
1323
+ await _commandError(state, e, this);
1159
1324
  }
1160
1325
  finally {
1161
- const endTime = Date.now();
1162
- this._reportToWorld(world, {
1163
- element_name: selectors.element_name,
1164
- type: Types.SET_DATE_TIME,
1165
- screenshotId,
1166
- value: value,
1167
- text: `setDateTime input with value: ${value}`,
1168
- result: error
1169
- ? {
1170
- status: "FAILED",
1171
- startTime,
1172
- endTime,
1173
- message: error === null || error === void 0 ? void 0 : error.message,
1174
- }
1175
- : {
1176
- status: "PASSED",
1177
- startTime,
1178
- endTime,
1179
- },
1180
- info: info,
1181
- });
1326
+ _commandFinally(state, this);
1182
1327
  }
1183
1328
  }
1184
1329
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1330
+ _value = unEscapeString(_value);
1331
+ const newValue = await this._replaceWithLocalData(_value, world);
1185
1332
  const state = {
1186
1333
  selectors,
1187
1334
  _params,
1188
- value: unEscapeString(_value),
1335
+ value: newValue,
1336
+ originalValue: _value,
1189
1337
  options,
1190
1338
  world,
1191
1339
  type: Types.FILL,
1192
1340
  text: `Click type input with value: ${_value}`,
1193
1341
  operation: "clickType",
1194
- log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
1342
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1195
1343
  };
1196
- const newValue = await this._replaceWithLocalData(state.value, world);
1197
1344
  if (newValue !== _value) {
1198
1345
  //this.logger.info(_value + "=" + newValue);
1199
1346
  _value = newValue;
@@ -1205,7 +1352,7 @@ class StableBrowser {
1205
1352
  try {
1206
1353
  let currentValue = await state.element.inputValue();
1207
1354
  if (currentValue) {
1208
- await element.fill("");
1355
+ await state.element.fill("");
1209
1356
  }
1210
1357
  }
1211
1358
  catch (e) {
@@ -1313,6 +1460,7 @@ class StableBrowser {
1313
1460
  let screenshotPath = null;
1314
1461
  if (!info.log) {
1315
1462
  info.log = "";
1463
+ info.locatorLog = new LocatorLog(selectors);
1316
1464
  }
1317
1465
  info.operation = "getText";
1318
1466
  info.selectors = selectors;
@@ -1352,7 +1500,6 @@ class StableBrowser {
1352
1500
  }
1353
1501
  }
1354
1502
  async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
1355
- var _a;
1356
1503
  if (!pattern) {
1357
1504
  throw new Error("pattern is null");
1358
1505
  }
@@ -1363,7 +1510,7 @@ class StableBrowser {
1363
1510
  selectors,
1364
1511
  _params,
1365
1512
  pattern,
1366
- value: text,
1513
+ value: pattern,
1367
1514
  options,
1368
1515
  world,
1369
1516
  locate: false,
@@ -1392,14 +1539,14 @@ class StableBrowser {
1392
1539
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1393
1540
  pattern = pattern.replace("{text}", escapedText);
1394
1541
  let regex = new RegExp(pattern, "im");
1395
- if (!regex.test(foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) && !((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(text))) {
1396
- state.info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1542
+ if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
1543
+ state.info.foundText = foundObj?.text;
1397
1544
  throw new Error("element doesn't contain text " + text);
1398
1545
  }
1399
1546
  return state.info;
1400
1547
  }
1401
1548
  catch (e) {
1402
- this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
1549
+ this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
1403
1550
  await _commandError(state, e, this);
1404
1551
  }
1405
1552
  finally {
@@ -1407,7 +1554,6 @@ class StableBrowser {
1407
1554
  }
1408
1555
  }
1409
1556
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1410
- var _a, _b, _c;
1411
1557
  const state = {
1412
1558
  selectors,
1413
1559
  _params,
@@ -1444,8 +1590,8 @@ class StableBrowser {
1444
1590
  const numberAlternatives = findNumberAlternatives(text);
1445
1591
  if (dateAlternatives.date) {
1446
1592
  for (let i = 0; i < dateAlternatives.dates.length; i++) {
1447
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(dateAlternatives.dates[i])) ||
1448
- ((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(dateAlternatives.dates[i]))) {
1593
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1594
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1449
1595
  return state.info;
1450
1596
  }
1451
1597
  }
@@ -1453,16 +1599,16 @@ class StableBrowser {
1453
1599
  }
1454
1600
  else if (numberAlternatives.number) {
1455
1601
  for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1456
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(numberAlternatives.numbers[i])) ||
1457
- ((_b = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _b === void 0 ? void 0 : _b.includes(numberAlternatives.numbers[i]))) {
1602
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1603
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1458
1604
  return state.info;
1459
1605
  }
1460
1606
  }
1461
1607
  throw new Error("element doesn't contain text " + text);
1462
1608
  }
1463
- else if (!(foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(text)) && !((_c = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _c === void 0 ? void 0 : _c.includes(text))) {
1464
- state.info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1465
- state.info.value = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value;
1609
+ else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1610
+ state.info.foundText = foundObj?.text;
1611
+ state.info.value = foundObj?.value;
1466
1612
  throw new Error("element doesn't contain text " + text);
1467
1613
  }
1468
1614
  return state.info;
@@ -1653,11 +1799,9 @@ class StableBrowser {
1653
1799
  if (!fs.existsSync(world.screenshotPath)) {
1654
1800
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1655
1801
  }
1656
- let nextIndex = 1;
1657
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1658
- nextIndex++;
1659
- }
1660
- 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");
1661
1805
  try {
1662
1806
  await this.takeScreenshot(screenshotPath);
1663
1807
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1671,7 +1815,7 @@ class StableBrowser {
1671
1815
  catch (e) {
1672
1816
  this.logger.info("unable to take screenshot, ignored");
1673
1817
  }
1674
- result.screenshotId = nextIndex;
1818
+ result.screenshotId = uuidStr;
1675
1819
  result.screenshotPath = screenshotPath;
1676
1820
  if (info && info.box) {
1677
1821
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1769,74 +1913,101 @@ class StableBrowser {
1769
1913
  }
1770
1914
  }
1771
1915
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1772
- _validateSelectors(selectors);
1773
- const startTime = Date.now();
1774
- let error = null;
1775
- let screenshotId = null;
1776
- 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
+ };
1777
1928
  await new Promise((resolve) => setTimeout(resolve, 2000));
1778
- const info = {};
1779
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1780
- info.operation = "extract";
1781
- info.selectors = selectors;
1782
1929
  try {
1783
- const element = await this._locate(selectors, info, _params);
1784
- await this._highlightElements(element);
1785
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1930
+ await _preCommand(state, this);
1786
1931
  switch (attribute) {
1787
1932
  case "inner_text":
1788
- info.value = await element.innerText();
1933
+ state.value = await state.element.innerText();
1789
1934
  break;
1790
1935
  case "href":
1791
- info.value = await element.getAttribute("href");
1936
+ state.value = await state.element.getAttribute("href");
1792
1937
  break;
1793
1938
  case "value":
1794
- info.value = await element.inputValue();
1939
+ state.value = await state.element.inputValue();
1795
1940
  break;
1796
1941
  default:
1797
- info.value = await element.getAttribute(attribute);
1942
+ state.value = await state.element.getAttribute(attribute);
1798
1943
  break;
1799
1944
  }
1800
- this[variable] = info.value;
1801
- if (world) {
1802
- 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;
1803
1990
  }
1804
- this.setTestData({ [variable]: info.value }, world);
1805
- this.logger.info("set test data: " + variable + "=" + info.value);
1806
- 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;
1807
2005
  }
1808
2006
  catch (e) {
1809
- //await this.closeUnexpectedPopups();
1810
- this.logger.error("extract failed " + info.log);
1811
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1812
- info.screenshotPath = screenshotPath;
1813
- Object.assign(e, { info: info });
1814
- error = e;
1815
- throw e;
2007
+ await _commandError(state, e, this);
1816
2008
  }
1817
2009
  finally {
1818
- const endTime = Date.now();
1819
- this._reportToWorld(world, {
1820
- element_name: selectors.element_name,
1821
- type: Types.EXTRACT_ATTRIBUTE,
1822
- variable: variable,
1823
- value: info.value,
1824
- text: "Extract attribute from element",
1825
- screenshotId,
1826
- result: error
1827
- ? {
1828
- status: "FAILED",
1829
- startTime,
1830
- endTime,
1831
- message: error === null || error === void 0 ? void 0 : error.message,
1832
- }
1833
- : {
1834
- status: "PASSED",
1835
- startTime,
1836
- endTime,
1837
- },
1838
- info: info,
1839
- });
2010
+ _commandFinally(state, this);
1840
2011
  }
1841
2012
  }
1842
2013
  async extractEmailData(emailAddress, options, world) {
@@ -1913,7 +2084,8 @@ class StableBrowser {
1913
2084
  catch (e) {
1914
2085
  errorCount++;
1915
2086
  if (errorCount > 3) {
1916
- throw e;
2087
+ // throw e;
2088
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1917
2089
  }
1918
2090
  // ignore
1919
2091
  }
@@ -2024,11 +2196,12 @@ class StableBrowser {
2024
2196
  info.screenshotPath = screenshotPath;
2025
2197
  Object.assign(e, { info: info });
2026
2198
  error = e;
2027
- throw e;
2199
+ // throw e;
2200
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2028
2201
  }
2029
2202
  finally {
2030
2203
  const endTime = Date.now();
2031
- this._reportToWorld(world, {
2204
+ _reportToWorld(world, {
2032
2205
  type: Types.VERIFY_PAGE_PATH,
2033
2206
  text: "Verify page path",
2034
2207
  screenshotId,
@@ -2037,7 +2210,7 @@ class StableBrowser {
2037
2210
  status: "FAILED",
2038
2211
  startTime,
2039
2212
  endTime,
2040
- message: error === null || error === void 0 ? void 0 : error.message,
2213
+ message: error?.message,
2041
2214
  }
2042
2215
  : {
2043
2216
  status: "PASSED",
@@ -2050,52 +2223,58 @@ class StableBrowser {
2050
2223
  }
2051
2224
  async verifyTextExistInPage(text, options = {}, world = null) {
2052
2225
  text = unEscapeString(text);
2053
- 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
+ };
2054
2238
  const timeout = this._getLoadTimeout(options);
2055
- let error = null;
2056
- let screenshotId = null;
2057
- let screenshotPath = null;
2058
2239
  await new Promise((resolve) => setTimeout(resolve, 2000));
2059
- const info = {};
2060
- info.log = "***** verify text " + text + " exists in page *****\n";
2061
- info.operation = "verifyTextExistInPage";
2062
2240
  const newValue = await this._replaceWithLocalData(text, world);
2063
2241
  if (newValue !== text) {
2064
2242
  this.logger.info(text + "=" + newValue);
2065
2243
  text = newValue;
2066
2244
  }
2067
- info.text = text;
2068
2245
  let dateAlternatives = findDateAlternatives(text);
2069
2246
  let numberAlternatives = findNumberAlternatives(text);
2070
2247
  try {
2248
+ await _preCommand(state, this);
2249
+ state.info.text = text;
2071
2250
  while (true) {
2072
2251
  const frames = this.page.frames();
2073
2252
  let results = [];
2074
2253
  for (let i = 0; i < frames.length; i++) {
2075
2254
  if (dateAlternatives.date) {
2076
2255
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2077
- 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, {});
2078
2257
  result.frame = frames[i];
2079
2258
  results.push(result);
2080
2259
  }
2081
2260
  }
2082
2261
  else if (numberAlternatives.number) {
2083
2262
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2084
- 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, {});
2085
2264
  result.frame = frames[i];
2086
2265
  results.push(result);
2087
2266
  }
2088
2267
  }
2089
2268
  else {
2090
- 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, {});
2091
2270
  result.frame = frames[i];
2092
2271
  results.push(result);
2093
2272
  }
2094
2273
  }
2095
- info.results = results;
2274
+ state.info.results = results;
2096
2275
  const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2097
2276
  if (resultWithElementsFound.length === 0) {
2098
- if (Date.now() - startTime > timeout) {
2277
+ if (Date.now() - state.startTime > timeout) {
2099
2278
  throw new Error(`Text ${text} not found in page`);
2100
2279
  }
2101
2280
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2107,44 +2286,89 @@ class StableBrowser {
2107
2286
  await this._highlightElements(frame, dataAttribute);
2108
2287
  const element = await frame.$(dataAttribute);
2109
2288
  if (element) {
2110
- await this.scrollIfNeeded(element, info);
2289
+ await this.scrollIfNeeded(element, state.info);
2111
2290
  await element.dispatchEvent("bvt_verify_page_contains_text");
2112
2291
  }
2113
2292
  }
2114
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2115
- return info;
2293
+ await _screenshot(state, this);
2294
+ return state.info;
2116
2295
  }
2117
2296
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2118
2297
  }
2119
2298
  catch (e) {
2120
- //await this.closeUnexpectedPopups();
2121
- this.logger.error("verify text exist in page failed " + info.log);
2122
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2123
- info.screenshotPath = screenshotPath;
2124
- Object.assign(e, { info: info });
2125
- error = e;
2126
- throw e;
2299
+ await _commandError(state, e, this);
2127
2300
  }
2128
2301
  finally {
2129
- const endTime = Date.now();
2130
- this._reportToWorld(world, {
2131
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2132
- text: "Verify text exists in page",
2133
- screenshotId,
2134
- result: error
2135
- ? {
2136
- status: "FAILED",
2137
- startTime,
2138
- endTime,
2139
- message: error === null || error === void 0 ? void 0 : 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
+ }
2140
2341
  }
2141
- : {
2142
- status: "PASSED",
2143
- startTime,
2144
- endTime,
2145
- },
2146
- info: info,
2147
- });
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);
2148
2372
  }
2149
2373
  }
2150
2374
  _getServerUrl() {
@@ -2207,11 +2431,12 @@ class StableBrowser {
2207
2431
  info.screenshotPath = screenshotPath;
2208
2432
  Object.assign(e, { info: info });
2209
2433
  error = e;
2210
- throw e;
2434
+ // throw e;
2435
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2211
2436
  }
2212
2437
  finally {
2213
2438
  const endTime = Date.now();
2214
- this._reportToWorld(world, {
2439
+ _reportToWorld(world, {
2215
2440
  type: Types.VERIFY_VISUAL,
2216
2441
  text: "Visual verification",
2217
2442
  screenshotId,
@@ -2220,7 +2445,7 @@ class StableBrowser {
2220
2445
  status: "FAILED",
2221
2446
  startTime,
2222
2447
  endTime,
2223
- message: error === null || error === void 0 ? void 0 : error.message,
2448
+ message: error?.message,
2224
2449
  }
2225
2450
  : {
2226
2451
  status: "PASSED",
@@ -2259,6 +2484,7 @@ class StableBrowser {
2259
2484
  let screenshotPath = null;
2260
2485
  const info = {};
2261
2486
  info.log = "";
2487
+ info.locatorLog = new LocatorLog(selectors);
2262
2488
  info.operation = "getTableData";
2263
2489
  info.selectors = selectors;
2264
2490
  try {
@@ -2274,11 +2500,12 @@ class StableBrowser {
2274
2500
  info.screenshotPath = screenshotPath;
2275
2501
  Object.assign(e, { info: info });
2276
2502
  error = e;
2277
- throw e;
2503
+ // throw e;
2504
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2278
2505
  }
2279
2506
  finally {
2280
2507
  const endTime = Date.now();
2281
- this._reportToWorld(world, {
2508
+ _reportToWorld(world, {
2282
2509
  element_name: selectors.element_name,
2283
2510
  type: Types.GET_TABLE_DATA,
2284
2511
  text: "Get table data",
@@ -2288,7 +2515,7 @@ class StableBrowser {
2288
2515
  status: "FAILED",
2289
2516
  startTime,
2290
2517
  endTime,
2291
- message: error === null || error === void 0 ? void 0 : error.message,
2518
+ message: error?.message,
2292
2519
  }
2293
2520
  : {
2294
2521
  status: "PASSED",
@@ -2439,11 +2666,12 @@ class StableBrowser {
2439
2666
  info.screenshotPath = screenshotPath;
2440
2667
  Object.assign(e, { info: info });
2441
2668
  error = e;
2442
- throw e;
2669
+ // throw e;
2670
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2443
2671
  }
2444
2672
  finally {
2445
2673
  const endTime = Date.now();
2446
- this._reportToWorld(world, {
2674
+ _reportToWorld(world, {
2447
2675
  element_name: selectors.element_name,
2448
2676
  type: Types.ANALYZE_TABLE,
2449
2677
  text: "Analyze table",
@@ -2453,7 +2681,7 @@ class StableBrowser {
2453
2681
  status: "FAILED",
2454
2682
  startTime,
2455
2683
  endTime,
2456
- message: error === null || error === void 0 ? void 0 : error.message,
2684
+ message: error?.message,
2457
2685
  }
2458
2686
  : {
2459
2687
  status: "PASSED",
@@ -2465,27 +2693,7 @@ class StableBrowser {
2465
2693
  }
2466
2694
  }
2467
2695
  async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
2468
- if (!value) {
2469
- return value;
2470
- }
2471
- // find all the accurance of {{(.*?)}} and replace with the value
2472
- let regex = /{{(.*?)}}/g;
2473
- let matches = value.match(regex);
2474
- if (matches) {
2475
- const testData = this.getTestData(world);
2476
- for (let i = 0; i < matches.length; i++) {
2477
- let match = matches[i];
2478
- let key = match.substring(2, match.length - 2);
2479
- let newValue = objectPath.get(testData, key, null);
2480
- if (newValue !== null) {
2481
- value = value.replace(match, newValue);
2482
- }
2483
- }
2484
- }
2485
- if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
2486
- return await decrypt(value, null, totpWait);
2487
- }
2488
- return value;
2696
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2489
2697
  }
2490
2698
  _getLoadTimeout(options) {
2491
2699
  let timeout = 15000;
@@ -2536,7 +2744,7 @@ class StableBrowser {
2536
2744
  await new Promise((resolve) => setTimeout(resolve, 2000));
2537
2745
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2538
2746
  const endTime = Date.now();
2539
- this._reportToWorld(world, {
2747
+ _reportToWorld(world, {
2540
2748
  type: Types.GET_PAGE_STATUS,
2541
2749
  text: "Wait for page load",
2542
2750
  screenshotId,
@@ -2545,7 +2753,7 @@ class StableBrowser {
2545
2753
  status: "FAILED",
2546
2754
  startTime,
2547
2755
  endTime,
2548
- message: error === null || error === void 0 ? void 0 : error.message,
2756
+ message: error?.message,
2549
2757
  }
2550
2758
  : {
2551
2759
  status: "PASSED",
@@ -2556,41 +2764,35 @@ class StableBrowser {
2556
2764
  }
2557
2765
  }
2558
2766
  async closePage(options = {}, world = null) {
2559
- const startTime = Date.now();
2560
- let error = null;
2561
- let screenshotId = null;
2562
- let screenshotPath = null;
2563
- 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
+ };
2564
2779
  try {
2780
+ await _preCommand(state, this);
2565
2781
  await this.page.close();
2566
2782
  }
2567
2783
  catch (e) {
2568
2784
  console.log(".");
2785
+ await _commandError(state, e, this);
2569
2786
  }
2570
2787
  finally {
2571
- await new Promise((resolve) => setTimeout(resolve, 2000));
2572
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2573
- const endTime = Date.now();
2574
- this._reportToWorld(world, {
2575
- type: Types.CLOSE_PAGE,
2576
- text: "close page",
2577
- screenshotId,
2578
- result: error
2579
- ? {
2580
- status: "FAILED",
2581
- startTime,
2582
- endTime,
2583
- message: error === null || error === void 0 ? void 0 : error.message,
2584
- }
2585
- : {
2586
- status: "PASSED",
2587
- startTime,
2588
- endTime,
2589
- },
2590
- info: info,
2591
- });
2788
+ _commandFinally(state, this);
2592
2789
  }
2593
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
+ }
2594
2796
  async setViewportSize(width, hight, options = {}, world = null) {
2595
2797
  const startTime = Date.now();
2596
2798
  let error = null;
@@ -2608,12 +2810,13 @@ class StableBrowser {
2608
2810
  }
2609
2811
  catch (e) {
2610
2812
  console.log(".");
2813
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2611
2814
  }
2612
2815
  finally {
2613
2816
  await new Promise((resolve) => setTimeout(resolve, 2000));
2614
2817
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2615
2818
  const endTime = Date.now();
2616
- this._reportToWorld(world, {
2819
+ _reportToWorld(world, {
2617
2820
  type: Types.SET_VIEWPORT,
2618
2821
  text: "set viewport size to " + width + "x" + hight,
2619
2822
  screenshotId,
@@ -2622,7 +2825,7 @@ class StableBrowser {
2622
2825
  status: "FAILED",
2623
2826
  startTime,
2624
2827
  endTime,
2625
- message: error === null || error === void 0 ? void 0 : error.message,
2828
+ message: error?.message,
2626
2829
  }
2627
2830
  : {
2628
2831
  status: "PASSED",
@@ -2644,12 +2847,13 @@ class StableBrowser {
2644
2847
  }
2645
2848
  catch (e) {
2646
2849
  console.log(".");
2850
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2647
2851
  }
2648
2852
  finally {
2649
2853
  await new Promise((resolve) => setTimeout(resolve, 2000));
2650
2854
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2651
2855
  const endTime = Date.now();
2652
- this._reportToWorld(world, {
2856
+ _reportToWorld(world, {
2653
2857
  type: Types.GET_PAGE_STATUS,
2654
2858
  text: "page relaod",
2655
2859
  screenshotId,
@@ -2658,7 +2862,7 @@ class StableBrowser {
2658
2862
  status: "FAILED",
2659
2863
  startTime,
2660
2864
  endTime,
2661
- message: error === null || error === void 0 ? void 0 : error.message,
2865
+ message: error?.message,
2662
2866
  }
2663
2867
  : {
2664
2868
  status: "PASSED",
@@ -2685,11 +2889,37 @@ class StableBrowser {
2685
2889
  console.log("#-#");
2686
2890
  }
2687
2891
  }
2688
- _reportToWorld(world, properties) {
2689
- if (!world || !world.attach) {
2690
- 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
+ }
2691
2922
  }
2692
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2693
2923
  }
2694
2924
  }
2695
2925
  function createTimedPromise(promise, label) {