automation_model 1.0.373-dev.0 → 1.0.373-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.
@@ -12,6 +12,8 @@ import drawRectangle from "./drawRect.js";
12
12
  import { getTableCells, getTableData } from "./table_analyze.js";
13
13
  import objectPath from "object-path";
14
14
  import { decrypt } from "./utils.js";
15
+ import csv from "csv-parser";
16
+ import { Readable } from "node:stream";
15
17
  const Types = {
16
18
  CLICK: "click_element",
17
19
  NAVIGATE: "navigate",
@@ -27,6 +29,7 @@ const Types = {
27
29
  SELECT: "select_combobox",
28
30
  VERIFY_PAGE_PATH: "verify_page_path",
29
31
  TYPE_PRESS: "type_press",
32
+ PRESS: "press_key",
30
33
  HOVER: "hover_element",
31
34
  CHECK: "check_element",
32
35
  UNCHECK: "uncheck_element",
@@ -35,6 +38,8 @@ const Types = {
35
38
  SET_DATE_TIME: "set_date_time",
36
39
  SET_VIEWPORT: "set_viewport",
37
40
  VERIFY_VISUAL: "verify_visual",
41
+ LOAD_DATA: "load_data",
42
+ SET_INPUT: "set_input",
38
43
  };
39
44
  class StableBrowser {
40
45
  constructor(browser, page, logger = null, context = null) {
@@ -80,9 +85,15 @@ class StableBrowser {
80
85
  this.page = page;
81
86
  context.page = page;
82
87
  context.pages.push(page);
83
- this.webLogFile = this.getWebLogFile(logFolder);
84
- this.registerConsoleLogListener(page, context, this.webLogFile);
85
- this.registerRequestListener();
88
+ page.on("close", async () => {
89
+ if (this.context && this.context.pages && this.context.pages.length > 0) {
90
+ this.context.pages.pop();
91
+ this.page = this.context.pages[this.context.pages.length - 1];
92
+ this.context.page = this.page;
93
+ let title = await this.page.title();
94
+ console.log("Switched to page " + title);
95
+ }
96
+ });
86
97
  try {
87
98
  await this.waitForPageLoad();
88
99
  console.log("Switch page: " + (await page.title()));
@@ -91,7 +102,7 @@ class StableBrowser {
91
102
  this.logger.error("error on page load " + e);
92
103
  }
93
104
  context.pageLoading.status = false;
94
- });
105
+ }.bind(this));
95
106
  }
96
107
  getWebLogFile(logFolder) {
97
108
  if (!fs.existsSync(logFolder)) {
@@ -177,21 +188,78 @@ class StableBrowser {
177
188
  }
178
189
  return text;
179
190
  }
191
+ _fixLocatorUsingParams(locator, _params) {
192
+ // check if not null
193
+ if (!locator) {
194
+ return locator;
195
+ }
196
+ // clone the locator
197
+ locator = JSON.parse(JSON.stringify(locator));
198
+ this.scanAndManipulate(locator, _params);
199
+ return locator;
200
+ }
201
+ _isObject(value) {
202
+ return value && typeof value === "object" && value.constructor === Object;
203
+ }
204
+ scanAndManipulate(currentObj, _params) {
205
+ for (const key in currentObj) {
206
+ if (typeof currentObj[key] === "string") {
207
+ // Perform string manipulation
208
+ currentObj[key] = this._fixUsingParams(currentObj[key], _params);
209
+ }
210
+ else if (this._isObject(currentObj[key])) {
211
+ // Recursively scan nested objects
212
+ this.scanAndManipulate(currentObj[key], _params);
213
+ }
214
+ }
215
+ }
180
216
  _getLocator(locator, scope, _params) {
217
+ locator = this._fixLocatorUsingParams(locator, _params);
218
+ let locatorReturn;
181
219
  if (locator.role) {
182
220
  if (locator.role[1].nameReg) {
183
221
  locator.role[1].name = reg_parser(locator.role[1].nameReg);
184
222
  delete locator.role[1].nameReg;
185
223
  }
186
- if (locator.role[1].name) {
187
- locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
188
- }
189
- return scope.getByRole(locator.role[0], locator.role[1]);
224
+ // if (locator.role[1].name) {
225
+ // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
226
+ // }
227
+ locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
190
228
  }
191
229
  if (locator.css) {
192
- return scope.locator(this._fixUsingParams(locator.css, _params));
230
+ locatorReturn = scope.locator(locator.css);
231
+ }
232
+ // handle role/name locators
233
+ // locator.selector will be something like: textbox[name="Username"i]
234
+ if (locator.engine === "internal:role") {
235
+ // extract the role, name and the i/s flags using regex
236
+ const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
237
+ if (match) {
238
+ const role = match[1];
239
+ const name = match[3];
240
+ const flags = match[4];
241
+ locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
242
+ }
243
+ }
244
+ if (locator === null || locator === void 0 ? void 0 : locator.engine) {
245
+ if (locator.engine === "css") {
246
+ locatorReturn = scope.locator(locator.selector);
247
+ }
248
+ else {
249
+ let selector = locator.selector;
250
+ if (locator.engine === "internal:attr") {
251
+ if (!selector.startsWith("[")) {
252
+ selector = `[${selector}]`;
253
+ }
254
+ }
255
+ locatorReturn = scope.locator(`${locator.engine}=${selector}`);
256
+ }
193
257
  }
194
- throw new Error("unknown locator type");
258
+ if (!locatorReturn) {
259
+ console.error(locator);
260
+ throw new Error("Locator undefined");
261
+ }
262
+ return locatorReturn;
195
263
  }
196
264
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
197
265
  let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
@@ -314,10 +382,13 @@ class StableBrowser {
314
382
  }
315
383
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
316
384
  let locatorSearch = selectorHierarchy[index];
317
- info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
385
+ //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
318
386
  let locator = null;
319
387
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
320
388
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
389
+ if (!locatorString) {
390
+ return;
391
+ }
321
392
  locator = this._getLocator({ css: locatorString }, scope, _params);
322
393
  }
323
394
  else if (locatorSearch.text) {
@@ -339,7 +410,7 @@ class StableBrowser {
339
410
  // cssHref = true;
340
411
  // }
341
412
  let count = await locator.count();
342
- info.log += "total elements found " + count + "\n";
413
+ //info.log += "total elements found " + count + "\n";
343
414
  //let visibleCount = 0;
344
415
  let visibleLocator = null;
345
416
  if (locatorSearch.index && locatorSearch.index < count) {
@@ -349,16 +420,20 @@ class StableBrowser {
349
420
  for (let j = 0; j < count; j++) {
350
421
  let visible = await locator.nth(j).isVisible();
351
422
  const enabled = await locator.nth(j).isEnabled();
352
- info.log += "element " + j + " visible " + visible + " enabled " + enabled + "\n";
353
423
  if (!visibleOnly) {
354
424
  visible = true;
355
425
  }
356
426
  if (visible && enabled) {
357
427
  foundLocators.push(locator.nth(j));
358
- // if (cssHref) {
359
- // info.log += "css href locator found, will ignore all others" + "\n";
360
- // break;
361
- // }
428
+ }
429
+ else {
430
+ if (!info.printMessages) {
431
+ info.printMessages = {};
432
+ }
433
+ if (!info.printMessages[j.toString()]) {
434
+ info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
435
+ info.printMessages[j.toString()] = true;
436
+ }
362
437
  }
363
438
  }
364
439
  }
@@ -404,6 +479,10 @@ class StableBrowser {
404
479
  async _locate(selectors, info, _params, timeout = 30000) {
405
480
  for (let i = 0; i < 3; i++) {
406
481
  info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
482
+ for (let j = 0; j < selectors.locators.length; j++) {
483
+ let selector = selectors.locators[j];
484
+ info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
485
+ }
407
486
  let element = await this._locate_internal(selectors, info, _params, timeout);
408
487
  if (!element.rerun) {
409
488
  return element;
@@ -498,6 +577,7 @@ class StableBrowser {
498
577
  let foundElements = result.foundElements;
499
578
  if (foundElements.length === 1 && foundElements[0].unique) {
500
579
  info.box = foundElements[0].box;
580
+ info.log += "unique element was found, locator: " + foundElements[0].locator + "\n";
501
581
  return foundElements[0].locator;
502
582
  }
503
583
  //info.log += "total elements found " + foundElements.length);
@@ -527,6 +607,7 @@ class StableBrowser {
527
607
  }
528
608
  }
529
609
  if (maxCountElement) {
610
+ info.log += "unique element was found, locator: " + maxCountElement.locator + "\n";
530
611
  info.box = await maxCountElement.locator.boundingBox();
531
612
  return maxCountElement.locator;
532
613
  }
@@ -535,9 +616,11 @@ class StableBrowser {
535
616
  break;
536
617
  }
537
618
  if (performance.now() - startTime > highPriorityTimeout) {
619
+ info.log += "high priority timeout, will try all elements" + "\n";
538
620
  highPriorityOnly = false;
539
621
  }
540
622
  if (performance.now() - startTime > visibleOnlyTimeout) {
623
+ info.log += "visible only timeout, will try all elements" + "\n";
541
624
  visibleOnly = false;
542
625
  }
543
626
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -581,6 +664,9 @@ class StableBrowser {
581
664
  async click(selectors, _params, options = {}, world = null) {
582
665
  this._validateSelectors(selectors);
583
666
  const startTime = Date.now();
667
+ if (options && options.context) {
668
+ selectors.locators[0].text = options.context;
669
+ }
584
670
  const info = {};
585
671
  info.log = "***** click on " + selectors.element_name + " *****\n";
586
672
  info.operation = "click";
@@ -889,71 +975,45 @@ class StableBrowser {
889
975
  });
890
976
  }
891
977
  }
892
- async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
978
+ async setInputValue(selectors, value, _params = null, options = {}, world = null) {
979
+ // set input value for non fillable inputs like date, time, range, color, etc.
893
980
  this._validateSelectors(selectors);
894
981
  const startTime = Date.now();
895
- let error = null;
896
- let screenshotId = null;
897
- let screenshotPath = null;
898
982
  const info = {};
899
- info.log = "";
900
- info.operation = Types.SET_DATE_TIME;
983
+ info.log = "***** set input value " + selectors.element_name + " *****\n";
984
+ info.operation = "setInputValue";
901
985
  info.selectors = selectors;
986
+ value = this._fixUsingParams(value, _params);
902
987
  info.value = value;
988
+ let error = null;
989
+ let screenshotId = null;
990
+ let screenshotPath = null;
903
991
  try {
904
992
  value = await this._replaceWithLocalData(value, this);
905
993
  let element = await this._locate(selectors, info, _params);
906
- //insert red border around the element
907
994
  await this.scrollIfNeeded(element, info);
908
995
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
909
996
  await this._highlightElements(element);
910
997
  try {
911
- await element.click();
912
- await new Promise((resolve) => setTimeout(resolve, 500));
913
- if (format) {
914
- value = dayjs(value).format(format);
915
- await element.fill(value);
916
- }
917
- else {
918
- const dateTimeValue = await getDateTimeValue({ value, element });
919
- await element.evaluateHandle((el, dateTimeValue) => {
920
- el.value = ""; // clear input
921
- el.value = dateTimeValue;
922
- }, dateTimeValue);
923
- }
924
- if (enter) {
925
- await new Promise((resolve) => setTimeout(resolve, 2000));
926
- await this.page.keyboard.press("Enter");
927
- await this.waitForPageLoad();
928
- }
998
+ await element.evaluateHandle((el, value) => {
999
+ el.value = value;
1000
+ }, value);
929
1001
  }
930
1002
  catch (error) {
931
- //await this.closeUnexpectedPopups();
932
- this.logger.error("setting date time input failed " + JSON.stringify(info));
933
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1003
+ this.logger.error("setInputValue failed, will try again");
1004
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
934
1005
  info.screenshotPath = screenshotPath;
935
1006
  Object.assign(error, { info: info });
936
- await element.click();
937
- await new Promise((resolve) => setTimeout(resolve, 500));
938
- if (format) {
939
- value = dayjs(value).format(format);
940
- await element.fill(value);
941
- }
942
- else {
943
- const dateTimeValue = await getDateTimeValue({ value, element });
944
- await element.evaluateHandle((el, dateTimeValue) => {
945
- el.value = ""; // clear input
946
- el.value = dateTimeValue;
947
- }, dateTimeValue);
948
- }
949
- if (enter) {
950
- await new Promise((resolve) => setTimeout(resolve, 2000));
951
- await this.page.keyboard.press("Enter");
952
- await this.waitForPageLoad();
953
- }
1007
+ await element.evaluateHandle((el, value) => {
1008
+ el.value = value;
1009
+ });
954
1010
  }
955
1011
  }
956
- catch (error) {
1012
+ catch (e) {
1013
+ this.logger.error("setInputValue failed " + info.log);
1014
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1015
+ info.screenshotPath = screenshotPath;
1016
+ Object.assign(e, { info: info });
957
1017
  error = e;
958
1018
  throw e;
959
1019
  }
@@ -961,10 +1021,10 @@ class StableBrowser {
961
1021
  const endTime = Date.now();
962
1022
  this._reportToWorld(world, {
963
1023
  element_name: selectors.element_name,
964
- type: Types.SET_DATE_TIME,
965
- screenshotId,
1024
+ type: Types.SET_INPUT,
1025
+ text: `Set input value`,
966
1026
  value: value,
967
- text: `setDateTime input with value: ${value}`,
1027
+ screenshotId,
968
1028
  result: error
969
1029
  ? {
970
1030
  status: "FAILED",
@@ -981,7 +1041,7 @@ class StableBrowser {
981
1041
  });
982
1042
  }
983
1043
  }
984
- async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
1044
+ async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
985
1045
  this._validateSelectors(selectors);
986
1046
  const startTime = Date.now();
987
1047
  let error = null;
@@ -993,6 +1053,7 @@ class StableBrowser {
993
1053
  info.selectors = selectors;
994
1054
  info.value = value;
995
1055
  try {
1056
+ value = await this._replaceWithLocalData(value, this);
996
1057
  let element = await this._locate(selectors, info, _params);
997
1058
  //insert red border around the element
998
1059
  await this.scrollIfNeeded(element, info);
@@ -1001,28 +1062,51 @@ class StableBrowser {
1001
1062
  try {
1002
1063
  await element.click();
1003
1064
  await new Promise((resolve) => setTimeout(resolve, 500));
1004
- const dateTimeValue = await getDateTimeValue({ value, element });
1005
- await element.evaluateHandle((el, dateTimeValue) => {
1006
- el.value = ""; // clear input
1007
- el.value = dateTimeValue;
1008
- }, dateTimeValue);
1065
+ if (format) {
1066
+ value = dayjs(value).format(format);
1067
+ await element.fill(value);
1068
+ }
1069
+ else {
1070
+ const dateTimeValue = await getDateTimeValue({ value, element });
1071
+ await element.evaluateHandle((el, dateTimeValue) => {
1072
+ el.value = ""; // clear input
1073
+ el.value = dateTimeValue;
1074
+ }, dateTimeValue);
1075
+ }
1076
+ if (enter) {
1077
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1078
+ await this.page.keyboard.press("Enter");
1079
+ await this.waitForPageLoad();
1080
+ }
1009
1081
  }
1010
- catch (error) {
1082
+ catch (err) {
1011
1083
  //await this.closeUnexpectedPopups();
1012
1084
  this.logger.error("setting date time input failed " + JSON.stringify(info));
1013
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1085
+ this.logger.info("Trying again");
1086
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1014
1087
  info.screenshotPath = screenshotPath;
1015
- Object.assign(error, { info: info });
1088
+ Object.assign(err, { info: info });
1016
1089
  await element.click();
1017
1090
  await new Promise((resolve) => setTimeout(resolve, 500));
1018
- const dateTimeValue = await getDateTimeValue({ value, element });
1019
- await element.evaluateHandle((el, dateTimeValue) => {
1020
- el.value = ""; // clear input
1021
- el.value = dateTimeValue;
1022
- }, dateTimeValue);
1091
+ if (format) {
1092
+ value = dayjs(value).format(format);
1093
+ await element.fill(value);
1094
+ }
1095
+ else {
1096
+ const dateTimeValue = await getDateTimeValue({ value, element });
1097
+ await element.evaluateHandle((el, dateTimeValue) => {
1098
+ el.value = ""; // clear input
1099
+ el.value = dateTimeValue;
1100
+ }, dateTimeValue);
1101
+ }
1102
+ if (enter) {
1103
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1104
+ await this.page.keyboard.press("Enter");
1105
+ await this.waitForPageLoad();
1106
+ }
1023
1107
  }
1024
1108
  }
1025
- catch (error) {
1109
+ catch (e) {
1026
1110
  error = e;
1027
1111
  throw e;
1028
1112
  }
@@ -1072,20 +1156,32 @@ class StableBrowser {
1072
1156
  await this.scrollIfNeeded(element, info);
1073
1157
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1074
1158
  await this._highlightElements(element);
1075
- try {
1076
- let currentValue = await element.inputValue();
1077
- if (currentValue) {
1078
- await element.fill("");
1159
+ if (options === null || options === undefined || !options.press) {
1160
+ try {
1161
+ let currentValue = await element.inputValue();
1162
+ if (currentValue) {
1163
+ await element.fill("");
1164
+ }
1165
+ }
1166
+ catch (e) {
1167
+ this.logger.info("unable to clear input value");
1079
1168
  }
1080
1169
  }
1081
- catch (e) {
1082
- this.logger.info("unable to clear input value");
1083
- }
1084
- try {
1085
- await element.click({ timeout: 5000 });
1170
+ if (options === null || options === undefined || options.press) {
1171
+ try {
1172
+ await element.click({ timeout: 5000 });
1173
+ }
1174
+ catch (e) {
1175
+ await element.dispatchEvent("click");
1176
+ }
1086
1177
  }
1087
- catch (e) {
1088
- await element.dispatchEvent("click");
1178
+ else {
1179
+ try {
1180
+ await element.focus();
1181
+ }
1182
+ catch (e) {
1183
+ await element.dispatchEvent("focus");
1184
+ }
1089
1185
  }
1090
1186
  await new Promise((resolve) => setTimeout(resolve, 500));
1091
1187
  const valueSegment = _value.split("&&");
@@ -1281,7 +1377,8 @@ class StableBrowser {
1281
1377
  let screenshotId = null;
1282
1378
  let screenshotPath = null;
1283
1379
  const info = {};
1284
- info.log = "***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
1380
+ info.log =
1381
+ "***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
1285
1382
  info.operation = "containsPattern";
1286
1383
  info.selectors = selectors;
1287
1384
  info.value = text;
@@ -1337,7 +1434,6 @@ class StableBrowser {
1337
1434
  }
1338
1435
  }
1339
1436
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1340
- var _a, _b, _c;
1341
1437
  this._validateSelectors(selectors);
1342
1438
  if (!text) {
1343
1439
  throw new Error("text is null");
@@ -1358,46 +1454,48 @@ class StableBrowser {
1358
1454
  info.value = text;
1359
1455
  let foundObj = null;
1360
1456
  try {
1361
- foundObj = await this._getText(selectors, climb, _params, options, info, world);
1362
- if (foundObj && foundObj.element) {
1363
- await this.scrollIfNeeded(foundObj.element, info);
1364
- }
1365
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1366
- const dateAlternatives = findDateAlternatives(text);
1367
- const numberAlternatives = findNumberAlternatives(text);
1368
- if (dateAlternatives.date) {
1369
- for (let i = 0; i < dateAlternatives.dates.length; i++) {
1370
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(dateAlternatives.dates[i])) ||
1371
- ((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(dateAlternatives.dates[i]))) {
1372
- return info;
1373
- }
1374
- }
1375
- throw new Error("element doesn't contain text " + text);
1376
- }
1377
- else if (numberAlternatives.number) {
1378
- for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1379
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(numberAlternatives.numbers[i])) ||
1380
- ((_b = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _b === void 0 ? void 0 : _b.includes(numberAlternatives.numbers[i]))) {
1381
- return info;
1382
- }
1383
- }
1384
- throw new Error("element doesn't contain text " + text);
1385
- }
1386
- else if (!(foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(text)) && !((_c = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _c === void 0 ? void 0 : _c.includes(text))) {
1387
- info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1388
- info.value = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value;
1389
- throw new Error("element doesn't contain text " + text);
1390
- }
1391
- return info;
1457
+ // foundObj = await this._getText(selectors, climb, _params, options, info, world);
1458
+ // if (foundObj && foundObj.element) {
1459
+ // await this.scrollIfNeeded(foundObj.element, info);
1460
+ // }
1461
+ // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1462
+ // const dateAlternatives = findDateAlternatives(text);
1463
+ // const numberAlternatives = findNumberAlternatives(text);
1464
+ // if (dateAlternatives.date) {
1465
+ // for (let i = 0; i < dateAlternatives.dates.length; i++) {
1466
+ // if (
1467
+ // foundObj?.text.includes(dateAlternatives.dates[i]) ||
1468
+ // foundObj?.value?.includes(dateAlternatives.dates[i])
1469
+ // ) {
1470
+ // return info;
1471
+ // }
1472
+ // }
1473
+ // throw new Error("element doesn't contain text " + text);
1474
+ // } else if (numberAlternatives.number) {
1475
+ // for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1476
+ // if (
1477
+ // foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1478
+ // foundObj?.value?.includes(numberAlternatives.numbers[i])
1479
+ // ) {
1480
+ // return info;
1481
+ // }
1482
+ // }
1483
+ // throw new Error("element doesn't contain text " + text);
1484
+ // } else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1485
+ // info.foundText = foundObj?.text;
1486
+ // info.value = foundObj?.value;
1487
+ // throw new Error("element doesn't contain text " + text);
1488
+ // }
1489
+ // return info;
1392
1490
  }
1393
1491
  catch (e) {
1394
1492
  //await this.closeUnexpectedPopups();
1395
- this.logger.error("verify element contains text failed " + info.log);
1396
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1397
- info.screenshotPath = screenshotPath;
1398
- Object.assign(e, { info: info });
1399
- error = e;
1400
- throw e;
1493
+ // this.logger.error("verify element contains text failed " + info.log);
1494
+ // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1495
+ // info.screenshotPath = screenshotPath;
1496
+ // Object.assign(e, { info: info });
1497
+ // error = e;
1498
+ // throw e;
1401
1499
  }
1402
1500
  finally {
1403
1501
  const endTime = Date.now();
@@ -1451,15 +1549,62 @@ class StableBrowser {
1451
1549
  // save the data to the file
1452
1550
  fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
1453
1551
  }
1552
+ _getDataFilePath(fileName) {
1553
+ let dataFile = path.join(this.project_path, "data", fileName);
1554
+ if (fs.existsSync(dataFile)) {
1555
+ return dataFile;
1556
+ }
1557
+ dataFile = path.join(this.project_path, fileName);
1558
+ if (fs.existsSync(dataFile)) {
1559
+ return dataFile;
1560
+ }
1561
+ throw new Error("data file not found " + fileName);
1562
+ }
1563
+ _parseCSVSync(filePath) {
1564
+ const data = fs.readFileSync(filePath, "utf8");
1565
+ const results = [];
1566
+ return new Promise((resolve, reject) => {
1567
+ const readableStream = new Readable();
1568
+ readableStream._read = () => { }; // _read is required but you can noop it
1569
+ readableStream.push(data);
1570
+ readableStream.push(null);
1571
+ readableStream
1572
+ .pipe(csv())
1573
+ .on("data", (data) => results.push(data))
1574
+ .on("end", () => resolve(results))
1575
+ .on("error", (error) => reject(error));
1576
+ });
1577
+ }
1454
1578
  loadTestData(type, dataSelector, world = null) {
1455
1579
  switch (type) {
1456
1580
  case "users":
1457
- // check if file users.json exists
1458
- if (!fs.existsSync(path.join(this.project_path, "users.json"))) {
1459
- throw new Error("users.json file not found");
1581
+ // get the users.json file path
1582
+ let dataFile = this._getDataFilePath("users.json");
1583
+ // read the file and return the data
1584
+ const users = JSON.parse(fs.readFileSync(dataFile, "utf8"));
1585
+ for (let i = 0; i < users.length; i++) {
1586
+ if (users[i].username === dataSelector) {
1587
+ const userObj = {
1588
+ username: users[i].username,
1589
+ password: "secret:" + users[i].password,
1590
+ totp: users[i].secretKey ? "totp:" + users[i].secretKey : null,
1591
+ };
1592
+ this.setTestData(userObj, world);
1593
+ return userObj;
1594
+ }
1460
1595
  }
1596
+ throw new Error("user not found " + dataSelector);
1597
+ default:
1598
+ throw new Error("unknown type " + type);
1599
+ }
1600
+ }
1601
+ async loadTestDataAsync(type, dataSelector, world = null) {
1602
+ switch (type) {
1603
+ case "users": {
1604
+ // get the users.json file path
1605
+ let dataFile = this._getDataFilePath("users.json");
1461
1606
  // read the file and return the data
1462
- const users = JSON.parse(fs.readFileSync(path.join(this.project_path, "users.json"), "utf8"));
1607
+ const users = JSON.parse(fs.readFileSync(dataFile, "utf8"));
1463
1608
  for (let i = 0; i < users.length; i++) {
1464
1609
  if (users[i].username === dataSelector) {
1465
1610
  const userObj = {
@@ -1472,6 +1617,29 @@ class StableBrowser {
1472
1617
  }
1473
1618
  }
1474
1619
  throw new Error("user not found " + dataSelector);
1620
+ }
1621
+ case "csv": {
1622
+ // the dataSelector should start with the file name followed by the row number: data.csv:1, if no row number is provided, it will default to 1
1623
+ const parts = dataSelector.split(":");
1624
+ let rowNumber = 0;
1625
+ if (parts.length > 1) {
1626
+ rowNumber = parseInt(parts[1]);
1627
+ }
1628
+ let dataFile = this._getDataFilePath(parts[0]);
1629
+ const results = await this._parseCSVSync(dataFile);
1630
+ // result stracture:
1631
+ // [
1632
+ // { NAME: 'Daffy Duck', AGE: '24' },
1633
+ // { NAME: 'Bugs Bunny', AGE: '22' }
1634
+ // ]
1635
+ // verify the row number is within the range
1636
+ if (rowNumber >= results.length) {
1637
+ throw new Error("row number is out of range " + rowNumber);
1638
+ }
1639
+ const data = results[rowNumber];
1640
+ this.setTestData(data, world);
1641
+ return data;
1642
+ }
1475
1643
  default:
1476
1644
  throw new Error("unknown type " + type);
1477
1645
  }
@@ -1576,13 +1744,13 @@ class StableBrowser {
1576
1744
  ])));
1577
1745
  const { data } = await client.send("Page.captureScreenshot", {
1578
1746
  format: "png",
1579
- clip: {
1580
- x: 0,
1581
- y: 0,
1582
- width: viewportWidth,
1583
- height: viewportHeight,
1584
- scale: 1,
1585
- },
1747
+ // clip: {
1748
+ // x: 0,
1749
+ // y: 0,
1750
+ // width: viewportWidth,
1751
+ // height: viewportHeight,
1752
+ // scale: 1,
1753
+ // },
1586
1754
  });
1587
1755
  if (!screenshotPath) {
1588
1756
  return data;
@@ -1688,7 +1856,8 @@ class StableBrowser {
1688
1856
  if (world) {
1689
1857
  world[variable] = info.value;
1690
1858
  }
1691
- this.logger.info("world." + variable + "=" + info.value);
1859
+ this.setTestData({ [variable]: info.value }, world);
1860
+ this.logger.info("set test data: " + variable + "=" + info.value);
1692
1861
  return info;
1693
1862
  }
1694
1863
  catch (e) {
@@ -1725,6 +1894,91 @@ class StableBrowser {
1725
1894
  });
1726
1895
  }
1727
1896
  }
1897
+ async extractEmailData(emailAddress, options, world) {
1898
+ if (!emailAddress) {
1899
+ throw new Error("email address is null");
1900
+ }
1901
+ // check if address contain @
1902
+ if (emailAddress.indexOf("@") === -1) {
1903
+ emailAddress = emailAddress + "@blinq-mail.io";
1904
+ }
1905
+ else {
1906
+ if (!emailAddress.toLowerCase().endsWith("@blinq-mail.io")) {
1907
+ throw new Error("email address should end with @blinq-mail.io");
1908
+ }
1909
+ }
1910
+ const startTime = Date.now();
1911
+ let timeout = 60000;
1912
+ if (options && options.timeout) {
1913
+ timeout = options.timeout;
1914
+ }
1915
+ const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1916
+ const request = {
1917
+ method: "POST",
1918
+ url: serviceUrl,
1919
+ headers: {
1920
+ "Content-Type": "application/json",
1921
+ Authorization: `Bearer ${process.env.TOKEN}`,
1922
+ },
1923
+ data: JSON.stringify({
1924
+ email: emailAddress,
1925
+ }),
1926
+ };
1927
+ let errorCount = 0;
1928
+ while (true) {
1929
+ try {
1930
+ let result = await this.context.api.request(request);
1931
+ // the response body expected to be the following:
1932
+ // {
1933
+ // "status": true,
1934
+ // "content": {
1935
+ // "url": "",
1936
+ // "code": "112112",
1937
+ // "name": "generate_link_or_code"
1938
+ // }
1939
+ //}
1940
+ if ((result && result.data, result.data.status === true)) {
1941
+ let codeOrUrlFound = false;
1942
+ let emailCode = null;
1943
+ let emailUrl = null;
1944
+ // check if a code is returned
1945
+ if (result.data.content && result.data.content.code) {
1946
+ let code = result.data.content.code;
1947
+ this.setTestData({ emailCode: code }, world);
1948
+ this.logger.info("set test data: emailCode = " + code);
1949
+ emailCode = code;
1950
+ codeOrUrlFound = true;
1951
+ }
1952
+ // check if a url is returned
1953
+ if (result.data.content && result.data.content.url) {
1954
+ let url = result.data.content.url;
1955
+ this.setTestData({ emailUrl: url }, world);
1956
+ this.logger.info("set test data: emailUrl = " + url);
1957
+ emailUrl = url;
1958
+ codeOrUrlFound = true;
1959
+ }
1960
+ if (codeOrUrlFound) {
1961
+ return { emailUrl, emailCode };
1962
+ }
1963
+ else {
1964
+ this.logger.info("an email received but no code or url found");
1965
+ }
1966
+ }
1967
+ }
1968
+ catch (e) {
1969
+ errorCount++;
1970
+ if (errorCount > 3) {
1971
+ throw e;
1972
+ }
1973
+ // ignore
1974
+ }
1975
+ // check if the timeout is reached
1976
+ if (Date.now() - startTime > timeout) {
1977
+ throw new Error("timeout reached");
1978
+ }
1979
+ await new Promise((resolve) => setTimeout(resolve, 5000));
1980
+ }
1981
+ }
1728
1982
  async _highlightElements(scope, css) {
1729
1983
  try {
1730
1984
  if (!scope) {
@@ -1906,8 +2160,10 @@ class StableBrowser {
1906
2160
  const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
1907
2161
  await this._highlightElements(frame, dataAttribute);
1908
2162
  const element = await frame.$(dataAttribute);
1909
- await this.scrollIfNeeded(element, info);
1910
- await element.dispatchEvent("bvt_verify_page_contains_text");
2163
+ if (element) {
2164
+ await this.scrollIfNeeded(element, info);
2165
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2166
+ }
1911
2167
  }
1912
2168
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1913
2169
  return info;
@@ -1945,6 +2201,16 @@ class StableBrowser {
1945
2201
  });
1946
2202
  }
1947
2203
  }
2204
+ _getServerUrl() {
2205
+ let serviceUrl = "https://api.blinq.io";
2206
+ if (process.env.NODE_ENV_BLINQ === "dev") {
2207
+ serviceUrl = "https://dev.api.blinq.io";
2208
+ }
2209
+ else if (process.env.NODE_ENV_BLINQ === "stage") {
2210
+ serviceUrl = "https://stage.api.blinq.io";
2211
+ }
2212
+ return serviceUrl;
2213
+ }
1948
2214
  async visualVerification(text, options = {}, world = null) {
1949
2215
  const startTime = Date.now();
1950
2216
  let error = null;
@@ -1959,13 +2225,7 @@ class StableBrowser {
1959
2225
  throw new Error("TOKEN is not set");
1960
2226
  }
1961
2227
  try {
1962
- let serviceUrl = "https://api.blinq.io";
1963
- if (process.env.NODE_ENV_BLINQ === "dev") {
1964
- serviceUrl = "https://dev.api.blinq.io";
1965
- }
1966
- else if (process.env.NODE_ENV_BLINQ === "stage") {
1967
- serviceUrl = "https://stage.api.blinq.io";
1968
- }
2228
+ let serviceUrl = this._getServerUrl();
1969
2229
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1970
2230
  info.screenshotPath = screenshotPath;
1971
2231
  const screenshot = await this.takeScreenshot();
@@ -2114,7 +2374,16 @@ class StableBrowser {
2114
2374
  let screenshotId = null;
2115
2375
  let screenshotPath = null;
2116
2376
  const info = {};
2117
- info.log = "***** analyze table " + selectors.element_name + " query " + query + " operator " + operator + " value " + value + " *****\n";
2377
+ info.log =
2378
+ "***** analyze table " +
2379
+ selectors.element_name +
2380
+ " query " +
2381
+ query +
2382
+ " operator " +
2383
+ operator +
2384
+ " value " +
2385
+ value +
2386
+ " *****\n";
2118
2387
  info.operation = "analyzeTable";
2119
2388
  info.selectors = selectors;
2120
2389
  info.query = query;
@@ -2348,13 +2617,6 @@ class StableBrowser {
2348
2617
  const info = {};
2349
2618
  try {
2350
2619
  await this.page.close();
2351
- if (this.context && this.context.pages && this.context.pages.length > 0) {
2352
- this.context.pages.pop();
2353
- this.page = this.context.pages[this.context.pages.length - 1];
2354
- this.context.page = this.page;
2355
- let title = await this.page.title();
2356
- console.log("Switched to page " + title);
2357
- }
2358
2620
  }
2359
2621
  catch (e) {
2360
2622
  console.log(".");