automation_model 1.0.477-dev → 1.0.477-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 +490 -295
  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,6 +922,12 @@ 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
  }
@@ -809,12 +940,12 @@ class StableBrowser {
809
940
  while (true) {
810
941
  try {
811
942
  const result = await locate_element(this.context, elementDescription, "click");
812
- if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
943
+ if (result?.elementNumber >= 0) {
813
944
  const selectors = {
814
- frame: result === null || result === void 0 ? void 0 : result.frame,
945
+ frame: result?.frame,
815
946
  locators: [
816
947
  {
817
- css: result === null || result === void 0 ? void 0 : result.css,
948
+ css: result?.css,
818
949
  },
819
950
  ],
820
951
  };
@@ -823,8 +954,9 @@ class StableBrowser {
823
954
  }
824
955
  }
825
956
  catch (e) {
826
- if (performance.now() - startTime > timeout) {
827
- throw e;
957
+ if (Date.now() - startTime > timeout) {
958
+ // throw e;
959
+ await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
828
960
  }
829
961
  }
830
962
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -839,12 +971,12 @@ class StableBrowser {
839
971
  while (true) {
840
972
  try {
841
973
  const result = await locate_element(this.context, elementDescription, "fill", value);
842
- if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
974
+ if (result?.elementNumber >= 0) {
843
975
  const selectors = {
844
- frame: result === null || result === void 0 ? void 0 : result.frame,
976
+ frame: result?.frame,
845
977
  locators: [
846
978
  {
847
- css: result === null || result === void 0 ? void 0 : result.css,
979
+ css: result?.css,
848
980
  },
849
981
  ],
850
982
  };
@@ -853,8 +985,9 @@ class StableBrowser {
853
985
  }
854
986
  }
855
987
  catch (e) {
856
- if (performance.now() - startTime > timeout) {
857
- throw e;
988
+ if (Date.now() - startTime > timeout) {
989
+ // throw e;
990
+ await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
858
991
  }
859
992
  }
860
993
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -878,13 +1011,13 @@ class StableBrowser {
878
1011
  }
879
1012
  try {
880
1013
  await state.element.click();
881
- await new Promise((resolve) => setTimeout(resolve, 1000));
1014
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
882
1015
  }
883
1016
  catch (e) {
884
1017
  // await this.closeUnexpectedPopups();
885
1018
  state.element = await this._locate(selectors, state.info, _params);
886
1019
  await state.element.dispatchEvent("click");
887
- await new Promise((resolve) => setTimeout(resolve, 1000));
1020
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
888
1021
  }
889
1022
  await this.waitForPageLoad();
890
1023
  return state.info;
@@ -908,7 +1041,7 @@ class StableBrowser {
908
1041
  log: "***** check " + selectors.element_name + " *****\n",
909
1042
  };
910
1043
  try {
911
- _preCommand(state, this);
1044
+ await _preCommand(state, this);
912
1045
  state.info.checked = checked;
913
1046
  // let element = await this._locate(selectors, info, _params);
914
1047
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
@@ -923,7 +1056,7 @@ class StableBrowser {
923
1056
  }
924
1057
  else {
925
1058
  //await this.closeUnexpectedPopups();
926
- info.log += "setCheck failed, will try again" + "\n";
1059
+ state.info.log += "setCheck failed, will try again" + "\n";
927
1060
  state.element = await this._locate(selectors, state.info, _params);
928
1061
  await state.element.setChecked(checked, { timeout: 5000, force: true });
929
1062
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -951,7 +1084,7 @@ class StableBrowser {
951
1084
  log: "***** hover " + selectors.element_name + " *****\n",
952
1085
  };
953
1086
  try {
954
- _preCommand(state, this);
1087
+ await _preCommand(state, this);
955
1088
  try {
956
1089
  await state.element.hover();
957
1090
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -982,6 +1115,7 @@ class StableBrowser {
982
1115
  _params,
983
1116
  options,
984
1117
  world,
1118
+ value: values.toString(),
985
1119
  type: Types.SELECT,
986
1120
  text: `Select option: ${values}`,
987
1121
  operation: "selectOption",
@@ -1089,33 +1223,30 @@ class StableBrowser {
1089
1223
  }
1090
1224
  }
1091
1225
  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;
1226
+ const state = {
1227
+ selectors,
1228
+ _params,
1229
+ value: await this._replaceWithLocalData(value, this),
1230
+ options,
1231
+ world,
1232
+ type: Types.SET_DATE_TIME,
1233
+ text: `Set date time value: ${value}`,
1234
+ operation: "setDateTime",
1235
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1236
+ throwError: false,
1237
+ };
1102
1238
  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);
1239
+ await _preCommand(state, this);
1109
1240
  try {
1110
- await element.click();
1241
+ await state.element.click();
1111
1242
  await new Promise((resolve) => setTimeout(resolve, 500));
1112
1243
  if (format) {
1113
- value = dayjs(value).format(format);
1114
- await element.fill(value);
1244
+ state.value = dayjs(state.value).format(format);
1245
+ await state.element.fill(state.value);
1115
1246
  }
1116
1247
  else {
1117
- const dateTimeValue = await getDateTimeValue({ value, element });
1118
- await element.evaluateHandle((el, dateTimeValue) => {
1248
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1249
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1119
1250
  el.value = ""; // clear input
1120
1251
  el.value = dateTimeValue;
1121
1252
  }, dateTimeValue);
@@ -1128,20 +1259,19 @@ class StableBrowser {
1128
1259
  }
1129
1260
  catch (err) {
1130
1261
  //await this.closeUnexpectedPopups();
1131
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1262
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1132
1263
  this.logger.info("Trying again");
1133
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1134
- info.screenshotPath = screenshotPath;
1135
- Object.assign(err, { info: info });
1264
+ await _screenshot(state, this);
1265
+ Object.assign(err, { info: state.info });
1136
1266
  await element.click();
1137
1267
  await new Promise((resolve) => setTimeout(resolve, 500));
1138
1268
  if (format) {
1139
- value = dayjs(value).format(format);
1140
- await element.fill(value);
1269
+ state.value = dayjs(state.value).format(format);
1270
+ await state.element.fill(state.value);
1141
1271
  }
1142
1272
  else {
1143
- const dateTimeValue = await getDateTimeValue({ value, element });
1144
- await element.evaluateHandle((el, dateTimeValue) => {
1273
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1274
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1145
1275
  el.value = ""; // clear input
1146
1276
  el.value = dateTimeValue;
1147
1277
  }, dateTimeValue);
@@ -1154,46 +1284,27 @@ class StableBrowser {
1154
1284
  }
1155
1285
  }
1156
1286
  catch (e) {
1157
- error = e;
1158
- throw e;
1287
+ await _commandError(state, e, this);
1159
1288
  }
1160
1289
  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
- });
1290
+ _commandFinally(state, this);
1182
1291
  }
1183
1292
  }
1184
1293
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1294
+ _value = unEscapeString(_value);
1295
+ const newValue = await this._replaceWithLocalData(_value, world);
1185
1296
  const state = {
1186
1297
  selectors,
1187
1298
  _params,
1188
- value: unEscapeString(_value),
1299
+ value: newValue,
1300
+ originalValue: _value,
1189
1301
  options,
1190
1302
  world,
1191
1303
  type: Types.FILL,
1192
1304
  text: `Click type input with value: ${_value}`,
1193
1305
  operation: "clickType",
1194
- log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
1306
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1195
1307
  };
1196
- const newValue = await this._replaceWithLocalData(state.value, world);
1197
1308
  if (newValue !== _value) {
1198
1309
  //this.logger.info(_value + "=" + newValue);
1199
1310
  _value = newValue;
@@ -1205,7 +1316,7 @@ class StableBrowser {
1205
1316
  try {
1206
1317
  let currentValue = await state.element.inputValue();
1207
1318
  if (currentValue) {
1208
- await element.fill("");
1319
+ await state.element.fill("");
1209
1320
  }
1210
1321
  }
1211
1322
  catch (e) {
@@ -1313,6 +1424,7 @@ class StableBrowser {
1313
1424
  let screenshotPath = null;
1314
1425
  if (!info.log) {
1315
1426
  info.log = "";
1427
+ info.locatorLog = new LocatorLog(selectors);
1316
1428
  }
1317
1429
  info.operation = "getText";
1318
1430
  info.selectors = selectors;
@@ -1352,7 +1464,6 @@ class StableBrowser {
1352
1464
  }
1353
1465
  }
1354
1466
  async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
1355
- var _a;
1356
1467
  if (!pattern) {
1357
1468
  throw new Error("pattern is null");
1358
1469
  }
@@ -1363,7 +1474,7 @@ class StableBrowser {
1363
1474
  selectors,
1364
1475
  _params,
1365
1476
  pattern,
1366
- value: text,
1477
+ value: pattern,
1367
1478
  options,
1368
1479
  world,
1369
1480
  locate: false,
@@ -1392,14 +1503,14 @@ class StableBrowser {
1392
1503
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1393
1504
  pattern = pattern.replace("{text}", escapedText);
1394
1505
  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;
1506
+ if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
1507
+ state.info.foundText = foundObj?.text;
1397
1508
  throw new Error("element doesn't contain text " + text);
1398
1509
  }
1399
1510
  return state.info;
1400
1511
  }
1401
1512
  catch (e) {
1402
- this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
1513
+ this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
1403
1514
  await _commandError(state, e, this);
1404
1515
  }
1405
1516
  finally {
@@ -1407,7 +1518,6 @@ class StableBrowser {
1407
1518
  }
1408
1519
  }
1409
1520
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1410
- var _a, _b, _c;
1411
1521
  const state = {
1412
1522
  selectors,
1413
1523
  _params,
@@ -1434,6 +1544,7 @@ class StableBrowser {
1434
1544
  }
1435
1545
  let foundObj = null;
1436
1546
  try {
1547
+ await _preCommand(state, this);
1437
1548
  foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1438
1549
  if (foundObj && foundObj.element) {
1439
1550
  await this.scrollIfNeeded(foundObj.element, state.info);
@@ -1443,8 +1554,8 @@ class StableBrowser {
1443
1554
  const numberAlternatives = findNumberAlternatives(text);
1444
1555
  if (dateAlternatives.date) {
1445
1556
  for (let i = 0; i < dateAlternatives.dates.length; i++) {
1446
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(dateAlternatives.dates[i])) ||
1447
- ((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(dateAlternatives.dates[i]))) {
1557
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1558
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1448
1559
  return state.info;
1449
1560
  }
1450
1561
  }
@@ -1452,16 +1563,16 @@ class StableBrowser {
1452
1563
  }
1453
1564
  else if (numberAlternatives.number) {
1454
1565
  for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1455
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(numberAlternatives.numbers[i])) ||
1456
- ((_b = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _b === void 0 ? void 0 : _b.includes(numberAlternatives.numbers[i]))) {
1566
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1567
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1457
1568
  return state.info;
1458
1569
  }
1459
1570
  }
1460
1571
  throw new Error("element doesn't contain text " + text);
1461
1572
  }
1462
- 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))) {
1463
- state.info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1464
- state.info.value = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value;
1573
+ else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1574
+ state.info.foundText = foundObj?.text;
1575
+ state.info.value = foundObj?.value;
1465
1576
  throw new Error("element doesn't contain text " + text);
1466
1577
  }
1467
1578
  return state.info;
@@ -1652,11 +1763,9 @@ class StableBrowser {
1652
1763
  if (!fs.existsSync(world.screenshotPath)) {
1653
1764
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1654
1765
  }
1655
- let nextIndex = 1;
1656
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1657
- nextIndex++;
1658
- }
1659
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1766
+ // to make sure the path doesn't start with -
1767
+ const uuidStr = "id_" + randomUUID();
1768
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1660
1769
  try {
1661
1770
  await this.takeScreenshot(screenshotPath);
1662
1771
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1670,7 +1779,7 @@ class StableBrowser {
1670
1779
  catch (e) {
1671
1780
  this.logger.info("unable to take screenshot, ignored");
1672
1781
  }
1673
- result.screenshotId = nextIndex;
1782
+ result.screenshotId = uuidStr;
1674
1783
  result.screenshotPath = screenshotPath;
1675
1784
  if (info && info.box) {
1676
1785
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1768,74 +1877,101 @@ class StableBrowser {
1768
1877
  }
1769
1878
  }
1770
1879
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1771
- _validateSelectors(selectors);
1772
- const startTime = Date.now();
1773
- let error = null;
1774
- let screenshotId = null;
1775
- let screenshotPath = null;
1880
+ const state = {
1881
+ selectors,
1882
+ _params,
1883
+ attribute,
1884
+ variable,
1885
+ options,
1886
+ world,
1887
+ type: Types.EXTRACT,
1888
+ text: `Extract attribute from element`,
1889
+ operation: "extractAttribute",
1890
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1891
+ };
1776
1892
  await new Promise((resolve) => setTimeout(resolve, 2000));
1777
- const info = {};
1778
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1779
- info.operation = "extract";
1780
- info.selectors = selectors;
1781
1893
  try {
1782
- const element = await this._locate(selectors, info, _params);
1783
- await this._highlightElements(element);
1784
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1894
+ await _preCommand(state, this);
1785
1895
  switch (attribute) {
1786
1896
  case "inner_text":
1787
- info.value = await element.innerText();
1897
+ state.value = await state.element.innerText();
1788
1898
  break;
1789
1899
  case "href":
1790
- info.value = await element.getAttribute("href");
1900
+ state.value = await state.element.getAttribute("href");
1791
1901
  break;
1792
1902
  case "value":
1793
- info.value = await element.inputValue();
1903
+ state.value = await state.element.inputValue();
1794
1904
  break;
1795
1905
  default:
1796
- info.value = await element.getAttribute(attribute);
1906
+ state.value = await state.element.getAttribute(attribute);
1797
1907
  break;
1798
1908
  }
1799
- this[variable] = info.value;
1800
- if (world) {
1801
- world[variable] = info.value;
1909
+ state.info.value = state.value;
1910
+ this.setTestData({ [variable]: state.value }, world);
1911
+ this.logger.info("set test data: " + variable + "=" + state.value);
1912
+ return state.info;
1913
+ }
1914
+ catch (e) {
1915
+ await _commandError(state, e, this);
1916
+ }
1917
+ finally {
1918
+ _commandFinally(state, this);
1919
+ }
1920
+ }
1921
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1922
+ const state = {
1923
+ selectors,
1924
+ _params,
1925
+ attribute,
1926
+ value,
1927
+ options,
1928
+ world,
1929
+ type: Types.VERIFY_ATTRIBUTE,
1930
+ text: `Verify element attribute`,
1931
+ operation: "verifyAttribute",
1932
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1933
+ };
1934
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1935
+ let val;
1936
+ try {
1937
+ await _preCommand(state, this);
1938
+ switch (attribute) {
1939
+ case "innerText":
1940
+ val = String(await state.element.innerText());
1941
+ break;
1942
+ case "value":
1943
+ val = String(await state.element.inputValue());
1944
+ break;
1945
+ case "checked":
1946
+ val = String(await state.element.isChecked());
1947
+ break;
1948
+ case "disabled":
1949
+ val = String(await state.element.isDisabled());
1950
+ break;
1951
+ default:
1952
+ val = String(await state.element.getAttribute(attribute));
1953
+ break;
1802
1954
  }
1803
- this.setTestData({ [variable]: info.value }, world);
1804
- this.logger.info("set test data: " + variable + "=" + info.value);
1805
- return info;
1955
+ let regex;
1956
+ if (value.startsWith("/") && value.endsWith("/")) {
1957
+ const patternBody = value.slice(1, -1);
1958
+ regex = new RegExp(patternBody, "g");
1959
+ }
1960
+ else {
1961
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1962
+ regex = new RegExp(escapedPattern, "g");
1963
+ }
1964
+ if (!val.match(regex)) {
1965
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1966
+ }
1967
+ state.info.value = val;
1968
+ return state.info;
1806
1969
  }
1807
1970
  catch (e) {
1808
- //await this.closeUnexpectedPopups();
1809
- this.logger.error("extract failed " + info.log);
1810
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1811
- info.screenshotPath = screenshotPath;
1812
- Object.assign(e, { info: info });
1813
- error = e;
1814
- throw e;
1971
+ await _commandError(state, e, this);
1815
1972
  }
1816
1973
  finally {
1817
- const endTime = Date.now();
1818
- this._reportToWorld(world, {
1819
- element_name: selectors.element_name,
1820
- type: Types.EXTRACT_ATTRIBUTE,
1821
- variable: variable,
1822
- value: info.value,
1823
- text: "Extract attribute from element",
1824
- screenshotId,
1825
- result: error
1826
- ? {
1827
- status: "FAILED",
1828
- startTime,
1829
- endTime,
1830
- message: error === null || error === void 0 ? void 0 : error.message,
1831
- }
1832
- : {
1833
- status: "PASSED",
1834
- startTime,
1835
- endTime,
1836
- },
1837
- info: info,
1838
- });
1974
+ _commandFinally(state, this);
1839
1975
  }
1840
1976
  }
1841
1977
  async extractEmailData(emailAddress, options, world) {
@@ -1912,7 +2048,8 @@ class StableBrowser {
1912
2048
  catch (e) {
1913
2049
  errorCount++;
1914
2050
  if (errorCount > 3) {
1915
- throw e;
2051
+ // throw e;
2052
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1916
2053
  }
1917
2054
  // ignore
1918
2055
  }
@@ -2023,11 +2160,12 @@ class StableBrowser {
2023
2160
  info.screenshotPath = screenshotPath;
2024
2161
  Object.assign(e, { info: info });
2025
2162
  error = e;
2026
- throw e;
2163
+ // throw e;
2164
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2027
2165
  }
2028
2166
  finally {
2029
2167
  const endTime = Date.now();
2030
- this._reportToWorld(world, {
2168
+ _reportToWorld(world, {
2031
2169
  type: Types.VERIFY_PAGE_PATH,
2032
2170
  text: "Verify page path",
2033
2171
  screenshotId,
@@ -2036,7 +2174,7 @@ class StableBrowser {
2036
2174
  status: "FAILED",
2037
2175
  startTime,
2038
2176
  endTime,
2039
- message: error === null || error === void 0 ? void 0 : error.message,
2177
+ message: error?.message,
2040
2178
  }
2041
2179
  : {
2042
2180
  status: "PASSED",
@@ -2049,52 +2187,58 @@ class StableBrowser {
2049
2187
  }
2050
2188
  async verifyTextExistInPage(text, options = {}, world = null) {
2051
2189
  text = unEscapeString(text);
2052
- const startTime = Date.now();
2190
+ const state = {
2191
+ text_search: text,
2192
+ options,
2193
+ world,
2194
+ locate: false,
2195
+ scroll: false,
2196
+ highlight: false,
2197
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2198
+ text: `Verify text exists in page`,
2199
+ operation: "verifyTextExistInPage",
2200
+ log: "***** verify text " + text + " exists in page *****\n",
2201
+ };
2053
2202
  const timeout = this._getLoadTimeout(options);
2054
- let error = null;
2055
- let screenshotId = null;
2056
- let screenshotPath = null;
2057
2203
  await new Promise((resolve) => setTimeout(resolve, 2000));
2058
- const info = {};
2059
- info.log = "***** verify text " + text + " exists in page *****\n";
2060
- info.operation = "verifyTextExistInPage";
2061
2204
  const newValue = await this._replaceWithLocalData(text, world);
2062
2205
  if (newValue !== text) {
2063
2206
  this.logger.info(text + "=" + newValue);
2064
2207
  text = newValue;
2065
2208
  }
2066
- info.text = text;
2067
2209
  let dateAlternatives = findDateAlternatives(text);
2068
2210
  let numberAlternatives = findNumberAlternatives(text);
2069
2211
  try {
2212
+ await _preCommand(state, this);
2213
+ state.info.text = text;
2070
2214
  while (true) {
2071
2215
  const frames = this.page.frames();
2072
2216
  let results = [];
2073
2217
  for (let i = 0; i < frames.length; i++) {
2074
2218
  if (dateAlternatives.date) {
2075
2219
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2076
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2220
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2077
2221
  result.frame = frames[i];
2078
2222
  results.push(result);
2079
2223
  }
2080
2224
  }
2081
2225
  else if (numberAlternatives.number) {
2082
2226
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2083
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2227
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2084
2228
  result.frame = frames[i];
2085
2229
  results.push(result);
2086
2230
  }
2087
2231
  }
2088
2232
  else {
2089
- const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2233
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2090
2234
  result.frame = frames[i];
2091
2235
  results.push(result);
2092
2236
  }
2093
2237
  }
2094
- info.results = results;
2238
+ state.info.results = results;
2095
2239
  const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2096
2240
  if (resultWithElementsFound.length === 0) {
2097
- if (Date.now() - startTime > timeout) {
2241
+ if (Date.now() - state.startTime > timeout) {
2098
2242
  throw new Error(`Text ${text} not found in page`);
2099
2243
  }
2100
2244
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2106,44 +2250,89 @@ class StableBrowser {
2106
2250
  await this._highlightElements(frame, dataAttribute);
2107
2251
  const element = await frame.$(dataAttribute);
2108
2252
  if (element) {
2109
- await this.scrollIfNeeded(element, info);
2253
+ await this.scrollIfNeeded(element, state.info);
2110
2254
  await element.dispatchEvent("bvt_verify_page_contains_text");
2111
2255
  }
2112
2256
  }
2113
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2114
- return info;
2257
+ await _screenshot(state, this);
2258
+ return state.info;
2115
2259
  }
2116
2260
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2117
2261
  }
2118
2262
  catch (e) {
2119
- //await this.closeUnexpectedPopups();
2120
- this.logger.error("verify text exist in page failed " + info.log);
2121
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2122
- info.screenshotPath = screenshotPath;
2123
- Object.assign(e, { info: info });
2124
- error = e;
2125
- throw e;
2263
+ await _commandError(state, e, this);
2126
2264
  }
2127
2265
  finally {
2128
- const endTime = Date.now();
2129
- this._reportToWorld(world, {
2130
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2131
- text: "Verify text exists in page",
2132
- screenshotId,
2133
- result: error
2134
- ? {
2135
- status: "FAILED",
2136
- startTime,
2137
- endTime,
2138
- message: error === null || error === void 0 ? void 0 : error.message,
2266
+ _commandFinally(state, this);
2267
+ }
2268
+ }
2269
+ async waitForTextToDisappear(text, options = {}, world = null) {
2270
+ text = unEscapeString(text);
2271
+ const state = {
2272
+ text_search: text,
2273
+ options,
2274
+ world,
2275
+ locate: false,
2276
+ scroll: false,
2277
+ highlight: false,
2278
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2279
+ text: `Verify text does not exist in page`,
2280
+ operation: "verifyTextNotExistInPage",
2281
+ log: "***** verify text " + text + " does not exist in page *****\n",
2282
+ };
2283
+ const timeout = this._getLoadTimeout(options);
2284
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2285
+ const newValue = await this._replaceWithLocalData(text, world);
2286
+ if (newValue !== text) {
2287
+ this.logger.info(text + "=" + newValue);
2288
+ text = newValue;
2289
+ }
2290
+ let dateAlternatives = findDateAlternatives(text);
2291
+ let numberAlternatives = findNumberAlternatives(text);
2292
+ try {
2293
+ await _preCommand(state, this);
2294
+ state.info.text = text;
2295
+ while (true) {
2296
+ const frames = this.page.frames();
2297
+ let results = [];
2298
+ for (let i = 0; i < frames.length; i++) {
2299
+ if (dateAlternatives.date) {
2300
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2301
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2302
+ result.frame = frames[i];
2303
+ results.push(result);
2304
+ }
2139
2305
  }
2140
- : {
2141
- status: "PASSED",
2142
- startTime,
2143
- endTime,
2144
- },
2145
- info: info,
2146
- });
2306
+ else if (numberAlternatives.number) {
2307
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2308
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2309
+ result.frame = frames[i];
2310
+ results.push(result);
2311
+ }
2312
+ }
2313
+ else {
2314
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2315
+ result.frame = frames[i];
2316
+ results.push(result);
2317
+ }
2318
+ }
2319
+ state.info.results = results;
2320
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2321
+ if (resultWithElementsFound.length === 0) {
2322
+ await _screenshot(state, this);
2323
+ return state.info;
2324
+ }
2325
+ if (Date.now() - state.startTime > timeout) {
2326
+ throw new Error(`Text ${text} found in page`);
2327
+ }
2328
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2329
+ }
2330
+ }
2331
+ catch (e) {
2332
+ await _commandError(state, e, this);
2333
+ }
2334
+ finally {
2335
+ _commandFinally(state, this);
2147
2336
  }
2148
2337
  }
2149
2338
  _getServerUrl() {
@@ -2206,11 +2395,12 @@ class StableBrowser {
2206
2395
  info.screenshotPath = screenshotPath;
2207
2396
  Object.assign(e, { info: info });
2208
2397
  error = e;
2209
- throw e;
2398
+ // throw e;
2399
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2210
2400
  }
2211
2401
  finally {
2212
2402
  const endTime = Date.now();
2213
- this._reportToWorld(world, {
2403
+ _reportToWorld(world, {
2214
2404
  type: Types.VERIFY_VISUAL,
2215
2405
  text: "Visual verification",
2216
2406
  screenshotId,
@@ -2219,7 +2409,7 @@ class StableBrowser {
2219
2409
  status: "FAILED",
2220
2410
  startTime,
2221
2411
  endTime,
2222
- message: error === null || error === void 0 ? void 0 : error.message,
2412
+ message: error?.message,
2223
2413
  }
2224
2414
  : {
2225
2415
  status: "PASSED",
@@ -2258,6 +2448,7 @@ class StableBrowser {
2258
2448
  let screenshotPath = null;
2259
2449
  const info = {};
2260
2450
  info.log = "";
2451
+ info.locatorLog = new LocatorLog(selectors);
2261
2452
  info.operation = "getTableData";
2262
2453
  info.selectors = selectors;
2263
2454
  try {
@@ -2273,11 +2464,12 @@ class StableBrowser {
2273
2464
  info.screenshotPath = screenshotPath;
2274
2465
  Object.assign(e, { info: info });
2275
2466
  error = e;
2276
- throw e;
2467
+ // throw e;
2468
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2277
2469
  }
2278
2470
  finally {
2279
2471
  const endTime = Date.now();
2280
- this._reportToWorld(world, {
2472
+ _reportToWorld(world, {
2281
2473
  element_name: selectors.element_name,
2282
2474
  type: Types.GET_TABLE_DATA,
2283
2475
  text: "Get table data",
@@ -2287,7 +2479,7 @@ class StableBrowser {
2287
2479
  status: "FAILED",
2288
2480
  startTime,
2289
2481
  endTime,
2290
- message: error === null || error === void 0 ? void 0 : error.message,
2482
+ message: error?.message,
2291
2483
  }
2292
2484
  : {
2293
2485
  status: "PASSED",
@@ -2438,11 +2630,12 @@ class StableBrowser {
2438
2630
  info.screenshotPath = screenshotPath;
2439
2631
  Object.assign(e, { info: info });
2440
2632
  error = e;
2441
- throw e;
2633
+ // throw e;
2634
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2442
2635
  }
2443
2636
  finally {
2444
2637
  const endTime = Date.now();
2445
- this._reportToWorld(world, {
2638
+ _reportToWorld(world, {
2446
2639
  element_name: selectors.element_name,
2447
2640
  type: Types.ANALYZE_TABLE,
2448
2641
  text: "Analyze table",
@@ -2452,7 +2645,7 @@ class StableBrowser {
2452
2645
  status: "FAILED",
2453
2646
  startTime,
2454
2647
  endTime,
2455
- message: error === null || error === void 0 ? void 0 : error.message,
2648
+ message: error?.message,
2456
2649
  }
2457
2650
  : {
2458
2651
  status: "PASSED",
@@ -2464,27 +2657,7 @@ class StableBrowser {
2464
2657
  }
2465
2658
  }
2466
2659
  async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
2467
- if (!value) {
2468
- return value;
2469
- }
2470
- // find all the accurance of {{(.*?)}} and replace with the value
2471
- let regex = /{{(.*?)}}/g;
2472
- let matches = value.match(regex);
2473
- if (matches) {
2474
- const testData = this.getTestData(world);
2475
- for (let i = 0; i < matches.length; i++) {
2476
- let match = matches[i];
2477
- let key = match.substring(2, match.length - 2);
2478
- let newValue = objectPath.get(testData, key, null);
2479
- if (newValue !== null) {
2480
- value = value.replace(match, newValue);
2481
- }
2482
- }
2483
- }
2484
- if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
2485
- return await decrypt(value, null, totpWait);
2486
- }
2487
- return value;
2660
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2488
2661
  }
2489
2662
  _getLoadTimeout(options) {
2490
2663
  let timeout = 15000;
@@ -2535,7 +2708,7 @@ class StableBrowser {
2535
2708
  await new Promise((resolve) => setTimeout(resolve, 2000));
2536
2709
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2537
2710
  const endTime = Date.now();
2538
- this._reportToWorld(world, {
2711
+ _reportToWorld(world, {
2539
2712
  type: Types.GET_PAGE_STATUS,
2540
2713
  text: "Wait for page load",
2541
2714
  screenshotId,
@@ -2544,7 +2717,7 @@ class StableBrowser {
2544
2717
  status: "FAILED",
2545
2718
  startTime,
2546
2719
  endTime,
2547
- message: error === null || error === void 0 ? void 0 : error.message,
2720
+ message: error?.message,
2548
2721
  }
2549
2722
  : {
2550
2723
  status: "PASSED",
@@ -2555,41 +2728,35 @@ class StableBrowser {
2555
2728
  }
2556
2729
  }
2557
2730
  async closePage(options = {}, world = null) {
2558
- const startTime = Date.now();
2559
- let error = null;
2560
- let screenshotId = null;
2561
- let screenshotPath = null;
2562
- const info = {};
2731
+ const state = {
2732
+ options,
2733
+ world,
2734
+ locate: false,
2735
+ scroll: false,
2736
+ highlight: false,
2737
+ type: Types.CLOSE_PAGE,
2738
+ text: `Close page`,
2739
+ operation: "closePage",
2740
+ log: "***** close page *****\n",
2741
+ throwError: false,
2742
+ };
2563
2743
  try {
2744
+ await _preCommand(state, this);
2564
2745
  await this.page.close();
2565
2746
  }
2566
2747
  catch (e) {
2567
2748
  console.log(".");
2749
+ await _commandError(state, e, this);
2568
2750
  }
2569
2751
  finally {
2570
- await new Promise((resolve) => setTimeout(resolve, 2000));
2571
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2572
- const endTime = Date.now();
2573
- this._reportToWorld(world, {
2574
- type: Types.CLOSE_PAGE,
2575
- text: "close page",
2576
- screenshotId,
2577
- result: error
2578
- ? {
2579
- status: "FAILED",
2580
- startTime,
2581
- endTime,
2582
- message: error === null || error === void 0 ? void 0 : error.message,
2583
- }
2584
- : {
2585
- status: "PASSED",
2586
- startTime,
2587
- endTime,
2588
- },
2589
- info: info,
2590
- });
2752
+ _commandFinally(state, this);
2591
2753
  }
2592
2754
  }
2755
+ saveTestDataAsGlobal(options, world) {
2756
+ const dataFile = this._getDataFile(world);
2757
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2758
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2759
+ }
2593
2760
  async setViewportSize(width, hight, options = {}, world = null) {
2594
2761
  const startTime = Date.now();
2595
2762
  let error = null;
@@ -2607,12 +2774,13 @@ class StableBrowser {
2607
2774
  }
2608
2775
  catch (e) {
2609
2776
  console.log(".");
2777
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2610
2778
  }
2611
2779
  finally {
2612
2780
  await new Promise((resolve) => setTimeout(resolve, 2000));
2613
2781
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2614
2782
  const endTime = Date.now();
2615
- this._reportToWorld(world, {
2783
+ _reportToWorld(world, {
2616
2784
  type: Types.SET_VIEWPORT,
2617
2785
  text: "set viewport size to " + width + "x" + hight,
2618
2786
  screenshotId,
@@ -2621,7 +2789,7 @@ class StableBrowser {
2621
2789
  status: "FAILED",
2622
2790
  startTime,
2623
2791
  endTime,
2624
- message: error === null || error === void 0 ? void 0 : error.message,
2792
+ message: error?.message,
2625
2793
  }
2626
2794
  : {
2627
2795
  status: "PASSED",
@@ -2643,12 +2811,13 @@ class StableBrowser {
2643
2811
  }
2644
2812
  catch (e) {
2645
2813
  console.log(".");
2814
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2646
2815
  }
2647
2816
  finally {
2648
2817
  await new Promise((resolve) => setTimeout(resolve, 2000));
2649
2818
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2650
2819
  const endTime = Date.now();
2651
- this._reportToWorld(world, {
2820
+ _reportToWorld(world, {
2652
2821
  type: Types.GET_PAGE_STATUS,
2653
2822
  text: "page relaod",
2654
2823
  screenshotId,
@@ -2657,7 +2826,7 @@ class StableBrowser {
2657
2826
  status: "FAILED",
2658
2827
  startTime,
2659
2828
  endTime,
2660
- message: error === null || error === void 0 ? void 0 : error.message,
2829
+ message: error?.message,
2661
2830
  }
2662
2831
  : {
2663
2832
  status: "PASSED",
@@ -2684,11 +2853,37 @@ class StableBrowser {
2684
2853
  console.log("#-#");
2685
2854
  }
2686
2855
  }
2687
- _reportToWorld(world, properties) {
2688
- if (!world || !world.attach) {
2689
- return;
2856
+ async beforeStep(world, step) {
2857
+ this.stepName = step.pickleStep.text;
2858
+ this.logger.info("step: " + this.stepName);
2859
+ if (this.stepIndex === undefined) {
2860
+ this.stepIndex = 0;
2861
+ }
2862
+ else {
2863
+ this.stepIndex++;
2864
+ }
2865
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2866
+ if (this.context.browserObject.context) {
2867
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2868
+ }
2869
+ }
2870
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2871
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2872
+ // check if @global_test_data tag is present
2873
+ if (this.tags.includes("@global_test_data")) {
2874
+ this.saveTestDataAsGlobal({}, world);
2875
+ }
2876
+ }
2877
+ }
2878
+ async afterStep(world, step) {
2879
+ this.stepName = null;
2880
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2881
+ if (this.context.browserObject.context) {
2882
+ await this.context.browserObject.context.tracing.stopChunk({
2883
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2884
+ });
2885
+ }
2690
2886
  }
2691
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2692
2887
  }
2693
2888
  }
2694
2889
  function createTimedPromise(promise, label) {