automation_model 1.0.367 → 1.0.368-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)) {
@@ -168,23 +179,77 @@ class StableBrowser {
168
179
  return text;
169
180
  }
170
181
  for (let key in _params) {
171
- text = text.replaceAll(new RegExp("{" + key + "}", "g"), _params[key]);
182
+ let regValue = key;
183
+ if (key.startsWith("_")) {
184
+ // remove the _ prefix
185
+ regValue = key.substring(1);
186
+ }
187
+ text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
172
188
  }
173
189
  return text;
174
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
+ }
175
216
  _getLocator(locator, scope, _params) {
217
+ locator = this._fixLocatorUsingParams(locator, _params);
176
218
  if (locator.role) {
177
219
  if (locator.role[1].nameReg) {
178
220
  locator.role[1].name = reg_parser(locator.role[1].nameReg);
179
221
  delete locator.role[1].nameReg;
180
222
  }
181
- if (locator.role[1].name) {
182
- locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
183
- }
223
+ // if (locator.role[1].name) {
224
+ // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
225
+ // }
184
226
  return scope.getByRole(locator.role[0], locator.role[1]);
185
227
  }
186
228
  if (locator.css) {
187
- return scope.locator(this._fixUsingParams(locator.css, _params));
229
+ return scope.locator(locator.css);
230
+ }
231
+ // handle role/name locators
232
+ // locator.selector will be something like: textbox[name="Username"i]
233
+ if (locator.engine === "internal:role") {
234
+ // extract the role, name and the i/s flags using regex
235
+ const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
236
+ if (match) {
237
+ const role = match[1];
238
+ const name = match[3];
239
+ const flags = match[4];
240
+ return scope.getByRole(role, { name }, { exact: flags === "i" });
241
+ }
242
+ }
243
+ // handle css locators
244
+ if (locator.engine === "css") {
245
+ return scope.locator(locator.selector);
246
+ }
247
+ if ((locator === null || locator === void 0 ? void 0 : locator.engine) && (locator === null || locator === void 0 ? void 0 : locator.score) <= 520) {
248
+ if (locator.engine === "internal:attr") {
249
+ selector = `[${selector}]`;
250
+ }
251
+ const newLocator = scope.locator(`${locator.engine}=${selector}`);
252
+ return newLocator;
188
253
  }
189
254
  throw new Error("unknown locator type");
190
255
  }
@@ -309,10 +374,13 @@ class StableBrowser {
309
374
  }
310
375
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
311
376
  let locatorSearch = selectorHierarchy[index];
312
- info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
377
+ //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
313
378
  let locator = null;
314
379
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
315
380
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
381
+ if (!locatorString) {
382
+ return;
383
+ }
316
384
  locator = this._getLocator({ css: locatorString }, scope, _params);
317
385
  }
318
386
  else if (locatorSearch.text) {
@@ -334,7 +402,7 @@ class StableBrowser {
334
402
  // cssHref = true;
335
403
  // }
336
404
  let count = await locator.count();
337
- info.log += "total elements found " + count + "\n";
405
+ //info.log += "total elements found " + count + "\n";
338
406
  //let visibleCount = 0;
339
407
  let visibleLocator = null;
340
408
  if (locatorSearch.index && locatorSearch.index < count) {
@@ -344,16 +412,20 @@ class StableBrowser {
344
412
  for (let j = 0; j < count; j++) {
345
413
  let visible = await locator.nth(j).isVisible();
346
414
  const enabled = await locator.nth(j).isEnabled();
347
- info.log += "element " + j + " visible " + visible + " enabled " + enabled + "\n";
348
415
  if (!visibleOnly) {
349
416
  visible = true;
350
417
  }
351
418
  if (visible && enabled) {
352
419
  foundLocators.push(locator.nth(j));
353
- // if (cssHref) {
354
- // info.log += "css href locator found, will ignore all others" + "\n";
355
- // break;
356
- // }
420
+ }
421
+ else {
422
+ if (!info.printMessages) {
423
+ info.printMessages = {};
424
+ }
425
+ if (!info.printMessages[j.toString()]) {
426
+ info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
427
+ info.printMessages[j.toString()] = true;
428
+ }
357
429
  }
358
430
  }
359
431
  }
@@ -399,6 +471,10 @@ class StableBrowser {
399
471
  async _locate(selectors, info, _params, timeout = 30000) {
400
472
  for (let i = 0; i < 3; i++) {
401
473
  info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
474
+ for (let j = 0; j < selectors.locators.length; j++) {
475
+ let selector = selectors.locators[j];
476
+ info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
477
+ }
402
478
  let element = await this._locate_internal(selectors, info, _params, timeout);
403
479
  if (!element.rerun) {
404
480
  return element;
@@ -482,14 +558,18 @@ class StableBrowser {
482
558
  // info.log += "scanning locators in priority 2" + "\n";
483
559
  result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
484
560
  }
485
- let shouldNotTestPriority3 = highPriorityOnly && !onlyPriority3;
486
- if (result.foundElements.length === 0 && shouldTestPriority3) {
487
- // info.log += "scanning locators in priority 3" + "\n";
561
+ if (result.foundElements.length === 0 && onlyPriority3) {
488
562
  result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
489
563
  }
564
+ else {
565
+ if (result.foundElements.length === 0 && !highPriorityOnly) {
566
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
567
+ }
568
+ }
490
569
  let foundElements = result.foundElements;
491
570
  if (foundElements.length === 1 && foundElements[0].unique) {
492
571
  info.box = foundElements[0].box;
572
+ info.log += "unique element was found, locator: " + foundElements[0].locator + "\n";
493
573
  return foundElements[0].locator;
494
574
  }
495
575
  //info.log += "total elements found " + foundElements.length);
@@ -519,6 +599,7 @@ class StableBrowser {
519
599
  }
520
600
  }
521
601
  if (maxCountElement) {
602
+ info.log += "unique element was found, locator: " + maxCountElement.locator + "\n";
522
603
  info.box = await maxCountElement.locator.boundingBox();
523
604
  return maxCountElement.locator;
524
605
  }
@@ -527,9 +608,11 @@ class StableBrowser {
527
608
  break;
528
609
  }
529
610
  if (performance.now() - startTime > highPriorityTimeout) {
611
+ info.log += "high priority timeout, will try all elements" + "\n";
530
612
  highPriorityOnly = false;
531
613
  }
532
614
  if (performance.now() - startTime > visibleOnlyTimeout) {
615
+ info.log += "visible only timeout, will try all elements" + "\n";
533
616
  visibleOnly = false;
534
617
  }
535
618
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -881,71 +964,45 @@ class StableBrowser {
881
964
  });
882
965
  }
883
966
  }
884
- async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
967
+ async setInputValue(selectors, value, _params = null, options = {}, world = null) {
968
+ // set input value for non fillable inputs like date, time, range, color, etc.
885
969
  this._validateSelectors(selectors);
886
970
  const startTime = Date.now();
887
- let error = null;
888
- let screenshotId = null;
889
- let screenshotPath = null;
890
971
  const info = {};
891
- info.log = "";
892
- info.operation = Types.SET_DATE_TIME;
972
+ info.log = "***** set input value " + selectors.element_name + " *****\n";
973
+ info.operation = "setInputValue";
893
974
  info.selectors = selectors;
975
+ value = this._fixUsingParams(value, _params);
894
976
  info.value = value;
977
+ let error = null;
978
+ let screenshotId = null;
979
+ let screenshotPath = null;
895
980
  try {
896
981
  value = await this._replaceWithLocalData(value, this);
897
982
  let element = await this._locate(selectors, info, _params);
898
- //insert red border around the element
899
983
  await this.scrollIfNeeded(element, info);
900
984
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
901
985
  await this._highlightElements(element);
902
986
  try {
903
- await element.click();
904
- await new Promise((resolve) => setTimeout(resolve, 500));
905
- if (format) {
906
- value = dayjs(value).format(format);
907
- await element.fill(value);
908
- }
909
- else {
910
- const dateTimeValue = await getDateTimeValue({ value, element });
911
- await element.evaluateHandle((el, dateTimeValue) => {
912
- el.value = ""; // clear input
913
- el.value = dateTimeValue;
914
- }, dateTimeValue);
915
- }
916
- if (enter) {
917
- await new Promise((resolve) => setTimeout(resolve, 2000));
918
- await this.page.keyboard.press("Enter");
919
- await this.waitForPageLoad();
920
- }
987
+ await element.evaluateHandle((el, value) => {
988
+ el.value = value;
989
+ }, value);
921
990
  }
922
991
  catch (error) {
923
- //await this.closeUnexpectedPopups();
924
- this.logger.error("setting date time input failed " + JSON.stringify(info));
925
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
992
+ this.logger.error("setInputValue failed, will try again");
993
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
926
994
  info.screenshotPath = screenshotPath;
927
995
  Object.assign(error, { info: info });
928
- await element.click();
929
- await new Promise((resolve) => setTimeout(resolve, 500));
930
- if (format) {
931
- value = dayjs(value).format(format);
932
- await element.fill(value);
933
- }
934
- else {
935
- const dateTimeValue = await getDateTimeValue({ value, element });
936
- await element.evaluateHandle((el, dateTimeValue) => {
937
- el.value = ""; // clear input
938
- el.value = dateTimeValue;
939
- }, dateTimeValue);
940
- }
941
- if (enter) {
942
- await new Promise((resolve) => setTimeout(resolve, 2000));
943
- await this.page.keyboard.press("Enter");
944
- await this.waitForPageLoad();
945
- }
996
+ await element.evaluateHandle((el, value) => {
997
+ el.value = value;
998
+ });
946
999
  }
947
1000
  }
948
- catch (error) {
1001
+ catch (e) {
1002
+ this.logger.error("setInputValue failed " + info.log);
1003
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1004
+ info.screenshotPath = screenshotPath;
1005
+ Object.assign(e, { info: info });
949
1006
  error = e;
950
1007
  throw e;
951
1008
  }
@@ -953,10 +1010,10 @@ class StableBrowser {
953
1010
  const endTime = Date.now();
954
1011
  this._reportToWorld(world, {
955
1012
  element_name: selectors.element_name,
956
- type: Types.SET_DATE_TIME,
957
- screenshotId,
1013
+ type: Types.SET_INPUT,
1014
+ text: `Set input value`,
958
1015
  value: value,
959
- text: `setDateTime input with value: ${value}`,
1016
+ screenshotId,
960
1017
  result: error
961
1018
  ? {
962
1019
  status: "FAILED",
@@ -973,7 +1030,7 @@ class StableBrowser {
973
1030
  });
974
1031
  }
975
1032
  }
976
- async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
1033
+ async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
977
1034
  this._validateSelectors(selectors);
978
1035
  const startTime = Date.now();
979
1036
  let error = null;
@@ -985,6 +1042,7 @@ class StableBrowser {
985
1042
  info.selectors = selectors;
986
1043
  info.value = value;
987
1044
  try {
1045
+ value = await this._replaceWithLocalData(value, this);
988
1046
  let element = await this._locate(selectors, info, _params);
989
1047
  //insert red border around the element
990
1048
  await this.scrollIfNeeded(element, info);
@@ -993,28 +1051,51 @@ class StableBrowser {
993
1051
  try {
994
1052
  await element.click();
995
1053
  await new Promise((resolve) => setTimeout(resolve, 500));
996
- const dateTimeValue = await getDateTimeValue({ value, element });
997
- await element.evaluateHandle((el, dateTimeValue) => {
998
- el.value = ""; // clear input
999
- el.value = dateTimeValue;
1000
- }, dateTimeValue);
1054
+ if (format) {
1055
+ value = dayjs(value).format(format);
1056
+ await element.fill(value);
1057
+ }
1058
+ else {
1059
+ const dateTimeValue = await getDateTimeValue({ value, element });
1060
+ await element.evaluateHandle((el, dateTimeValue) => {
1061
+ el.value = ""; // clear input
1062
+ el.value = dateTimeValue;
1063
+ }, dateTimeValue);
1064
+ }
1065
+ if (enter) {
1066
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1067
+ await this.page.keyboard.press("Enter");
1068
+ await this.waitForPageLoad();
1069
+ }
1001
1070
  }
1002
- catch (error) {
1071
+ catch (err) {
1003
1072
  //await this.closeUnexpectedPopups();
1004
1073
  this.logger.error("setting date time input failed " + JSON.stringify(info));
1005
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1074
+ this.logger.info("Trying again");
1075
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1006
1076
  info.screenshotPath = screenshotPath;
1007
- Object.assign(error, { info: info });
1077
+ Object.assign(err, { info: info });
1008
1078
  await element.click();
1009
1079
  await new Promise((resolve) => setTimeout(resolve, 500));
1010
- const dateTimeValue = await getDateTimeValue({ value, element });
1011
- await element.evaluateHandle((el, dateTimeValue) => {
1012
- el.value = ""; // clear input
1013
- el.value = dateTimeValue;
1014
- }, dateTimeValue);
1080
+ if (format) {
1081
+ value = dayjs(value).format(format);
1082
+ await element.fill(value);
1083
+ }
1084
+ else {
1085
+ const dateTimeValue = await getDateTimeValue({ value, element });
1086
+ await element.evaluateHandle((el, dateTimeValue) => {
1087
+ el.value = ""; // clear input
1088
+ el.value = dateTimeValue;
1089
+ }, dateTimeValue);
1090
+ }
1091
+ if (enter) {
1092
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1093
+ await this.page.keyboard.press("Enter");
1094
+ await this.waitForPageLoad();
1095
+ }
1015
1096
  }
1016
1097
  }
1017
- catch (error) {
1098
+ catch (e) {
1018
1099
  error = e;
1019
1100
  throw e;
1020
1101
  }
@@ -1064,20 +1145,32 @@ class StableBrowser {
1064
1145
  await this.scrollIfNeeded(element, info);
1065
1146
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1066
1147
  await this._highlightElements(element);
1067
- try {
1068
- let currentValue = await element.inputValue();
1069
- if (currentValue) {
1070
- await element.fill("");
1148
+ if (options === null || options === undefined || !options.press) {
1149
+ try {
1150
+ let currentValue = await element.inputValue();
1151
+ if (currentValue) {
1152
+ await element.fill("");
1153
+ }
1154
+ }
1155
+ catch (e) {
1156
+ this.logger.info("unable to clear input value");
1071
1157
  }
1072
1158
  }
1073
- catch (e) {
1074
- this.logger.info("unable to clear input value");
1075
- }
1076
- try {
1077
- await element.click({ timeout: 5000 });
1159
+ if (options === null || options === undefined || options.press) {
1160
+ try {
1161
+ await element.click({ timeout: 5000 });
1162
+ }
1163
+ catch (e) {
1164
+ await element.dispatchEvent("click");
1165
+ }
1078
1166
  }
1079
- catch (e) {
1080
- await element.dispatchEvent("click");
1167
+ else {
1168
+ try {
1169
+ await element.focus();
1170
+ }
1171
+ catch (e) {
1172
+ await element.dispatchEvent("focus");
1173
+ }
1081
1174
  }
1082
1175
  await new Promise((resolve) => setTimeout(resolve, 500));
1083
1176
  const valueSegment = _value.split("&&");
@@ -1273,7 +1366,8 @@ class StableBrowser {
1273
1366
  let screenshotId = null;
1274
1367
  let screenshotPath = null;
1275
1368
  const info = {};
1276
- info.log = "***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
1369
+ info.log =
1370
+ "***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
1277
1371
  info.operation = "containsPattern";
1278
1372
  info.selectors = selectors;
1279
1373
  info.value = text;
@@ -1443,15 +1537,62 @@ class StableBrowser {
1443
1537
  // save the data to the file
1444
1538
  fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
1445
1539
  }
1540
+ _getDataFilePath(fileName) {
1541
+ let dataFile = path.join(this.project_path, "data", fileName);
1542
+ if (fs.existsSync(dataFile)) {
1543
+ return dataFile;
1544
+ }
1545
+ dataFile = path.join(this.project_path, fileName);
1546
+ if (fs.existsSync(dataFile)) {
1547
+ return dataFile;
1548
+ }
1549
+ throw new Error("data file not found " + fileName);
1550
+ }
1551
+ _parseCSVSync(filePath) {
1552
+ const data = fs.readFileSync(filePath, "utf8");
1553
+ const results = [];
1554
+ return new Promise((resolve, reject) => {
1555
+ const readableStream = new Readable();
1556
+ readableStream._read = () => { }; // _read is required but you can noop it
1557
+ readableStream.push(data);
1558
+ readableStream.push(null);
1559
+ readableStream
1560
+ .pipe(csv())
1561
+ .on("data", (data) => results.push(data))
1562
+ .on("end", () => resolve(results))
1563
+ .on("error", (error) => reject(error));
1564
+ });
1565
+ }
1446
1566
  loadTestData(type, dataSelector, world = null) {
1447
1567
  switch (type) {
1448
1568
  case "users":
1449
- // check if file users.json exists
1450
- if (!fs.existsSync(path.join(this.project_path, "users.json"))) {
1451
- throw new Error("users.json file not found");
1569
+ // get the users.json file path
1570
+ let dataFile = this._getDataFilePath("users.json");
1571
+ // read the file and return the data
1572
+ const users = JSON.parse(fs.readFileSync(dataFile, "utf8"));
1573
+ for (let i = 0; i < users.length; i++) {
1574
+ if (users[i].username === dataSelector) {
1575
+ const userObj = {
1576
+ username: users[i].username,
1577
+ password: "secret:" + users[i].password,
1578
+ totp: users[i].secretKey ? "totp:" + users[i].secretKey : null,
1579
+ };
1580
+ this.setTestData(userObj, world);
1581
+ return userObj;
1582
+ }
1452
1583
  }
1584
+ throw new Error("user not found " + dataSelector);
1585
+ default:
1586
+ throw new Error("unknown type " + type);
1587
+ }
1588
+ }
1589
+ async loadTestDataAsync(type, dataSelector, world = null) {
1590
+ switch (type) {
1591
+ case "users": {
1592
+ // get the users.json file path
1593
+ let dataFile = this._getDataFilePath("users.json");
1453
1594
  // read the file and return the data
1454
- const users = JSON.parse(fs.readFileSync(path.join(this.project_path, "users.json"), "utf8"));
1595
+ const users = JSON.parse(fs.readFileSync(dataFile, "utf8"));
1455
1596
  for (let i = 0; i < users.length; i++) {
1456
1597
  if (users[i].username === dataSelector) {
1457
1598
  const userObj = {
@@ -1464,6 +1605,29 @@ class StableBrowser {
1464
1605
  }
1465
1606
  }
1466
1607
  throw new Error("user not found " + dataSelector);
1608
+ }
1609
+ case "csv": {
1610
+ // 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
1611
+ const parts = dataSelector.split(":");
1612
+ let rowNumber = 0;
1613
+ if (parts.length > 1) {
1614
+ rowNumber = parseInt(parts[1]);
1615
+ }
1616
+ let dataFile = this._getDataFilePath(parts[0]);
1617
+ const results = await this._parseCSVSync(dataFile);
1618
+ // result stracture:
1619
+ // [
1620
+ // { NAME: 'Daffy Duck', AGE: '24' },
1621
+ // { NAME: 'Bugs Bunny', AGE: '22' }
1622
+ // ]
1623
+ // verify the row number is within the range
1624
+ if (rowNumber >= results.length) {
1625
+ throw new Error("row number is out of range " + rowNumber);
1626
+ }
1627
+ const data = results[rowNumber];
1628
+ this.setTestData(data, world);
1629
+ return data;
1630
+ }
1467
1631
  default:
1468
1632
  throw new Error("unknown type " + type);
1469
1633
  }
@@ -1680,7 +1844,8 @@ class StableBrowser {
1680
1844
  if (world) {
1681
1845
  world[variable] = info.value;
1682
1846
  }
1683
- this.logger.info("world." + variable + "=" + info.value);
1847
+ this.setTestData({ [variable]: info.value }, world);
1848
+ this.logger.info("set test data: " + variable + "=" + info.value);
1684
1849
  return info;
1685
1850
  }
1686
1851
  catch (e) {
@@ -1717,6 +1882,91 @@ class StableBrowser {
1717
1882
  });
1718
1883
  }
1719
1884
  }
1885
+ async extractEmailData(emailAddress, options, world) {
1886
+ if (!emailAddress) {
1887
+ throw new Error("email address is null");
1888
+ }
1889
+ // check if address contain @
1890
+ if (emailAddress.indexOf("@") === -1) {
1891
+ emailAddress = emailAddress + "@blinq-mail.io";
1892
+ }
1893
+ else {
1894
+ if (!emailAddress.toLowerCase().endsWith("@blinq-mail.io")) {
1895
+ throw new Error("email address should end with @blinq-mail.io");
1896
+ }
1897
+ }
1898
+ const startTime = Date.now();
1899
+ let timeout = 60000;
1900
+ if (options && options.timeout) {
1901
+ timeout = options.timeout;
1902
+ }
1903
+ const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1904
+ const request = {
1905
+ method: "POST",
1906
+ url: serviceUrl,
1907
+ headers: {
1908
+ "Content-Type": "application/json",
1909
+ Authorization: `Bearer ${process.env.TOKEN}`,
1910
+ },
1911
+ data: JSON.stringify({
1912
+ email: emailAddress,
1913
+ }),
1914
+ };
1915
+ let errorCount = 0;
1916
+ while (true) {
1917
+ try {
1918
+ let result = await this.context.api.request(request);
1919
+ // the response body expected to be the following:
1920
+ // {
1921
+ // "status": true,
1922
+ // "content": {
1923
+ // "url": "",
1924
+ // "code": "112112",
1925
+ // "name": "generate_link_or_code"
1926
+ // }
1927
+ //}
1928
+ if ((result && result.data, result.data.status === true)) {
1929
+ let codeOrUrlFound = false;
1930
+ let emailCode = null;
1931
+ let emailUrl = null;
1932
+ // check if a code is returned
1933
+ if (result.data.content && result.data.content.code) {
1934
+ let code = result.data.content.code;
1935
+ this.setTestData({ emailCode: code }, world);
1936
+ this.logger.info("set test data: emailCode = " + code);
1937
+ emailCode = code;
1938
+ codeOrUrlFound = true;
1939
+ }
1940
+ // check if a url is returned
1941
+ if (result.data.content && result.data.content.url) {
1942
+ let url = result.data.content.url;
1943
+ this.setTestData({ emailUrl: url }, world);
1944
+ this.logger.info("set test data: emailUrl = " + url);
1945
+ emailUrl = url;
1946
+ codeOrUrlFound = true;
1947
+ }
1948
+ if (codeOrUrlFound) {
1949
+ return { emailUrl, emailCode };
1950
+ }
1951
+ else {
1952
+ this.logger.info("an email received but no code or url found");
1953
+ }
1954
+ }
1955
+ }
1956
+ catch (e) {
1957
+ errorCount++;
1958
+ if (errorCount > 3) {
1959
+ throw e;
1960
+ }
1961
+ // ignore
1962
+ }
1963
+ // check if the timeout is reached
1964
+ if (Date.now() - startTime > timeout) {
1965
+ throw new Error("timeout reached");
1966
+ }
1967
+ await new Promise((resolve) => setTimeout(resolve, 5000));
1968
+ }
1969
+ }
1720
1970
  async _highlightElements(scope, css) {
1721
1971
  try {
1722
1972
  if (!scope) {
@@ -1898,8 +2148,10 @@ class StableBrowser {
1898
2148
  const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
1899
2149
  await this._highlightElements(frame, dataAttribute);
1900
2150
  const element = await frame.$(dataAttribute);
1901
- await this.scrollIfNeeded(element, info);
1902
- await element.dispatchEvent("bvt_verify_page_contains_text");
2151
+ if (element) {
2152
+ await this.scrollIfNeeded(element, info);
2153
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2154
+ }
1903
2155
  }
1904
2156
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1905
2157
  return info;
@@ -1937,6 +2189,16 @@ class StableBrowser {
1937
2189
  });
1938
2190
  }
1939
2191
  }
2192
+ _getServerUrl() {
2193
+ let serviceUrl = "https://api.blinq.io";
2194
+ if (process.env.NODE_ENV_BLINQ === "dev") {
2195
+ serviceUrl = "https://dev.api.blinq.io";
2196
+ }
2197
+ else if (process.env.NODE_ENV_BLINQ === "stage") {
2198
+ serviceUrl = "https://stage.api.blinq.io";
2199
+ }
2200
+ return serviceUrl;
2201
+ }
1940
2202
  async visualVerification(text, options = {}, world = null) {
1941
2203
  const startTime = Date.now();
1942
2204
  let error = null;
@@ -1951,13 +2213,7 @@ class StableBrowser {
1951
2213
  throw new Error("TOKEN is not set");
1952
2214
  }
1953
2215
  try {
1954
- let serviceUrl = "https://api.blinq.io";
1955
- if (process.env.NODE_ENV_BLINQ === "dev") {
1956
- serviceUrl = "https://dev.api.blinq.io";
1957
- }
1958
- else if (process.env.NODE_ENV_BLINQ === "stage") {
1959
- serviceUrl = "https://stage.api.blinq.io";
1960
- }
2216
+ let serviceUrl = this._getServerUrl();
1961
2217
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1962
2218
  info.screenshotPath = screenshotPath;
1963
2219
  const screenshot = await this.takeScreenshot();
@@ -2106,7 +2362,16 @@ class StableBrowser {
2106
2362
  let screenshotId = null;
2107
2363
  let screenshotPath = null;
2108
2364
  const info = {};
2109
- info.log = "***** analyze table " + selectors.element_name + " query " + query + " operator " + operator + " value " + value + " *****\n";
2365
+ info.log =
2366
+ "***** analyze table " +
2367
+ selectors.element_name +
2368
+ " query " +
2369
+ query +
2370
+ " operator " +
2371
+ operator +
2372
+ " value " +
2373
+ value +
2374
+ " *****\n";
2110
2375
  info.operation = "analyzeTable";
2111
2376
  info.selectors = selectors;
2112
2377
  info.query = query;
@@ -2340,13 +2605,6 @@ class StableBrowser {
2340
2605
  const info = {};
2341
2606
  try {
2342
2607
  await this.page.close();
2343
- if (this.context && this.context.pages && this.context.pages.length > 0) {
2344
- this.context.pages.pop();
2345
- this.page = this.context.pages[this.context.pages.length - 1];
2346
- this.context.page = this.page;
2347
- let title = await this.page.title();
2348
- console.log("Switched to page " + title);
2349
- }
2350
2608
  }
2351
2609
  catch (e) {
2352
2610
  console.log(".");