automation_model 1.0.474-dev → 1.0.474-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 (47) 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 +53 -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/network.d.ts +3 -0
  33. package/lib/network.js +150 -0
  34. package/lib/network.js.map +1 -0
  35. package/lib/stable_browser.d.ts +16 -10
  36. package/lib/stable_browser.js +448 -291
  37. package/lib/stable_browser.js.map +1 -1
  38. package/lib/table.d.ts +13 -0
  39. package/lib/table.js +187 -0
  40. package/lib/table.js.map +1 -0
  41. package/lib/test_context.d.ts +1 -0
  42. package/lib/test_context.js +12 -12
  43. package/lib/test_context.js.map +1 -1
  44. package/lib/utils.d.ts +3 -1
  45. package/lib/utils.js +68 -1
  46. package/lib/utils.js.map +1 -1
  47. package/package.json +5 -4
@@ -10,14 +10,15 @@ 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";
19
+ import { randomUUID } from "crypto";
20
+ import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
21
+ import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
21
22
  const Types = {
22
23
  CLICK: "click_element",
23
24
  NAVIGATE: "navigate",
@@ -44,20 +45,28 @@ const Types = {
44
45
  VERIFY_VISUAL: "verify_visual",
45
46
  LOAD_DATA: "load_data",
46
47
  SET_INPUT: "set_input",
48
+ WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
49
+ VERIFY_ATTRIBUTE: "verify_element_attribute",
47
50
  };
48
51
  export const apps = {};
49
52
  class StableBrowser {
53
+ browser;
54
+ page;
55
+ logger;
56
+ context;
57
+ world;
58
+ project_path = null;
59
+ webLogFile = null;
60
+ networkLogger = null;
61
+ configuration = null;
62
+ appName = "main";
63
+ tags = null;
50
64
  constructor(browser, page, logger = null, context = null, world = null) {
51
65
  this.browser = browser;
52
66
  this.page = page;
53
67
  this.logger = logger;
54
68
  this.context = context;
55
69
  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
70
  if (!this.logger) {
62
71
  this.logger = console;
63
72
  }
@@ -82,11 +91,31 @@ class StableBrowser {
82
91
  catch (e) {
83
92
  this.logger.error("unable to read ai_config.json");
84
93
  }
85
- context.pageLoading = { status: false };
86
- context.pages = [this.page];
87
94
  const logFolder = path.join(this.project_path, "logs", "web");
88
95
  this.world = world;
96
+ context.pages = [this.page];
97
+ context.pageLoading = { status: false };
89
98
  this.registerEventListeners(this.context);
99
+ registerNetworkEvents(this.world, this, this.context, this.page);
100
+ registerDownloadEvent(this.page, this.world, this.context);
101
+ }
102
+ async scrollPageToLoadLazyElements() {
103
+ let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
104
+ let retry = 0;
105
+ while (true) {
106
+ await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
107
+ await new Promise((resolve) => setTimeout(resolve, 1000));
108
+ let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
109
+ if (newHeight === lastHeight) {
110
+ break;
111
+ }
112
+ lastHeight = newHeight;
113
+ retry++;
114
+ if (retry > 10) {
115
+ break;
116
+ }
117
+ }
118
+ await this.page.evaluate(() => window.scrollTo(0, 0));
90
119
  }
91
120
  registerEventListeners(context) {
92
121
  this.registerConsoleLogListener(this.page, context);
@@ -95,10 +124,17 @@ class StableBrowser {
95
124
  context.pageLoading = { status: false };
96
125
  }
97
126
  context.playContext.on("page", async function (page) {
127
+ if (this.configuration && this.configuration.closePopups === true) {
128
+ console.log("close unexpected popups");
129
+ await page.close();
130
+ return;
131
+ }
98
132
  context.pageLoading.status = true;
99
133
  this.page = page;
100
134
  context.page = page;
101
135
  context.pages.push(page);
136
+ registerNetworkEvents(this.world, this, context, this.page);
137
+ registerDownloadEvent(this.page, this.world, context);
102
138
  page.on("close", async () => {
103
139
  if (this.context && this.context.pages && this.context.pages.length > 1) {
104
140
  this.context.pages.pop();
@@ -128,9 +164,9 @@ class StableBrowser {
128
164
  if (this.appName === appName) {
129
165
  return;
130
166
  }
131
- let newContextCreated = false;
167
+ let navigate = false;
132
168
  if (!apps[appName]) {
133
- let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this);
169
+ let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
134
170
  newContextCreated = true;
135
171
  apps[appName] = {
136
172
  context: newContext,
@@ -143,8 +179,7 @@ class StableBrowser {
143
179
  this._copyContext(apps[appName], this);
144
180
  apps[this.appName] = tempContext;
145
181
  this.appName = appName;
146
- if (newContextCreated) {
147
- this.registerEventListeners(this.context);
182
+ if (navigate) {
148
183
  await this.goto(this.context.environment.baseUrl);
149
184
  await this.waitForPageLoad();
150
185
  }
@@ -170,7 +205,6 @@ class StableBrowser {
170
205
  this.context.webLogger = [];
171
206
  }
172
207
  page.on("console", async (msg) => {
173
- var _a;
174
208
  const obj = {
175
209
  type: msg.type(),
176
210
  text: msg.text(),
@@ -179,7 +213,7 @@ class StableBrowser {
179
213
  };
180
214
  this.context.webLogger.push(obj);
181
215
  if (msg.type() === "error") {
182
- (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
216
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
183
217
  }
184
218
  });
185
219
  }
@@ -188,7 +222,6 @@ class StableBrowser {
188
222
  this.context.networkLogger = [];
189
223
  }
190
224
  page.on("request", async (data) => {
191
- var _a;
192
225
  const startTime = new Date().getTime();
193
226
  try {
194
227
  const pageUrl = new URL(page.url());
@@ -213,10 +246,10 @@ class StableBrowser {
213
246
  startTime,
214
247
  };
215
248
  context.networkLogger.push(obj);
216
- (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
249
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
217
250
  }
218
251
  catch (error) {
219
- console.error("Error in request listener", error);
252
+ // console.error("Error in request listener", error);
220
253
  context.networkLogger.push({
221
254
  error: "not able to listen",
222
255
  message: error.message,
@@ -305,7 +338,7 @@ class StableBrowser {
305
338
  locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
306
339
  }
307
340
  }
308
- if (locator === null || locator === void 0 ? void 0 : locator.engine) {
341
+ if (locator?.engine) {
309
342
  if (locator.engine === "css") {
310
343
  locatorReturn = scope.locator(locator.selector);
311
344
  }
@@ -329,7 +362,7 @@ class StableBrowser {
329
362
  if (css && css.locator) {
330
363
  css = css.locator;
331
364
  }
332
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
365
+ let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
333
366
  if (result.elementCount === 0) {
334
367
  return;
335
368
  }
@@ -381,7 +414,7 @@ class StableBrowser {
381
414
  }
382
415
  document.collectAllShadowDomElements = collectAllShadowDomElements;
383
416
  if (!tag) {
384
- tag = "*";
417
+ tag = "*:not(script, style, head)";
385
418
  }
386
419
  let regexpSearch = document.getRegex(text);
387
420
  if (regexpSearch) {
@@ -463,6 +496,15 @@ class StableBrowser {
463
496
  }, [text1, tag1, regex1, partial1]);
464
497
  }
465
498
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
499
+ if (!info) {
500
+ info = {};
501
+ }
502
+ if (!info.failCause) {
503
+ info.failCause = {};
504
+ }
505
+ if (!info.log) {
506
+ info.log = "";
507
+ }
466
508
  let locatorSearch = selectorHierarchy[index];
467
509
  try {
468
510
  locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
@@ -475,13 +517,18 @@ class StableBrowser {
475
517
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
476
518
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
477
519
  if (!locatorString) {
520
+ info.failCause.textNotFound = true;
521
+ info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
478
522
  return;
479
523
  }
480
524
  locator = this._getLocator({ css: locatorString }, scope, _params);
481
525
  }
482
526
  else if (locatorSearch.text) {
483
- let result = await this._locateElementByText(scope, this._fixUsingParams(locatorSearch.text, _params), locatorSearch.tag, false, locatorSearch.partial === true, _params);
527
+ let text = this._fixUsingParams(locatorSearch.text, _params);
528
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
484
529
  if (result.elementCount === 0) {
530
+ info.failCause.textNotFound = true;
531
+ info.failCause.lastError = "failed to locate element by text: " + text;
485
532
  return;
486
533
  }
487
534
  locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
@@ -498,10 +545,13 @@ class StableBrowser {
498
545
  // cssHref = true;
499
546
  // }
500
547
  let count = await locator.count();
548
+ if (count > 0 && !info.failCause.count) {
549
+ info.failCause.count = count;
550
+ }
501
551
  //info.log += "total elements found " + count + "\n";
502
552
  //let visibleCount = 0;
503
553
  let visibleLocator = null;
504
- if (locatorSearch.index && locatorSearch.index < count) {
554
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
505
555
  foundLocators.push(locator.nth(locatorSearch.index));
506
556
  return;
507
557
  }
@@ -515,6 +565,8 @@ class StableBrowser {
515
565
  foundLocators.push(locator.nth(j));
516
566
  }
517
567
  else {
568
+ info.failCause.visible = visible;
569
+ info.failCause.enabled = enabled;
518
570
  if (!info.printMessages) {
519
571
  info.printMessages = {};
520
572
  }
@@ -526,6 +578,11 @@ class StableBrowser {
526
578
  }
527
579
  }
528
580
  async closeUnexpectedPopups(info, _params) {
581
+ if (!info) {
582
+ info = {};
583
+ info.failCause = {};
584
+ info.log = "";
585
+ }
529
586
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
530
587
  if (!info) {
531
588
  info = {};
@@ -557,16 +614,31 @@ class StableBrowser {
557
614
  }
558
615
  if (result.foundElements.length > 0) {
559
616
  let dialogCloseLocator = result.foundElements[0].locator;
560
- await dialogCloseLocator.click();
561
- // wait for the dialog to close
562
- await dialogCloseLocator.waitFor({ state: "hidden" });
617
+ try {
618
+ await scope?.evaluate(() => {
619
+ window.__isClosingPopups = true;
620
+ });
621
+ await dialogCloseLocator.click();
622
+ // wait for the dialog to close
623
+ await dialogCloseLocator.waitFor({ state: "hidden" });
624
+ }
625
+ catch (e) {
626
+ }
627
+ finally {
628
+ await scope?.evaluate(() => {
629
+ window.__isClosingPopups = false;
630
+ });
631
+ }
563
632
  return { rerun: true };
564
633
  }
565
634
  }
566
635
  }
567
636
  return { rerun: false };
568
637
  }
569
- async _locate(selectors, info, _params, timeout = 30000) {
638
+ async _locate(selectors, info, _params, timeout) {
639
+ if (!timeout) {
640
+ timeout = 30000;
641
+ }
570
642
  for (let i = 0; i < 3; i++) {
571
643
  info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
572
644
  for (let j = 0; j < selectors.locators.length; j++) {
@@ -580,8 +652,14 @@ class StableBrowser {
580
652
  }
581
653
  throw new Error("unable to locate element " + JSON.stringify(selectors));
582
654
  }
583
- async _findFrameScope(selectors, timeout = 30000) {
584
- let scope = null;
655
+ async _findFrameScope(selectors, timeout = 30000, info) {
656
+ if (!info) {
657
+ info = {};
658
+ info.failCause = {};
659
+ info.log = "";
660
+ }
661
+ let startTime = Date.now();
662
+ let scope = this.page;
585
663
  if (selectors.frame) {
586
664
  return selectors.frame;
587
665
  }
@@ -633,7 +711,9 @@ class StableBrowser {
633
711
  }
634
712
  if (!scope) {
635
713
  info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
636
- if (performance.now() - startTime > timeout) {
714
+ if (Date.now() - startTime > timeout) {
715
+ info.failCause.iframeNotFound = true;
716
+ info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
637
717
  throw new Error("unable to locate iframe " + selectors.iframe_src);
638
718
  }
639
719
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -648,20 +728,26 @@ class StableBrowser {
648
728
  }
649
729
  return scope;
650
730
  }
651
- async _getDocumentBody(selectors, timeout = 30000) {
652
- let scope = await this._findFrameScope(selectors, timeout);
731
+ async _getDocumentBody(selectors, timeout = 30000, info) {
732
+ let scope = await this._findFrameScope(selectors, timeout, info);
653
733
  return scope.evaluate(() => {
654
734
  var bodyContent = document.body.innerHTML;
655
735
  return bodyContent;
656
736
  });
657
737
  }
658
738
  async _locate_internal(selectors, info, _params, timeout = 30000) {
739
+ if (!info) {
740
+ info = {};
741
+ info.failCause = {};
742
+ info.log = "";
743
+ }
659
744
  let highPriorityTimeout = 5000;
660
745
  let visibleOnlyTimeout = 6000;
661
- let startTime = performance.now();
746
+ let startTime = Date.now();
662
747
  let locatorsCount = 0;
748
+ let lazy_scroll = false;
663
749
  //let arrayMode = Array.isArray(selectors);
664
- let scope = await this._findFrameScope(selectors, timeout);
750
+ let scope = await this._findFrameScope(selectors, timeout, info);
665
751
  let selectorsLocators = null;
666
752
  selectorsLocators = selectors.locators;
667
753
  // group selectors by priority
@@ -748,14 +834,18 @@ class StableBrowser {
748
834
  return maxCountElement.locator;
749
835
  }
750
836
  }
751
- if (performance.now() - startTime > timeout) {
837
+ if (Date.now() - startTime > timeout) {
752
838
  break;
753
839
  }
754
- if (performance.now() - startTime > highPriorityTimeout) {
840
+ if (Date.now() - startTime > highPriorityTimeout) {
755
841
  info.log += "high priority timeout, will try all elements" + "\n";
756
842
  highPriorityOnly = false;
843
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
844
+ lazy_scroll = true;
845
+ await this.scrollPageToLoadLazyElements();
846
+ }
757
847
  }
758
- if (performance.now() - startTime > visibleOnlyTimeout) {
848
+ if (Date.now() - startTime > visibleOnlyTimeout) {
759
849
  info.log += "visible only timeout, will try all elements" + "\n";
760
850
  visibleOnly = false;
761
851
  }
@@ -763,6 +853,8 @@ class StableBrowser {
763
853
  }
764
854
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
765
855
  info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
856
+ info.failCause.locatorNotFound = true;
857
+ info.failCause.lastError = "failed to locate unique element";
766
858
  throw new Error("failed to locate first element no elements found, " + info.log);
767
859
  }
768
860
  async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
@@ -794,6 +886,9 @@ class StableBrowser {
794
886
  });
795
887
  result.locatorIndex = i;
796
888
  }
889
+ if (foundLocators.length > 1) {
890
+ info.failCause.foundMultiple = true;
891
+ }
797
892
  }
798
893
  return result;
799
894
  }
@@ -806,12 +901,12 @@ class StableBrowser {
806
901
  while (true) {
807
902
  try {
808
903
  const result = await locate_element(this.context, elementDescription, "click");
809
- if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
904
+ if (result?.elementNumber >= 0) {
810
905
  const selectors = {
811
- frame: result === null || result === void 0 ? void 0 : result.frame,
906
+ frame: result?.frame,
812
907
  locators: [
813
908
  {
814
- css: result === null || result === void 0 ? void 0 : result.css,
909
+ css: result?.css,
815
910
  },
816
911
  ],
817
912
  };
@@ -820,8 +915,9 @@ class StableBrowser {
820
915
  }
821
916
  }
822
917
  catch (e) {
823
- if (performance.now() - startTime > timeout) {
824
- throw e;
918
+ if (Date.now() - startTime > timeout) {
919
+ // throw e;
920
+ await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
825
921
  }
826
922
  }
827
923
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -836,12 +932,12 @@ class StableBrowser {
836
932
  while (true) {
837
933
  try {
838
934
  const result = await locate_element(this.context, elementDescription, "fill", value);
839
- if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
935
+ if (result?.elementNumber >= 0) {
840
936
  const selectors = {
841
- frame: result === null || result === void 0 ? void 0 : result.frame,
937
+ frame: result?.frame,
842
938
  locators: [
843
939
  {
844
- css: result === null || result === void 0 ? void 0 : result.css,
940
+ css: result?.css,
845
941
  },
846
942
  ],
847
943
  };
@@ -850,8 +946,9 @@ class StableBrowser {
850
946
  }
851
947
  }
852
948
  catch (e) {
853
- if (performance.now() - startTime > timeout) {
854
- throw e;
949
+ if (Date.now() - startTime > timeout) {
950
+ // throw e;
951
+ await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
855
952
  }
856
953
  }
857
954
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -875,13 +972,13 @@ class StableBrowser {
875
972
  }
876
973
  try {
877
974
  await state.element.click();
878
- await new Promise((resolve) => setTimeout(resolve, 1000));
975
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
879
976
  }
880
977
  catch (e) {
881
978
  // await this.closeUnexpectedPopups();
882
979
  state.element = await this._locate(selectors, state.info, _params);
883
980
  await state.element.dispatchEvent("click");
884
- await new Promise((resolve) => setTimeout(resolve, 1000));
981
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
885
982
  }
886
983
  await this.waitForPageLoad();
887
984
  return state.info;
@@ -905,7 +1002,7 @@ class StableBrowser {
905
1002
  log: "***** check " + selectors.element_name + " *****\n",
906
1003
  };
907
1004
  try {
908
- _preCommand(state, this);
1005
+ await _preCommand(state, this);
909
1006
  state.info.checked = checked;
910
1007
  // let element = await this._locate(selectors, info, _params);
911
1008
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
@@ -920,8 +1017,8 @@ class StableBrowser {
920
1017
  }
921
1018
  else {
922
1019
  //await this.closeUnexpectedPopups();
923
- info.log += "setCheck failed, will try again" + "\n";
924
- state.element = await this._locate(selectors, info, _params);
1020
+ state.info.log += "setCheck failed, will try again" + "\n";
1021
+ state.element = await this._locate(selectors, state.info, _params);
925
1022
  await state.element.setChecked(checked, { timeout: 5000, force: true });
926
1023
  await new Promise((resolve) => setTimeout(resolve, 1000));
927
1024
  }
@@ -948,7 +1045,7 @@ class StableBrowser {
948
1045
  log: "***** hover " + selectors.element_name + " *****\n",
949
1046
  };
950
1047
  try {
951
- _preCommand(state, this);
1048
+ await _preCommand(state, this);
952
1049
  try {
953
1050
  await state.element.hover();
954
1051
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -979,6 +1076,7 @@ class StableBrowser {
979
1076
  _params,
980
1077
  options,
981
1078
  world,
1079
+ value: values.toString(),
982
1080
  type: Types.SELECT,
983
1081
  text: `Select option: ${values}`,
984
1082
  operation: "selectOption",
@@ -1086,33 +1184,30 @@ class StableBrowser {
1086
1184
  }
1087
1185
  }
1088
1186
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1089
- _validateSelectors(selectors);
1090
- const startTime = Date.now();
1091
- let error = null;
1092
- let screenshotId = null;
1093
- let screenshotPath = null;
1094
- const info = {};
1095
- info.log = "";
1096
- info.operation = Types.SET_DATE_TIME;
1097
- info.selectors = selectors;
1098
- info.value = value;
1187
+ const state = {
1188
+ selectors,
1189
+ _params,
1190
+ value: await this._replaceWithLocalData(value, this),
1191
+ options,
1192
+ world,
1193
+ type: Types.SET_DATE_TIME,
1194
+ text: `Set date time value: ${value}`,
1195
+ operation: "setDateTime",
1196
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1197
+ throwError: false,
1198
+ };
1099
1199
  try {
1100
- value = await this._replaceWithLocalData(value, this);
1101
- let element = await this._locate(selectors, info, _params);
1102
- //insert red border around the element
1103
- await this.scrollIfNeeded(element, info);
1104
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1105
- await this._highlightElements(element);
1200
+ await _preCommand(state, this);
1106
1201
  try {
1107
- await element.click();
1202
+ await state.element.click();
1108
1203
  await new Promise((resolve) => setTimeout(resolve, 500));
1109
1204
  if (format) {
1110
- value = dayjs(value).format(format);
1111
- await element.fill(value);
1205
+ state.value = dayjs(state.value).format(format);
1206
+ await state.element.fill(state.value);
1112
1207
  }
1113
1208
  else {
1114
- const dateTimeValue = await getDateTimeValue({ value, element });
1115
- await element.evaluateHandle((el, dateTimeValue) => {
1209
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1210
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1116
1211
  el.value = ""; // clear input
1117
1212
  el.value = dateTimeValue;
1118
1213
  }, dateTimeValue);
@@ -1125,20 +1220,19 @@ class StableBrowser {
1125
1220
  }
1126
1221
  catch (err) {
1127
1222
  //await this.closeUnexpectedPopups();
1128
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1223
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1129
1224
  this.logger.info("Trying again");
1130
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1131
- info.screenshotPath = screenshotPath;
1132
- Object.assign(err, { info: info });
1225
+ await _screenshot(state, this);
1226
+ Object.assign(err, { info: state.info });
1133
1227
  await element.click();
1134
1228
  await new Promise((resolve) => setTimeout(resolve, 500));
1135
1229
  if (format) {
1136
- value = dayjs(value).format(format);
1137
- await element.fill(value);
1230
+ state.value = dayjs(state.value).format(format);
1231
+ await state.element.fill(state.value);
1138
1232
  }
1139
1233
  else {
1140
- const dateTimeValue = await getDateTimeValue({ value, element });
1141
- await element.evaluateHandle((el, dateTimeValue) => {
1234
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1235
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1142
1236
  el.value = ""; // clear input
1143
1237
  el.value = dateTimeValue;
1144
1238
  }, dateTimeValue);
@@ -1151,46 +1245,27 @@ class StableBrowser {
1151
1245
  }
1152
1246
  }
1153
1247
  catch (e) {
1154
- error = e;
1155
- throw e;
1248
+ await _commandError(state, e, this);
1156
1249
  }
1157
1250
  finally {
1158
- const endTime = Date.now();
1159
- this._reportToWorld(world, {
1160
- element_name: selectors.element_name,
1161
- type: Types.SET_DATE_TIME,
1162
- screenshotId,
1163
- value: value,
1164
- text: `setDateTime input with value: ${value}`,
1165
- result: error
1166
- ? {
1167
- status: "FAILED",
1168
- startTime,
1169
- endTime,
1170
- message: error === null || error === void 0 ? void 0 : error.message,
1171
- }
1172
- : {
1173
- status: "PASSED",
1174
- startTime,
1175
- endTime,
1176
- },
1177
- info: info,
1178
- });
1251
+ _commandFinally(state, this);
1179
1252
  }
1180
1253
  }
1181
1254
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1255
+ _value = unEscapeString(_value);
1256
+ const newValue = await this._replaceWithLocalData(_value, world);
1182
1257
  const state = {
1183
1258
  selectors,
1184
1259
  _params,
1185
- value: unEscapeString(_value),
1260
+ value: newValue,
1261
+ originalValue: _value,
1186
1262
  options,
1187
1263
  world,
1188
1264
  type: Types.FILL,
1189
1265
  text: `Click type input with value: ${_value}`,
1190
1266
  operation: "clickType",
1191
- log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
1267
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1192
1268
  };
1193
- const newValue = await this._replaceWithLocalData(state.value, world);
1194
1269
  if (newValue !== _value) {
1195
1270
  //this.logger.info(_value + "=" + newValue);
1196
1271
  _value = newValue;
@@ -1202,7 +1277,7 @@ class StableBrowser {
1202
1277
  try {
1203
1278
  let currentValue = await state.element.inputValue();
1204
1279
  if (currentValue) {
1205
- await element.fill("");
1280
+ await state.element.fill("");
1206
1281
  }
1207
1282
  }
1208
1283
  catch (e) {
@@ -1349,7 +1424,6 @@ class StableBrowser {
1349
1424
  }
1350
1425
  }
1351
1426
  async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
1352
- var _a;
1353
1427
  if (!pattern) {
1354
1428
  throw new Error("pattern is null");
1355
1429
  }
@@ -1360,7 +1434,7 @@ class StableBrowser {
1360
1434
  selectors,
1361
1435
  _params,
1362
1436
  pattern,
1363
- value: text,
1437
+ value: pattern,
1364
1438
  options,
1365
1439
  world,
1366
1440
  locate: false,
@@ -1389,14 +1463,14 @@ class StableBrowser {
1389
1463
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1390
1464
  pattern = pattern.replace("{text}", escapedText);
1391
1465
  let regex = new RegExp(pattern, "im");
1392
- 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))) {
1393
- state.info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1466
+ if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
1467
+ state.info.foundText = foundObj?.text;
1394
1468
  throw new Error("element doesn't contain text " + text);
1395
1469
  }
1396
1470
  return state.info;
1397
1471
  }
1398
1472
  catch (e) {
1399
- this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
1473
+ this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
1400
1474
  await _commandError(state, e, this);
1401
1475
  }
1402
1476
  finally {
@@ -1404,7 +1478,6 @@ class StableBrowser {
1404
1478
  }
1405
1479
  }
1406
1480
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1407
- var _a, _b, _c;
1408
1481
  const state = {
1409
1482
  selectors,
1410
1483
  _params,
@@ -1431,6 +1504,7 @@ class StableBrowser {
1431
1504
  }
1432
1505
  let foundObj = null;
1433
1506
  try {
1507
+ await _preCommand(state, this);
1434
1508
  foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1435
1509
  if (foundObj && foundObj.element) {
1436
1510
  await this.scrollIfNeeded(foundObj.element, state.info);
@@ -1440,8 +1514,8 @@ class StableBrowser {
1440
1514
  const numberAlternatives = findNumberAlternatives(text);
1441
1515
  if (dateAlternatives.date) {
1442
1516
  for (let i = 0; i < dateAlternatives.dates.length; i++) {
1443
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(dateAlternatives.dates[i])) ||
1444
- ((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(dateAlternatives.dates[i]))) {
1517
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1518
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1445
1519
  return state.info;
1446
1520
  }
1447
1521
  }
@@ -1449,16 +1523,16 @@ class StableBrowser {
1449
1523
  }
1450
1524
  else if (numberAlternatives.number) {
1451
1525
  for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1452
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(numberAlternatives.numbers[i])) ||
1453
- ((_b = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _b === void 0 ? void 0 : _b.includes(numberAlternatives.numbers[i]))) {
1526
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1527
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1454
1528
  return state.info;
1455
1529
  }
1456
1530
  }
1457
1531
  throw new Error("element doesn't contain text " + text);
1458
1532
  }
1459
- 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))) {
1460
- state.info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1461
- state.info.value = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value;
1533
+ else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1534
+ state.info.foundText = foundObj?.text;
1535
+ state.info.value = foundObj?.value;
1462
1536
  throw new Error("element doesn't contain text " + text);
1463
1537
  }
1464
1538
  return state.info;
@@ -1649,11 +1723,9 @@ class StableBrowser {
1649
1723
  if (!fs.existsSync(world.screenshotPath)) {
1650
1724
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1651
1725
  }
1652
- let nextIndex = 1;
1653
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1654
- nextIndex++;
1655
- }
1656
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1726
+ // to make sure the path doesn't start with -
1727
+ const uuidStr = "id_" + randomUUID();
1728
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1657
1729
  try {
1658
1730
  await this.takeScreenshot(screenshotPath);
1659
1731
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1667,7 +1739,7 @@ class StableBrowser {
1667
1739
  catch (e) {
1668
1740
  this.logger.info("unable to take screenshot, ignored");
1669
1741
  }
1670
- result.screenshotId = nextIndex;
1742
+ result.screenshotId = uuidStr;
1671
1743
  result.screenshotPath = screenshotPath;
1672
1744
  if (info && info.box) {
1673
1745
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1765,74 +1837,101 @@ class StableBrowser {
1765
1837
  }
1766
1838
  }
1767
1839
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1768
- _validateSelectors(selectors);
1769
- const startTime = Date.now();
1770
- let error = null;
1771
- let screenshotId = null;
1772
- let screenshotPath = null;
1840
+ const state = {
1841
+ selectors,
1842
+ _params,
1843
+ attribute,
1844
+ variable,
1845
+ options,
1846
+ world,
1847
+ type: Types.EXTRACT,
1848
+ text: `Extract attribute from element`,
1849
+ operation: "extractAttribute",
1850
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1851
+ };
1773
1852
  await new Promise((resolve) => setTimeout(resolve, 2000));
1774
- const info = {};
1775
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1776
- info.operation = "extract";
1777
- info.selectors = selectors;
1778
1853
  try {
1779
- const element = await this._locate(selectors, info, _params);
1780
- await this._highlightElements(element);
1781
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1854
+ await _preCommand(state, this);
1782
1855
  switch (attribute) {
1783
1856
  case "inner_text":
1784
- info.value = await element.innerText();
1857
+ state.value = await state.element.innerText();
1785
1858
  break;
1786
1859
  case "href":
1787
- info.value = await element.getAttribute("href");
1860
+ state.value = await state.element.getAttribute("href");
1788
1861
  break;
1789
1862
  case "value":
1790
- info.value = await element.inputValue();
1863
+ state.value = await state.element.inputValue();
1791
1864
  break;
1792
1865
  default:
1793
- info.value = await element.getAttribute(attribute);
1866
+ state.value = await state.element.getAttribute(attribute);
1794
1867
  break;
1795
1868
  }
1796
- this[variable] = info.value;
1797
- if (world) {
1798
- world[variable] = info.value;
1869
+ state.info.value = state.value;
1870
+ this.setTestData({ [variable]: state.value }, world);
1871
+ this.logger.info("set test data: " + variable + "=" + state.value);
1872
+ return state.info;
1873
+ }
1874
+ catch (e) {
1875
+ await _commandError(state, e, this);
1876
+ }
1877
+ finally {
1878
+ _commandFinally(state, this);
1879
+ }
1880
+ }
1881
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1882
+ const state = {
1883
+ selectors,
1884
+ _params,
1885
+ attribute,
1886
+ value,
1887
+ options,
1888
+ world,
1889
+ type: Types.VERIFY_ATTRIBUTE,
1890
+ text: `Verify element attribute`,
1891
+ operation: "verifyAttribute",
1892
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1893
+ };
1894
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1895
+ let val;
1896
+ try {
1897
+ await _preCommand(state, this);
1898
+ switch (attribute) {
1899
+ case "innerText":
1900
+ val = String(await state.element.innerText());
1901
+ break;
1902
+ case "value":
1903
+ val = String(await state.element.inputValue());
1904
+ break;
1905
+ case "checked":
1906
+ val = String(await state.element.isChecked());
1907
+ break;
1908
+ case "disabled":
1909
+ val = String(await state.element.isDisabled());
1910
+ break;
1911
+ default:
1912
+ val = String(await state.element.getAttribute(attribute));
1913
+ break;
1799
1914
  }
1800
- this.setTestData({ [variable]: info.value }, world);
1801
- this.logger.info("set test data: " + variable + "=" + info.value);
1802
- return info;
1915
+ let regex;
1916
+ if (value.startsWith("/") && value.endsWith("/")) {
1917
+ const patternBody = value.slice(1, -1);
1918
+ regex = new RegExp(patternBody, "g");
1919
+ }
1920
+ else {
1921
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1922
+ regex = new RegExp(escapedPattern, "g");
1923
+ }
1924
+ if (!val.match(regex)) {
1925
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1926
+ }
1927
+ state.info.value = val;
1928
+ return state.info;
1803
1929
  }
1804
1930
  catch (e) {
1805
- //await this.closeUnexpectedPopups();
1806
- this.logger.error("extract failed " + info.log);
1807
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1808
- info.screenshotPath = screenshotPath;
1809
- Object.assign(e, { info: info });
1810
- error = e;
1811
- throw e;
1931
+ await _commandError(state, e, this);
1812
1932
  }
1813
1933
  finally {
1814
- const endTime = Date.now();
1815
- this._reportToWorld(world, {
1816
- element_name: selectors.element_name,
1817
- type: Types.EXTRACT_ATTRIBUTE,
1818
- variable: variable,
1819
- value: info.value,
1820
- text: "Extract attribute from element",
1821
- screenshotId,
1822
- result: error
1823
- ? {
1824
- status: "FAILED",
1825
- startTime,
1826
- endTime,
1827
- message: error === null || error === void 0 ? void 0 : error.message,
1828
- }
1829
- : {
1830
- status: "PASSED",
1831
- startTime,
1832
- endTime,
1833
- },
1834
- info: info,
1835
- });
1934
+ _commandFinally(state, this);
1836
1935
  }
1837
1936
  }
1838
1937
  async extractEmailData(emailAddress, options, world) {
@@ -1909,7 +2008,8 @@ class StableBrowser {
1909
2008
  catch (e) {
1910
2009
  errorCount++;
1911
2010
  if (errorCount > 3) {
1912
- throw e;
2011
+ // throw e;
2012
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1913
2013
  }
1914
2014
  // ignore
1915
2015
  }
@@ -2020,11 +2120,12 @@ class StableBrowser {
2020
2120
  info.screenshotPath = screenshotPath;
2021
2121
  Object.assign(e, { info: info });
2022
2122
  error = e;
2023
- throw e;
2123
+ // throw e;
2124
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2024
2125
  }
2025
2126
  finally {
2026
2127
  const endTime = Date.now();
2027
- this._reportToWorld(world, {
2128
+ _reportToWorld(world, {
2028
2129
  type: Types.VERIFY_PAGE_PATH,
2029
2130
  text: "Verify page path",
2030
2131
  screenshotId,
@@ -2033,7 +2134,7 @@ class StableBrowser {
2033
2134
  status: "FAILED",
2034
2135
  startTime,
2035
2136
  endTime,
2036
- message: error === null || error === void 0 ? void 0 : error.message,
2137
+ message: error?.message,
2037
2138
  }
2038
2139
  : {
2039
2140
  status: "PASSED",
@@ -2046,52 +2147,58 @@ class StableBrowser {
2046
2147
  }
2047
2148
  async verifyTextExistInPage(text, options = {}, world = null) {
2048
2149
  text = unEscapeString(text);
2049
- const startTime = Date.now();
2150
+ const state = {
2151
+ text_search: text,
2152
+ options,
2153
+ world,
2154
+ locate: false,
2155
+ scroll: false,
2156
+ highlight: false,
2157
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2158
+ text: `Verify text exists in page`,
2159
+ operation: "verifyTextExistInPage",
2160
+ log: "***** verify text " + text + " exists in page *****\n",
2161
+ };
2050
2162
  const timeout = this._getLoadTimeout(options);
2051
- let error = null;
2052
- let screenshotId = null;
2053
- let screenshotPath = null;
2054
2163
  await new Promise((resolve) => setTimeout(resolve, 2000));
2055
- const info = {};
2056
- info.log = "***** verify text " + text + " exists in page *****\n";
2057
- info.operation = "verifyTextExistInPage";
2058
2164
  const newValue = await this._replaceWithLocalData(text, world);
2059
2165
  if (newValue !== text) {
2060
2166
  this.logger.info(text + "=" + newValue);
2061
2167
  text = newValue;
2062
2168
  }
2063
- info.text = text;
2064
2169
  let dateAlternatives = findDateAlternatives(text);
2065
2170
  let numberAlternatives = findNumberAlternatives(text);
2066
2171
  try {
2172
+ await _preCommand(state, this);
2173
+ state.info.text = text;
2067
2174
  while (true) {
2068
2175
  const frames = this.page.frames();
2069
2176
  let results = [];
2070
2177
  for (let i = 0; i < frames.length; i++) {
2071
2178
  if (dateAlternatives.date) {
2072
2179
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2073
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2180
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2074
2181
  result.frame = frames[i];
2075
2182
  results.push(result);
2076
2183
  }
2077
2184
  }
2078
2185
  else if (numberAlternatives.number) {
2079
2186
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2080
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2187
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2081
2188
  result.frame = frames[i];
2082
2189
  results.push(result);
2083
2190
  }
2084
2191
  }
2085
2192
  else {
2086
- const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2193
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2087
2194
  result.frame = frames[i];
2088
2195
  results.push(result);
2089
2196
  }
2090
2197
  }
2091
- info.results = results;
2198
+ state.info.results = results;
2092
2199
  const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2093
2200
  if (resultWithElementsFound.length === 0) {
2094
- if (Date.now() - startTime > timeout) {
2201
+ if (Date.now() - state.startTime > timeout) {
2095
2202
  throw new Error(`Text ${text} not found in page`);
2096
2203
  }
2097
2204
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2103,44 +2210,89 @@ class StableBrowser {
2103
2210
  await this._highlightElements(frame, dataAttribute);
2104
2211
  const element = await frame.$(dataAttribute);
2105
2212
  if (element) {
2106
- await this.scrollIfNeeded(element, info);
2213
+ await this.scrollIfNeeded(element, state.info);
2107
2214
  await element.dispatchEvent("bvt_verify_page_contains_text");
2108
2215
  }
2109
2216
  }
2110
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2111
- return info;
2217
+ await _screenshot(state, this);
2218
+ return state.info;
2112
2219
  }
2113
2220
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2114
2221
  }
2115
2222
  catch (e) {
2116
- //await this.closeUnexpectedPopups();
2117
- this.logger.error("verify text exist in page failed " + info.log);
2118
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2119
- info.screenshotPath = screenshotPath;
2120
- Object.assign(e, { info: info });
2121
- error = e;
2122
- throw e;
2223
+ await _commandError(state, e, this);
2123
2224
  }
2124
2225
  finally {
2125
- const endTime = Date.now();
2126
- this._reportToWorld(world, {
2127
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2128
- text: "Verify text exists in page",
2129
- screenshotId,
2130
- result: error
2131
- ? {
2132
- status: "FAILED",
2133
- startTime,
2134
- endTime,
2135
- message: error === null || error === void 0 ? void 0 : error.message,
2226
+ _commandFinally(state, this);
2227
+ }
2228
+ }
2229
+ async waitForTextToDisappear(text, options = {}, world = null) {
2230
+ text = unEscapeString(text);
2231
+ const state = {
2232
+ text_search: text,
2233
+ options,
2234
+ world,
2235
+ locate: false,
2236
+ scroll: false,
2237
+ highlight: false,
2238
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2239
+ text: `Verify text does not exist in page`,
2240
+ operation: "verifyTextNotExistInPage",
2241
+ log: "***** verify text " + text + " does not exist in page *****\n",
2242
+ };
2243
+ const timeout = this._getLoadTimeout(options);
2244
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2245
+ const newValue = await this._replaceWithLocalData(text, world);
2246
+ if (newValue !== text) {
2247
+ this.logger.info(text + "=" + newValue);
2248
+ text = newValue;
2249
+ }
2250
+ let dateAlternatives = findDateAlternatives(text);
2251
+ let numberAlternatives = findNumberAlternatives(text);
2252
+ try {
2253
+ await _preCommand(state, this);
2254
+ state.info.text = text;
2255
+ while (true) {
2256
+ const frames = this.page.frames();
2257
+ let results = [];
2258
+ for (let i = 0; i < frames.length; i++) {
2259
+ if (dateAlternatives.date) {
2260
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2261
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2262
+ result.frame = frames[i];
2263
+ results.push(result);
2264
+ }
2136
2265
  }
2137
- : {
2138
- status: "PASSED",
2139
- startTime,
2140
- endTime,
2141
- },
2142
- info: info,
2143
- });
2266
+ else if (numberAlternatives.number) {
2267
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2268
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2269
+ result.frame = frames[i];
2270
+ results.push(result);
2271
+ }
2272
+ }
2273
+ else {
2274
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2275
+ result.frame = frames[i];
2276
+ results.push(result);
2277
+ }
2278
+ }
2279
+ state.info.results = results;
2280
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2281
+ if (resultWithElementsFound.length === 0) {
2282
+ await _screenshot(state, this);
2283
+ return state.info;
2284
+ }
2285
+ if (Date.now() - state.startTime > timeout) {
2286
+ throw new Error(`Text ${text} found in page`);
2287
+ }
2288
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2289
+ }
2290
+ }
2291
+ catch (e) {
2292
+ await _commandError(state, e, this);
2293
+ }
2294
+ finally {
2295
+ _commandFinally(state, this);
2144
2296
  }
2145
2297
  }
2146
2298
  _getServerUrl() {
@@ -2203,11 +2355,12 @@ class StableBrowser {
2203
2355
  info.screenshotPath = screenshotPath;
2204
2356
  Object.assign(e, { info: info });
2205
2357
  error = e;
2206
- throw e;
2358
+ // throw e;
2359
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2207
2360
  }
2208
2361
  finally {
2209
2362
  const endTime = Date.now();
2210
- this._reportToWorld(world, {
2363
+ _reportToWorld(world, {
2211
2364
  type: Types.VERIFY_VISUAL,
2212
2365
  text: "Visual verification",
2213
2366
  screenshotId,
@@ -2216,7 +2369,7 @@ class StableBrowser {
2216
2369
  status: "FAILED",
2217
2370
  startTime,
2218
2371
  endTime,
2219
- message: error === null || error === void 0 ? void 0 : error.message,
2372
+ message: error?.message,
2220
2373
  }
2221
2374
  : {
2222
2375
  status: "PASSED",
@@ -2270,11 +2423,12 @@ class StableBrowser {
2270
2423
  info.screenshotPath = screenshotPath;
2271
2424
  Object.assign(e, { info: info });
2272
2425
  error = e;
2273
- throw e;
2426
+ // throw e;
2427
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2274
2428
  }
2275
2429
  finally {
2276
2430
  const endTime = Date.now();
2277
- this._reportToWorld(world, {
2431
+ _reportToWorld(world, {
2278
2432
  element_name: selectors.element_name,
2279
2433
  type: Types.GET_TABLE_DATA,
2280
2434
  text: "Get table data",
@@ -2284,7 +2438,7 @@ class StableBrowser {
2284
2438
  status: "FAILED",
2285
2439
  startTime,
2286
2440
  endTime,
2287
- message: error === null || error === void 0 ? void 0 : error.message,
2441
+ message: error?.message,
2288
2442
  }
2289
2443
  : {
2290
2444
  status: "PASSED",
@@ -2435,11 +2589,12 @@ class StableBrowser {
2435
2589
  info.screenshotPath = screenshotPath;
2436
2590
  Object.assign(e, { info: info });
2437
2591
  error = e;
2438
- throw e;
2592
+ // throw e;
2593
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2439
2594
  }
2440
2595
  finally {
2441
2596
  const endTime = Date.now();
2442
- this._reportToWorld(world, {
2597
+ _reportToWorld(world, {
2443
2598
  element_name: selectors.element_name,
2444
2599
  type: Types.ANALYZE_TABLE,
2445
2600
  text: "Analyze table",
@@ -2449,7 +2604,7 @@ class StableBrowser {
2449
2604
  status: "FAILED",
2450
2605
  startTime,
2451
2606
  endTime,
2452
- message: error === null || error === void 0 ? void 0 : error.message,
2607
+ message: error?.message,
2453
2608
  }
2454
2609
  : {
2455
2610
  status: "PASSED",
@@ -2461,27 +2616,7 @@ class StableBrowser {
2461
2616
  }
2462
2617
  }
2463
2618
  async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
2464
- if (!value) {
2465
- return value;
2466
- }
2467
- // find all the accurance of {{(.*?)}} and replace with the value
2468
- let regex = /{{(.*?)}}/g;
2469
- let matches = value.match(regex);
2470
- if (matches) {
2471
- const testData = this.getTestData(world);
2472
- for (let i = 0; i < matches.length; i++) {
2473
- let match = matches[i];
2474
- let key = match.substring(2, match.length - 2);
2475
- let newValue = objectPath.get(testData, key, null);
2476
- if (newValue !== null) {
2477
- value = value.replace(match, newValue);
2478
- }
2479
- }
2480
- }
2481
- if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
2482
- return await decrypt(value, null, totpWait);
2483
- }
2484
- return value;
2619
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2485
2620
  }
2486
2621
  _getLoadTimeout(options) {
2487
2622
  let timeout = 15000;
@@ -2532,7 +2667,7 @@ class StableBrowser {
2532
2667
  await new Promise((resolve) => setTimeout(resolve, 2000));
2533
2668
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2534
2669
  const endTime = Date.now();
2535
- this._reportToWorld(world, {
2670
+ _reportToWorld(world, {
2536
2671
  type: Types.GET_PAGE_STATUS,
2537
2672
  text: "Wait for page load",
2538
2673
  screenshotId,
@@ -2541,7 +2676,7 @@ class StableBrowser {
2541
2676
  status: "FAILED",
2542
2677
  startTime,
2543
2678
  endTime,
2544
- message: error === null || error === void 0 ? void 0 : error.message,
2679
+ message: error?.message,
2545
2680
  }
2546
2681
  : {
2547
2682
  status: "PASSED",
@@ -2552,41 +2687,35 @@ class StableBrowser {
2552
2687
  }
2553
2688
  }
2554
2689
  async closePage(options = {}, world = null) {
2555
- const startTime = Date.now();
2556
- let error = null;
2557
- let screenshotId = null;
2558
- let screenshotPath = null;
2559
- const info = {};
2690
+ const state = {
2691
+ options,
2692
+ world,
2693
+ locate: false,
2694
+ scroll: false,
2695
+ highlight: false,
2696
+ type: Types.CLOSE_PAGE,
2697
+ text: `Close page`,
2698
+ operation: "closePage",
2699
+ log: "***** close page *****\n",
2700
+ throwError: false,
2701
+ };
2560
2702
  try {
2703
+ await _preCommand(state, this);
2561
2704
  await this.page.close();
2562
2705
  }
2563
2706
  catch (e) {
2564
2707
  console.log(".");
2708
+ await _commandError(state, e, this);
2565
2709
  }
2566
2710
  finally {
2567
- await new Promise((resolve) => setTimeout(resolve, 2000));
2568
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2569
- const endTime = Date.now();
2570
- this._reportToWorld(world, {
2571
- type: Types.CLOSE_PAGE,
2572
- text: "close page",
2573
- screenshotId,
2574
- result: error
2575
- ? {
2576
- status: "FAILED",
2577
- startTime,
2578
- endTime,
2579
- message: error === null || error === void 0 ? void 0 : error.message,
2580
- }
2581
- : {
2582
- status: "PASSED",
2583
- startTime,
2584
- endTime,
2585
- },
2586
- info: info,
2587
- });
2711
+ _commandFinally(state, this);
2588
2712
  }
2589
2713
  }
2714
+ saveTestDataAsGlobal(options, world) {
2715
+ const dataFile = this._getDataFile(world);
2716
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2717
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2718
+ }
2590
2719
  async setViewportSize(width, hight, options = {}, world = null) {
2591
2720
  const startTime = Date.now();
2592
2721
  let error = null;
@@ -2604,12 +2733,13 @@ class StableBrowser {
2604
2733
  }
2605
2734
  catch (e) {
2606
2735
  console.log(".");
2736
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2607
2737
  }
2608
2738
  finally {
2609
2739
  await new Promise((resolve) => setTimeout(resolve, 2000));
2610
2740
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2611
2741
  const endTime = Date.now();
2612
- this._reportToWorld(world, {
2742
+ _reportToWorld(world, {
2613
2743
  type: Types.SET_VIEWPORT,
2614
2744
  text: "set viewport size to " + width + "x" + hight,
2615
2745
  screenshotId,
@@ -2618,7 +2748,7 @@ class StableBrowser {
2618
2748
  status: "FAILED",
2619
2749
  startTime,
2620
2750
  endTime,
2621
- message: error === null || error === void 0 ? void 0 : error.message,
2751
+ message: error?.message,
2622
2752
  }
2623
2753
  : {
2624
2754
  status: "PASSED",
@@ -2640,12 +2770,13 @@ class StableBrowser {
2640
2770
  }
2641
2771
  catch (e) {
2642
2772
  console.log(".");
2773
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2643
2774
  }
2644
2775
  finally {
2645
2776
  await new Promise((resolve) => setTimeout(resolve, 2000));
2646
2777
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2647
2778
  const endTime = Date.now();
2648
- this._reportToWorld(world, {
2779
+ _reportToWorld(world, {
2649
2780
  type: Types.GET_PAGE_STATUS,
2650
2781
  text: "page relaod",
2651
2782
  screenshotId,
@@ -2654,7 +2785,7 @@ class StableBrowser {
2654
2785
  status: "FAILED",
2655
2786
  startTime,
2656
2787
  endTime,
2657
- message: error === null || error === void 0 ? void 0 : error.message,
2788
+ message: error?.message,
2658
2789
  }
2659
2790
  : {
2660
2791
  status: "PASSED",
@@ -2681,11 +2812,37 @@ class StableBrowser {
2681
2812
  console.log("#-#");
2682
2813
  }
2683
2814
  }
2684
- _reportToWorld(world, properties) {
2685
- if (!world || !world.attach) {
2686
- return;
2815
+ async beforeStep(world, step) {
2816
+ this.stepName = step.pickleStep.text;
2817
+ this.logger.info("step: " + this.stepName);
2818
+ if (this.stepIndex === undefined) {
2819
+ this.stepIndex = 0;
2820
+ }
2821
+ else {
2822
+ this.stepIndex++;
2823
+ }
2824
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2825
+ if (this.context.browserObject.context) {
2826
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2827
+ }
2828
+ }
2829
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2830
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2831
+ // check if @global_test_data tag is present
2832
+ if (this.tags.includes("@global_test_data")) {
2833
+ this.saveTestDataAsGlobal({}, world);
2834
+ }
2835
+ }
2836
+ }
2837
+ async afterStep(world, step) {
2838
+ this.stepName = null;
2839
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2840
+ if (this.context.browserObject.context) {
2841
+ await this.context.browserObject.context.tracing.stopChunk({
2842
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2843
+ });
2844
+ }
2687
2845
  }
2688
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2689
2846
  }
2690
2847
  }
2691
2848
  function createTimedPromise(promise, label) {