automation_model 1.0.597-dev → 1.0.597-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.
@@ -7,20 +7,25 @@ import path from "path";
7
7
  import reg_parser from "regex-parser";
8
8
  import { findDateAlternatives, findNumberAlternatives } from "./analyze_helper.js";
9
9
  import { getDateTimeValue } from "./date_time.js";
10
+ import drawRectangle from "./drawRect.js";
10
11
  //import { closeUnexpectedPopups } from "./popups.js";
11
12
  import { getTableCells, getTableData } from "./table_analyze.js";
12
- import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
13
+ import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
13
14
  import csv from "csv-parser";
14
15
  import { Readable } from "node:stream";
15
16
  import readline from "readline";
16
- import { getContext } from "./init_browser.js";
17
+ import { getContext, refreshBrowser } from "./init_browser.js";
18
+ import { getTestData } from "./auto_page.js";
17
19
  import { locate_element } from "./locate_element.js";
18
20
  import { randomUUID } from "crypto";
19
21
  import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
20
22
  import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
21
23
  import { LocatorLog } from "./locator_log.js";
24
+ import axios from "axios";
25
+ import { _findCellArea, findElementsInArea } from "./table_helper.js";
22
26
  export const Types = {
23
27
  CLICK: "click_element",
28
+ WAIT_ELEMENT: "wait_element",
24
29
  NAVIGATE: "navigate",
25
30
  FILL: "fill_element",
26
31
  EXECUTE: "execute_page_method",
@@ -42,6 +47,7 @@ export const Types = {
42
47
  UNCHECK: "uncheck_element",
43
48
  EXTRACT: "extract_attribute",
44
49
  CLOSE_PAGE: "close_page",
50
+ TABLE_OPERATION: "table_operation",
45
51
  SET_DATE_TIME: "set_date_time",
46
52
  SET_VIEWPORT: "set_viewport",
47
53
  VERIFY_VISUAL: "verify_visual",
@@ -50,8 +56,12 @@ export const Types = {
50
56
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
51
57
  VERIFY_ATTRIBUTE: "verify_element_attribute",
52
58
  VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
59
+ BRUNO: "bruno",
53
60
  };
54
61
  export const apps = {};
62
+ const formatElementName = (elementName) => {
63
+ return elementName ? JSON.stringify(elementName) : "element";
64
+ };
55
65
  class StableBrowser {
56
66
  browser;
57
67
  page;
@@ -65,6 +75,7 @@ class StableBrowser {
65
75
  appName = "main";
66
76
  tags = null;
67
77
  isRecording = false;
78
+ initSnapshotTaken = false;
68
79
  constructor(browser, page, logger = null, context = null, world = null) {
69
80
  this.browser = browser;
70
81
  this.page = page;
@@ -171,6 +182,30 @@ class StableBrowser {
171
182
  await this.waitForPageLoad();
172
183
  }
173
184
  }
185
+ async switchTab(tabTitleOrIndex) {
186
+ // first check if the tabNameOrIndex is a number
187
+ let index = parseInt(tabTitleOrIndex);
188
+ if (!isNaN(index)) {
189
+ if (index >= 0 && index < this.context.pages.length) {
190
+ this.page = this.context.pages[index];
191
+ this.context.page = this.page;
192
+ await this.page.bringToFront();
193
+ return;
194
+ }
195
+ }
196
+ // if the tabNameOrIndex is a string, find the tab by name
197
+ for (let i = 0; i < this.context.pages.length; i++) {
198
+ let page = this.context.pages[i];
199
+ let title = await page.title();
200
+ if (title.includes(tabTitleOrIndex)) {
201
+ this.page = page;
202
+ this.context.page = this.page;
203
+ await this.page.bringToFront();
204
+ return;
205
+ }
206
+ }
207
+ throw new Error("Tab not found: " + tabTitleOrIndex);
208
+ }
174
209
  registerConsoleLogListener(page, context) {
175
210
  if (!this.context.webLogger) {
176
211
  this.context.webLogger = [];
@@ -235,6 +270,9 @@ class StableBrowser {
235
270
  // await closeUnexpectedPopups(this.page);
236
271
  // }
237
272
  async goto(url, world = null) {
273
+ if (!url) {
274
+ throw new Error("url is null, verify that the environment file is correct");
275
+ }
238
276
  if (!url.startsWith("http")) {
239
277
  url = "https://" + url;
240
278
  }
@@ -252,7 +290,7 @@ class StableBrowser {
252
290
  highlight: false,
253
291
  };
254
292
  try {
255
- await _preCommand(state, this, world);
293
+ await _preCommand(state, this);
256
294
  await this.page.goto(url, {
257
295
  timeout: 60000,
258
296
  });
@@ -263,7 +301,7 @@ class StableBrowser {
263
301
  _commandError(state, error, this);
264
302
  }
265
303
  finally {
266
- _commandFinally(state, this);
304
+ await _commandFinally(state, this);
267
305
  }
268
306
  }
269
307
  async _getLocator(locator, scope, _params) {
@@ -344,7 +382,7 @@ class StableBrowser {
344
382
  return resultCss;
345
383
  }
346
384
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
347
- const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
385
+ const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
348
386
  const locator = scope.locator(query);
349
387
  const count = await locator.count();
350
388
  if (!tag1) {
@@ -364,6 +402,12 @@ class StableBrowser {
364
402
  if (!el.setAttribute) {
365
403
  el = el.parentElement;
366
404
  }
405
+ // remove any attributes start with data-blinq-id
406
+ // for (let i = 0; i < el.attributes.length; i++) {
407
+ // if (el.attributes[i].name.startsWith("data-blinq-id")) {
408
+ // el.removeAttribute(el.attributes[i].name);
409
+ // }
410
+ // }
367
411
  el.setAttribute("data-blinq-id-" + randomToken, "");
368
412
  return true;
369
413
  }, [tag1, randomToken]))) {
@@ -373,7 +417,7 @@ class StableBrowser {
373
417
  }
374
418
  return { elementCount: tagCount, randomToken };
375
419
  }
376
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false) {
420
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
377
421
  if (!info) {
378
422
  info = {};
379
423
  }
@@ -400,7 +444,7 @@ class StableBrowser {
400
444
  let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
401
445
  if (!locatorString) {
402
446
  info.failCause.textNotFound = true;
403
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
447
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
404
448
  return;
405
449
  }
406
450
  locator = await this._getLocator({ css: locatorString }, scope, _params);
@@ -410,7 +454,7 @@ class StableBrowser {
410
454
  let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
411
455
  if (result.elementCount === 0) {
412
456
  info.failCause.textNotFound = true;
413
- info.failCause.lastError = "failed to locate element by text: " + text;
457
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
414
458
  return;
415
459
  }
416
460
  locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
@@ -462,11 +506,11 @@ class StableBrowser {
462
506
  info.printMessages = {};
463
507
  }
464
508
  if (info.locatorLog && !visible) {
465
- info.failCause.lastError = "element " + originalLocatorSearch + " is not visible";
509
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
466
510
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
467
511
  }
468
512
  if (info.locatorLog && !enabled) {
469
- info.failCause.lastError = "element " + originalLocatorSearch + " is disabled, ";
513
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
470
514
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
471
515
  }
472
516
  if (!info.printMessages[j.toString()]) {
@@ -546,7 +590,28 @@ class StableBrowser {
546
590
  }
547
591
  let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
548
592
  if (!element.rerun) {
549
- return element;
593
+ const randomToken = Math.random().toString(36).substring(7);
594
+ element.evaluate((el, randomToken) => {
595
+ el.setAttribute("data-blinq-id-" + randomToken, "");
596
+ }, randomToken);
597
+ // if (element._frame) {
598
+ // return element;
599
+ // }
600
+ const scope = element._frame ?? element.page();
601
+ let newElementSelector = "[data-blinq-id-" + randomToken + "]";
602
+ let prefixSelector = "";
603
+ const frameControlSelector = " >> internal:control=enter-frame";
604
+ const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
605
+ if (frameSelectorIndex !== -1) {
606
+ // remove everything after the >> internal:control=enter-frame
607
+ const frameSelector = element._selector.substring(0, frameSelectorIndex);
608
+ prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
609
+ }
610
+ // if (element?._frame?._selector) {
611
+ // prefixSelector = element._frame._selector + " >> " + prefixSelector;
612
+ // }
613
+ const newSelector = prefixSelector + newElementSelector;
614
+ return scope.locator(newSelector);
550
615
  }
551
616
  }
552
617
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -619,7 +684,7 @@ class StableBrowser {
619
684
  //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
620
685
  if (Date.now() - startTime > timeout) {
621
686
  info.failCause.iframeNotFound = true;
622
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
687
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
623
688
  throw new Error("unable to locate iframe " + selectors.iframe_src);
624
689
  }
625
690
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -693,18 +758,13 @@ class StableBrowser {
693
758
  }
694
759
  // info.log += "scanning locators in priority 1" + "\n";
695
760
  let onlyPriority3 = selectorsLocators[0].priority === 3;
696
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled);
761
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
697
762
  if (result.foundElements.length === 0) {
698
763
  // info.log += "scanning locators in priority 2" + "\n";
699
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled);
700
- }
701
- if (result.foundElements.length === 0 && onlyPriority3) {
702
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
764
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
703
765
  }
704
- else {
705
- if (result.foundElements.length === 0 && !highPriorityOnly) {
706
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
707
- }
766
+ if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
767
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
708
768
  }
709
769
  let foundElements = result.foundElements;
710
770
  if (foundElements.length === 1 && foundElements[0].unique) {
@@ -760,6 +820,11 @@ class StableBrowser {
760
820
  visibleOnly = false;
761
821
  }
762
822
  await new Promise((resolve) => setTimeout(resolve, 1000));
823
+ // sheck of more of half of the timeout has passed
824
+ if (Date.now() - startTime > timeout / 2) {
825
+ highPriorityOnly = false;
826
+ visibleOnly = false;
827
+ }
763
828
  }
764
829
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
765
830
  // if (info.locatorLog) {
@@ -771,11 +836,11 @@ class StableBrowser {
771
836
  //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
772
837
  info.failCause.locatorNotFound = true;
773
838
  if (!info?.failCause?.lastError) {
774
- info.failCause.lastError = "failed to locate unique element";
839
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
775
840
  }
776
841
  throw new Error("failed to locate first element no elements found, " + info.log);
777
842
  }
778
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false) {
843
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
779
844
  let foundElements = [];
780
845
  const result = {
781
846
  foundElements: foundElements,
@@ -783,7 +848,7 @@ class StableBrowser {
783
848
  for (let i = 0; i < locatorsGroup.length; i++) {
784
849
  let foundLocators = [];
785
850
  try {
786
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled);
851
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
787
852
  }
788
853
  catch (e) {
789
854
  // this call can fail it the browser is navigating
@@ -791,7 +856,7 @@ class StableBrowser {
791
856
  // this.logger.debug(e);
792
857
  foundLocators = [];
793
858
  try {
794
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled);
859
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
795
860
  }
796
861
  catch (e) {
797
862
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -806,9 +871,40 @@ class StableBrowser {
806
871
  result.locatorIndex = i;
807
872
  }
808
873
  if (foundLocators.length > 1) {
809
- info.failCause.foundMultiple = true;
810
- if (info.locatorLog) {
811
- info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
874
+ // remove elements that consume the same space with 10 pixels tolerance
875
+ const boxes = [];
876
+ for (let j = 0; j < foundLocators.length; j++) {
877
+ boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
878
+ }
879
+ for (let j = 0; j < boxes.length; j++) {
880
+ for (let k = 0; k < boxes.length; k++) {
881
+ if (j === k) {
882
+ continue;
883
+ }
884
+ // check if x, y, width, height are the same with 10 pixels tolerance
885
+ if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
886
+ Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
887
+ Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
888
+ Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
889
+ // as the element is not unique, will remove it
890
+ boxes.splice(k, 1);
891
+ k--;
892
+ }
893
+ }
894
+ }
895
+ if (boxes.length === 1) {
896
+ result.foundElements.push({
897
+ locator: boxes[0].locator.first(),
898
+ box: boxes[0].box,
899
+ unique: true,
900
+ });
901
+ result.locatorIndex = i;
902
+ }
903
+ else {
904
+ info.failCause.foundMultiple = true;
905
+ if (info.locatorLog) {
906
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
907
+ }
812
908
  }
813
909
  }
814
910
  }
@@ -827,7 +923,7 @@ class StableBrowser {
827
923
  operation: "simpleClick",
828
924
  log: "***** click on " + elementDescription + " *****\n",
829
925
  };
830
- _preCommand(state, this, world);
926
+ _preCommand(state, this);
831
927
  const startTime = Date.now();
832
928
  let timeout = 30000;
833
929
  if (options && options.timeout) {
@@ -856,7 +952,7 @@ class StableBrowser {
856
952
  await _commandError(state, "timeout looking for " + elementDescription, this);
857
953
  }
858
954
  finally {
859
- _commandFinally(state, this);
955
+ await _commandFinally(state, this);
860
956
  }
861
957
  }
862
958
  }
@@ -876,7 +972,7 @@ class StableBrowser {
876
972
  operation: "simpleClickType",
877
973
  log: "***** click type on " + elementDescription + " *****\n",
878
974
  };
879
- _preCommand(state, this, world);
975
+ _preCommand(state, this);
880
976
  const startTime = Date.now();
881
977
  let timeout = 30000;
882
978
  if (options && options.timeout) {
@@ -905,7 +1001,7 @@ class StableBrowser {
905
1001
  await _commandError(state, "timeout looking for " + elementDescription, this);
906
1002
  }
907
1003
  finally {
908
- _commandFinally(state, this);
1004
+ await _commandFinally(state, this);
909
1005
  }
910
1006
  }
911
1007
  }
@@ -919,25 +1015,14 @@ class StableBrowser {
919
1015
  options,
920
1016
  world,
921
1017
  text: "Click element",
1018
+ _text: "Click on " + selectors.element_name,
922
1019
  type: Types.CLICK,
923
1020
  operation: "click",
924
1021
  log: "***** click on " + selectors.element_name + " *****\n",
925
1022
  };
926
1023
  try {
927
- await _preCommand(state, this, world);
928
- // if (state.options && state.options.context) {
929
- // state.selectors.locators[0].text = state.options.context;
930
- // }
931
- try {
932
- await state.element.click();
933
- // await new Promise((resolve) => setTimeout(resolve, 1000));
934
- }
935
- catch (e) {
936
- // await this.closeUnexpectedPopups();
937
- state.element = await this._locate(selectors, state.info, _params);
938
- await state.element.dispatchEvent("click");
939
- // await new Promise((resolve) => setTimeout(resolve, 1000));
940
- }
1024
+ await _preCommand(state, this);
1025
+ await performAction("click", state.element, options, this, state, _params);
941
1026
  await this.waitForPageLoad();
942
1027
  return state.info;
943
1028
  }
@@ -945,8 +1030,40 @@ class StableBrowser {
945
1030
  await _commandError(state, e, this);
946
1031
  }
947
1032
  finally {
948
- _commandFinally(state, this);
1033
+ await _commandFinally(state, this);
1034
+ }
1035
+ }
1036
+ async waitForElement(selectors, _params, options = {}, world = null) {
1037
+ const timeout = this._getFindElementTimeout(options);
1038
+ const state = {
1039
+ selectors,
1040
+ _params,
1041
+ options,
1042
+ world,
1043
+ text: "Wait for element",
1044
+ _text: "Wait for " + selectors.element_name,
1045
+ type: Types.WAIT_ELEMENT,
1046
+ operation: "waitForElement",
1047
+ log: "***** wait for " + selectors.element_name + " *****\n",
1048
+ };
1049
+ let found = false;
1050
+ try {
1051
+ await _preCommand(state, this);
1052
+ // if (state.options && state.options.context) {
1053
+ // state.selectors.locators[0].text = state.options.context;
1054
+ // }
1055
+ await state.element.waitFor({ timeout: timeout });
1056
+ found = true;
1057
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1058
+ }
1059
+ catch (e) {
1060
+ console.error("Error on waitForElement", e);
1061
+ // await _commandError(state, e, this);
1062
+ }
1063
+ finally {
1064
+ await _commandFinally(state, this);
949
1065
  }
1066
+ return found;
950
1067
  }
951
1068
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
952
1069
  const state = {
@@ -956,22 +1073,23 @@ class StableBrowser {
956
1073
  world,
957
1074
  type: checked ? Types.CHECK : Types.UNCHECK,
958
1075
  text: checked ? `Check element` : `Uncheck element`,
1076
+ _text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
959
1077
  operation: "setCheck",
960
1078
  log: "***** check " + selectors.element_name + " *****\n",
961
1079
  };
962
1080
  try {
963
- await _preCommand(state, this, world);
1081
+ await _preCommand(state, this);
964
1082
  state.info.checked = checked;
965
1083
  // let element = await this._locate(selectors, info, _params);
966
1084
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
967
1085
  try {
968
- if (world && world.screenshot && !world.screenshotPath) {
969
- // console.log(`Highlighting while running from recorder`);
970
- await this._highlightElements(element);
971
- await new Promise((resolve) => setTimeout(resolve, 100));
972
- await this._unHighlightElements(element);
973
- }
1086
+ // if (world && world.screenshot && !world.screenshotPath) {
1087
+ // console.log(`Highlighting while running from recorder`);
1088
+ await this._highlightElements(state.element);
974
1089
  await state.element.setChecked(checked);
1090
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1091
+ // await this._unHighlightElements(element);
1092
+ // }
975
1093
  // await new Promise((resolve) => setTimeout(resolve, 1000));
976
1094
  // await this._unHighlightElements(element);
977
1095
  }
@@ -994,7 +1112,7 @@ class StableBrowser {
994
1112
  await _commandError(state, e, this);
995
1113
  }
996
1114
  finally {
997
- _commandFinally(state, this);
1115
+ await _commandFinally(state, this);
998
1116
  }
999
1117
  }
1000
1118
  async hover(selectors, _params, options = {}, world = null) {
@@ -1005,25 +1123,14 @@ class StableBrowser {
1005
1123
  world,
1006
1124
  type: Types.HOVER,
1007
1125
  text: `Hover element`,
1126
+ _text: `Hover on ${selectors.element_name}`,
1008
1127
  operation: "hover",
1009
1128
  log: "***** hover " + selectors.element_name + " *****\n",
1010
1129
  };
1011
1130
  try {
1012
- await _preCommand(state, this, world);
1013
- try {
1014
- await state.element.hover();
1015
- await _screenshot(state, this);
1016
- await new Promise((resolve) => setTimeout(resolve, 1000));
1017
- }
1018
- catch (e) {
1019
- //await this.closeUnexpectedPopups();
1020
- state.info.log += "hover failed, will try again" + "\n";
1021
- state.element = await this._locate(selectors, state.info, _params);
1022
- await state.element.hover({ timeout: 10000 });
1023
- await _screenshot(state, this);
1024
- await new Promise((resolve) => setTimeout(resolve, 1000));
1025
- }
1026
- // await _screenshot(state, this);
1131
+ await _preCommand(state, this);
1132
+ await performAction("hover", state.element, options, this, state, _params);
1133
+ await _screenshot(state, this);
1027
1134
  await this.waitForPageLoad();
1028
1135
  return state.info;
1029
1136
  }
@@ -1031,7 +1138,7 @@ class StableBrowser {
1031
1138
  await _commandError(state, e, this);
1032
1139
  }
1033
1140
  finally {
1034
- _commandFinally(state, this);
1141
+ await _commandFinally(state, this);
1035
1142
  }
1036
1143
  }
1037
1144
  async selectOption(selectors, values, _params = null, options = {}, world = null) {
@@ -1046,11 +1153,12 @@ class StableBrowser {
1046
1153
  value: values.toString(),
1047
1154
  type: Types.SELECT,
1048
1155
  text: `Select option: ${values}`,
1156
+ _text: `Select option: ${values} on ${selectors.element_name}`,
1049
1157
  operation: "selectOption",
1050
1158
  log: "***** select option " + selectors.element_name + " *****\n",
1051
1159
  };
1052
1160
  try {
1053
- await _preCommand(state, this, world);
1161
+ await _preCommand(state, this);
1054
1162
  try {
1055
1163
  await state.element.selectOption(values);
1056
1164
  }
@@ -1066,7 +1174,7 @@ class StableBrowser {
1066
1174
  await _commandError(state, e, this);
1067
1175
  }
1068
1176
  finally {
1069
- _commandFinally(state, this);
1177
+ await _commandFinally(state, this);
1070
1178
  }
1071
1179
  }
1072
1180
  async type(_value, _params = null, options = {}, world = null) {
@@ -1080,11 +1188,12 @@ class StableBrowser {
1080
1188
  highlight: false,
1081
1189
  type: Types.TYPE_PRESS,
1082
1190
  text: `Type value: ${_value}`,
1191
+ _text: `Type value: ${_value}`,
1083
1192
  operation: "type",
1084
1193
  log: "",
1085
1194
  };
1086
1195
  try {
1087
- await _preCommand(state, this, world);
1196
+ await _preCommand(state, this);
1088
1197
  const valueSegment = state.value.split("&&");
1089
1198
  for (let i = 0; i < valueSegment.length; i++) {
1090
1199
  if (i > 0) {
@@ -1111,7 +1220,7 @@ class StableBrowser {
1111
1220
  await _commandError(state, e, this);
1112
1221
  }
1113
1222
  finally {
1114
- _commandFinally(state, this);
1223
+ await _commandFinally(state, this);
1115
1224
  }
1116
1225
  }
1117
1226
  async setInputValue(selectors, value, _params = null, options = {}, world = null) {
@@ -1127,7 +1236,7 @@ class StableBrowser {
1127
1236
  log: "***** set input value " + selectors.element_name + " *****\n",
1128
1237
  };
1129
1238
  try {
1130
- await _preCommand(state, this, world);
1239
+ await _preCommand(state, this);
1131
1240
  let value = await this._replaceWithLocalData(state.value, this);
1132
1241
  try {
1133
1242
  await state.element.evaluateHandle((el, value) => {
@@ -1147,7 +1256,7 @@ class StableBrowser {
1147
1256
  await _commandError(state, e, this);
1148
1257
  }
1149
1258
  finally {
1150
- _commandFinally(state, this);
1259
+ await _commandFinally(state, this);
1151
1260
  }
1152
1261
  }
1153
1262
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
@@ -1159,14 +1268,15 @@ class StableBrowser {
1159
1268
  world,
1160
1269
  type: Types.SET_DATE_TIME,
1161
1270
  text: `Set date time value: ${value}`,
1271
+ _text: `Set date time value: ${value} on ${selectors.element_name}`,
1162
1272
  operation: "setDateTime",
1163
1273
  log: "***** set date time value " + selectors.element_name + " *****\n",
1164
1274
  throwError: false,
1165
1275
  };
1166
1276
  try {
1167
- await _preCommand(state, this, world);
1277
+ await _preCommand(state, this);
1168
1278
  try {
1169
- await state.element.click();
1279
+ await performAction("click", state.element, options, this, state, _params);
1170
1280
  await new Promise((resolve) => setTimeout(resolve, 500));
1171
1281
  if (format) {
1172
1282
  state.value = dayjs(state.value).format(format);
@@ -1215,7 +1325,7 @@ class StableBrowser {
1215
1325
  await _commandError(state, e, this);
1216
1326
  }
1217
1327
  finally {
1218
- _commandFinally(state, this);
1328
+ await _commandFinally(state, this);
1219
1329
  }
1220
1330
  }
1221
1331
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
@@ -1230,17 +1340,21 @@ class StableBrowser {
1230
1340
  world,
1231
1341
  type: Types.FILL,
1232
1342
  text: `Click type input with value: ${_value}`,
1343
+ _text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
1233
1344
  operation: "clickType",
1234
1345
  log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1235
1346
  };
1347
+ if (!options) {
1348
+ options = {};
1349
+ }
1236
1350
  if (newValue !== _value) {
1237
1351
  //this.logger.info(_value + "=" + newValue);
1238
1352
  _value = newValue;
1239
1353
  }
1240
1354
  try {
1241
- await _preCommand(state, this, world);
1355
+ await _preCommand(state, this);
1242
1356
  state.info.value = _value;
1243
- if (options === null || options === undefined || !options.press) {
1357
+ if (!options.press) {
1244
1358
  try {
1245
1359
  let currentValue = await state.element.inputValue();
1246
1360
  if (currentValue) {
@@ -1251,13 +1365,9 @@ class StableBrowser {
1251
1365
  this.logger.info("unable to clear input value");
1252
1366
  }
1253
1367
  }
1254
- if (options === null || options === undefined || options.press) {
1255
- try {
1256
- await state.element.click({ timeout: 5000 });
1257
- }
1258
- catch (e) {
1259
- await state.element.dispatchEvent("click");
1260
- }
1368
+ if (options.press) {
1369
+ options.timeout = 5000;
1370
+ await performAction("click", state.element, options, this, state, _params);
1261
1371
  }
1262
1372
  else {
1263
1373
  try {
@@ -1315,7 +1425,7 @@ class StableBrowser {
1315
1425
  await _commandError(state, e, this);
1316
1426
  }
1317
1427
  finally {
1318
- _commandFinally(state, this);
1428
+ await _commandFinally(state, this);
1319
1429
  }
1320
1430
  }
1321
1431
  async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
@@ -1331,7 +1441,7 @@ class StableBrowser {
1331
1441
  log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
1332
1442
  };
1333
1443
  try {
1334
- await _preCommand(state, this, world);
1444
+ await _preCommand(state, this);
1335
1445
  await state.element.fill(value);
1336
1446
  await state.element.dispatchEvent("change");
1337
1447
  if (enter) {
@@ -1345,13 +1455,14 @@ class StableBrowser {
1345
1455
  await _commandError(state, e, this);
1346
1456
  }
1347
1457
  finally {
1348
- _commandFinally(state, this);
1458
+ await _commandFinally(state, this);
1349
1459
  }
1350
1460
  }
1351
1461
  async getText(selectors, _params = null, options = {}, info = {}, world = null) {
1352
1462
  return await this._getText(selectors, 0, _params, options, info, world);
1353
1463
  }
1354
1464
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1465
+ const timeout = this._getFindElementTimeout(options);
1355
1466
  _validateSelectors(selectors);
1356
1467
  let screenshotId = null;
1357
1468
  let screenshotPath = null;
@@ -1361,7 +1472,7 @@ class StableBrowser {
1361
1472
  }
1362
1473
  info.operation = "getText";
1363
1474
  info.selectors = selectors;
1364
- let element = await this._locate(selectors, info, _params);
1475
+ let element = await this._locate(selectors, info, _params, timeout);
1365
1476
  if (climb > 0) {
1366
1477
  const climbArray = [];
1367
1478
  for (let i = 0; i < climb; i++) {
@@ -1377,19 +1488,21 @@ class StableBrowser {
1377
1488
  catch (e) {
1378
1489
  //ignore
1379
1490
  }
1380
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info, element));
1491
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1381
1492
  try {
1382
- if (world && world.screenshot && !world.screenshotPath) {
1383
- // console.log(`Highlighting for get text while running from recorder`);
1384
- this._highlightElements(element)
1385
- .then(async () => {
1386
- await new Promise((resolve) => setTimeout(resolve, 1000));
1387
- this._unhighlightElements(element).then(() => { }
1388
- // console.log(`Unhighlighting vrtr in recorder is successful`)
1389
- );
1390
- })
1391
- .catch(e);
1392
- }
1493
+ await this._highlightElements(element);
1494
+ // if (world && world.screenshot && !world.screenshotPath) {
1495
+ // // console.log(`Highlighting for get text while running from recorder`);
1496
+ // this._highlightElements(element)
1497
+ // .then(async () => {
1498
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1499
+ // this._unhighlightElements(element).then(
1500
+ // () => {}
1501
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1502
+ // );
1503
+ // })
1504
+ // .catch(e);
1505
+ // }
1393
1506
  const elementText = await element.innerText();
1394
1507
  return {
1395
1508
  text: elementText,
@@ -1426,6 +1539,7 @@ class StableBrowser {
1426
1539
  highlight: false,
1427
1540
  type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1428
1541
  text: `Verify element contains pattern: ${pattern}`,
1542
+ _text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
1429
1543
  operation: "containsPattern",
1430
1544
  log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1431
1545
  };
@@ -1436,13 +1550,13 @@ class StableBrowser {
1436
1550
  }
1437
1551
  let foundObj = null;
1438
1552
  try {
1439
- await _preCommand(state, this, world);
1553
+ await _preCommand(state, this);
1440
1554
  state.info.pattern = pattern;
1441
1555
  foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
1442
1556
  if (foundObj && foundObj.element) {
1443
1557
  await this.scrollIfNeeded(foundObj.element, state.info);
1444
1558
  }
1445
- await _screenshot(state, this, foundObj.element);
1559
+ await _screenshot(state, this);
1446
1560
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1447
1561
  pattern = pattern.replace("{text}", escapedText);
1448
1562
  let regex = new RegExp(pattern, "im");
@@ -1457,10 +1571,12 @@ class StableBrowser {
1457
1571
  await _commandError(state, e, this);
1458
1572
  }
1459
1573
  finally {
1460
- _commandFinally(state, this);
1574
+ await _commandFinally(state, this);
1461
1575
  }
1462
1576
  }
1463
1577
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1578
+ const timeout = this._getFindElementTimeout(options);
1579
+ const startTime = Date.now();
1464
1580
  const state = {
1465
1581
  selectors,
1466
1582
  _params,
@@ -1487,61 +1603,53 @@ class StableBrowser {
1487
1603
  }
1488
1604
  let foundObj = null;
1489
1605
  try {
1490
- await _preCommand(state, this, world);
1491
- foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1492
- if (foundObj && foundObj.element) {
1493
- await this.scrollIfNeeded(foundObj.element, state.info);
1494
- }
1495
- await _screenshot(state, this, foundObj.element);
1496
- const dateAlternatives = findDateAlternatives(text);
1497
- const numberAlternatives = findNumberAlternatives(text);
1498
- if (dateAlternatives.date) {
1499
- for (let i = 0; i < dateAlternatives.dates.length; i++) {
1500
- if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1501
- foundObj?.value?.includes(dateAlternatives.dates[i])) {
1502
- return state.info;
1606
+ while (Date.now() - startTime < timeout) {
1607
+ try {
1608
+ await _preCommand(state, this);
1609
+ foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
1610
+ if (foundObj && foundObj.element) {
1611
+ await this.scrollIfNeeded(foundObj.element, state.info);
1503
1612
  }
1504
- }
1505
- throw new Error("element doesn't contain text " + text);
1506
- }
1507
- else if (numberAlternatives.number) {
1508
- for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1509
- if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1510
- foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1613
+ await _screenshot(state, this);
1614
+ const dateAlternatives = findDateAlternatives(text);
1615
+ const numberAlternatives = findNumberAlternatives(text);
1616
+ if (dateAlternatives.date) {
1617
+ for (let i = 0; i < dateAlternatives.dates.length; i++) {
1618
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1619
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1620
+ return state.info;
1621
+ }
1622
+ }
1623
+ }
1624
+ else if (numberAlternatives.number) {
1625
+ for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1626
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1627
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1628
+ return state.info;
1629
+ }
1630
+ }
1631
+ }
1632
+ else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
1511
1633
  return state.info;
1512
1634
  }
1513
1635
  }
1514
- throw new Error("element doesn't contain text " + text);
1515
- }
1516
- else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1517
- state.info.foundText = foundObj?.text;
1518
- state.info.value = foundObj?.value;
1519
- throw new Error("element doesn't contain text " + text);
1636
+ catch (e) {
1637
+ // Log error but continue retrying until timeout is reached
1638
+ this.logger.warn("Retrying containsText due to: " + e.message);
1639
+ }
1640
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
1520
1641
  }
1521
- return state.info;
1642
+ state.info.foundText = foundObj?.text;
1643
+ state.info.value = foundObj?.value;
1644
+ throw new Error("element doesn't contain text " + text);
1522
1645
  }
1523
1646
  catch (e) {
1524
1647
  await _commandError(state, e, this);
1648
+ throw e;
1525
1649
  }
1526
1650
  finally {
1527
- _commandFinally(state, this);
1528
- }
1529
- }
1530
- _getDataFile(world = null) {
1531
- let dataFile = null;
1532
- if (world && world.reportFolder) {
1533
- dataFile = path.join(world.reportFolder, "data.json");
1534
- }
1535
- else if (this.reportFolder) {
1536
- dataFile = path.join(this.reportFolder, "data.json");
1537
- }
1538
- else if (this.context && this.context.reportFolder) {
1539
- dataFile = path.join(this.context.reportFolder, "data.json");
1651
+ await _commandFinally(state, this);
1540
1652
  }
1541
- else {
1542
- dataFile = "data.json";
1543
- }
1544
- return dataFile;
1545
1653
  }
1546
1654
  async waitForUserInput(message, world = null) {
1547
1655
  if (!message) {
@@ -1571,13 +1679,22 @@ class StableBrowser {
1571
1679
  return;
1572
1680
  }
1573
1681
  // if data file exists, load it
1574
- const dataFile = this._getDataFile(world);
1682
+ const dataFile = _getDataFile(world, this.context, this);
1575
1683
  let data = this.getTestData(world);
1576
1684
  // merge the testData with the existing data
1577
1685
  Object.assign(data, testData);
1578
1686
  // save the data to the file
1579
1687
  fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
1580
1688
  }
1689
+ overwriteTestData(testData, world = null) {
1690
+ if (!testData) {
1691
+ return;
1692
+ }
1693
+ // if data file exists, load it
1694
+ const dataFile = _getDataFile(world, this.context, this);
1695
+ // save the data to the file
1696
+ fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
1697
+ }
1581
1698
  _getDataFilePath(fileName) {
1582
1699
  let dataFile = path.join(this.project_path, "data", fileName);
1583
1700
  if (fs.existsSync(dataFile)) {
@@ -1674,14 +1791,14 @@ class StableBrowser {
1674
1791
  }
1675
1792
  }
1676
1793
  getTestData(world = null) {
1677
- const dataFile = this._getDataFile(world);
1794
+ const dataFile = _getDataFile(world, this.context, this);
1678
1795
  let data = {};
1679
1796
  if (fs.existsSync(dataFile)) {
1680
1797
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
1681
1798
  }
1682
1799
  return data;
1683
1800
  }
1684
- async _screenShot(options = {}, world = null, info = null, focusedElement = null) {
1801
+ async _screenShot(options = {}, world = null, info = null) {
1685
1802
  // collect url/path/title
1686
1803
  if (info) {
1687
1804
  if (!info.title) {
@@ -1710,7 +1827,7 @@ class StableBrowser {
1710
1827
  const uuidStr = "id_" + randomUUID();
1711
1828
  const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1712
1829
  try {
1713
- await this.takeScreenshot(screenshotPath, focusedElement);
1830
+ await this.takeScreenshot(screenshotPath);
1714
1831
  // let buffer = await this.page.screenshot({ timeout: 4000 });
1715
1832
  // // save the buffer to the screenshot path asynchrously
1716
1833
  // fs.writeFile(screenshotPath, buffer, (err) => {
@@ -1721,7 +1838,7 @@ class StableBrowser {
1721
1838
  result.screenshotId = uuidStr;
1722
1839
  result.screenshotPath = screenshotPath;
1723
1840
  if (info && info.box) {
1724
- // await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1841
+ await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1725
1842
  }
1726
1843
  }
1727
1844
  catch (e) {
@@ -1731,7 +1848,7 @@ class StableBrowser {
1731
1848
  else if (options && options.screenshot) {
1732
1849
  result.screenshotPath = options.screenshotPath;
1733
1850
  try {
1734
- await this.takeScreenshot(options.screenshotPath, focusedElement);
1851
+ await this.takeScreenshot(options.screenshotPath);
1735
1852
  // let buffer = await this.page.screenshot({ timeout: 4000 });
1736
1853
  // // save the buffer to the screenshot path asynchrously
1737
1854
  // fs.writeFile(options.screenshotPath, buffer, (err) => {
@@ -1744,12 +1861,12 @@ class StableBrowser {
1744
1861
  this.logger.info("unable to take screenshot, ignored");
1745
1862
  }
1746
1863
  if (info && info.box) {
1747
- // await drawRectangle(options.screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1864
+ await drawRectangle(options.screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1748
1865
  }
1749
1866
  }
1750
1867
  return result;
1751
1868
  }
1752
- async takeScreenshot(screenshotPath, focusedElement = null) {
1869
+ async takeScreenshot(screenshotPath) {
1753
1870
  const playContext = this.context.playContext;
1754
1871
  // Using CDP to capture the screenshot
1755
1872
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
@@ -1767,9 +1884,9 @@ class StableBrowser {
1767
1884
  // await new Promise((resolve) => setTimeout(resolve, 100));
1768
1885
  // console.log(`Unhighlighted previous element`);
1769
1886
  // }
1770
- if (focusedElement) {
1771
- await this._highlightElements(focusedElement);
1772
- }
1887
+ // if (focusedElement) {
1888
+ // await this._highlightElements(focusedElement);
1889
+ // }
1773
1890
  if (this.context.browserName === "chromium") {
1774
1891
  const client = await playContext.newCDPSession(this.page);
1775
1892
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1791,10 +1908,10 @@ class StableBrowser {
1791
1908
  else {
1792
1909
  screenshotBuffer = await this.page.screenshot();
1793
1910
  }
1794
- if (focusedElement) {
1795
- // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1796
- await this._unhighlightElements(focusedElement);
1797
- }
1911
+ // if (focusedElement) {
1912
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1913
+ // await this._unhighlightElements(focusedElement);
1914
+ // }
1798
1915
  let image = await Jimp.read(screenshotBuffer);
1799
1916
  // Get the image dimensions
1800
1917
  const { width, height } = image.bitmap;
@@ -1807,6 +1924,7 @@ class StableBrowser {
1807
1924
  else {
1808
1925
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1809
1926
  }
1927
+ return screenshotBuffer;
1810
1928
  }
1811
1929
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1812
1930
  const state = {
@@ -1821,7 +1939,7 @@ class StableBrowser {
1821
1939
  };
1822
1940
  await new Promise((resolve) => setTimeout(resolve, 2000));
1823
1941
  try {
1824
- await _preCommand(state, this, world);
1942
+ await _preCommand(state, this);
1825
1943
  await expect(state.element).toHaveCount(1, { timeout: 10000 });
1826
1944
  return state.info;
1827
1945
  }
@@ -1829,7 +1947,7 @@ class StableBrowser {
1829
1947
  await _commandError(state, e, this);
1830
1948
  }
1831
1949
  finally {
1832
- _commandFinally(state, this);
1950
+ await _commandFinally(state, this);
1833
1951
  }
1834
1952
  }
1835
1953
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
@@ -1842,12 +1960,14 @@ class StableBrowser {
1842
1960
  world,
1843
1961
  type: Types.EXTRACT,
1844
1962
  text: `Extract attribute from element`,
1963
+ _text: `Extract attribute ${attribute} from ${selectors.element_name}`,
1845
1964
  operation: "extractAttribute",
1846
1965
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1966
+ allowDisabled: true,
1847
1967
  };
1848
1968
  await new Promise((resolve) => setTimeout(resolve, 2000));
1849
1969
  try {
1850
- await _preCommand(state, this, world);
1970
+ await _preCommand(state, this);
1851
1971
  switch (attribute) {
1852
1972
  case "inner_text":
1853
1973
  state.value = await state.element.innerText();
@@ -1858,6 +1978,9 @@ class StableBrowser {
1858
1978
  case "value":
1859
1979
  state.value = await state.element.inputValue();
1860
1980
  break;
1981
+ case "text":
1982
+ state.value = await state.element.textContent();
1983
+ break;
1861
1984
  default:
1862
1985
  state.value = await state.element.getAttribute(attribute);
1863
1986
  break;
@@ -1865,14 +1988,14 @@ class StableBrowser {
1865
1988
  state.info.value = state.value;
1866
1989
  this.setTestData({ [variable]: state.value }, world);
1867
1990
  this.logger.info("set test data: " + variable + "=" + state.value);
1868
- await new Promise((resolve) => setTimeout(resolve, 500));
1991
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1869
1992
  return state.info;
1870
1993
  }
1871
1994
  catch (e) {
1872
1995
  await _commandError(state, e, this);
1873
1996
  }
1874
1997
  finally {
1875
- _commandFinally(state, this);
1998
+ await _commandFinally(state, this);
1876
1999
  }
1877
2000
  }
1878
2001
  async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
@@ -1887,18 +2010,25 @@ class StableBrowser {
1887
2010
  highlight: true,
1888
2011
  screenshot: true,
1889
2012
  text: `Verify element attribute`,
2013
+ _text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
1890
2014
  operation: "verifyAttribute",
1891
2015
  log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1892
2016
  allowDisabled: true,
1893
2017
  };
1894
2018
  await new Promise((resolve) => setTimeout(resolve, 2000));
1895
2019
  let val;
2020
+ let expectedValue;
1896
2021
  try {
1897
- await _preCommand(state, this, world);
2022
+ await _preCommand(state, this);
2023
+ expectedValue = await replaceWithLocalTestData(state.value, world);
2024
+ state.info.expectedValue = expectedValue;
1898
2025
  switch (attribute) {
1899
2026
  case "innerText":
1900
2027
  val = String(await state.element.innerText());
1901
2028
  break;
2029
+ case "text":
2030
+ val = String(await state.element.textContent());
2031
+ break;
1902
2032
  case "value":
1903
2033
  val = String(await state.element.inputValue());
1904
2034
  break;
@@ -1916,26 +2046,29 @@ class StableBrowser {
1916
2046
  val = String(await state.element.getAttribute(attribute));
1917
2047
  break;
1918
2048
  }
2049
+ state.info.value = val;
1919
2050
  let regex;
1920
- if (value.startsWith("/") && value.endsWith("/")) {
1921
- const patternBody = value.slice(1, -1);
2051
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
2052
+ const patternBody = expectedValue.slice(1, -1);
1922
2053
  regex = new RegExp(patternBody, "g");
1923
2054
  }
1924
2055
  else {
1925
- const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2056
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1926
2057
  regex = new RegExp(escapedPattern, "g");
1927
2058
  }
1928
2059
  if (!val.match(regex)) {
1929
- throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
2060
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
2061
+ state.info.failCause.assertionFailed = true;
2062
+ state.info.failCause.lastError = errorMessage;
2063
+ throw new Error(errorMessage);
1930
2064
  }
1931
- state.info.expectedValue = val;
1932
2065
  return state.info;
1933
2066
  }
1934
2067
  catch (e) {
1935
2068
  await _commandError(state, e, this);
1936
2069
  }
1937
2070
  finally {
1938
- _commandFinally(state, this);
2071
+ await _commandFinally(state, this);
1939
2072
  }
1940
2073
  }
1941
2074
  async extractEmailData(emailAddress, options, world) {
@@ -2036,17 +2169,17 @@ class StableBrowser {
2036
2169
  if (node && node.style) {
2037
2170
  let originalOutline = node.style.outline;
2038
2171
  // console.log(`Original outline was: ${originalOutline}`);
2039
- node.__previousOutline = originalOutline;
2172
+ // node.__previousOutline = originalOutline;
2040
2173
  node.style.outline = "2px solid red";
2041
2174
  // console.log(`New outline is: ${node.style.outline}`);
2042
- // if (window) {
2043
- // window.addEventListener("beforeunload", function (e) {
2044
- // node.style.outline = originalBorder;
2045
- // });
2046
- // }
2047
- // setTimeout(function () {
2048
- // node.style.outline = originalBorder;
2049
- //}, 2000);
2175
+ if (window) {
2176
+ window.addEventListener("beforeunload", function (e) {
2177
+ node.style.outline = originalOutline;
2178
+ });
2179
+ }
2180
+ setTimeout(function () {
2181
+ node.style.outline = originalOutline;
2182
+ }, 2000);
2050
2183
  }
2051
2184
  })
2052
2185
  .then(() => { })
@@ -2072,15 +2205,15 @@ class StableBrowser {
2072
2205
  element.__previousOutline = originalOutline;
2073
2206
  // Set the new border to be red and 2px solid
2074
2207
  element.style.outline = "2px solid red";
2075
- // if (window) {
2076
- // window.addEventListener("beforeunload", function (e) {
2077
- // element.style.outline = originalBorder;
2078
- // });
2079
- // }
2208
+ if (window) {
2209
+ window.addEventListener("beforeunload", function (e) {
2210
+ element.style.outline = originalOutline;
2211
+ });
2212
+ }
2080
2213
  // Set a timeout to revert to the original border after 2 seconds
2081
- // setTimeout(function () {
2082
- // element.style.outline = originalBorder;
2083
- // }, 2000);
2214
+ setTimeout(function () {
2215
+ element.style.outline = originalOutline;
2216
+ }, 2000);
2084
2217
  }
2085
2218
  return;
2086
2219
  }, [css])
@@ -2095,58 +2228,54 @@ class StableBrowser {
2095
2228
  console.debug(error);
2096
2229
  }
2097
2230
  }
2098
- async _unhighlightElements(scope, css) {
2099
- try {
2100
- if (!scope) {
2101
- return;
2102
- }
2103
- if (!css) {
2104
- scope
2105
- .evaluate((node) => {
2106
- if (node && node.style) {
2107
- if (!node.__previousOutline) {
2108
- node.style.outline = "";
2109
- }
2110
- else {
2111
- node.style.outline = node.__previousOutline;
2112
- }
2113
- }
2114
- })
2115
- .then(() => { })
2116
- .catch((e) => {
2117
- // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2118
- });
2119
- }
2120
- else {
2121
- scope
2122
- .evaluate(([css]) => {
2123
- if (!css) {
2124
- return;
2125
- }
2126
- let elements = Array.from(document.querySelectorAll(css));
2127
- for (i = 0; i < elements.length; i++) {
2128
- let element = elements[i];
2129
- if (!element.style) {
2130
- return;
2131
- }
2132
- if (!element.__previousOutline) {
2133
- element.style.outline = "";
2134
- }
2135
- else {
2136
- element.style.outline = element.__previousOutline;
2137
- }
2138
- }
2139
- })
2140
- .then(() => { })
2141
- .catch((e) => {
2142
- // console.error(`Error while unhighlighting element in css: ${e}`);
2143
- });
2144
- }
2145
- }
2146
- catch (error) {
2147
- // console.debug(error);
2148
- }
2149
- }
2231
+ // async _unhighlightElements(scope, css) {
2232
+ // try {
2233
+ // if (!scope) {
2234
+ // return;
2235
+ // }
2236
+ // if (!css) {
2237
+ // scope
2238
+ // .evaluate((node) => {
2239
+ // if (node && node.style) {
2240
+ // if (!node.__previousOutline) {
2241
+ // node.style.outline = "";
2242
+ // } else {
2243
+ // node.style.outline = node.__previousOutline;
2244
+ // }
2245
+ // }
2246
+ // })
2247
+ // .then(() => {})
2248
+ // .catch((e) => {
2249
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2250
+ // });
2251
+ // } else {
2252
+ // scope
2253
+ // .evaluate(([css]) => {
2254
+ // if (!css) {
2255
+ // return;
2256
+ // }
2257
+ // let elements = Array.from(document.querySelectorAll(css));
2258
+ // for (i = 0; i < elements.length; i++) {
2259
+ // let element = elements[i];
2260
+ // if (!element.style) {
2261
+ // return;
2262
+ // }
2263
+ // if (!element.__previousOutline) {
2264
+ // element.style.outline = "";
2265
+ // } else {
2266
+ // element.style.outline = element.__previousOutline;
2267
+ // }
2268
+ // }
2269
+ // })
2270
+ // .then(() => {})
2271
+ // .catch((e) => {
2272
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2273
+ // });
2274
+ // }
2275
+ // } catch (error) {
2276
+ // // console.debug(error);
2277
+ // }
2278
+ // }
2150
2279
  async verifyPagePath(pathPart, options = {}, world = null) {
2151
2280
  const startTime = Date.now();
2152
2281
  let error = null;
@@ -2191,6 +2320,7 @@ class StableBrowser {
2191
2320
  _reportToWorld(world, {
2192
2321
  type: Types.VERIFY_PAGE_PATH,
2193
2322
  text: "Verify page path",
2323
+ _text: "Verify the page path contains " + pathPart,
2194
2324
  screenshotId,
2195
2325
  result: error
2196
2326
  ? {
@@ -2208,27 +2338,89 @@ class StableBrowser {
2208
2338
  });
2209
2339
  }
2210
2340
  }
2211
- async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2341
+ async verifyPageTitle(title, options = {}, world = null) {
2342
+ const startTime = Date.now();
2343
+ let error = null;
2344
+ let screenshotId = null;
2345
+ let screenshotPath = null;
2346
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2347
+ const info = {};
2348
+ info.log = "***** verify page title " + title + " *****\n";
2349
+ info.operation = "verifyPageTitle";
2350
+ const newValue = await this._replaceWithLocalData(title, world);
2351
+ if (newValue !== title) {
2352
+ this.logger.info(title + "=" + newValue);
2353
+ title = newValue;
2354
+ }
2355
+ info.title = title;
2356
+ try {
2357
+ for (let i = 0; i < 30; i++) {
2358
+ const foundTitle = await this.page.title();
2359
+ if (!foundTitle.includes(title)) {
2360
+ if (i === 29) {
2361
+ throw new Error(`url ${foundTitle} doesn't contain ${title}`);
2362
+ }
2363
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2364
+ continue;
2365
+ }
2366
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2367
+ return info;
2368
+ }
2369
+ }
2370
+ catch (e) {
2371
+ //await this.closeUnexpectedPopups();
2372
+ this.logger.error("verify page title failed " + info.log);
2373
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2374
+ info.screenshotPath = screenshotPath;
2375
+ Object.assign(e, { info: info });
2376
+ error = e;
2377
+ // throw e;
2378
+ await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
2379
+ }
2380
+ finally {
2381
+ const endTime = Date.now();
2382
+ _reportToWorld(world, {
2383
+ type: Types.VERIFY_PAGE_PATH,
2384
+ text: "Verify page title",
2385
+ _text: "Verify the page title contains " + title,
2386
+ screenshotId,
2387
+ result: error
2388
+ ? {
2389
+ status: "FAILED",
2390
+ startTime,
2391
+ endTime,
2392
+ message: error?.message,
2393
+ }
2394
+ : {
2395
+ status: "PASSED",
2396
+ startTime,
2397
+ endTime,
2398
+ },
2399
+ info: info,
2400
+ });
2401
+ }
2402
+ }
2403
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
2212
2404
  const frames = this.page.frames();
2213
2405
  let results = [];
2214
- let ignoreCase = false;
2406
+ // let ignoreCase = false;
2215
2407
  for (let i = 0; i < frames.length; i++) {
2216
2408
  if (dateAlternatives.date) {
2217
2409
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2218
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2410
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2219
2411
  result.frame = frames[i];
2220
2412
  results.push(result);
2221
2413
  }
2222
2414
  }
2223
2415
  else if (numberAlternatives.number) {
2224
2416
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2225
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2417
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2226
2418
  result.frame = frames[i];
2227
2419
  results.push(result);
2228
2420
  }
2229
2421
  }
2230
2422
  else {
2231
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2423
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
2232
2424
  result.frame = frames[i];
2233
2425
  results.push(result);
2234
2426
  }
@@ -2247,11 +2439,15 @@ class StableBrowser {
2247
2439
  scroll: false,
2248
2440
  highlight: false,
2249
2441
  type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2250
- text: `Verify text exists in page`,
2442
+ text: `Verify the text '${text}' exists in page`,
2443
+ _text: `Verify the text '${text}' exists in page`,
2251
2444
  operation: "verifyTextExistInPage",
2252
2445
  log: "***** verify text " + text + " exists in page *****\n",
2253
2446
  };
2254
- const timeout = this._getLoadTimeout(options);
2447
+ if (testForRegex(text)) {
2448
+ text = text.replace(/\\"/g, '"');
2449
+ }
2450
+ const timeout = this._getFindElementTimeout(options);
2255
2451
  await new Promise((resolve) => setTimeout(resolve, 2000));
2256
2452
  const newValue = await this._replaceWithLocalData(text, world);
2257
2453
  if (newValue !== text) {
@@ -2261,10 +2457,18 @@ class StableBrowser {
2261
2457
  let dateAlternatives = findDateAlternatives(text);
2262
2458
  let numberAlternatives = findNumberAlternatives(text);
2263
2459
  try {
2264
- await _preCommand(state, this, world);
2460
+ await _preCommand(state, this);
2265
2461
  state.info.text = text;
2266
2462
  while (true) {
2267
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2463
+ let resultWithElementsFound = {
2464
+ length: 0,
2465
+ };
2466
+ try {
2467
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2468
+ }
2469
+ catch (error) {
2470
+ // ignore
2471
+ }
2268
2472
  if (resultWithElementsFound.length === 0) {
2269
2473
  if (Date.now() - state.startTime > timeout) {
2270
2474
  throw new Error(`Text ${text} not found in page`);
@@ -2272,35 +2476,40 @@ class StableBrowser {
2272
2476
  await new Promise((resolve) => setTimeout(resolve, 1000));
2273
2477
  continue;
2274
2478
  }
2275
- if (resultWithElementsFound[0].randomToken) {
2276
- const frame = resultWithElementsFound[0].frame;
2277
- const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2278
- if (world && world.screenshot && !world.screenshotPath) {
2479
+ try {
2480
+ if (resultWithElementsFound[0].randomToken) {
2481
+ const frame = resultWithElementsFound[0].frame;
2482
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2483
+ await this._highlightElements(frame, dataAttribute);
2484
+ // if (world && world.screenshot && !world.screenshotPath) {
2279
2485
  // console.log(`Highlighting for verify text is found while running from recorder`);
2280
- this._highlightElements(frame, dataAttribute).then(async () => {
2281
- await new Promise((resolve) => setTimeout(resolve, 1000));
2282
- this._unhighlightElements(frame, dataAttribute)
2283
- .then(async () => {
2284
- // console.log(`Unhighlighted frame dataAttribute successfully`);
2285
- })
2286
- .catch((e) => { }
2287
- // console.error(e)
2288
- );
2289
- });
2290
- }
2291
- const element = await frame.locator(dataAttribute).first();
2292
- // await new Promise((resolve) => setTimeout(resolve, 100));
2293
- // await this._unhighlightElements(frame, dataAttribute);
2294
- if (element) {
2295
- await this.scrollIfNeeded(element, state.info);
2296
- await element.dispatchEvent("bvt_verify_page_contains_text");
2297
- await _screenshot(state, this, element);
2486
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2487
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2488
+ // this._unhighlightElements(frame, dataAttribute)
2489
+ // .then(async () => {
2490
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2491
+ // })
2492
+ // .catch(
2493
+ // (e) => {}
2494
+ // console.error(e)
2495
+ // );
2496
+ // });
2497
+ // }
2498
+ const element = await frame.locator(dataAttribute).first();
2499
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2500
+ // await this._unhighlightElements(frame, dataAttribute);
2501
+ if (element) {
2502
+ await this.scrollIfNeeded(element, state.info);
2503
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2504
+ // await _screenshot(state, this, element);
2505
+ }
2298
2506
  }
2299
- }
2300
- else {
2301
2507
  await _screenshot(state, this);
2508
+ return state.info;
2509
+ }
2510
+ catch (error) {
2511
+ console.error(error);
2302
2512
  }
2303
- return state.info;
2304
2513
  }
2305
2514
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2306
2515
  }
@@ -2308,7 +2517,7 @@ class StableBrowser {
2308
2517
  await _commandError(state, e, this);
2309
2518
  }
2310
2519
  finally {
2311
- _commandFinally(state, this);
2520
+ await _commandFinally(state, this);
2312
2521
  }
2313
2522
  }
2314
2523
  async waitForTextToDisappear(text, options = {}, world = null) {
@@ -2321,11 +2530,15 @@ class StableBrowser {
2321
2530
  scroll: false,
2322
2531
  highlight: false,
2323
2532
  type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2324
- text: `Verify text does not exist in page`,
2533
+ text: `Verify the text '${text}' does not exist in page`,
2534
+ _text: `Verify the text '${text}' does not exist in page`,
2325
2535
  operation: "verifyTextNotExistInPage",
2326
2536
  log: "***** verify text " + text + " does not exist in page *****\n",
2327
2537
  };
2328
- const timeout = this._getLoadTimeout(options);
2538
+ if (testForRegex(text)) {
2539
+ text = text.replace(/\\"/g, '"');
2540
+ }
2541
+ const timeout = this._getFindElementTimeout(options);
2329
2542
  await new Promise((resolve) => setTimeout(resolve, 2000));
2330
2543
  const newValue = await this._replaceWithLocalData(text, world);
2331
2544
  if (newValue !== text) {
@@ -2335,10 +2548,18 @@ class StableBrowser {
2335
2548
  let dateAlternatives = findDateAlternatives(text);
2336
2549
  let numberAlternatives = findNumberAlternatives(text);
2337
2550
  try {
2338
- await _preCommand(state, this, world);
2551
+ await _preCommand(state, this);
2339
2552
  state.info.text = text;
2553
+ let resultWithElementsFound = {
2554
+ length: null, // initial cannot be 0
2555
+ };
2340
2556
  while (true) {
2341
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2557
+ try {
2558
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2559
+ }
2560
+ catch (error) {
2561
+ // ignore
2562
+ }
2342
2563
  if (resultWithElementsFound.length === 0) {
2343
2564
  await _screenshot(state, this);
2344
2565
  return state.info;
@@ -2353,7 +2574,7 @@ class StableBrowser {
2353
2574
  await _commandError(state, e, this);
2354
2575
  }
2355
2576
  finally {
2356
- _commandFinally(state, this);
2577
+ await _commandFinally(state, this);
2357
2578
  }
2358
2579
  }
2359
2580
  async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
@@ -2368,10 +2589,11 @@ class StableBrowser {
2368
2589
  highlight: false,
2369
2590
  type: Types.VERIFY_TEXT_WITH_RELATION,
2370
2591
  text: `Verify text with relation to another text`,
2592
+ _text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
2371
2593
  operation: "verify_text_with_relation",
2372
2594
  log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2373
2595
  };
2374
- const timeout = this._getLoadTimeout(options);
2596
+ const timeout = this._getFindElementTimeout(options);
2375
2597
  await new Promise((resolve) => setTimeout(resolve, 2000));
2376
2598
  let newValue = await this._replaceWithLocalData(textAnchor, world);
2377
2599
  if (newValue !== textAnchor) {
@@ -2387,10 +2609,18 @@ class StableBrowser {
2387
2609
  let numberAlternatives = findNumberAlternatives(textToVerify);
2388
2610
  let foundAncore = false;
2389
2611
  try {
2390
- await _preCommand(state, this, world);
2612
+ await _preCommand(state, this);
2391
2613
  state.info.text = textToVerify;
2614
+ let resultWithElementsFound = {
2615
+ length: 0,
2616
+ };
2392
2617
  while (true) {
2393
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2618
+ try {
2619
+ resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
2620
+ }
2621
+ catch (error) {
2622
+ // ignore
2623
+ }
2394
2624
  if (resultWithElementsFound.length === 0) {
2395
2625
  if (Date.now() - state.startTime > timeout) {
2396
2626
  throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
@@ -2398,52 +2628,56 @@ class StableBrowser {
2398
2628
  await new Promise((resolve) => setTimeout(resolve, 1000));
2399
2629
  continue;
2400
2630
  }
2401
- for (let i = 0; i < resultWithElementsFound.length; i++) {
2402
- foundAncore = true;
2403
- const result = resultWithElementsFound[i];
2404
- const token = result.randomToken;
2405
- const frame = result.frame;
2406
- let css = `[data-blinq-id-${token}]`;
2407
- const climbArray1 = [];
2408
- for (let i = 0; i < climb; i++) {
2409
- climbArray1.push("..");
2410
- }
2411
- let climbXpath = "xpath=" + climbArray1.join("/");
2412
- css = css + " >> " + climbXpath;
2413
- const count = await frame.locator(css).count();
2414
- for (let j = 0; j < count; j++) {
2415
- const continer = await frame.locator(css).nth(j);
2416
- const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2417
- if (result.elementCount > 0) {
2418
- const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2419
- //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2420
- if (world && world.screenshot && !world.screenshotPath) {
2631
+ try {
2632
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2633
+ foundAncore = true;
2634
+ const result = resultWithElementsFound[i];
2635
+ const token = result.randomToken;
2636
+ const frame = result.frame;
2637
+ let css = `[data-blinq-id-${token}]`;
2638
+ const climbArray1 = [];
2639
+ for (let i = 0; i < climb; i++) {
2640
+ climbArray1.push("..");
2641
+ }
2642
+ let climbXpath = "xpath=" + climbArray1.join("/");
2643
+ css = css + " >> " + climbXpath;
2644
+ const count = await frame.locator(css).count();
2645
+ for (let j = 0; j < count; j++) {
2646
+ const continer = await frame.locator(css).nth(j);
2647
+ const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
2648
+ if (result.elementCount > 0) {
2649
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2650
+ await this._highlightElements(frame, dataAttribute);
2651
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2652
+ // if (world && world.screenshot && !world.screenshotPath) {
2421
2653
  // console.log(`Highlighting for vtrt while running from recorder`);
2422
- this._highlightElements(frame, dataAttribute)
2423
- .then(async () => {
2424
- await new Promise((resolve) => setTimeout(resolve, 1000));
2425
- this._unhighlightElements(frame, dataAttribute).then(() => { }
2426
- // console.log(`Unhighlighting vrtr in recorder is successful`)
2427
- );
2428
- })
2429
- .catch(e);
2430
- }
2431
- //await this._highlightElements(frame, cssAnchor);
2432
- const element = await frame.locator(dataAttribute).first();
2433
- // await new Promise((resolve) => setTimeout(resolve, 100));
2434
- // await this._unhighlightElements(frame, dataAttribute);
2435
- if (element) {
2436
- await this.scrollIfNeeded(element, state.info);
2437
- await element.dispatchEvent("bvt_verify_page_contains_text");
2438
- await _screenshot(state, this, element);
2439
- }
2440
- else {
2654
+ // this._highlightElements(frame, dataAttribute)
2655
+ // .then(async () => {
2656
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2657
+ // this._unhighlightElements(frame, dataAttribute).then(
2658
+ // () => {}
2659
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2660
+ // );
2661
+ // })
2662
+ // .catch(e);
2663
+ // }
2664
+ //await this._highlightElements(frame, cssAnchor);
2665
+ const element = await frame.locator(dataAttribute).first();
2666
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2667
+ // await this._unhighlightElements(frame, dataAttribute);
2668
+ if (element) {
2669
+ await this.scrollIfNeeded(element, state.info);
2670
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2671
+ }
2441
2672
  await _screenshot(state, this);
2673
+ return state.info;
2442
2674
  }
2443
- return state.info;
2444
2675
  }
2445
2676
  }
2446
2677
  }
2678
+ catch (error) {
2679
+ console.error(error);
2680
+ }
2447
2681
  }
2448
2682
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2449
2683
  }
@@ -2451,8 +2685,32 @@ class StableBrowser {
2451
2685
  await _commandError(state, e, this);
2452
2686
  }
2453
2687
  finally {
2454
- _commandFinally(state, this);
2688
+ await _commandFinally(state, this);
2689
+ }
2690
+ }
2691
+ async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
2692
+ const frames = this.page.frames();
2693
+ let results = [];
2694
+ let ignoreCase = false;
2695
+ for (let i = 0; i < frames.length; i++) {
2696
+ const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
2697
+ result.frame = frames[i];
2698
+ const climbArray = [];
2699
+ for (let i = 0; i < climb; i++) {
2700
+ climbArray.push("..");
2701
+ }
2702
+ let climbXpath = "xpath=" + climbArray.join("/");
2703
+ const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
2704
+ const count = await frames[i].locator(newLocator).count();
2705
+ if (count > 0) {
2706
+ result.elementCount = count;
2707
+ result.locator = newLocator;
2708
+ results.push(result);
2709
+ }
2455
2710
  }
2711
+ // state.info.results = results;
2712
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2713
+ return resultWithElementsFound;
2456
2714
  }
2457
2715
  async visualVerification(text, options = {}, world = null) {
2458
2716
  const startTime = Date.now();
@@ -2472,10 +2730,13 @@ class StableBrowser {
2472
2730
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2473
2731
  info.screenshotPath = screenshotPath;
2474
2732
  const screenshot = await this.takeScreenshot();
2475
- const request = {
2476
- method: "POST",
2733
+ let request = {
2734
+ method: "post",
2735
+ maxBodyLength: Infinity,
2477
2736
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2478
2737
  headers: {
2738
+ "x-bvt-project-id": path.basename(this.project_path),
2739
+ "x-source": "aaa",
2479
2740
  "Content-Type": "application/json",
2480
2741
  Authorization: `Bearer ${process.env.TOKEN}`,
2481
2742
  },
@@ -2484,7 +2745,7 @@ class StableBrowser {
2484
2745
  screenshot: screenshot,
2485
2746
  }),
2486
2747
  };
2487
- let result = await this.context.api.request(request);
2748
+ const result = await axios.request(request);
2488
2749
  if (result.data.status !== true) {
2489
2750
  throw new Error("Visual validation failed");
2490
2751
  }
@@ -2512,6 +2773,7 @@ class StableBrowser {
2512
2773
  _reportToWorld(world, {
2513
2774
  type: Types.VERIFY_VISUAL,
2514
2775
  text: "Visual verification",
2776
+ _text: "Visual verification of " + text,
2515
2777
  screenshotId,
2516
2778
  result: error
2517
2779
  ? {
@@ -2562,14 +2824,14 @@ class StableBrowser {
2562
2824
  info.selectors = selectors;
2563
2825
  try {
2564
2826
  let table = await this._locate(selectors, info, _params);
2565
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info, table));
2827
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2566
2828
  const tableData = await getTableData(this.page, table);
2567
2829
  return tableData;
2568
2830
  }
2569
2831
  catch (e) {
2570
2832
  this.logger.error("getTableData failed " + info.log);
2571
2833
  this.logger.error(e);
2572
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info, table));
2834
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2573
2835
  info.screenshotPath = screenshotPath;
2574
2836
  Object.assign(e, { info: info });
2575
2837
  error = e;
@@ -2778,6 +3040,32 @@ class StableBrowser {
2778
3040
  }
2779
3041
  return timeout;
2780
3042
  }
3043
+ _getFindElementTimeout(options) {
3044
+ if (options && options.timeout) {
3045
+ return options.timeout;
3046
+ }
3047
+ if (this.configuration.find_element_timeout) {
3048
+ return this.configuration.find_element_timeout;
3049
+ }
3050
+ return 30000;
3051
+ }
3052
+ async saveStoreState(path = null, world = null) {
3053
+ const storageState = await this.page.context().storageState();
3054
+ //const testDataFile = _getDataFile(world, this.context, this);
3055
+ if (path) {
3056
+ // save { storageState: storageState } into the path
3057
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
3058
+ }
3059
+ else {
3060
+ await this.setTestData({ storageState: storageState }, world);
3061
+ }
3062
+ }
3063
+ async restoreSaveState(path = null, world = null) {
3064
+ await refreshBrowser(this, path, world);
3065
+ this.registerEventListeners(this.context);
3066
+ registerNetworkEvents(this.world, this, this.context, this.page);
3067
+ registerDownloadEvent(this.page, this.world, this.context);
3068
+ }
2781
3069
  async waitForPageLoad(options = {}, world = null) {
2782
3070
  let timeout = this._getLoadTimeout(options);
2783
3071
  const promiseArray = [];
@@ -2845,12 +3133,13 @@ class StableBrowser {
2845
3133
  highlight: false,
2846
3134
  type: Types.CLOSE_PAGE,
2847
3135
  text: `Close page`,
3136
+ _text: `Close the page`,
2848
3137
  operation: "closePage",
2849
3138
  log: "***** close page *****\n",
2850
3139
  throwError: false,
2851
3140
  };
2852
3141
  try {
2853
- await _preCommand(state, this, world);
3142
+ await _preCommand(state, this);
2854
3143
  await this.page.close();
2855
3144
  }
2856
3145
  catch (e) {
@@ -2858,11 +3147,98 @@ class StableBrowser {
2858
3147
  await _commandError(state, e, this);
2859
3148
  }
2860
3149
  finally {
2861
- _commandFinally(state, this);
3150
+ await _commandFinally(state, this);
3151
+ }
3152
+ }
3153
+ async tableCellOperation(headerText, rowText, options, _params, world = null) {
3154
+ let operation = null;
3155
+ if (!options || !options.operation) {
3156
+ throw new Error("operation is not defined");
3157
+ }
3158
+ operation = options.operation;
3159
+ // validate operation is one of the supported operations
3160
+ if (operation != "click" && operation != "hover+click") {
3161
+ throw new Error("operation is not supported");
3162
+ }
3163
+ const state = {
3164
+ options,
3165
+ world,
3166
+ locate: false,
3167
+ scroll: false,
3168
+ highlight: false,
3169
+ type: Types.TABLE_OPERATION,
3170
+ text: `Table operation`,
3171
+ _text: `Table ${operation} operation`,
3172
+ operation: operation,
3173
+ log: "***** Table operation *****\n",
3174
+ };
3175
+ const timeout = this._getFindElementTimeout(options);
3176
+ try {
3177
+ await _preCommand(state, this);
3178
+ const start = Date.now();
3179
+ let cellArea = null;
3180
+ while (true) {
3181
+ try {
3182
+ cellArea = await _findCellArea(headerText, rowText, this, state);
3183
+ if (cellArea) {
3184
+ break;
3185
+ }
3186
+ }
3187
+ catch (e) {
3188
+ // ignore
3189
+ }
3190
+ if (Date.now() - start > timeout) {
3191
+ throw new Error(`Cell not found in table`);
3192
+ }
3193
+ await new Promise((resolve) => setTimeout(resolve, 1000));
3194
+ }
3195
+ switch (operation) {
3196
+ case "click":
3197
+ if (!options.css) {
3198
+ // will click in the center of the cell
3199
+ let xOffset = 0;
3200
+ let yOffset = 0;
3201
+ if (options.xOffset) {
3202
+ xOffset = options.xOffset;
3203
+ }
3204
+ if (options.yOffset) {
3205
+ yOffset = options.yOffset;
3206
+ }
3207
+ await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
3208
+ }
3209
+ else {
3210
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3211
+ if (results.length === 0) {
3212
+ throw new Error(`Element not found in cell area`);
3213
+ }
3214
+ state.element = results[0];
3215
+ await performAction("click", state.element, options, this, state, _params);
3216
+ }
3217
+ break;
3218
+ case "hover+click":
3219
+ if (!options.css) {
3220
+ throw new Error("css is not defined");
3221
+ }
3222
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3223
+ if (results.length === 0) {
3224
+ throw new Error(`Element not found in cell area`);
3225
+ }
3226
+ state.element = results[0];
3227
+ await performAction("hover+click", state.element, options, this, state, _params);
3228
+ break;
3229
+ default:
3230
+ throw new Error("operation is not supported");
3231
+ }
3232
+ }
3233
+ catch (e) {
3234
+ await _commandError(state, e, this);
3235
+ }
3236
+ finally {
3237
+ await _commandFinally(state, this);
2862
3238
  }
2863
3239
  }
2864
3240
  saveTestDataAsGlobal(options, world) {
2865
- const dataFile = this._getDataFile(world);
3241
+ const dataFile = _getDataFile(world, this.context, this);
2866
3242
  process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2867
3243
  this.logger.info("Save the scenario test data as global for the following scenarios.");
2868
3244
  }
@@ -2892,6 +3268,7 @@ class StableBrowser {
2892
3268
  _reportToWorld(world, {
2893
3269
  type: Types.SET_VIEWPORT,
2894
3270
  text: "set viewport size to " + width + "x" + hight,
3271
+ _text: "Set the viewport size to " + width + "x" + hight,
2895
3272
  screenshotId,
2896
3273
  result: error
2897
3274
  ? {
@@ -2962,7 +3339,38 @@ class StableBrowser {
2962
3339
  console.log("#-#");
2963
3340
  }
2964
3341
  }
3342
+ async beforeScenario(world, scenario) {
3343
+ this.beforeScenarioCalled = true;
3344
+ if (scenario && scenario.pickle && scenario.pickle.name) {
3345
+ this.scenarioName = scenario.pickle.name;
3346
+ }
3347
+ if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
3348
+ this.featureName = scenario.gherkinDocument.feature.name;
3349
+ }
3350
+ if (this.context) {
3351
+ this.context.examplesRow = extractStepExampleParameters(scenario);
3352
+ }
3353
+ if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
3354
+ this.tags = scenario.pickle.tags.map((tag) => tag.name);
3355
+ // check if @global_test_data tag is present
3356
+ if (this.tags.includes("@global_test_data")) {
3357
+ this.saveTestDataAsGlobal({}, world);
3358
+ }
3359
+ }
3360
+ // update test data based on feature/scenario
3361
+ let envName = null;
3362
+ if (this.context && this.context.environment) {
3363
+ envName = this.context.environment.name;
3364
+ }
3365
+ if (!process.env.TEMP_RUN) {
3366
+ await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
3367
+ }
3368
+ }
3369
+ async afterScenario(world, scenario) { }
2965
3370
  async beforeStep(world, step) {
3371
+ if (!this.beforeScenarioCalled) {
3372
+ this.beforeScenario(world, step);
3373
+ }
2966
3374
  if (this.stepIndex === undefined) {
2967
3375
  this.stepIndex = 0;
2968
3376
  }
@@ -2979,22 +3387,47 @@ class StableBrowser {
2979
3387
  else {
2980
3388
  this.stepName = "step " + this.stepIndex;
2981
3389
  }
2982
- if (this.context) {
2983
- this.context.examplesRow = extractStepExampleParameters(step);
2984
- }
2985
3390
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2986
3391
  if (this.context.browserObject.context) {
2987
3392
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2988
3393
  }
2989
3394
  }
2990
- if (this.tags === null && step && step.pickle && step.pickle.tags) {
2991
- this.tags = step.pickle.tags.map((tag) => tag.name);
2992
- // check if @global_test_data tag is present
2993
- if (this.tags.includes("@global_test_data")) {
2994
- this.saveTestDataAsGlobal({}, world);
3395
+ if (this.initSnapshotTaken === false) {
3396
+ this.initSnapshotTaken = true;
3397
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3398
+ const snapshot = await this.getAriaSnapshot();
3399
+ if (snapshot) {
3400
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
3401
+ }
2995
3402
  }
2996
3403
  }
2997
3404
  }
3405
+ async getAriaSnapshot() {
3406
+ try {
3407
+ // find the page url
3408
+ const url = await this.page.url();
3409
+ // extract the path from the url
3410
+ const path = new URL(url).pathname;
3411
+ // get the page title
3412
+ const title = await this.page.title();
3413
+ // go over other frams
3414
+ const frames = this.page.frames();
3415
+ const snapshots = [];
3416
+ const content = [`- path: ${path}`, `- title: ${title}`];
3417
+ const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
3418
+ for (let i = 0; i < frames.length; i++) {
3419
+ content.push(`- frame: ${i}`);
3420
+ const frame = frames[i];
3421
+ const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
3422
+ content.push(snapshot);
3423
+ }
3424
+ return content.join("\n");
3425
+ }
3426
+ catch (e) {
3427
+ console.error(e);
3428
+ }
3429
+ return null;
3430
+ }
2998
3431
  async afterStep(world, step) {
2999
3432
  this.stepName = null;
3000
3433
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
@@ -3002,11 +3435,25 @@ class StableBrowser {
3002
3435
  await this.context.browserObject.context.tracing.stopChunk({
3003
3436
  path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
3004
3437
  });
3438
+ if (world && world.attach) {
3439
+ await world.attach(JSON.stringify({
3440
+ type: "trace",
3441
+ traceFilePath: `trace-${this.stepIndex}.zip`,
3442
+ }), "application/json+trace");
3443
+ }
3444
+ // console.log("trace file created", `trace-${this.stepIndex}.zip`);
3005
3445
  }
3006
3446
  }
3007
3447
  if (this.context) {
3008
3448
  this.context.examplesRow = null;
3009
3449
  }
3450
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3451
+ const snapshot = await this.getAriaSnapshot();
3452
+ if (snapshot) {
3453
+ const obj = {};
3454
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
3455
+ }
3456
+ }
3010
3457
  }
3011
3458
  }
3012
3459
  function createTimedPromise(promise, label) {