automation_model 1.0.472-dev → 1.0.472-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.
@@ -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,27 @@ 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",
47
49
  };
48
50
  export const apps = {};
49
51
  class StableBrowser {
52
+ browser;
53
+ page;
54
+ logger;
55
+ context;
56
+ world;
57
+ project_path = null;
58
+ webLogFile = null;
59
+ networkLogger = null;
60
+ configuration = null;
61
+ appName = "main";
62
+ tags = null;
50
63
  constructor(browser, page, logger = null, context = null, world = null) {
51
64
  this.browser = browser;
52
65
  this.page = page;
53
66
  this.logger = logger;
54
67
  this.context = context;
55
68
  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
69
  if (!this.logger) {
62
70
  this.logger = console;
63
71
  }
@@ -82,11 +90,31 @@ class StableBrowser {
82
90
  catch (e) {
83
91
  this.logger.error("unable to read ai_config.json");
84
92
  }
85
- context.pageLoading = { status: false };
86
- context.pages = [this.page];
87
93
  const logFolder = path.join(this.project_path, "logs", "web");
88
94
  this.world = world;
95
+ context.pages = [this.page];
96
+ context.pageLoading = { status: false };
89
97
  this.registerEventListeners(this.context);
98
+ registerNetworkEvents(this.world, this, this.context, this.page);
99
+ registerDownloadEvent(this.page, this.world, this.context);
100
+ }
101
+ async scrollPageToLoadLazyElements() {
102
+ let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
103
+ let retry = 0;
104
+ while (true) {
105
+ await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
106
+ await new Promise((resolve) => setTimeout(resolve, 1000));
107
+ let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
108
+ if (newHeight === lastHeight) {
109
+ break;
110
+ }
111
+ lastHeight = newHeight;
112
+ retry++;
113
+ if (retry > 10) {
114
+ break;
115
+ }
116
+ }
117
+ await this.page.evaluate(() => window.scrollTo(0, 0));
90
118
  }
91
119
  registerEventListeners(context) {
92
120
  this.registerConsoleLogListener(this.page, context);
@@ -95,10 +123,17 @@ class StableBrowser {
95
123
  context.pageLoading = { status: false };
96
124
  }
97
125
  context.playContext.on("page", async function (page) {
126
+ if (this.configuration && this.configuration.closePopups === true) {
127
+ console.log("close unexpected popups");
128
+ await page.close();
129
+ return;
130
+ }
98
131
  context.pageLoading.status = true;
99
132
  this.page = page;
100
133
  context.page = page;
101
134
  context.pages.push(page);
135
+ registerNetworkEvents(this.world, this, context, this.page);
136
+ registerDownloadEvent(this.page, this.world, context);
102
137
  page.on("close", async () => {
103
138
  if (this.context && this.context.pages && this.context.pages.length > 1) {
104
139
  this.context.pages.pop();
@@ -128,9 +163,9 @@ class StableBrowser {
128
163
  if (this.appName === appName) {
129
164
  return;
130
165
  }
131
- let newContextCreated = false;
166
+ let navigate = false;
132
167
  if (!apps[appName]) {
133
- let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this);
168
+ let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
134
169
  newContextCreated = true;
135
170
  apps[appName] = {
136
171
  context: newContext,
@@ -143,8 +178,7 @@ class StableBrowser {
143
178
  this._copyContext(apps[appName], this);
144
179
  apps[this.appName] = tempContext;
145
180
  this.appName = appName;
146
- if (newContextCreated) {
147
- this.registerEventListeners(this.context);
181
+ if (navigate) {
148
182
  await this.goto(this.context.environment.baseUrl);
149
183
  await this.waitForPageLoad();
150
184
  }
@@ -170,7 +204,6 @@ class StableBrowser {
170
204
  this.context.webLogger = [];
171
205
  }
172
206
  page.on("console", async (msg) => {
173
- var _a;
174
207
  const obj = {
175
208
  type: msg.type(),
176
209
  text: msg.text(),
@@ -179,7 +212,7 @@ class StableBrowser {
179
212
  };
180
213
  this.context.webLogger.push(obj);
181
214
  if (msg.type() === "error") {
182
- (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
215
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
183
216
  }
184
217
  });
185
218
  }
@@ -188,7 +221,6 @@ class StableBrowser {
188
221
  this.context.networkLogger = [];
189
222
  }
190
223
  page.on("request", async (data) => {
191
- var _a;
192
224
  const startTime = new Date().getTime();
193
225
  try {
194
226
  const pageUrl = new URL(page.url());
@@ -213,10 +245,10 @@ class StableBrowser {
213
245
  startTime,
214
246
  };
215
247
  context.networkLogger.push(obj);
216
- (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
248
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
217
249
  }
218
250
  catch (error) {
219
- console.error("Error in request listener", error);
251
+ // console.error("Error in request listener", error);
220
252
  context.networkLogger.push({
221
253
  error: "not able to listen",
222
254
  message: error.message,
@@ -305,7 +337,7 @@ class StableBrowser {
305
337
  locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
306
338
  }
307
339
  }
308
- if (locator === null || locator === void 0 ? void 0 : locator.engine) {
340
+ if (locator?.engine) {
309
341
  if (locator.engine === "css") {
310
342
  locatorReturn = scope.locator(locator.selector);
311
343
  }
@@ -329,7 +361,7 @@ class StableBrowser {
329
361
  if (css && css.locator) {
330
362
  css = css.locator;
331
363
  }
332
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
364
+ let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
333
365
  if (result.elementCount === 0) {
334
366
  return;
335
367
  }
@@ -344,7 +376,7 @@ class StableBrowser {
344
376
  }
345
377
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
346
378
  //const stringifyText = JSON.stringify(text);
347
- return await scope.evaluate(([text, tag, regex, partial]) => {
379
+ return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
348
380
  function isParent(parent, child) {
349
381
  let currentNode = child.parentNode;
350
382
  while (currentNode !== null) {
@@ -381,7 +413,7 @@ class StableBrowser {
381
413
  }
382
414
  document.collectAllShadowDomElements = collectAllShadowDomElements;
383
415
  if (!tag) {
384
- tag = "*";
416
+ tag = "*:not(script, style, head)";
385
417
  }
386
418
  let regexpSearch = document.getRegex(text);
387
419
  if (regexpSearch) {
@@ -463,6 +495,15 @@ class StableBrowser {
463
495
  }, [text1, tag1, regex1, partial1]);
464
496
  }
465
497
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
498
+ if (!info) {
499
+ info = {};
500
+ }
501
+ if (!info.failCause) {
502
+ info.failCause = {};
503
+ }
504
+ if (!info.log) {
505
+ info.log = "";
506
+ }
466
507
  let locatorSearch = selectorHierarchy[index];
467
508
  try {
468
509
  locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
@@ -475,13 +516,18 @@ class StableBrowser {
475
516
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
476
517
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
477
518
  if (!locatorString) {
519
+ info.failCause.textNotFound = true;
520
+ info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
478
521
  return;
479
522
  }
480
523
  locator = this._getLocator({ css: locatorString }, scope, _params);
481
524
  }
482
525
  else if (locatorSearch.text) {
483
- let result = await this._locateElementByText(scope, this._fixUsingParams(locatorSearch.text, _params), locatorSearch.tag, false, locatorSearch.partial === true, _params);
526
+ let text = this._fixUsingParams(locatorSearch.text, _params);
527
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
484
528
  if (result.elementCount === 0) {
529
+ info.failCause.textNotFound = true;
530
+ info.failCause.lastError = "failed to locate element by text: " + text;
485
531
  return;
486
532
  }
487
533
  locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
@@ -498,10 +544,13 @@ class StableBrowser {
498
544
  // cssHref = true;
499
545
  // }
500
546
  let count = await locator.count();
547
+ if (count > 0 && !info.failCause.count) {
548
+ info.failCause.count = count;
549
+ }
501
550
  //info.log += "total elements found " + count + "\n";
502
551
  //let visibleCount = 0;
503
552
  let visibleLocator = null;
504
- if (locatorSearch.index && locatorSearch.index < count) {
553
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
505
554
  foundLocators.push(locator.nth(locatorSearch.index));
506
555
  return;
507
556
  }
@@ -515,6 +564,8 @@ class StableBrowser {
515
564
  foundLocators.push(locator.nth(j));
516
565
  }
517
566
  else {
567
+ info.failCause.visible = visible;
568
+ info.failCause.enabled = enabled;
518
569
  if (!info.printMessages) {
519
570
  info.printMessages = {};
520
571
  }
@@ -526,6 +577,11 @@ class StableBrowser {
526
577
  }
527
578
  }
528
579
  async closeUnexpectedPopups(info, _params) {
580
+ if (!info) {
581
+ info = {};
582
+ info.failCause = {};
583
+ info.log = "";
584
+ }
529
585
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
530
586
  if (!info) {
531
587
  info = {};
@@ -557,16 +613,31 @@ class StableBrowser {
557
613
  }
558
614
  if (result.foundElements.length > 0) {
559
615
  let dialogCloseLocator = result.foundElements[0].locator;
560
- await dialogCloseLocator.click();
561
- // wait for the dialog to close
562
- await dialogCloseLocator.waitFor({ state: "hidden" });
616
+ try {
617
+ await scope?.evaluate(() => {
618
+ window.__isClosingPopups = true;
619
+ });
620
+ await dialogCloseLocator.click();
621
+ // wait for the dialog to close
622
+ await dialogCloseLocator.waitFor({ state: "hidden" });
623
+ }
624
+ catch (e) {
625
+ }
626
+ finally {
627
+ await scope?.evaluate(() => {
628
+ window.__isClosingPopups = false;
629
+ });
630
+ }
563
631
  return { rerun: true };
564
632
  }
565
633
  }
566
634
  }
567
635
  return { rerun: false };
568
636
  }
569
- async _locate(selectors, info, _params, timeout = 30000) {
637
+ async _locate(selectors, info, _params, timeout) {
638
+ if (!timeout) {
639
+ timeout = 30000;
640
+ }
570
641
  for (let i = 0; i < 3; i++) {
571
642
  info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
572
643
  for (let j = 0; j < selectors.locators.length; j++) {
@@ -580,16 +651,16 @@ class StableBrowser {
580
651
  }
581
652
  throw new Error("unable to locate element " + JSON.stringify(selectors));
582
653
  }
583
- async _locate_internal(selectors, info, _params, timeout = 30000) {
584
- let highPriorityTimeout = 5000;
585
- let visibleOnlyTimeout = 6000;
586
- let startTime = performance.now();
587
- let locatorsCount = 0;
588
- //let arrayMode = Array.isArray(selectors);
654
+ async _findFrameScope(selectors, timeout = 30000, info) {
655
+ if (!info) {
656
+ info = {};
657
+ info.failCause = {};
658
+ info.log = "";
659
+ }
660
+ let startTime = Date.now();
589
661
  let scope = this.page;
590
- // for the simple click usecase
591
662
  if (selectors.frame) {
592
- scope = selectors.frame;
663
+ return selectors.frame;
593
664
  }
594
665
  if (selectors.iframe_src || selectors.frameLocators) {
595
666
  const findFrame = async (frame, framescope) => {
@@ -617,7 +688,6 @@ class StableBrowser {
617
688
  }
618
689
  return framescope;
619
690
  };
620
- info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
621
691
  while (true) {
622
692
  let frameFound = false;
623
693
  if (selectors.nestFrmLoc) {
@@ -640,7 +710,9 @@ class StableBrowser {
640
710
  }
641
711
  if (!scope) {
642
712
  info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
643
- if (performance.now() - startTime > timeout) {
713
+ if (Date.now() - startTime > timeout) {
714
+ info.failCause.iframeNotFound = true;
715
+ info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
644
716
  throw new Error("unable to locate iframe " + selectors.iframe_src);
645
717
  }
646
718
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -650,6 +722,31 @@ class StableBrowser {
650
722
  }
651
723
  }
652
724
  }
725
+ if (!scope) {
726
+ scope = this.page;
727
+ }
728
+ return scope;
729
+ }
730
+ async _getDocumentBody(selectors, timeout = 30000, info) {
731
+ let scope = await this._findFrameScope(selectors, timeout, info);
732
+ return scope.evaluate(() => {
733
+ var bodyContent = document.body.innerHTML;
734
+ return bodyContent;
735
+ });
736
+ }
737
+ async _locate_internal(selectors, info, _params, timeout = 30000) {
738
+ if (!info) {
739
+ info = {};
740
+ info.failCause = {};
741
+ info.log = "";
742
+ }
743
+ let highPriorityTimeout = 5000;
744
+ let visibleOnlyTimeout = 6000;
745
+ let startTime = Date.now();
746
+ let locatorsCount = 0;
747
+ let lazy_scroll = false;
748
+ //let arrayMode = Array.isArray(selectors);
749
+ let scope = await this._findFrameScope(selectors, timeout, info);
653
750
  let selectorsLocators = null;
654
751
  selectorsLocators = selectors.locators;
655
752
  // group selectors by priority
@@ -736,14 +833,18 @@ class StableBrowser {
736
833
  return maxCountElement.locator;
737
834
  }
738
835
  }
739
- if (performance.now() - startTime > timeout) {
836
+ if (Date.now() - startTime > timeout) {
740
837
  break;
741
838
  }
742
- if (performance.now() - startTime > highPriorityTimeout) {
839
+ if (Date.now() - startTime > highPriorityTimeout) {
743
840
  info.log += "high priority timeout, will try all elements" + "\n";
744
841
  highPriorityOnly = false;
842
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
843
+ lazy_scroll = true;
844
+ await this.scrollPageToLoadLazyElements();
845
+ }
745
846
  }
746
- if (performance.now() - startTime > visibleOnlyTimeout) {
847
+ if (Date.now() - startTime > visibleOnlyTimeout) {
747
848
  info.log += "visible only timeout, will try all elements" + "\n";
748
849
  visibleOnly = false;
749
850
  }
@@ -751,6 +852,8 @@ class StableBrowser {
751
852
  }
752
853
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
753
854
  info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
855
+ info.failCause.locatorNotFound = true;
856
+ info.failCause.lastError = "failed to locate unique element";
754
857
  throw new Error("failed to locate first element no elements found, " + info.log);
755
858
  }
756
859
  async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
@@ -782,6 +885,9 @@ class StableBrowser {
782
885
  });
783
886
  result.locatorIndex = i;
784
887
  }
888
+ if (foundLocators.length > 1) {
889
+ info.failCause.foundMultiple = true;
890
+ }
785
891
  }
786
892
  return result;
787
893
  }
@@ -794,12 +900,12 @@ class StableBrowser {
794
900
  while (true) {
795
901
  try {
796
902
  const result = await locate_element(this.context, elementDescription, "click");
797
- if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
903
+ if (result?.elementNumber >= 0) {
798
904
  const selectors = {
799
- frame: result === null || result === void 0 ? void 0 : result.frame,
905
+ frame: result?.frame,
800
906
  locators: [
801
907
  {
802
- css: result === null || result === void 0 ? void 0 : result.css,
908
+ css: result?.css,
803
909
  },
804
910
  ],
805
911
  };
@@ -808,8 +914,9 @@ class StableBrowser {
808
914
  }
809
915
  }
810
916
  catch (e) {
811
- if (performance.now() - startTime > timeout) {
812
- throw e;
917
+ if (Date.now() - startTime > timeout) {
918
+ // throw e;
919
+ await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
813
920
  }
814
921
  }
815
922
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -824,12 +931,12 @@ class StableBrowser {
824
931
  while (true) {
825
932
  try {
826
933
  const result = await locate_element(this.context, elementDescription, "fill", value);
827
- if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
934
+ if (result?.elementNumber >= 0) {
828
935
  const selectors = {
829
- frame: result === null || result === void 0 ? void 0 : result.frame,
936
+ frame: result?.frame,
830
937
  locators: [
831
938
  {
832
- css: result === null || result === void 0 ? void 0 : result.css,
939
+ css: result?.css,
833
940
  },
834
941
  ],
835
942
  };
@@ -838,8 +945,9 @@ class StableBrowser {
838
945
  }
839
946
  }
840
947
  catch (e) {
841
- if (performance.now() - startTime > timeout) {
842
- throw e;
948
+ if (Date.now() - startTime > timeout) {
949
+ // throw e;
950
+ await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
843
951
  }
844
952
  }
845
953
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -863,13 +971,13 @@ class StableBrowser {
863
971
  }
864
972
  try {
865
973
  await state.element.click();
866
- await new Promise((resolve) => setTimeout(resolve, 1000));
974
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
867
975
  }
868
976
  catch (e) {
869
977
  // await this.closeUnexpectedPopups();
870
978
  state.element = await this._locate(selectors, state.info, _params);
871
979
  await state.element.dispatchEvent("click");
872
- await new Promise((resolve) => setTimeout(resolve, 1000));
980
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
873
981
  }
874
982
  await this.waitForPageLoad();
875
983
  return state.info;
@@ -893,7 +1001,7 @@ class StableBrowser {
893
1001
  log: "***** check " + selectors.element_name + " *****\n",
894
1002
  };
895
1003
  try {
896
- _preCommand(state, this);
1004
+ await _preCommand(state, this);
897
1005
  state.info.checked = checked;
898
1006
  // let element = await this._locate(selectors, info, _params);
899
1007
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
@@ -908,8 +1016,8 @@ class StableBrowser {
908
1016
  }
909
1017
  else {
910
1018
  //await this.closeUnexpectedPopups();
911
- info.log += "setCheck failed, will try again" + "\n";
912
- state.element = await this._locate(selectors, info, _params);
1019
+ state.info.log += "setCheck failed, will try again" + "\n";
1020
+ state.element = await this._locate(selectors, state.info, _params);
913
1021
  await state.element.setChecked(checked, { timeout: 5000, force: true });
914
1022
  await new Promise((resolve) => setTimeout(resolve, 1000));
915
1023
  }
@@ -936,7 +1044,7 @@ class StableBrowser {
936
1044
  log: "***** hover " + selectors.element_name + " *****\n",
937
1045
  };
938
1046
  try {
939
- _preCommand(state, this);
1047
+ await _preCommand(state, this);
940
1048
  try {
941
1049
  await state.element.hover();
942
1050
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -967,6 +1075,7 @@ class StableBrowser {
967
1075
  _params,
968
1076
  options,
969
1077
  world,
1078
+ value: values.toString(),
970
1079
  type: Types.SELECT,
971
1080
  text: `Select option: ${values}`,
972
1081
  operation: "selectOption",
@@ -1074,33 +1183,30 @@ class StableBrowser {
1074
1183
  }
1075
1184
  }
1076
1185
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1077
- _validateSelectors(selectors);
1078
- const startTime = Date.now();
1079
- let error = null;
1080
- let screenshotId = null;
1081
- let screenshotPath = null;
1082
- const info = {};
1083
- info.log = "";
1084
- info.operation = Types.SET_DATE_TIME;
1085
- info.selectors = selectors;
1086
- info.value = value;
1186
+ const state = {
1187
+ selectors,
1188
+ _params,
1189
+ value: await this._replaceWithLocalData(value, this),
1190
+ options,
1191
+ world,
1192
+ type: Types.SET_DATE_TIME,
1193
+ text: `Set date time value: ${value}`,
1194
+ operation: "setDateTime",
1195
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1196
+ throwError: false,
1197
+ };
1087
1198
  try {
1088
- value = await this._replaceWithLocalData(value, this);
1089
- let element = await this._locate(selectors, info, _params);
1090
- //insert red border around the element
1091
- await this.scrollIfNeeded(element, info);
1092
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1093
- await this._highlightElements(element);
1199
+ await _preCommand(state, this);
1094
1200
  try {
1095
- await element.click();
1201
+ await state.element.click();
1096
1202
  await new Promise((resolve) => setTimeout(resolve, 500));
1097
1203
  if (format) {
1098
- value = dayjs(value).format(format);
1099
- await element.fill(value);
1204
+ state.value = dayjs(state.value).format(format);
1205
+ await state.element.fill(state.value);
1100
1206
  }
1101
1207
  else {
1102
- const dateTimeValue = await getDateTimeValue({ value, element });
1103
- await element.evaluateHandle((el, dateTimeValue) => {
1208
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1209
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1104
1210
  el.value = ""; // clear input
1105
1211
  el.value = dateTimeValue;
1106
1212
  }, dateTimeValue);
@@ -1113,20 +1219,19 @@ class StableBrowser {
1113
1219
  }
1114
1220
  catch (err) {
1115
1221
  //await this.closeUnexpectedPopups();
1116
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1222
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1117
1223
  this.logger.info("Trying again");
1118
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1119
- info.screenshotPath = screenshotPath;
1120
- Object.assign(err, { info: info });
1224
+ await _screenshot(state, this);
1225
+ Object.assign(err, { info: state.info });
1121
1226
  await element.click();
1122
1227
  await new Promise((resolve) => setTimeout(resolve, 500));
1123
1228
  if (format) {
1124
- value = dayjs(value).format(format);
1125
- await element.fill(value);
1229
+ state.value = dayjs(state.value).format(format);
1230
+ await state.element.fill(state.value);
1126
1231
  }
1127
1232
  else {
1128
- const dateTimeValue = await getDateTimeValue({ value, element });
1129
- await element.evaluateHandle((el, dateTimeValue) => {
1233
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1234
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1130
1235
  el.value = ""; // clear input
1131
1236
  el.value = dateTimeValue;
1132
1237
  }, dateTimeValue);
@@ -1139,46 +1244,27 @@ class StableBrowser {
1139
1244
  }
1140
1245
  }
1141
1246
  catch (e) {
1142
- error = e;
1143
- throw e;
1247
+ await _commandError(state, e, this);
1144
1248
  }
1145
1249
  finally {
1146
- const endTime = Date.now();
1147
- this._reportToWorld(world, {
1148
- element_name: selectors.element_name,
1149
- type: Types.SET_DATE_TIME,
1150
- screenshotId,
1151
- value: value,
1152
- text: `setDateTime input with value: ${value}`,
1153
- result: error
1154
- ? {
1155
- status: "FAILED",
1156
- startTime,
1157
- endTime,
1158
- message: error === null || error === void 0 ? void 0 : error.message,
1159
- }
1160
- : {
1161
- status: "PASSED",
1162
- startTime,
1163
- endTime,
1164
- },
1165
- info: info,
1166
- });
1250
+ _commandFinally(state, this);
1167
1251
  }
1168
1252
  }
1169
1253
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1254
+ _value = unEscapeString(_value);
1255
+ const newValue = await this._replaceWithLocalData(_value, world);
1170
1256
  const state = {
1171
1257
  selectors,
1172
1258
  _params,
1173
- value: unEscapeString(_value),
1259
+ value: newValue,
1260
+ originalValue: _value,
1174
1261
  options,
1175
1262
  world,
1176
1263
  type: Types.FILL,
1177
1264
  text: `Click type input with value: ${_value}`,
1178
1265
  operation: "clickType",
1179
- log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
1266
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1180
1267
  };
1181
- const newValue = await this._replaceWithLocalData(state.value, world);
1182
1268
  if (newValue !== _value) {
1183
1269
  //this.logger.info(_value + "=" + newValue);
1184
1270
  _value = newValue;
@@ -1190,7 +1276,7 @@ class StableBrowser {
1190
1276
  try {
1191
1277
  let currentValue = await state.element.inputValue();
1192
1278
  if (currentValue) {
1193
- await element.fill("");
1279
+ await state.element.fill("");
1194
1280
  }
1195
1281
  }
1196
1282
  catch (e) {
@@ -1337,7 +1423,6 @@ class StableBrowser {
1337
1423
  }
1338
1424
  }
1339
1425
  async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
1340
- var _a;
1341
1426
  if (!pattern) {
1342
1427
  throw new Error("pattern is null");
1343
1428
  }
@@ -1348,7 +1433,7 @@ class StableBrowser {
1348
1433
  selectors,
1349
1434
  _params,
1350
1435
  pattern,
1351
- value: text,
1436
+ value: pattern,
1352
1437
  options,
1353
1438
  world,
1354
1439
  locate: false,
@@ -1377,14 +1462,14 @@ class StableBrowser {
1377
1462
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1378
1463
  pattern = pattern.replace("{text}", escapedText);
1379
1464
  let regex = new RegExp(pattern, "im");
1380
- 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))) {
1381
- state.info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1465
+ if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
1466
+ state.info.foundText = foundObj?.text;
1382
1467
  throw new Error("element doesn't contain text " + text);
1383
1468
  }
1384
1469
  return state.info;
1385
1470
  }
1386
1471
  catch (e) {
1387
- this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
1472
+ this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
1388
1473
  await _commandError(state, e, this);
1389
1474
  }
1390
1475
  finally {
@@ -1392,7 +1477,6 @@ class StableBrowser {
1392
1477
  }
1393
1478
  }
1394
1479
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1395
- var _a, _b, _c;
1396
1480
  const state = {
1397
1481
  selectors,
1398
1482
  _params,
@@ -1419,6 +1503,7 @@ class StableBrowser {
1419
1503
  }
1420
1504
  let foundObj = null;
1421
1505
  try {
1506
+ await _preCommand(state, this);
1422
1507
  foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1423
1508
  if (foundObj && foundObj.element) {
1424
1509
  await this.scrollIfNeeded(foundObj.element, state.info);
@@ -1428,8 +1513,8 @@ class StableBrowser {
1428
1513
  const numberAlternatives = findNumberAlternatives(text);
1429
1514
  if (dateAlternatives.date) {
1430
1515
  for (let i = 0; i < dateAlternatives.dates.length; i++) {
1431
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(dateAlternatives.dates[i])) ||
1432
- ((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(dateAlternatives.dates[i]))) {
1516
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1517
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1433
1518
  return state.info;
1434
1519
  }
1435
1520
  }
@@ -1437,16 +1522,16 @@ class StableBrowser {
1437
1522
  }
1438
1523
  else if (numberAlternatives.number) {
1439
1524
  for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1440
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(numberAlternatives.numbers[i])) ||
1441
- ((_b = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _b === void 0 ? void 0 : _b.includes(numberAlternatives.numbers[i]))) {
1525
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1526
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1442
1527
  return state.info;
1443
1528
  }
1444
1529
  }
1445
1530
  throw new Error("element doesn't contain text " + text);
1446
1531
  }
1447
- 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))) {
1448
- state.info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1449
- state.info.value = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value;
1532
+ else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1533
+ state.info.foundText = foundObj?.text;
1534
+ state.info.value = foundObj?.value;
1450
1535
  throw new Error("element doesn't contain text " + text);
1451
1536
  }
1452
1537
  return state.info;
@@ -1637,11 +1722,9 @@ class StableBrowser {
1637
1722
  if (!fs.existsSync(world.screenshotPath)) {
1638
1723
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1639
1724
  }
1640
- let nextIndex = 1;
1641
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1642
- nextIndex++;
1643
- }
1644
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1725
+ // to make sure the path doesn't start with -
1726
+ const uuidStr = "id_" + randomUUID();
1727
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1645
1728
  try {
1646
1729
  await this.takeScreenshot(screenshotPath);
1647
1730
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1655,7 +1738,7 @@ class StableBrowser {
1655
1738
  catch (e) {
1656
1739
  this.logger.info("unable to take screenshot, ignored");
1657
1740
  }
1658
- result.screenshotId = nextIndex;
1741
+ result.screenshotId = uuidStr;
1659
1742
  result.screenshotPath = screenshotPath;
1660
1743
  if (info && info.box) {
1661
1744
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1753,74 +1836,45 @@ class StableBrowser {
1753
1836
  }
1754
1837
  }
1755
1838
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1756
- _validateSelectors(selectors);
1757
- const startTime = Date.now();
1758
- let error = null;
1759
- let screenshotId = null;
1760
- let screenshotPath = null;
1839
+ const state = {
1840
+ selectors,
1841
+ _params,
1842
+ attribute,
1843
+ variable,
1844
+ options,
1845
+ world,
1846
+ type: Types.EXTRACT,
1847
+ text: `Extract attribute from element`,
1848
+ operation: "extractAttribute",
1849
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1850
+ };
1761
1851
  await new Promise((resolve) => setTimeout(resolve, 2000));
1762
- const info = {};
1763
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1764
- info.operation = "extract";
1765
- info.selectors = selectors;
1766
1852
  try {
1767
- const element = await this._locate(selectors, info, _params);
1768
- await this._highlightElements(element);
1769
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1853
+ await _preCommand(state, this);
1770
1854
  switch (attribute) {
1771
1855
  case "inner_text":
1772
- info.value = await element.innerText();
1856
+ state.value = await state.element.innerText();
1773
1857
  break;
1774
1858
  case "href":
1775
- info.value = await element.getAttribute("href");
1859
+ state.value = await state.element.getAttribute("href");
1776
1860
  break;
1777
1861
  case "value":
1778
- info.value = await element.inputValue();
1862
+ state.value = await state.element.inputValue();
1779
1863
  break;
1780
1864
  default:
1781
- info.value = await element.getAttribute(attribute);
1865
+ state.value = await state.element.getAttribute(attribute);
1782
1866
  break;
1783
1867
  }
1784
- this[variable] = info.value;
1785
- if (world) {
1786
- world[variable] = info.value;
1787
- }
1788
- this.setTestData({ [variable]: info.value }, world);
1789
- this.logger.info("set test data: " + variable + "=" + info.value);
1790
- return info;
1868
+ state.info.value = state.value;
1869
+ this.setTestData({ [variable]: state.value }, world);
1870
+ this.logger.info("set test data: " + variable + "=" + state.value);
1871
+ return state.info;
1791
1872
  }
1792
1873
  catch (e) {
1793
- //await this.closeUnexpectedPopups();
1794
- this.logger.error("extract failed " + info.log);
1795
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1796
- info.screenshotPath = screenshotPath;
1797
- Object.assign(e, { info: info });
1798
- error = e;
1799
- throw e;
1874
+ await _commandError(state, e, this);
1800
1875
  }
1801
1876
  finally {
1802
- const endTime = Date.now();
1803
- this._reportToWorld(world, {
1804
- element_name: selectors.element_name,
1805
- type: Types.EXTRACT_ATTRIBUTE,
1806
- variable: variable,
1807
- value: info.value,
1808
- text: "Extract attribute from element",
1809
- screenshotId,
1810
- result: error
1811
- ? {
1812
- status: "FAILED",
1813
- startTime,
1814
- endTime,
1815
- message: error === null || error === void 0 ? void 0 : error.message,
1816
- }
1817
- : {
1818
- status: "PASSED",
1819
- startTime,
1820
- endTime,
1821
- },
1822
- info: info,
1823
- });
1877
+ _commandFinally(state, this);
1824
1878
  }
1825
1879
  }
1826
1880
  async extractEmailData(emailAddress, options, world) {
@@ -1897,7 +1951,8 @@ class StableBrowser {
1897
1951
  catch (e) {
1898
1952
  errorCount++;
1899
1953
  if (errorCount > 3) {
1900
- throw e;
1954
+ // throw e;
1955
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1901
1956
  }
1902
1957
  // ignore
1903
1958
  }
@@ -2008,11 +2063,12 @@ class StableBrowser {
2008
2063
  info.screenshotPath = screenshotPath;
2009
2064
  Object.assign(e, { info: info });
2010
2065
  error = e;
2011
- throw e;
2066
+ // throw e;
2067
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2012
2068
  }
2013
2069
  finally {
2014
2070
  const endTime = Date.now();
2015
- this._reportToWorld(world, {
2071
+ _reportToWorld(world, {
2016
2072
  type: Types.VERIFY_PAGE_PATH,
2017
2073
  text: "Verify page path",
2018
2074
  screenshotId,
@@ -2021,7 +2077,7 @@ class StableBrowser {
2021
2077
  status: "FAILED",
2022
2078
  startTime,
2023
2079
  endTime,
2024
- message: error === null || error === void 0 ? void 0 : error.message,
2080
+ message: error?.message,
2025
2081
  }
2026
2082
  : {
2027
2083
  status: "PASSED",
@@ -2034,52 +2090,58 @@ class StableBrowser {
2034
2090
  }
2035
2091
  async verifyTextExistInPage(text, options = {}, world = null) {
2036
2092
  text = unEscapeString(text);
2037
- const startTime = Date.now();
2093
+ const state = {
2094
+ text_search: text,
2095
+ options,
2096
+ world,
2097
+ locate: false,
2098
+ scroll: false,
2099
+ highlight: false,
2100
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2101
+ text: `Verify text exists in page`,
2102
+ operation: "verifyTextExistInPage",
2103
+ log: "***** verify text " + text + " exists in page *****\n",
2104
+ };
2038
2105
  const timeout = this._getLoadTimeout(options);
2039
- let error = null;
2040
- let screenshotId = null;
2041
- let screenshotPath = null;
2042
2106
  await new Promise((resolve) => setTimeout(resolve, 2000));
2043
- const info = {};
2044
- info.log = "***** verify text " + text + " exists in page *****\n";
2045
- info.operation = "verifyTextExistInPage";
2046
2107
  const newValue = await this._replaceWithLocalData(text, world);
2047
2108
  if (newValue !== text) {
2048
2109
  this.logger.info(text + "=" + newValue);
2049
2110
  text = newValue;
2050
2111
  }
2051
- info.text = text;
2052
2112
  let dateAlternatives = findDateAlternatives(text);
2053
2113
  let numberAlternatives = findNumberAlternatives(text);
2054
2114
  try {
2115
+ await _preCommand(state, this);
2116
+ state.info.text = text;
2055
2117
  while (true) {
2056
2118
  const frames = this.page.frames();
2057
2119
  let results = [];
2058
2120
  for (let i = 0; i < frames.length; i++) {
2059
2121
  if (dateAlternatives.date) {
2060
2122
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2061
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2123
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2062
2124
  result.frame = frames[i];
2063
2125
  results.push(result);
2064
2126
  }
2065
2127
  }
2066
2128
  else if (numberAlternatives.number) {
2067
2129
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2068
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2130
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2069
2131
  result.frame = frames[i];
2070
2132
  results.push(result);
2071
2133
  }
2072
2134
  }
2073
2135
  else {
2074
- const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2136
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2075
2137
  result.frame = frames[i];
2076
2138
  results.push(result);
2077
2139
  }
2078
2140
  }
2079
- info.results = results;
2141
+ state.info.results = results;
2080
2142
  const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2081
2143
  if (resultWithElementsFound.length === 0) {
2082
- if (Date.now() - startTime > timeout) {
2144
+ if (Date.now() - state.startTime > timeout) {
2083
2145
  throw new Error(`Text ${text} not found in page`);
2084
2146
  }
2085
2147
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2091,44 +2153,89 @@ class StableBrowser {
2091
2153
  await this._highlightElements(frame, dataAttribute);
2092
2154
  const element = await frame.$(dataAttribute);
2093
2155
  if (element) {
2094
- await this.scrollIfNeeded(element, info);
2156
+ await this.scrollIfNeeded(element, state.info);
2095
2157
  await element.dispatchEvent("bvt_verify_page_contains_text");
2096
2158
  }
2097
2159
  }
2098
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2099
- return info;
2160
+ await _screenshot(state, this);
2161
+ return state.info;
2100
2162
  }
2101
2163
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2102
2164
  }
2103
2165
  catch (e) {
2104
- //await this.closeUnexpectedPopups();
2105
- this.logger.error("verify text exist in page failed " + info.log);
2106
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2107
- info.screenshotPath = screenshotPath;
2108
- Object.assign(e, { info: info });
2109
- error = e;
2110
- throw e;
2166
+ await _commandError(state, e, this);
2111
2167
  }
2112
2168
  finally {
2113
- const endTime = Date.now();
2114
- this._reportToWorld(world, {
2115
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2116
- text: "Verify text exists in page",
2117
- screenshotId,
2118
- result: error
2119
- ? {
2120
- status: "FAILED",
2121
- startTime,
2122
- endTime,
2123
- message: error === null || error === void 0 ? void 0 : error.message,
2169
+ _commandFinally(state, this);
2170
+ }
2171
+ }
2172
+ async waitForTextToDisappear(text, options = {}, world = null) {
2173
+ text = unEscapeString(text);
2174
+ const state = {
2175
+ text_search: text,
2176
+ options,
2177
+ world,
2178
+ locate: false,
2179
+ scroll: false,
2180
+ highlight: false,
2181
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2182
+ text: `Verify text does not exist in page`,
2183
+ operation: "verifyTextNotExistInPage",
2184
+ log: "***** verify text " + text + " does not exist in page *****\n",
2185
+ };
2186
+ const timeout = this._getLoadTimeout(options);
2187
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2188
+ const newValue = await this._replaceWithLocalData(text, world);
2189
+ if (newValue !== text) {
2190
+ this.logger.info(text + "=" + newValue);
2191
+ text = newValue;
2192
+ }
2193
+ let dateAlternatives = findDateAlternatives(text);
2194
+ let numberAlternatives = findNumberAlternatives(text);
2195
+ try {
2196
+ await _preCommand(state, this);
2197
+ state.info.text = text;
2198
+ while (true) {
2199
+ const frames = this.page.frames();
2200
+ let results = [];
2201
+ for (let i = 0; i < frames.length; i++) {
2202
+ if (dateAlternatives.date) {
2203
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2204
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2205
+ result.frame = frames[i];
2206
+ results.push(result);
2207
+ }
2124
2208
  }
2125
- : {
2126
- status: "PASSED",
2127
- startTime,
2128
- endTime,
2129
- },
2130
- info: info,
2131
- });
2209
+ else if (numberAlternatives.number) {
2210
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2211
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2212
+ result.frame = frames[i];
2213
+ results.push(result);
2214
+ }
2215
+ }
2216
+ else {
2217
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2218
+ result.frame = frames[i];
2219
+ results.push(result);
2220
+ }
2221
+ }
2222
+ state.info.results = results;
2223
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2224
+ if (resultWithElementsFound.length === 0) {
2225
+ await _screenshot(state, this);
2226
+ return state.info;
2227
+ }
2228
+ if (Date.now() - state.startTime > timeout) {
2229
+ throw new Error(`Text ${text} found in page`);
2230
+ }
2231
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2232
+ }
2233
+ }
2234
+ catch (e) {
2235
+ await _commandError(state, e, this);
2236
+ }
2237
+ finally {
2238
+ _commandFinally(state, this);
2132
2239
  }
2133
2240
  }
2134
2241
  _getServerUrl() {
@@ -2191,11 +2298,12 @@ class StableBrowser {
2191
2298
  info.screenshotPath = screenshotPath;
2192
2299
  Object.assign(e, { info: info });
2193
2300
  error = e;
2194
- throw e;
2301
+ // throw e;
2302
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2195
2303
  }
2196
2304
  finally {
2197
2305
  const endTime = Date.now();
2198
- this._reportToWorld(world, {
2306
+ _reportToWorld(world, {
2199
2307
  type: Types.VERIFY_VISUAL,
2200
2308
  text: "Visual verification",
2201
2309
  screenshotId,
@@ -2204,7 +2312,7 @@ class StableBrowser {
2204
2312
  status: "FAILED",
2205
2313
  startTime,
2206
2314
  endTime,
2207
- message: error === null || error === void 0 ? void 0 : error.message,
2315
+ message: error?.message,
2208
2316
  }
2209
2317
  : {
2210
2318
  status: "PASSED",
@@ -2258,11 +2366,12 @@ class StableBrowser {
2258
2366
  info.screenshotPath = screenshotPath;
2259
2367
  Object.assign(e, { info: info });
2260
2368
  error = e;
2261
- throw e;
2369
+ // throw e;
2370
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2262
2371
  }
2263
2372
  finally {
2264
2373
  const endTime = Date.now();
2265
- this._reportToWorld(world, {
2374
+ _reportToWorld(world, {
2266
2375
  element_name: selectors.element_name,
2267
2376
  type: Types.GET_TABLE_DATA,
2268
2377
  text: "Get table data",
@@ -2272,7 +2381,7 @@ class StableBrowser {
2272
2381
  status: "FAILED",
2273
2382
  startTime,
2274
2383
  endTime,
2275
- message: error === null || error === void 0 ? void 0 : error.message,
2384
+ message: error?.message,
2276
2385
  }
2277
2386
  : {
2278
2387
  status: "PASSED",
@@ -2423,11 +2532,12 @@ class StableBrowser {
2423
2532
  info.screenshotPath = screenshotPath;
2424
2533
  Object.assign(e, { info: info });
2425
2534
  error = e;
2426
- throw e;
2535
+ // throw e;
2536
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2427
2537
  }
2428
2538
  finally {
2429
2539
  const endTime = Date.now();
2430
- this._reportToWorld(world, {
2540
+ _reportToWorld(world, {
2431
2541
  element_name: selectors.element_name,
2432
2542
  type: Types.ANALYZE_TABLE,
2433
2543
  text: "Analyze table",
@@ -2437,7 +2547,7 @@ class StableBrowser {
2437
2547
  status: "FAILED",
2438
2548
  startTime,
2439
2549
  endTime,
2440
- message: error === null || error === void 0 ? void 0 : error.message,
2550
+ message: error?.message,
2441
2551
  }
2442
2552
  : {
2443
2553
  status: "PASSED",
@@ -2449,27 +2559,7 @@ class StableBrowser {
2449
2559
  }
2450
2560
  }
2451
2561
  async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
2452
- if (!value) {
2453
- return value;
2454
- }
2455
- // find all the accurance of {{(.*?)}} and replace with the value
2456
- let regex = /{{(.*?)}}/g;
2457
- let matches = value.match(regex);
2458
- if (matches) {
2459
- const testData = this.getTestData(world);
2460
- for (let i = 0; i < matches.length; i++) {
2461
- let match = matches[i];
2462
- let key = match.substring(2, match.length - 2);
2463
- let newValue = objectPath.get(testData, key, null);
2464
- if (newValue !== null) {
2465
- value = value.replace(match, newValue);
2466
- }
2467
- }
2468
- }
2469
- if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
2470
- return await decrypt(value, null, totpWait);
2471
- }
2472
- return value;
2562
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2473
2563
  }
2474
2564
  _getLoadTimeout(options) {
2475
2565
  let timeout = 15000;
@@ -2520,7 +2610,7 @@ class StableBrowser {
2520
2610
  await new Promise((resolve) => setTimeout(resolve, 2000));
2521
2611
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2522
2612
  const endTime = Date.now();
2523
- this._reportToWorld(world, {
2613
+ _reportToWorld(world, {
2524
2614
  type: Types.GET_PAGE_STATUS,
2525
2615
  text: "Wait for page load",
2526
2616
  screenshotId,
@@ -2529,7 +2619,7 @@ class StableBrowser {
2529
2619
  status: "FAILED",
2530
2620
  startTime,
2531
2621
  endTime,
2532
- message: error === null || error === void 0 ? void 0 : error.message,
2622
+ message: error?.message,
2533
2623
  }
2534
2624
  : {
2535
2625
  status: "PASSED",
@@ -2540,41 +2630,35 @@ class StableBrowser {
2540
2630
  }
2541
2631
  }
2542
2632
  async closePage(options = {}, world = null) {
2543
- const startTime = Date.now();
2544
- let error = null;
2545
- let screenshotId = null;
2546
- let screenshotPath = null;
2547
- const info = {};
2633
+ const state = {
2634
+ options,
2635
+ world,
2636
+ locate: false,
2637
+ scroll: false,
2638
+ highlight: false,
2639
+ type: Types.CLOSE_PAGE,
2640
+ text: `Close page`,
2641
+ operation: "closePage",
2642
+ log: "***** close page *****\n",
2643
+ throwError: false,
2644
+ };
2548
2645
  try {
2646
+ await _preCommand(state, this);
2549
2647
  await this.page.close();
2550
2648
  }
2551
2649
  catch (e) {
2552
2650
  console.log(".");
2651
+ await _commandError(state, e, this);
2553
2652
  }
2554
2653
  finally {
2555
- await new Promise((resolve) => setTimeout(resolve, 2000));
2556
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2557
- const endTime = Date.now();
2558
- this._reportToWorld(world, {
2559
- type: Types.CLOSE_PAGE,
2560
- text: "close page",
2561
- screenshotId,
2562
- result: error
2563
- ? {
2564
- status: "FAILED",
2565
- startTime,
2566
- endTime,
2567
- message: error === null || error === void 0 ? void 0 : error.message,
2568
- }
2569
- : {
2570
- status: "PASSED",
2571
- startTime,
2572
- endTime,
2573
- },
2574
- info: info,
2575
- });
2654
+ _commandFinally(state, this);
2576
2655
  }
2577
2656
  }
2657
+ saveTestDataAsGlobal(options, world) {
2658
+ const dataFile = this._getDataFile(world);
2659
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2660
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2661
+ }
2578
2662
  async setViewportSize(width, hight, options = {}, world = null) {
2579
2663
  const startTime = Date.now();
2580
2664
  let error = null;
@@ -2592,12 +2676,13 @@ class StableBrowser {
2592
2676
  }
2593
2677
  catch (e) {
2594
2678
  console.log(".");
2679
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2595
2680
  }
2596
2681
  finally {
2597
2682
  await new Promise((resolve) => setTimeout(resolve, 2000));
2598
2683
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2599
2684
  const endTime = Date.now();
2600
- this._reportToWorld(world, {
2685
+ _reportToWorld(world, {
2601
2686
  type: Types.SET_VIEWPORT,
2602
2687
  text: "set viewport size to " + width + "x" + hight,
2603
2688
  screenshotId,
@@ -2606,7 +2691,7 @@ class StableBrowser {
2606
2691
  status: "FAILED",
2607
2692
  startTime,
2608
2693
  endTime,
2609
- message: error === null || error === void 0 ? void 0 : error.message,
2694
+ message: error?.message,
2610
2695
  }
2611
2696
  : {
2612
2697
  status: "PASSED",
@@ -2628,12 +2713,13 @@ class StableBrowser {
2628
2713
  }
2629
2714
  catch (e) {
2630
2715
  console.log(".");
2716
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2631
2717
  }
2632
2718
  finally {
2633
2719
  await new Promise((resolve) => setTimeout(resolve, 2000));
2634
2720
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2635
2721
  const endTime = Date.now();
2636
- this._reportToWorld(world, {
2722
+ _reportToWorld(world, {
2637
2723
  type: Types.GET_PAGE_STATUS,
2638
2724
  text: "page relaod",
2639
2725
  screenshotId,
@@ -2642,7 +2728,7 @@ class StableBrowser {
2642
2728
  status: "FAILED",
2643
2729
  startTime,
2644
2730
  endTime,
2645
- message: error === null || error === void 0 ? void 0 : error.message,
2731
+ message: error?.message,
2646
2732
  }
2647
2733
  : {
2648
2734
  status: "PASSED",
@@ -2669,11 +2755,37 @@ class StableBrowser {
2669
2755
  console.log("#-#");
2670
2756
  }
2671
2757
  }
2672
- _reportToWorld(world, properties) {
2673
- if (!world || !world.attach) {
2674
- return;
2758
+ async beforeStep(world, step) {
2759
+ this.stepName = step.pickleStep.text;
2760
+ this.logger.info("step: " + this.stepName);
2761
+ if (this.stepIndex === undefined) {
2762
+ this.stepIndex = 0;
2763
+ }
2764
+ else {
2765
+ this.stepIndex++;
2766
+ }
2767
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2768
+ if (this.context.browserObject.context) {
2769
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2770
+ }
2771
+ }
2772
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2773
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2774
+ // check if @global_test_data tag is present
2775
+ if (this.tags.includes("@global_test_data")) {
2776
+ this.saveTestDataAsGlobal({}, world);
2777
+ }
2778
+ }
2779
+ }
2780
+ async afterStep(world, step) {
2781
+ this.stepName = null;
2782
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2783
+ if (this.context.browserObject.context) {
2784
+ await this.context.browserObject.context.tracing.stopChunk({
2785
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2786
+ });
2787
+ }
2675
2788
  }
2676
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2677
2789
  }
2678
2790
  }
2679
2791
  function createTimedPromise(promise, label) {