automation_model 1.0.602-dev → 1.0.602-stage

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