automation_model 1.0.604-dev → 1.0.604-stage

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +130 -0
  2. package/lib/api.js +35 -21
  3. package/lib/api.js.map +1 -1
  4. package/lib/auto_page.d.ts +3 -1
  5. package/lib/auto_page.js +102 -40
  6. package/lib/auto_page.js.map +1 -1
  7. package/lib/browser_manager.js +53 -16
  8. package/lib/browser_manager.js.map +1 -1
  9. package/lib/bruno.d.ts +2 -0
  10. package/lib/bruno.js +381 -0
  11. package/lib/bruno.js.map +1 -0
  12. package/lib/command_common.d.ts +4 -4
  13. package/lib/command_common.js +34 -16
  14. package/lib/command_common.js.map +1 -1
  15. package/lib/error-messages.js +6 -0
  16. package/lib/error-messages.js.map +1 -1
  17. package/lib/file_checker.d.ts +1 -0
  18. package/lib/file_checker.js +61 -0
  19. package/lib/file_checker.js.map +1 -0
  20. package/lib/index.d.ts +2 -0
  21. package/lib/index.js +2 -0
  22. package/lib/index.js.map +1 -1
  23. package/lib/init_browser.d.ts +3 -2
  24. package/lib/init_browser.js +52 -12
  25. package/lib/init_browser.js.map +1 -1
  26. package/lib/locate_element.js +2 -2
  27. package/lib/locate_element.js.map +1 -1
  28. package/lib/network.d.ts +1 -1
  29. package/lib/network.js +5 -5
  30. package/lib/network.js.map +1 -1
  31. package/lib/stable_browser.d.ts +20 -1
  32. package/lib/stable_browser.js +645 -197
  33. package/lib/stable_browser.js.map +1 -1
  34. package/lib/table_helper.d.ts +19 -0
  35. package/lib/table_helper.js +116 -0
  36. package/lib/table_helper.js.map +1 -0
  37. package/lib/test_context.d.ts +3 -0
  38. package/lib/test_context.js +2 -0
  39. package/lib/test_context.js.map +1 -1
  40. package/lib/utils.d.ts +8 -4
  41. package/lib/utils.js +214 -19
  42. package/lib/utils.js.map +1 -1
  43. package/package.json +9 -8
@@ -10,18 +10,23 @@ 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 { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, } from "./utils.js";
13
+ import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
14
14
  import csv from "csv-parser";
15
15
  import { Readable } from "node:stream";
16
16
  import readline from "readline";
17
- import { getContext } from "./init_browser.js";
17
+ import { getContext, refreshBrowser } from "./init_browser.js";
18
+ import { getTestData } from "./auto_page.js";
18
19
  import { locate_element } from "./locate_element.js";
19
20
  import { randomUUID } from "crypto";
20
21
  import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
21
22
  import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
22
23
  import { LocatorLog } from "./locator_log.js";
24
+ import axios from "axios";
25
+ import { _findCellArea, findElementsInArea } from "./table_helper.js";
26
+ import { loadBrunoParams } from "./bruno.js";
23
27
  export const Types = {
24
28
  CLICK: "click_element",
29
+ WAIT_ELEMENT: "wait_element",
25
30
  NAVIGATE: "navigate",
26
31
  FILL: "fill_element",
27
32
  EXECUTE: "execute_page_method",
@@ -43,6 +48,7 @@ export const Types = {
43
48
  UNCHECK: "uncheck_element",
44
49
  EXTRACT: "extract_attribute",
45
50
  CLOSE_PAGE: "close_page",
51
+ TABLE_OPERATION: "table_operation",
46
52
  SET_DATE_TIME: "set_date_time",
47
53
  SET_VIEWPORT: "set_viewport",
48
54
  VERIFY_VISUAL: "verify_visual",
@@ -51,6 +57,8 @@ export const Types = {
51
57
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
52
58
  VERIFY_ATTRIBUTE: "verify_element_attribute",
53
59
  VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
60
+ BRUNO: "bruno",
61
+ VERIFY_FILE_EXISTS: "verify_file_exists",
54
62
  };
55
63
  export const apps = {};
56
64
  const formatElementName = (elementName) => {
@@ -69,6 +77,7 @@ class StableBrowser {
69
77
  appName = "main";
70
78
  tags = null;
71
79
  isRecording = false;
80
+ initSnapshotTaken = false;
72
81
  constructor(browser, page, logger = null, context = null, world = null) {
73
82
  this.browser = browser;
74
83
  this.page = page;
@@ -175,6 +184,30 @@ class StableBrowser {
175
184
  await this.waitForPageLoad();
176
185
  }
177
186
  }
187
+ async switchTab(tabTitleOrIndex) {
188
+ // first check if the tabNameOrIndex is a number
189
+ let index = parseInt(tabTitleOrIndex);
190
+ if (!isNaN(index)) {
191
+ if (index >= 0 && index < this.context.pages.length) {
192
+ this.page = this.context.pages[index];
193
+ this.context.page = this.page;
194
+ await this.page.bringToFront();
195
+ return;
196
+ }
197
+ }
198
+ // if the tabNameOrIndex is a string, find the tab by name
199
+ for (let i = 0; i < this.context.pages.length; i++) {
200
+ let page = this.context.pages[i];
201
+ let title = await page.title();
202
+ if (title.includes(tabTitleOrIndex)) {
203
+ this.page = page;
204
+ this.context.page = this.page;
205
+ await this.page.bringToFront();
206
+ return;
207
+ }
208
+ }
209
+ throw new Error("Tab not found: " + tabTitleOrIndex);
210
+ }
178
211
  registerConsoleLogListener(page, context) {
179
212
  if (!this.context.webLogger) {
180
213
  this.context.webLogger = [];
@@ -239,6 +272,9 @@ class StableBrowser {
239
272
  // await closeUnexpectedPopups(this.page);
240
273
  // }
241
274
  async goto(url, world = null) {
275
+ if (!url) {
276
+ throw new Error("url is null, verify that the environment file is correct");
277
+ }
242
278
  if (!url.startsWith("http")) {
243
279
  url = "https://" + url;
244
280
  }
@@ -267,7 +303,7 @@ class StableBrowser {
267
303
  _commandError(state, error, this);
268
304
  }
269
305
  finally {
270
- _commandFinally(state, this);
306
+ await _commandFinally(state, this);
271
307
  }
272
308
  }
273
309
  async _getLocator(locator, scope, _params) {
@@ -348,7 +384,7 @@ class StableBrowser {
348
384
  return resultCss;
349
385
  }
350
386
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
351
- const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
387
+ const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
352
388
  const locator = scope.locator(query);
353
389
  const count = await locator.count();
354
390
  if (!tag1) {
@@ -368,6 +404,12 @@ class StableBrowser {
368
404
  if (!el.setAttribute) {
369
405
  el = el.parentElement;
370
406
  }
407
+ // remove any attributes start with data-blinq-id
408
+ // for (let i = 0; i < el.attributes.length; i++) {
409
+ // if (el.attributes[i].name.startsWith("data-blinq-id")) {
410
+ // el.removeAttribute(el.attributes[i].name);
411
+ // }
412
+ // }
371
413
  el.setAttribute("data-blinq-id-" + randomToken, "");
372
414
  return true;
373
415
  }, [tag1, randomToken]))) {
@@ -554,9 +596,24 @@ class StableBrowser {
554
596
  element.evaluate((el, randomToken) => {
555
597
  el.setAttribute("data-blinq-id-" + randomToken, "");
556
598
  }, randomToken);
599
+ // if (element._frame) {
600
+ // return element;
601
+ // }
557
602
  const scope = element._frame ?? element.page();
558
- const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
559
- return newSelector;
603
+ let newElementSelector = "[data-blinq-id-" + randomToken + "]";
604
+ let prefixSelector = "";
605
+ const frameControlSelector = " >> internal:control=enter-frame";
606
+ const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
607
+ if (frameSelectorIndex !== -1) {
608
+ // remove everything after the >> internal:control=enter-frame
609
+ const frameSelector = element._selector.substring(0, frameSelectorIndex);
610
+ prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
611
+ }
612
+ // if (element?._frame?._selector) {
613
+ // prefixSelector = element._frame._selector + " >> " + prefixSelector;
614
+ // }
615
+ const newSelector = prefixSelector + newElementSelector;
616
+ return scope.locator(newSelector);
560
617
  }
561
618
  }
562
619
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -708,14 +765,9 @@ class StableBrowser {
708
765
  // info.log += "scanning locators in priority 2" + "\n";
709
766
  result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
710
767
  }
711
- if (result.foundElements.length === 0 && onlyPriority3) {
768
+ if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
712
769
  result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
713
770
  }
714
- else {
715
- if (result.foundElements.length === 0 && !highPriorityOnly) {
716
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
717
- }
718
- }
719
771
  let foundElements = result.foundElements;
720
772
  if (foundElements.length === 1 && foundElements[0].unique) {
721
773
  info.box = foundElements[0].box;
@@ -770,6 +822,11 @@ class StableBrowser {
770
822
  visibleOnly = false;
771
823
  }
772
824
  await new Promise((resolve) => setTimeout(resolve, 1000));
825
+ // sheck of more of half of the timeout has passed
826
+ if (Date.now() - startTime > timeout / 2) {
827
+ highPriorityOnly = false;
828
+ visibleOnly = false;
829
+ }
773
830
  }
774
831
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
775
832
  // if (info.locatorLog) {
@@ -816,9 +873,40 @@ class StableBrowser {
816
873
  result.locatorIndex = i;
817
874
  }
818
875
  if (foundLocators.length > 1) {
819
- info.failCause.foundMultiple = true;
820
- if (info.locatorLog) {
821
- info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
876
+ // remove elements that consume the same space with 10 pixels tolerance
877
+ const boxes = [];
878
+ for (let j = 0; j < foundLocators.length; j++) {
879
+ boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
880
+ }
881
+ for (let j = 0; j < boxes.length; j++) {
882
+ for (let k = 0; k < boxes.length; k++) {
883
+ if (j === k) {
884
+ continue;
885
+ }
886
+ // check if x, y, width, height are the same with 10 pixels tolerance
887
+ if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
888
+ Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
889
+ Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
890
+ Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
891
+ // as the element is not unique, will remove it
892
+ boxes.splice(k, 1);
893
+ k--;
894
+ }
895
+ }
896
+ }
897
+ if (boxes.length === 1) {
898
+ result.foundElements.push({
899
+ locator: boxes[0].locator.first(),
900
+ box: boxes[0].box,
901
+ unique: true,
902
+ });
903
+ result.locatorIndex = i;
904
+ }
905
+ else {
906
+ info.failCause.foundMultiple = true;
907
+ if (info.locatorLog) {
908
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
909
+ }
822
910
  }
823
911
  }
824
912
  }
@@ -866,7 +954,7 @@ class StableBrowser {
866
954
  await _commandError(state, "timeout looking for " + elementDescription, this);
867
955
  }
868
956
  finally {
869
- _commandFinally(state, this);
957
+ await _commandFinally(state, this);
870
958
  }
871
959
  }
872
960
  }
@@ -915,7 +1003,7 @@ class StableBrowser {
915
1003
  await _commandError(state, "timeout looking for " + elementDescription, this);
916
1004
  }
917
1005
  finally {
918
- _commandFinally(state, this);
1006
+ await _commandFinally(state, this);
919
1007
  }
920
1008
  }
921
1009
  }
@@ -929,25 +1017,14 @@ class StableBrowser {
929
1017
  options,
930
1018
  world,
931
1019
  text: "Click element",
1020
+ _text: "Click on " + selectors.element_name,
932
1021
  type: Types.CLICK,
933
1022
  operation: "click",
934
1023
  log: "***** click on " + selectors.element_name + " *****\n",
935
1024
  };
936
1025
  try {
937
1026
  await _preCommand(state, this);
938
- // if (state.options && state.options.context) {
939
- // state.selectors.locators[0].text = state.options.context;
940
- // }
941
- try {
942
- await state.element.click();
943
- // await new Promise((resolve) => setTimeout(resolve, 1000));
944
- }
945
- catch (e) {
946
- // await this.closeUnexpectedPopups();
947
- state.element = await this._locate(selectors, state.info, _params);
948
- await state.element.dispatchEvent("click");
949
- // await new Promise((resolve) => setTimeout(resolve, 1000));
950
- }
1027
+ await performAction("click", state.element, options, this, state, _params);
951
1028
  await this.waitForPageLoad();
952
1029
  return state.info;
953
1030
  }
@@ -955,9 +1032,41 @@ class StableBrowser {
955
1032
  await _commandError(state, e, this);
956
1033
  }
957
1034
  finally {
958
- _commandFinally(state, this);
1035
+ await _commandFinally(state, this);
959
1036
  }
960
1037
  }
1038
+ async waitForElement(selectors, _params, options = {}, world = null) {
1039
+ const timeout = this._getFindElementTimeout(options);
1040
+ const state = {
1041
+ selectors,
1042
+ _params,
1043
+ options,
1044
+ world,
1045
+ text: "Wait for element",
1046
+ _text: "Wait for " + selectors.element_name,
1047
+ type: Types.WAIT_ELEMENT,
1048
+ operation: "waitForElement",
1049
+ log: "***** wait for " + selectors.element_name + " *****\n",
1050
+ };
1051
+ let found = false;
1052
+ try {
1053
+ await _preCommand(state, this);
1054
+ // if (state.options && state.options.context) {
1055
+ // state.selectors.locators[0].text = state.options.context;
1056
+ // }
1057
+ await state.element.waitFor({ timeout: timeout });
1058
+ found = true;
1059
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1060
+ }
1061
+ catch (e) {
1062
+ console.error("Error on waitForElement", e);
1063
+ // await _commandError(state, e, this);
1064
+ }
1065
+ finally {
1066
+ await _commandFinally(state, this);
1067
+ }
1068
+ return found;
1069
+ }
961
1070
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
962
1071
  const state = {
963
1072
  selectors,
@@ -966,6 +1075,7 @@ class StableBrowser {
966
1075
  world,
967
1076
  type: checked ? Types.CHECK : Types.UNCHECK,
968
1077
  text: checked ? `Check element` : `Uncheck element`,
1078
+ _text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
969
1079
  operation: "setCheck",
970
1080
  log: "***** check " + selectors.element_name + " *****\n",
971
1081
  };
@@ -977,7 +1087,7 @@ class StableBrowser {
977
1087
  try {
978
1088
  // if (world && world.screenshot && !world.screenshotPath) {
979
1089
  // console.log(`Highlighting while running from recorder`);
980
- await this._highlightElements(element);
1090
+ await this._highlightElements(state.element);
981
1091
  await state.element.setChecked(checked);
982
1092
  await new Promise((resolve) => setTimeout(resolve, 1000));
983
1093
  // await this._unHighlightElements(element);
@@ -1004,7 +1114,7 @@ class StableBrowser {
1004
1114
  await _commandError(state, e, this);
1005
1115
  }
1006
1116
  finally {
1007
- _commandFinally(state, this);
1117
+ await _commandFinally(state, this);
1008
1118
  }
1009
1119
  }
1010
1120
  async hover(selectors, _params, options = {}, world = null) {
@@ -1015,24 +1125,13 @@ class StableBrowser {
1015
1125
  world,
1016
1126
  type: Types.HOVER,
1017
1127
  text: `Hover element`,
1128
+ _text: `Hover on ${selectors.element_name}`,
1018
1129
  operation: "hover",
1019
1130
  log: "***** hover " + selectors.element_name + " *****\n",
1020
1131
  };
1021
1132
  try {
1022
1133
  await _preCommand(state, this);
1023
- try {
1024
- await state.element.hover();
1025
- // await _screenshot(state, this);
1026
- await new Promise((resolve) => setTimeout(resolve, 1000));
1027
- }
1028
- catch (e) {
1029
- //await this.closeUnexpectedPopups();
1030
- state.info.log += "hover failed, will try again" + "\n";
1031
- state.element = await this._locate(selectors, state.info, _params);
1032
- await state.element.hover({ timeout: 10000 });
1033
- // await _screenshot(state, this);
1034
- await new Promise((resolve) => setTimeout(resolve, 1000));
1035
- }
1134
+ await performAction("hover", state.element, options, this, state, _params);
1036
1135
  await _screenshot(state, this);
1037
1136
  await this.waitForPageLoad();
1038
1137
  return state.info;
@@ -1041,7 +1140,7 @@ class StableBrowser {
1041
1140
  await _commandError(state, e, this);
1042
1141
  }
1043
1142
  finally {
1044
- _commandFinally(state, this);
1143
+ await _commandFinally(state, this);
1045
1144
  }
1046
1145
  }
1047
1146
  async selectOption(selectors, values, _params = null, options = {}, world = null) {
@@ -1056,6 +1155,7 @@ class StableBrowser {
1056
1155
  value: values.toString(),
1057
1156
  type: Types.SELECT,
1058
1157
  text: `Select option: ${values}`,
1158
+ _text: `Select option: ${values} on ${selectors.element_name}`,
1059
1159
  operation: "selectOption",
1060
1160
  log: "***** select option " + selectors.element_name + " *****\n",
1061
1161
  };
@@ -1076,7 +1176,7 @@ class StableBrowser {
1076
1176
  await _commandError(state, e, this);
1077
1177
  }
1078
1178
  finally {
1079
- _commandFinally(state, this);
1179
+ await _commandFinally(state, this);
1080
1180
  }
1081
1181
  }
1082
1182
  async type(_value, _params = null, options = {}, world = null) {
@@ -1090,6 +1190,7 @@ class StableBrowser {
1090
1190
  highlight: false,
1091
1191
  type: Types.TYPE_PRESS,
1092
1192
  text: `Type value: ${_value}`,
1193
+ _text: `Type value: ${_value}`,
1093
1194
  operation: "type",
1094
1195
  log: "",
1095
1196
  };
@@ -1121,7 +1222,7 @@ class StableBrowser {
1121
1222
  await _commandError(state, e, this);
1122
1223
  }
1123
1224
  finally {
1124
- _commandFinally(state, this);
1225
+ await _commandFinally(state, this);
1125
1226
  }
1126
1227
  }
1127
1228
  async setInputValue(selectors, value, _params = null, options = {}, world = null) {
@@ -1157,7 +1258,7 @@ class StableBrowser {
1157
1258
  await _commandError(state, e, this);
1158
1259
  }
1159
1260
  finally {
1160
- _commandFinally(state, this);
1261
+ await _commandFinally(state, this);
1161
1262
  }
1162
1263
  }
1163
1264
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
@@ -1169,6 +1270,7 @@ class StableBrowser {
1169
1270
  world,
1170
1271
  type: Types.SET_DATE_TIME,
1171
1272
  text: `Set date time value: ${value}`,
1273
+ _text: `Set date time value: ${value} on ${selectors.element_name}`,
1172
1274
  operation: "setDateTime",
1173
1275
  log: "***** set date time value " + selectors.element_name + " *****\n",
1174
1276
  throwError: false,
@@ -1176,7 +1278,7 @@ class StableBrowser {
1176
1278
  try {
1177
1279
  await _preCommand(state, this);
1178
1280
  try {
1179
- await state.element.click();
1281
+ await performAction("click", state.element, options, this, state, _params);
1180
1282
  await new Promise((resolve) => setTimeout(resolve, 500));
1181
1283
  if (format) {
1182
1284
  state.value = dayjs(state.value).format(format);
@@ -1225,7 +1327,7 @@ class StableBrowser {
1225
1327
  await _commandError(state, e, this);
1226
1328
  }
1227
1329
  finally {
1228
- _commandFinally(state, this);
1330
+ await _commandFinally(state, this);
1229
1331
  }
1230
1332
  }
1231
1333
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
@@ -1240,9 +1342,13 @@ class StableBrowser {
1240
1342
  world,
1241
1343
  type: Types.FILL,
1242
1344
  text: `Click type input with value: ${_value}`,
1345
+ _text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
1243
1346
  operation: "clickType",
1244
1347
  log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1245
1348
  };
1349
+ if (!options) {
1350
+ options = {};
1351
+ }
1246
1352
  if (newValue !== _value) {
1247
1353
  //this.logger.info(_value + "=" + newValue);
1248
1354
  _value = newValue;
@@ -1250,7 +1356,7 @@ class StableBrowser {
1250
1356
  try {
1251
1357
  await _preCommand(state, this);
1252
1358
  state.info.value = _value;
1253
- if (options === null || options === undefined || !options.press) {
1359
+ if (!options.press) {
1254
1360
  try {
1255
1361
  let currentValue = await state.element.inputValue();
1256
1362
  if (currentValue) {
@@ -1261,13 +1367,9 @@ class StableBrowser {
1261
1367
  this.logger.info("unable to clear input value");
1262
1368
  }
1263
1369
  }
1264
- if (options === null || options === undefined || options.press) {
1265
- try {
1266
- await state.element.click({ timeout: 5000 });
1267
- }
1268
- catch (e) {
1269
- await state.element.dispatchEvent("click");
1270
- }
1370
+ if (options.press) {
1371
+ options.timeout = 5000;
1372
+ await performAction("click", state.element, options, this, state, _params);
1271
1373
  }
1272
1374
  else {
1273
1375
  try {
@@ -1325,7 +1427,7 @@ class StableBrowser {
1325
1427
  await _commandError(state, e, this);
1326
1428
  }
1327
1429
  finally {
1328
- _commandFinally(state, this);
1430
+ await _commandFinally(state, this);
1329
1431
  }
1330
1432
  }
1331
1433
  async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
@@ -1355,13 +1457,14 @@ class StableBrowser {
1355
1457
  await _commandError(state, e, this);
1356
1458
  }
1357
1459
  finally {
1358
- _commandFinally(state, this);
1460
+ await _commandFinally(state, this);
1359
1461
  }
1360
1462
  }
1361
1463
  async getText(selectors, _params = null, options = {}, info = {}, world = null) {
1362
1464
  return await this._getText(selectors, 0, _params, options, info, world);
1363
1465
  }
1364
1466
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1467
+ const timeout = this._getFindElementTimeout(options);
1365
1468
  _validateSelectors(selectors);
1366
1469
  let screenshotId = null;
1367
1470
  let screenshotPath = null;
@@ -1371,7 +1474,7 @@ class StableBrowser {
1371
1474
  }
1372
1475
  info.operation = "getText";
1373
1476
  info.selectors = selectors;
1374
- let element = await this._locate(selectors, info, _params);
1477
+ let element = await this._locate(selectors, info, _params, timeout);
1375
1478
  if (climb > 0) {
1376
1479
  const climbArray = [];
1377
1480
  for (let i = 0; i < climb; i++) {
@@ -1438,6 +1541,7 @@ class StableBrowser {
1438
1541
  highlight: false,
1439
1542
  type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1440
1543
  text: `Verify element contains pattern: ${pattern}`,
1544
+ _text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
1441
1545
  operation: "containsPattern",
1442
1546
  log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1443
1547
  };
@@ -1469,10 +1573,12 @@ class StableBrowser {
1469
1573
  await _commandError(state, e, this);
1470
1574
  }
1471
1575
  finally {
1472
- _commandFinally(state, this);
1576
+ await _commandFinally(state, this);
1473
1577
  }
1474
1578
  }
1475
1579
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1580
+ const timeout = this._getFindElementTimeout(options);
1581
+ const startTime = Date.now();
1476
1582
  const state = {
1477
1583
  selectors,
1478
1584
  _params,
@@ -1499,44 +1605,52 @@ class StableBrowser {
1499
1605
  }
1500
1606
  let foundObj = null;
1501
1607
  try {
1502
- await _preCommand(state, this);
1503
- foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1504
- if (foundObj && foundObj.element) {
1505
- await this.scrollIfNeeded(foundObj.element, state.info);
1506
- }
1507
- await _screenshot(state, this);
1508
- const dateAlternatives = findDateAlternatives(text);
1509
- const numberAlternatives = findNumberAlternatives(text);
1510
- if (dateAlternatives.date) {
1511
- for (let i = 0; i < dateAlternatives.dates.length; i++) {
1512
- if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1513
- foundObj?.value?.includes(dateAlternatives.dates[i])) {
1514
- return state.info;
1608
+ while (Date.now() - startTime < timeout) {
1609
+ try {
1610
+ await _preCommand(state, this);
1611
+ foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
1612
+ if (foundObj && foundObj.element) {
1613
+ await this.scrollIfNeeded(foundObj.element, state.info);
1515
1614
  }
1516
- }
1517
- throw new Error("element doesn't contain text " + text);
1518
- }
1519
- else if (numberAlternatives.number) {
1520
- for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1521
- if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1522
- foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1615
+ await _screenshot(state, this);
1616
+ const dateAlternatives = findDateAlternatives(text);
1617
+ const numberAlternatives = findNumberAlternatives(text);
1618
+ if (dateAlternatives.date) {
1619
+ for (let i = 0; i < dateAlternatives.dates.length; i++) {
1620
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1621
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1622
+ return state.info;
1623
+ }
1624
+ }
1625
+ }
1626
+ else if (numberAlternatives.number) {
1627
+ for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1628
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1629
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1630
+ return state.info;
1631
+ }
1632
+ }
1633
+ }
1634
+ else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
1523
1635
  return state.info;
1524
1636
  }
1525
1637
  }
1526
- throw new Error("element doesn't contain text " + text);
1527
- }
1528
- else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1529
- state.info.foundText = foundObj?.text;
1530
- state.info.value = foundObj?.value;
1531
- throw new Error("element doesn't contain text " + text);
1638
+ catch (e) {
1639
+ // Log error but continue retrying until timeout is reached
1640
+ this.logger.warn("Retrying containsText due to: " + e.message);
1641
+ }
1642
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
1532
1643
  }
1533
- return state.info;
1644
+ state.info.foundText = foundObj?.text;
1645
+ state.info.value = foundObj?.value;
1646
+ throw new Error("element doesn't contain text " + text);
1534
1647
  }
1535
1648
  catch (e) {
1536
1649
  await _commandError(state, e, this);
1650
+ throw e;
1537
1651
  }
1538
1652
  finally {
1539
- _commandFinally(state, this);
1653
+ await _commandFinally(state, this);
1540
1654
  }
1541
1655
  }
1542
1656
  async waitForUserInput(message, world = null) {
@@ -1574,6 +1688,15 @@ class StableBrowser {
1574
1688
  // save the data to the file
1575
1689
  fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
1576
1690
  }
1691
+ overwriteTestData(testData, world = null) {
1692
+ if (!testData) {
1693
+ return;
1694
+ }
1695
+ // if data file exists, load it
1696
+ const dataFile = _getDataFile(world, this.context, this);
1697
+ // save the data to the file
1698
+ fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
1699
+ }
1577
1700
  _getDataFilePath(fileName) {
1578
1701
  let dataFile = path.join(this.project_path, "data", fileName);
1579
1702
  if (fs.existsSync(dataFile)) {
@@ -1803,6 +1926,7 @@ class StableBrowser {
1803
1926
  else {
1804
1927
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1805
1928
  }
1929
+ return screenshotBuffer;
1806
1930
  }
1807
1931
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1808
1932
  const state = {
@@ -1825,7 +1949,7 @@ class StableBrowser {
1825
1949
  await _commandError(state, e, this);
1826
1950
  }
1827
1951
  finally {
1828
- _commandFinally(state, this);
1952
+ await _commandFinally(state, this);
1829
1953
  }
1830
1954
  }
1831
1955
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
@@ -1838,6 +1962,7 @@ class StableBrowser {
1838
1962
  world,
1839
1963
  type: Types.EXTRACT,
1840
1964
  text: `Extract attribute from element`,
1965
+ _text: `Extract attribute ${attribute} from ${selectors.element_name}`,
1841
1966
  operation: "extractAttribute",
1842
1967
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1843
1968
  allowDisabled: true,
@@ -1855,6 +1980,9 @@ class StableBrowser {
1855
1980
  case "value":
1856
1981
  state.value = await state.element.inputValue();
1857
1982
  break;
1983
+ case "text":
1984
+ state.value = await state.element.textContent();
1985
+ break;
1858
1986
  default:
1859
1987
  state.value = await state.element.getAttribute(attribute);
1860
1988
  break;
@@ -1869,7 +1997,7 @@ class StableBrowser {
1869
1997
  await _commandError(state, e, this);
1870
1998
  }
1871
1999
  finally {
1872
- _commandFinally(state, this);
2000
+ await _commandFinally(state, this);
1873
2001
  }
1874
2002
  }
1875
2003
  async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
@@ -1884,18 +2012,25 @@ class StableBrowser {
1884
2012
  highlight: true,
1885
2013
  screenshot: true,
1886
2014
  text: `Verify element attribute`,
2015
+ _text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
1887
2016
  operation: "verifyAttribute",
1888
2017
  log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1889
2018
  allowDisabled: true,
1890
2019
  };
1891
2020
  await new Promise((resolve) => setTimeout(resolve, 2000));
1892
2021
  let val;
2022
+ let expectedValue;
1893
2023
  try {
1894
2024
  await _preCommand(state, this);
2025
+ expectedValue = await replaceWithLocalTestData(state.value, world);
2026
+ state.info.expectedValue = expectedValue;
1895
2027
  switch (attribute) {
1896
2028
  case "innerText":
1897
2029
  val = String(await state.element.innerText());
1898
2030
  break;
2031
+ case "text":
2032
+ val = String(await state.element.textContent());
2033
+ break;
1899
2034
  case "value":
1900
2035
  val = String(await state.element.inputValue());
1901
2036
  break;
@@ -1913,26 +2048,29 @@ class StableBrowser {
1913
2048
  val = String(await state.element.getAttribute(attribute));
1914
2049
  break;
1915
2050
  }
2051
+ state.info.value = val;
1916
2052
  let regex;
1917
- if (value.startsWith("/") && value.endsWith("/")) {
1918
- const patternBody = value.slice(1, -1);
2053
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
2054
+ const patternBody = expectedValue.slice(1, -1);
1919
2055
  regex = new RegExp(patternBody, "g");
1920
2056
  }
1921
2057
  else {
1922
- const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2058
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1923
2059
  regex = new RegExp(escapedPattern, "g");
1924
2060
  }
1925
2061
  if (!val.match(regex)) {
1926
- throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
2062
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
2063
+ state.info.failCause.assertionFailed = true;
2064
+ state.info.failCause.lastError = errorMessage;
2065
+ throw new Error(errorMessage);
1927
2066
  }
1928
- state.info.expectedValue = val;
1929
2067
  return state.info;
1930
2068
  }
1931
2069
  catch (e) {
1932
2070
  await _commandError(state, e, this);
1933
2071
  }
1934
2072
  finally {
1935
- _commandFinally(state, this);
2073
+ await _commandFinally(state, this);
1936
2074
  }
1937
2075
  }
1938
2076
  async extractEmailData(emailAddress, options, world) {
@@ -2184,6 +2322,7 @@ class StableBrowser {
2184
2322
  _reportToWorld(world, {
2185
2323
  type: Types.VERIFY_PAGE_PATH,
2186
2324
  text: "Verify page path",
2325
+ _text: "Verify the page path contains " + pathPart,
2187
2326
  screenshotId,
2188
2327
  result: error
2189
2328
  ? {
@@ -2201,27 +2340,89 @@ class StableBrowser {
2201
2340
  });
2202
2341
  }
2203
2342
  }
2204
- async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2343
+ async verifyPageTitle(title, options = {}, world = null) {
2344
+ const startTime = Date.now();
2345
+ let error = null;
2346
+ let screenshotId = null;
2347
+ let screenshotPath = null;
2348
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2349
+ const info = {};
2350
+ info.log = "***** verify page title " + title + " *****\n";
2351
+ info.operation = "verifyPageTitle";
2352
+ const newValue = await this._replaceWithLocalData(title, world);
2353
+ if (newValue !== title) {
2354
+ this.logger.info(title + "=" + newValue);
2355
+ title = newValue;
2356
+ }
2357
+ info.title = title;
2358
+ try {
2359
+ for (let i = 0; i < 30; i++) {
2360
+ const foundTitle = await this.page.title();
2361
+ if (!foundTitle.includes(title)) {
2362
+ if (i === 29) {
2363
+ throw new Error(`url ${foundTitle} doesn't contain ${title}`);
2364
+ }
2365
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2366
+ continue;
2367
+ }
2368
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2369
+ return info;
2370
+ }
2371
+ }
2372
+ catch (e) {
2373
+ //await this.closeUnexpectedPopups();
2374
+ this.logger.error("verify page title failed " + info.log);
2375
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2376
+ info.screenshotPath = screenshotPath;
2377
+ Object.assign(e, { info: info });
2378
+ error = e;
2379
+ // throw e;
2380
+ await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
2381
+ }
2382
+ finally {
2383
+ const endTime = Date.now();
2384
+ _reportToWorld(world, {
2385
+ type: Types.VERIFY_PAGE_PATH,
2386
+ text: "Verify page title",
2387
+ _text: "Verify the page title contains " + title,
2388
+ screenshotId,
2389
+ result: error
2390
+ ? {
2391
+ status: "FAILED",
2392
+ startTime,
2393
+ endTime,
2394
+ message: error?.message,
2395
+ }
2396
+ : {
2397
+ status: "PASSED",
2398
+ startTime,
2399
+ endTime,
2400
+ },
2401
+ info: info,
2402
+ });
2403
+ }
2404
+ }
2405
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
2205
2406
  const frames = this.page.frames();
2206
2407
  let results = [];
2207
- let ignoreCase = false;
2408
+ // let ignoreCase = false;
2208
2409
  for (let i = 0; i < frames.length; i++) {
2209
2410
  if (dateAlternatives.date) {
2210
2411
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2211
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2412
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2212
2413
  result.frame = frames[i];
2213
2414
  results.push(result);
2214
2415
  }
2215
2416
  }
2216
2417
  else if (numberAlternatives.number) {
2217
2418
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2218
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2419
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2219
2420
  result.frame = frames[i];
2220
2421
  results.push(result);
2221
2422
  }
2222
2423
  }
2223
2424
  else {
2224
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2425
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
2225
2426
  result.frame = frames[i];
2226
2427
  results.push(result);
2227
2428
  }
@@ -2240,11 +2441,15 @@ class StableBrowser {
2240
2441
  scroll: false,
2241
2442
  highlight: false,
2242
2443
  type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2243
- text: `Verify text exists in page`,
2444
+ text: `Verify the text '${maskValue(text)}' exists in page`,
2445
+ _text: `Verify the text '${text}' exists in page`,
2244
2446
  operation: "verifyTextExistInPage",
2245
2447
  log: "***** verify text " + text + " exists in page *****\n",
2246
2448
  };
2247
- const timeout = this._getLoadTimeout(options);
2449
+ if (testForRegex(text)) {
2450
+ text = text.replace(/\\"/g, '"');
2451
+ }
2452
+ const timeout = this._getFindElementTimeout(options);
2248
2453
  await new Promise((resolve) => setTimeout(resolve, 2000));
2249
2454
  const newValue = await this._replaceWithLocalData(text, world);
2250
2455
  if (newValue !== text) {
@@ -2257,7 +2462,15 @@ class StableBrowser {
2257
2462
  await _preCommand(state, this);
2258
2463
  state.info.text = text;
2259
2464
  while (true) {
2260
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2465
+ let resultWithElementsFound = {
2466
+ length: 0,
2467
+ };
2468
+ try {
2469
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2470
+ }
2471
+ catch (error) {
2472
+ // ignore
2473
+ }
2261
2474
  if (resultWithElementsFound.length === 0) {
2262
2475
  if (Date.now() - state.startTime > timeout) {
2263
2476
  throw new Error(`Text ${text} not found in page`);
@@ -2265,35 +2478,40 @@ class StableBrowser {
2265
2478
  await new Promise((resolve) => setTimeout(resolve, 1000));
2266
2479
  continue;
2267
2480
  }
2268
- if (resultWithElementsFound[0].randomToken) {
2269
- const frame = resultWithElementsFound[0].frame;
2270
- const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2271
- await this._highlightElements(frame, dataAttribute);
2272
- // if (world && world.screenshot && !world.screenshotPath) {
2273
- // console.log(`Highlighting for verify text is found while running from recorder`);
2274
- // this._highlightElements(frame, dataAttribute).then(async () => {
2275
- // await new Promise((resolve) => setTimeout(resolve, 1000));
2276
- // this._unhighlightElements(frame, dataAttribute)
2277
- // .then(async () => {
2278
- // console.log(`Unhighlighted frame dataAttribute successfully`);
2279
- // })
2280
- // .catch(
2281
- // (e) => {}
2282
- // console.error(e)
2283
- // );
2284
- // });
2285
- // }
2286
- const element = await frame.locator(dataAttribute).first();
2287
- // await new Promise((resolve) => setTimeout(resolve, 100));
2288
- // await this._unhighlightElements(frame, dataAttribute);
2289
- if (element) {
2290
- await this.scrollIfNeeded(element, state.info);
2291
- await element.dispatchEvent("bvt_verify_page_contains_text");
2292
- // await _screenshot(state, this, element);
2481
+ try {
2482
+ if (resultWithElementsFound[0].randomToken) {
2483
+ const frame = resultWithElementsFound[0].frame;
2484
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2485
+ await this._highlightElements(frame, dataAttribute);
2486
+ // if (world && world.screenshot && !world.screenshotPath) {
2487
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2488
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2489
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2490
+ // this._unhighlightElements(frame, dataAttribute)
2491
+ // .then(async () => {
2492
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2493
+ // })
2494
+ // .catch(
2495
+ // (e) => {}
2496
+ // console.error(e)
2497
+ // );
2498
+ // });
2499
+ // }
2500
+ const element = await frame.locator(dataAttribute).first();
2501
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2502
+ // await this._unhighlightElements(frame, dataAttribute);
2503
+ if (element) {
2504
+ await this.scrollIfNeeded(element, state.info);
2505
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2506
+ // await _screenshot(state, this, element);
2507
+ }
2293
2508
  }
2509
+ await _screenshot(state, this);
2510
+ return state.info;
2511
+ }
2512
+ catch (error) {
2513
+ console.error(error);
2294
2514
  }
2295
- await _screenshot(state, this);
2296
- return state.info;
2297
2515
  }
2298
2516
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2299
2517
  }
@@ -2301,7 +2519,7 @@ class StableBrowser {
2301
2519
  await _commandError(state, e, this);
2302
2520
  }
2303
2521
  finally {
2304
- _commandFinally(state, this);
2522
+ await _commandFinally(state, this);
2305
2523
  }
2306
2524
  }
2307
2525
  async waitForTextToDisappear(text, options = {}, world = null) {
@@ -2314,11 +2532,15 @@ class StableBrowser {
2314
2532
  scroll: false,
2315
2533
  highlight: false,
2316
2534
  type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2317
- text: `Verify text does not exist in page`,
2535
+ text: `Verify the text '${maskValue(text)}' does not exist in page`,
2536
+ _text: `Verify the text '${text}' does not exist in page`,
2318
2537
  operation: "verifyTextNotExistInPage",
2319
2538
  log: "***** verify text " + text + " does not exist in page *****\n",
2320
2539
  };
2321
- const timeout = this._getLoadTimeout(options);
2540
+ if (testForRegex(text)) {
2541
+ text = text.replace(/\\"/g, '"');
2542
+ }
2543
+ const timeout = this._getFindElementTimeout(options);
2322
2544
  await new Promise((resolve) => setTimeout(resolve, 2000));
2323
2545
  const newValue = await this._replaceWithLocalData(text, world);
2324
2546
  if (newValue !== text) {
@@ -2330,8 +2552,16 @@ class StableBrowser {
2330
2552
  try {
2331
2553
  await _preCommand(state, this);
2332
2554
  state.info.text = text;
2555
+ let resultWithElementsFound = {
2556
+ length: null, // initial cannot be 0
2557
+ };
2333
2558
  while (true) {
2334
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2559
+ try {
2560
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2561
+ }
2562
+ catch (error) {
2563
+ // ignore
2564
+ }
2335
2565
  if (resultWithElementsFound.length === 0) {
2336
2566
  await _screenshot(state, this);
2337
2567
  return state.info;
@@ -2346,7 +2576,7 @@ class StableBrowser {
2346
2576
  await _commandError(state, e, this);
2347
2577
  }
2348
2578
  finally {
2349
- _commandFinally(state, this);
2579
+ await _commandFinally(state, this);
2350
2580
  }
2351
2581
  }
2352
2582
  async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
@@ -2361,10 +2591,11 @@ class StableBrowser {
2361
2591
  highlight: false,
2362
2592
  type: Types.VERIFY_TEXT_WITH_RELATION,
2363
2593
  text: `Verify text with relation to another text`,
2594
+ _text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
2364
2595
  operation: "verify_text_with_relation",
2365
2596
  log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2366
2597
  };
2367
- const timeout = this._getLoadTimeout(options);
2598
+ const timeout = this._getFindElementTimeout(options);
2368
2599
  await new Promise((resolve) => setTimeout(resolve, 2000));
2369
2600
  let newValue = await this._replaceWithLocalData(textAnchor, world);
2370
2601
  if (newValue !== textAnchor) {
@@ -2382,8 +2613,16 @@ class StableBrowser {
2382
2613
  try {
2383
2614
  await _preCommand(state, this);
2384
2615
  state.info.text = textToVerify;
2616
+ let resultWithElementsFound = {
2617
+ length: 0,
2618
+ };
2385
2619
  while (true) {
2386
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2620
+ try {
2621
+ resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
2622
+ }
2623
+ catch (error) {
2624
+ // ignore
2625
+ }
2387
2626
  if (resultWithElementsFound.length === 0) {
2388
2627
  if (Date.now() - state.startTime > timeout) {
2389
2628
  throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
@@ -2391,51 +2630,56 @@ class StableBrowser {
2391
2630
  await new Promise((resolve) => setTimeout(resolve, 1000));
2392
2631
  continue;
2393
2632
  }
2394
- for (let i = 0; i < resultWithElementsFound.length; i++) {
2395
- foundAncore = true;
2396
- const result = resultWithElementsFound[i];
2397
- const token = result.randomToken;
2398
- const frame = result.frame;
2399
- let css = `[data-blinq-id-${token}]`;
2400
- const climbArray1 = [];
2401
- for (let i = 0; i < climb; i++) {
2402
- climbArray1.push("..");
2403
- }
2404
- let climbXpath = "xpath=" + climbArray1.join("/");
2405
- css = css + " >> " + climbXpath;
2406
- const count = await frame.locator(css).count();
2407
- for (let j = 0; j < count; j++) {
2408
- const continer = await frame.locator(css).nth(j);
2409
- const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2410
- if (result.elementCount > 0) {
2411
- const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2412
- await this._highlightElements(frame, dataAttribute);
2413
- //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2414
- // if (world && world.screenshot && !world.screenshotPath) {
2415
- // console.log(`Highlighting for vtrt while running from recorder`);
2416
- // this._highlightElements(frame, dataAttribute)
2417
- // .then(async () => {
2418
- // await new Promise((resolve) => setTimeout(resolve, 1000));
2419
- // this._unhighlightElements(frame, dataAttribute).then(
2420
- // () => {}
2421
- // console.log(`Unhighlighting vrtr in recorder is successful`)
2422
- // );
2423
- // })
2424
- // .catch(e);
2425
- // }
2426
- //await this._highlightElements(frame, cssAnchor);
2427
- const element = await frame.locator(dataAttribute).first();
2428
- // await new Promise((resolve) => setTimeout(resolve, 100));
2429
- // await this._unhighlightElements(frame, dataAttribute);
2430
- if (element) {
2431
- await this.scrollIfNeeded(element, state.info);
2432
- await element.dispatchEvent("bvt_verify_page_contains_text");
2633
+ try {
2634
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2635
+ foundAncore = true;
2636
+ const result = resultWithElementsFound[i];
2637
+ const token = result.randomToken;
2638
+ const frame = result.frame;
2639
+ let css = `[data-blinq-id-${token}]`;
2640
+ const climbArray1 = [];
2641
+ for (let i = 0; i < climb; i++) {
2642
+ climbArray1.push("..");
2643
+ }
2644
+ let climbXpath = "xpath=" + climbArray1.join("/");
2645
+ css = css + " >> " + climbXpath;
2646
+ const count = await frame.locator(css).count();
2647
+ for (let j = 0; j < count; j++) {
2648
+ const continer = await frame.locator(css).nth(j);
2649
+ const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
2650
+ if (result.elementCount > 0) {
2651
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2652
+ await this._highlightElements(frame, dataAttribute);
2653
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2654
+ // if (world && world.screenshot && !world.screenshotPath) {
2655
+ // console.log(`Highlighting for vtrt while running from recorder`);
2656
+ // this._highlightElements(frame, dataAttribute)
2657
+ // .then(async () => {
2658
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2659
+ // this._unhighlightElements(frame, dataAttribute).then(
2660
+ // () => {}
2661
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2662
+ // );
2663
+ // })
2664
+ // .catch(e);
2665
+ // }
2666
+ //await this._highlightElements(frame, cssAnchor);
2667
+ const element = await frame.locator(dataAttribute).first();
2668
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2669
+ // await this._unhighlightElements(frame, dataAttribute);
2670
+ if (element) {
2671
+ await this.scrollIfNeeded(element, state.info);
2672
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2673
+ }
2674
+ await _screenshot(state, this);
2675
+ return state.info;
2433
2676
  }
2434
- await _screenshot(state, this);
2435
- return state.info;
2436
2677
  }
2437
2678
  }
2438
2679
  }
2680
+ catch (error) {
2681
+ console.error(error);
2682
+ }
2439
2683
  }
2440
2684
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2441
2685
  }
@@ -2443,9 +2687,33 @@ class StableBrowser {
2443
2687
  await _commandError(state, e, this);
2444
2688
  }
2445
2689
  finally {
2446
- _commandFinally(state, this);
2690
+ await _commandFinally(state, this);
2447
2691
  }
2448
2692
  }
2693
+ async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
2694
+ const frames = this.page.frames();
2695
+ let results = [];
2696
+ let ignoreCase = false;
2697
+ for (let i = 0; i < frames.length; i++) {
2698
+ const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
2699
+ result.frame = frames[i];
2700
+ const climbArray = [];
2701
+ for (let i = 0; i < climb; i++) {
2702
+ climbArray.push("..");
2703
+ }
2704
+ let climbXpath = "xpath=" + climbArray.join("/");
2705
+ const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
2706
+ const count = await frames[i].locator(newLocator).count();
2707
+ if (count > 0) {
2708
+ result.elementCount = count;
2709
+ result.locator = newLocator;
2710
+ results.push(result);
2711
+ }
2712
+ }
2713
+ // state.info.results = results;
2714
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2715
+ return resultWithElementsFound;
2716
+ }
2449
2717
  async visualVerification(text, options = {}, world = null) {
2450
2718
  const startTime = Date.now();
2451
2719
  let error = null;
@@ -2464,10 +2732,13 @@ class StableBrowser {
2464
2732
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2465
2733
  info.screenshotPath = screenshotPath;
2466
2734
  const screenshot = await this.takeScreenshot();
2467
- const request = {
2468
- method: "POST",
2735
+ let request = {
2736
+ method: "post",
2737
+ maxBodyLength: Infinity,
2469
2738
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2470
2739
  headers: {
2740
+ "x-bvt-project-id": path.basename(this.project_path),
2741
+ "x-source": "aaa",
2471
2742
  "Content-Type": "application/json",
2472
2743
  Authorization: `Bearer ${process.env.TOKEN}`,
2473
2744
  },
@@ -2476,7 +2747,7 @@ class StableBrowser {
2476
2747
  screenshot: screenshot,
2477
2748
  }),
2478
2749
  };
2479
- let result = await this.context.api.request(request);
2750
+ const result = await axios.request(request);
2480
2751
  if (result.data.status !== true) {
2481
2752
  throw new Error("Visual validation failed");
2482
2753
  }
@@ -2504,6 +2775,7 @@ class StableBrowser {
2504
2775
  _reportToWorld(world, {
2505
2776
  type: Types.VERIFY_VISUAL,
2506
2777
  text: "Visual verification",
2778
+ _text: "Visual verification of " + text,
2507
2779
  screenshotId,
2508
2780
  result: error
2509
2781
  ? {
@@ -2770,6 +3042,15 @@ class StableBrowser {
2770
3042
  }
2771
3043
  return timeout;
2772
3044
  }
3045
+ _getFindElementTimeout(options) {
3046
+ if (options && options.timeout) {
3047
+ return options.timeout;
3048
+ }
3049
+ if (this.configuration.find_element_timeout) {
3050
+ return this.configuration.find_element_timeout;
3051
+ }
3052
+ return 30000;
3053
+ }
2773
3054
  async saveStoreState(path = null, world = null) {
2774
3055
  const storageState = await this.page.context().storageState();
2775
3056
  //const testDataFile = _getDataFile(world, this.context, this);
@@ -2781,6 +3062,12 @@ class StableBrowser {
2781
3062
  await this.setTestData({ storageState: storageState }, world);
2782
3063
  }
2783
3064
  }
3065
+ async restoreSaveState(path = null, world = null) {
3066
+ await refreshBrowser(this, path, world);
3067
+ this.registerEventListeners(this.context);
3068
+ registerNetworkEvents(this.world, this, this.context, this.page);
3069
+ registerDownloadEvent(this.page, this.world, this.context);
3070
+ }
2784
3071
  async waitForPageLoad(options = {}, world = null) {
2785
3072
  let timeout = this._getLoadTimeout(options);
2786
3073
  const promiseArray = [];
@@ -2848,6 +3135,7 @@ class StableBrowser {
2848
3135
  highlight: false,
2849
3136
  type: Types.CLOSE_PAGE,
2850
3137
  text: `Close page`,
3138
+ _text: `Close the page`,
2851
3139
  operation: "closePage",
2852
3140
  log: "***** close page *****\n",
2853
3141
  throwError: false,
@@ -2861,11 +3149,98 @@ class StableBrowser {
2861
3149
  await _commandError(state, e, this);
2862
3150
  }
2863
3151
  finally {
2864
- _commandFinally(state, this);
3152
+ await _commandFinally(state, this);
3153
+ }
3154
+ }
3155
+ async tableCellOperation(headerText, rowText, options, _params, world = null) {
3156
+ let operation = null;
3157
+ if (!options || !options.operation) {
3158
+ throw new Error("operation is not defined");
3159
+ }
3160
+ operation = options.operation;
3161
+ // validate operation is one of the supported operations
3162
+ if (operation != "click" && operation != "hover+click") {
3163
+ throw new Error("operation is not supported");
3164
+ }
3165
+ const state = {
3166
+ options,
3167
+ world,
3168
+ locate: false,
3169
+ scroll: false,
3170
+ highlight: false,
3171
+ type: Types.TABLE_OPERATION,
3172
+ text: `Table operation`,
3173
+ _text: `Table ${operation} operation`,
3174
+ operation: operation,
3175
+ log: "***** Table operation *****\n",
3176
+ };
3177
+ const timeout = this._getFindElementTimeout(options);
3178
+ try {
3179
+ await _preCommand(state, this);
3180
+ const start = Date.now();
3181
+ let cellArea = null;
3182
+ while (true) {
3183
+ try {
3184
+ cellArea = await _findCellArea(headerText, rowText, this, state);
3185
+ if (cellArea) {
3186
+ break;
3187
+ }
3188
+ }
3189
+ catch (e) {
3190
+ // ignore
3191
+ }
3192
+ if (Date.now() - start > timeout) {
3193
+ throw new Error(`Cell not found in table`);
3194
+ }
3195
+ await new Promise((resolve) => setTimeout(resolve, 1000));
3196
+ }
3197
+ switch (operation) {
3198
+ case "click":
3199
+ if (!options.css) {
3200
+ // will click in the center of the cell
3201
+ let xOffset = 0;
3202
+ let yOffset = 0;
3203
+ if (options.xOffset) {
3204
+ xOffset = options.xOffset;
3205
+ }
3206
+ if (options.yOffset) {
3207
+ yOffset = options.yOffset;
3208
+ }
3209
+ await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
3210
+ }
3211
+ else {
3212
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3213
+ if (results.length === 0) {
3214
+ throw new Error(`Element not found in cell area`);
3215
+ }
3216
+ state.element = results[0];
3217
+ await performAction("click", state.element, options, this, state, _params);
3218
+ }
3219
+ break;
3220
+ case "hover+click":
3221
+ if (!options.css) {
3222
+ throw new Error("css is not defined");
3223
+ }
3224
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3225
+ if (results.length === 0) {
3226
+ throw new Error(`Element not found in cell area`);
3227
+ }
3228
+ state.element = results[0];
3229
+ await performAction("hover+click", state.element, options, this, state, _params);
3230
+ break;
3231
+ default:
3232
+ throw new Error("operation is not supported");
3233
+ }
3234
+ }
3235
+ catch (e) {
3236
+ await _commandError(state, e, this);
3237
+ }
3238
+ finally {
3239
+ await _commandFinally(state, this);
2865
3240
  }
2866
3241
  }
2867
3242
  saveTestDataAsGlobal(options, world) {
2868
- const dataFile = this._getDataFile(world);
3243
+ const dataFile = _getDataFile(world, this.context, this);
2869
3244
  process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2870
3245
  this.logger.info("Save the scenario test data as global for the following scenarios.");
2871
3246
  }
@@ -2895,6 +3270,7 @@ class StableBrowser {
2895
3270
  _reportToWorld(world, {
2896
3271
  type: Types.SET_VIEWPORT,
2897
3272
  text: "set viewport size to " + width + "x" + hight,
3273
+ _text: "Set the viewport size to " + width + "x" + hight,
2898
3274
  screenshotId,
2899
3275
  result: error
2900
3276
  ? {
@@ -2965,7 +3341,39 @@ class StableBrowser {
2965
3341
  console.log("#-#");
2966
3342
  }
2967
3343
  }
3344
+ async beforeScenario(world, scenario) {
3345
+ this.beforeScenarioCalled = true;
3346
+ if (scenario && scenario.pickle && scenario.pickle.name) {
3347
+ this.scenarioName = scenario.pickle.name;
3348
+ }
3349
+ if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
3350
+ this.featureName = scenario.gherkinDocument.feature.name;
3351
+ }
3352
+ if (this.context) {
3353
+ this.context.examplesRow = extractStepExampleParameters(scenario);
3354
+ }
3355
+ if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
3356
+ this.tags = scenario.pickle.tags.map((tag) => tag.name);
3357
+ // check if @global_test_data tag is present
3358
+ if (this.tags.includes("@global_test_data")) {
3359
+ this.saveTestDataAsGlobal({}, world);
3360
+ }
3361
+ }
3362
+ // update test data based on feature/scenario
3363
+ let envName = null;
3364
+ if (this.context && this.context.environment) {
3365
+ envName = this.context.environment.name;
3366
+ }
3367
+ if (!process.env.TEMP_RUN) {
3368
+ await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
3369
+ }
3370
+ await loadBrunoParams(this.context, this.context.environment.name);
3371
+ }
3372
+ async afterScenario(world, scenario) { }
2968
3373
  async beforeStep(world, step) {
3374
+ if (!this.beforeScenarioCalled) {
3375
+ this.beforeScenario(world, step);
3376
+ }
2969
3377
  if (this.stepIndex === undefined) {
2970
3378
  this.stepIndex = 0;
2971
3379
  }
@@ -2982,22 +3390,48 @@ class StableBrowser {
2982
3390
  else {
2983
3391
  this.stepName = "step " + this.stepIndex;
2984
3392
  }
2985
- if (this.context) {
2986
- this.context.examplesRow = extractStepExampleParameters(step);
2987
- }
2988
3393
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2989
3394
  if (this.context.browserObject.context) {
2990
3395
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2991
3396
  }
2992
3397
  }
2993
- if (this.tags === null && step && step.pickle && step.pickle.tags) {
2994
- this.tags = step.pickle.tags.map((tag) => tag.name);
2995
- // check if @global_test_data tag is present
2996
- if (this.tags.includes("@global_test_data")) {
2997
- this.saveTestDataAsGlobal({}, world);
3398
+ if (this.initSnapshotTaken === false) {
3399
+ this.initSnapshotTaken = true;
3400
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3401
+ const snapshot = await this.getAriaSnapshot();
3402
+ if (snapshot) {
3403
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
3404
+ }
2998
3405
  }
2999
3406
  }
3000
3407
  }
3408
+ async getAriaSnapshot() {
3409
+ try {
3410
+ // find the page url
3411
+ const url = await this.page.url();
3412
+ // extract the path from the url
3413
+ const path = new URL(url).pathname;
3414
+ // get the page title
3415
+ const title = await this.page.title();
3416
+ // go over other frams
3417
+ const frames = this.page.frames();
3418
+ const snapshots = [];
3419
+ const content = [`- path: ${path}`, `- title: ${title}`];
3420
+ const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
3421
+ for (let i = 0; i < frames.length; i++) {
3422
+ content.push(`- frame: ${i}`);
3423
+ const frame = frames[i];
3424
+ const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
3425
+ content.push(snapshot);
3426
+ }
3427
+ return content.join("\n");
3428
+ }
3429
+ catch (e) {
3430
+ console.log("Error in getAriaSnapshot");
3431
+ console.debug(e);
3432
+ }
3433
+ return null;
3434
+ }
3001
3435
  async afterStep(world, step) {
3002
3436
  this.stepName = null;
3003
3437
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
@@ -3005,11 +3439,25 @@ class StableBrowser {
3005
3439
  await this.context.browserObject.context.tracing.stopChunk({
3006
3440
  path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
3007
3441
  });
3442
+ if (world && world.attach) {
3443
+ await world.attach(JSON.stringify({
3444
+ type: "trace",
3445
+ traceFilePath: `trace-${this.stepIndex}.zip`,
3446
+ }), "application/json+trace");
3447
+ }
3448
+ // console.log("trace file created", `trace-${this.stepIndex}.zip`);
3008
3449
  }
3009
3450
  }
3010
3451
  if (this.context) {
3011
3452
  this.context.examplesRow = null;
3012
3453
  }
3454
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3455
+ const snapshot = await this.getAriaSnapshot();
3456
+ if (snapshot) {
3457
+ const obj = {};
3458
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
3459
+ }
3460
+ }
3013
3461
  }
3014
3462
  }
3015
3463
  function createTimedPromise(promise, label) {