automation_model 1.0.386-dev → 1.0.386-main

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,20 @@ 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 > 1) {
90
+ this.context.pages.pop();
91
+ this.page = this.context.pages[this.context.pages.length - 1];
92
+ this.context.page = this.page;
93
+ try {
94
+ let title = await this.page.title();
95
+ console.log("Switched to page " + title);
96
+ }
97
+ catch (error) {
98
+ console.error("Error on page close", error);
99
+ }
100
+ }
101
+ });
86
102
  try {
87
103
  await this.waitForPageLoad();
88
104
  console.log("Switch page: " + (await page.title()));
@@ -91,7 +107,7 @@ class StableBrowser {
91
107
  this.logger.error("error on page load " + e);
92
108
  }
93
109
  context.pageLoading.status = false;
94
- });
110
+ }.bind(this));
95
111
  }
96
112
  getWebLogFile(logFolder) {
97
113
  if (!fs.existsSync(logFolder)) {
@@ -177,21 +193,78 @@ class StableBrowser {
177
193
  }
178
194
  return text;
179
195
  }
196
+ _fixLocatorUsingParams(locator, _params) {
197
+ // check if not null
198
+ if (!locator) {
199
+ return locator;
200
+ }
201
+ // clone the locator
202
+ locator = JSON.parse(JSON.stringify(locator));
203
+ this.scanAndManipulate(locator, _params);
204
+ return locator;
205
+ }
206
+ _isObject(value) {
207
+ return value && typeof value === "object" && value.constructor === Object;
208
+ }
209
+ scanAndManipulate(currentObj, _params) {
210
+ for (const key in currentObj) {
211
+ if (typeof currentObj[key] === "string") {
212
+ // Perform string manipulation
213
+ currentObj[key] = this._fixUsingParams(currentObj[key], _params);
214
+ }
215
+ else if (this._isObject(currentObj[key])) {
216
+ // Recursively scan nested objects
217
+ this.scanAndManipulate(currentObj[key], _params);
218
+ }
219
+ }
220
+ }
180
221
  _getLocator(locator, scope, _params) {
222
+ locator = this._fixLocatorUsingParams(locator, _params);
223
+ let locatorReturn;
181
224
  if (locator.role) {
182
225
  if (locator.role[1].nameReg) {
183
226
  locator.role[1].name = reg_parser(locator.role[1].nameReg);
184
227
  delete locator.role[1].nameReg;
185
228
  }
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]);
229
+ // if (locator.role[1].name) {
230
+ // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
231
+ // }
232
+ locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
190
233
  }
191
234
  if (locator.css) {
192
- return scope.locator(this._fixUsingParams(locator.css, _params));
235
+ locatorReturn = scope.locator(locator.css);
193
236
  }
194
- throw new Error("unknown locator type");
237
+ // handle role/name locators
238
+ // locator.selector will be something like: textbox[name="Username"i]
239
+ if (locator.engine === "internal:role") {
240
+ // extract the role, name and the i/s flags using regex
241
+ const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
242
+ if (match) {
243
+ const role = match[1];
244
+ const name = match[3];
245
+ const flags = match[4];
246
+ locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
247
+ }
248
+ }
249
+ if (locator === null || locator === void 0 ? void 0 : locator.engine) {
250
+ if (locator.engine === "css") {
251
+ locatorReturn = scope.locator(locator.selector);
252
+ }
253
+ else {
254
+ let selector = locator.selector;
255
+ if (locator.engine === "internal:attr") {
256
+ if (!selector.startsWith("[")) {
257
+ selector = `[${selector}]`;
258
+ }
259
+ }
260
+ locatorReturn = scope.locator(`${locator.engine}=${selector}`);
261
+ }
262
+ }
263
+ if (!locatorReturn) {
264
+ console.error(locator);
265
+ throw new Error("Locator undefined");
266
+ }
267
+ return locatorReturn;
195
268
  }
196
269
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
197
270
  let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
@@ -596,6 +669,9 @@ class StableBrowser {
596
669
  async click(selectors, _params, options = {}, world = null) {
597
670
  this._validateSelectors(selectors);
598
671
  const startTime = Date.now();
672
+ if (options && options.context) {
673
+ selectors.locators[0].text = options.context;
674
+ }
599
675
  const info = {};
600
676
  info.log = "***** click on " + selectors.element_name + " *****\n";
601
677
  info.operation = "click";
@@ -904,71 +980,45 @@ class StableBrowser {
904
980
  });
905
981
  }
906
982
  }
907
- async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
983
+ async setInputValue(selectors, value, _params = null, options = {}, world = null) {
984
+ // set input value for non fillable inputs like date, time, range, color, etc.
908
985
  this._validateSelectors(selectors);
909
986
  const startTime = Date.now();
910
- let error = null;
911
- let screenshotId = null;
912
- let screenshotPath = null;
913
987
  const info = {};
914
- info.log = "";
915
- info.operation = Types.SET_DATE_TIME;
988
+ info.log = "***** set input value " + selectors.element_name + " *****\n";
989
+ info.operation = "setInputValue";
916
990
  info.selectors = selectors;
991
+ value = this._fixUsingParams(value, _params);
917
992
  info.value = value;
993
+ let error = null;
994
+ let screenshotId = null;
995
+ let screenshotPath = null;
918
996
  try {
919
997
  value = await this._replaceWithLocalData(value, this);
920
998
  let element = await this._locate(selectors, info, _params);
921
- //insert red border around the element
922
999
  await this.scrollIfNeeded(element, info);
923
1000
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
924
1001
  await this._highlightElements(element);
925
1002
  try {
926
- await element.click();
927
- await new Promise((resolve) => setTimeout(resolve, 500));
928
- if (format) {
929
- value = dayjs(value).format(format);
930
- await element.fill(value);
931
- }
932
- else {
933
- const dateTimeValue = await getDateTimeValue({ value, element });
934
- await element.evaluateHandle((el, dateTimeValue) => {
935
- el.value = ""; // clear input
936
- el.value = dateTimeValue;
937
- }, dateTimeValue);
938
- }
939
- if (enter) {
940
- await new Promise((resolve) => setTimeout(resolve, 2000));
941
- await this.page.keyboard.press("Enter");
942
- await this.waitForPageLoad();
943
- }
1003
+ await element.evaluateHandle((el, value) => {
1004
+ el.value = value;
1005
+ }, value);
944
1006
  }
945
1007
  catch (error) {
946
- //await this.closeUnexpectedPopups();
947
- this.logger.error("setting date time input failed " + JSON.stringify(info));
948
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1008
+ this.logger.error("setInputValue failed, will try again");
1009
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
949
1010
  info.screenshotPath = screenshotPath;
950
1011
  Object.assign(error, { info: info });
951
- await element.click();
952
- await new Promise((resolve) => setTimeout(resolve, 500));
953
- if (format) {
954
- value = dayjs(value).format(format);
955
- await element.fill(value);
956
- }
957
- else {
958
- const dateTimeValue = await getDateTimeValue({ value, element });
959
- await element.evaluateHandle((el, dateTimeValue) => {
960
- el.value = ""; // clear input
961
- el.value = dateTimeValue;
962
- }, dateTimeValue);
963
- }
964
- if (enter) {
965
- await new Promise((resolve) => setTimeout(resolve, 2000));
966
- await this.page.keyboard.press("Enter");
967
- await this.waitForPageLoad();
968
- }
1012
+ await element.evaluateHandle((el, value) => {
1013
+ el.value = value;
1014
+ });
969
1015
  }
970
1016
  }
971
- catch (error) {
1017
+ catch (e) {
1018
+ this.logger.error("setInputValue failed " + info.log);
1019
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1020
+ info.screenshotPath = screenshotPath;
1021
+ Object.assign(e, { info: info });
972
1022
  error = e;
973
1023
  throw e;
974
1024
  }
@@ -976,10 +1026,10 @@ class StableBrowser {
976
1026
  const endTime = Date.now();
977
1027
  this._reportToWorld(world, {
978
1028
  element_name: selectors.element_name,
979
- type: Types.SET_DATE_TIME,
980
- screenshotId,
1029
+ type: Types.SET_INPUT,
1030
+ text: `Set input value`,
981
1031
  value: value,
982
- text: `setDateTime input with value: ${value}`,
1032
+ screenshotId,
983
1033
  result: error
984
1034
  ? {
985
1035
  status: "FAILED",
@@ -996,7 +1046,7 @@ class StableBrowser {
996
1046
  });
997
1047
  }
998
1048
  }
999
- async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
1049
+ async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1000
1050
  this._validateSelectors(selectors);
1001
1051
  const startTime = Date.now();
1002
1052
  let error = null;
@@ -1008,6 +1058,7 @@ class StableBrowser {
1008
1058
  info.selectors = selectors;
1009
1059
  info.value = value;
1010
1060
  try {
1061
+ value = await this._replaceWithLocalData(value, this);
1011
1062
  let element = await this._locate(selectors, info, _params);
1012
1063
  //insert red border around the element
1013
1064
  await this.scrollIfNeeded(element, info);
@@ -1016,28 +1067,51 @@ class StableBrowser {
1016
1067
  try {
1017
1068
  await element.click();
1018
1069
  await new Promise((resolve) => setTimeout(resolve, 500));
1019
- const dateTimeValue = await getDateTimeValue({ value, element });
1020
- await element.evaluateHandle((el, dateTimeValue) => {
1021
- el.value = ""; // clear input
1022
- el.value = dateTimeValue;
1023
- }, dateTimeValue);
1070
+ if (format) {
1071
+ value = dayjs(value).format(format);
1072
+ await element.fill(value);
1073
+ }
1074
+ else {
1075
+ const dateTimeValue = await getDateTimeValue({ value, element });
1076
+ await element.evaluateHandle((el, dateTimeValue) => {
1077
+ el.value = ""; // clear input
1078
+ el.value = dateTimeValue;
1079
+ }, dateTimeValue);
1080
+ }
1081
+ if (enter) {
1082
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1083
+ await this.page.keyboard.press("Enter");
1084
+ await this.waitForPageLoad();
1085
+ }
1024
1086
  }
1025
- catch (error) {
1087
+ catch (err) {
1026
1088
  //await this.closeUnexpectedPopups();
1027
1089
  this.logger.error("setting date time input failed " + JSON.stringify(info));
1028
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1090
+ this.logger.info("Trying again");
1091
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1029
1092
  info.screenshotPath = screenshotPath;
1030
- Object.assign(error, { info: info });
1093
+ Object.assign(err, { info: info });
1031
1094
  await element.click();
1032
1095
  await new Promise((resolve) => setTimeout(resolve, 500));
1033
- const dateTimeValue = await getDateTimeValue({ value, element });
1034
- await element.evaluateHandle((el, dateTimeValue) => {
1035
- el.value = ""; // clear input
1036
- el.value = dateTimeValue;
1037
- }, dateTimeValue);
1096
+ if (format) {
1097
+ value = dayjs(value).format(format);
1098
+ await element.fill(value);
1099
+ }
1100
+ else {
1101
+ const dateTimeValue = await getDateTimeValue({ value, element });
1102
+ await element.evaluateHandle((el, dateTimeValue) => {
1103
+ el.value = ""; // clear input
1104
+ el.value = dateTimeValue;
1105
+ }, dateTimeValue);
1106
+ }
1107
+ if (enter) {
1108
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1109
+ await this.page.keyboard.press("Enter");
1110
+ await this.waitForPageLoad();
1111
+ }
1038
1112
  }
1039
1113
  }
1040
- catch (error) {
1114
+ catch (e) {
1041
1115
  error = e;
1042
1116
  throw e;
1043
1117
  }
@@ -1087,20 +1161,32 @@ class StableBrowser {
1087
1161
  await this.scrollIfNeeded(element, info);
1088
1162
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1089
1163
  await this._highlightElements(element);
1090
- try {
1091
- let currentValue = await element.inputValue();
1092
- if (currentValue) {
1093
- await element.fill("");
1164
+ if (options === null || options === undefined || !options.press) {
1165
+ try {
1166
+ let currentValue = await element.inputValue();
1167
+ if (currentValue) {
1168
+ await element.fill("");
1169
+ }
1170
+ }
1171
+ catch (e) {
1172
+ this.logger.info("unable to clear input value");
1094
1173
  }
1095
1174
  }
1096
- catch (e) {
1097
- this.logger.info("unable to clear input value");
1098
- }
1099
- try {
1100
- await element.click({ timeout: 5000 });
1175
+ if (options === null || options === undefined || options.press) {
1176
+ try {
1177
+ await element.click({ timeout: 5000 });
1178
+ }
1179
+ catch (e) {
1180
+ await element.dispatchEvent("click");
1181
+ }
1101
1182
  }
1102
- catch (e) {
1103
- await element.dispatchEvent("click");
1183
+ else {
1184
+ try {
1185
+ await element.focus();
1186
+ }
1187
+ catch (e) {
1188
+ await element.dispatchEvent("focus");
1189
+ }
1104
1190
  }
1105
1191
  await new Promise((resolve) => setTimeout(resolve, 500));
1106
1192
  const valueSegment = _value.split("&&");
@@ -1296,7 +1382,8 @@ class StableBrowser {
1296
1382
  let screenshotId = null;
1297
1383
  let screenshotPath = null;
1298
1384
  const info = {};
1299
- info.log = "***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
1385
+ info.log =
1386
+ "***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
1300
1387
  info.operation = "containsPattern";
1301
1388
  info.selectors = selectors;
1302
1389
  info.value = text;
@@ -1406,7 +1493,7 @@ class StableBrowser {
1406
1493
  return info;
1407
1494
  }
1408
1495
  catch (e) {
1409
- //await this.closeUnexpectedPopups();
1496
+ await this.closeUnexpectedPopups();
1410
1497
  this.logger.error("verify element contains text failed " + info.log);
1411
1498
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1412
1499
  info.screenshotPath = screenshotPath;
@@ -1466,15 +1553,62 @@ class StableBrowser {
1466
1553
  // save the data to the file
1467
1554
  fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
1468
1555
  }
1556
+ _getDataFilePath(fileName) {
1557
+ let dataFile = path.join(this.project_path, "data", fileName);
1558
+ if (fs.existsSync(dataFile)) {
1559
+ return dataFile;
1560
+ }
1561
+ dataFile = path.join(this.project_path, fileName);
1562
+ if (fs.existsSync(dataFile)) {
1563
+ return dataFile;
1564
+ }
1565
+ throw new Error("data file not found " + fileName);
1566
+ }
1567
+ _parseCSVSync(filePath) {
1568
+ const data = fs.readFileSync(filePath, "utf8");
1569
+ const results = [];
1570
+ return new Promise((resolve, reject) => {
1571
+ const readableStream = new Readable();
1572
+ readableStream._read = () => { }; // _read is required but you can noop it
1573
+ readableStream.push(data);
1574
+ readableStream.push(null);
1575
+ readableStream
1576
+ .pipe(csv())
1577
+ .on("data", (data) => results.push(data))
1578
+ .on("end", () => resolve(results))
1579
+ .on("error", (error) => reject(error));
1580
+ });
1581
+ }
1469
1582
  loadTestData(type, dataSelector, world = null) {
1470
1583
  switch (type) {
1471
1584
  case "users":
1472
- // check if file users.json exists
1473
- if (!fs.existsSync(path.join(this.project_path, "users.json"))) {
1474
- throw new Error("users.json file not found");
1585
+ // get the users.json file path
1586
+ let dataFile = this._getDataFilePath("users.json");
1587
+ // read the file and return the data
1588
+ const users = JSON.parse(fs.readFileSync(dataFile, "utf8"));
1589
+ for (let i = 0; i < users.length; i++) {
1590
+ if (users[i].username === dataSelector) {
1591
+ const userObj = {
1592
+ username: users[i].username,
1593
+ password: "secret:" + users[i].password,
1594
+ totp: users[i].secretKey ? "totp:" + users[i].secretKey : null,
1595
+ };
1596
+ this.setTestData(userObj, world);
1597
+ return userObj;
1598
+ }
1475
1599
  }
1600
+ throw new Error("user not found " + dataSelector);
1601
+ default:
1602
+ throw new Error("unknown type " + type);
1603
+ }
1604
+ }
1605
+ async loadTestDataAsync(type, dataSelector, world = null) {
1606
+ switch (type) {
1607
+ case "users": {
1608
+ // get the users.json file path
1609
+ let dataFile = this._getDataFilePath("users.json");
1476
1610
  // read the file and return the data
1477
- const users = JSON.parse(fs.readFileSync(path.join(this.project_path, "users.json"), "utf8"));
1611
+ const users = JSON.parse(fs.readFileSync(dataFile, "utf8"));
1478
1612
  for (let i = 0; i < users.length; i++) {
1479
1613
  if (users[i].username === dataSelector) {
1480
1614
  const userObj = {
@@ -1487,6 +1621,29 @@ class StableBrowser {
1487
1621
  }
1488
1622
  }
1489
1623
  throw new Error("user not found " + dataSelector);
1624
+ }
1625
+ case "csv": {
1626
+ // 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
1627
+ const parts = dataSelector.split(":");
1628
+ let rowNumber = 0;
1629
+ if (parts.length > 1) {
1630
+ rowNumber = parseInt(parts[1]);
1631
+ }
1632
+ let dataFile = this._getDataFilePath(parts[0]);
1633
+ const results = await this._parseCSVSync(dataFile);
1634
+ // result stracture:
1635
+ // [
1636
+ // { NAME: 'Daffy Duck', AGE: '24' },
1637
+ // { NAME: 'Bugs Bunny', AGE: '22' }
1638
+ // ]
1639
+ // verify the row number is within the range
1640
+ if (rowNumber >= results.length) {
1641
+ throw new Error("row number is out of range " + rowNumber);
1642
+ }
1643
+ const data = results[rowNumber];
1644
+ this.setTestData(data, world);
1645
+ return data;
1646
+ }
1490
1647
  default:
1491
1648
  throw new Error("unknown type " + type);
1492
1649
  }
@@ -1591,13 +1748,13 @@ class StableBrowser {
1591
1748
  ])));
1592
1749
  const { data } = await client.send("Page.captureScreenshot", {
1593
1750
  format: "png",
1594
- clip: {
1595
- x: 0,
1596
- y: 0,
1597
- width: viewportWidth,
1598
- height: viewportHeight,
1599
- scale: 1,
1600
- },
1751
+ // clip: {
1752
+ // x: 0,
1753
+ // y: 0,
1754
+ // width: viewportWidth,
1755
+ // height: viewportHeight,
1756
+ // scale: 1,
1757
+ // },
1601
1758
  });
1602
1759
  if (!screenshotPath) {
1603
1760
  return data;
@@ -1703,7 +1860,8 @@ class StableBrowser {
1703
1860
  if (world) {
1704
1861
  world[variable] = info.value;
1705
1862
  }
1706
- this.logger.info("world." + variable + "=" + info.value);
1863
+ this.setTestData({ [variable]: info.value }, world);
1864
+ this.logger.info("set test data: " + variable + "=" + info.value);
1707
1865
  return info;
1708
1866
  }
1709
1867
  catch (e) {
@@ -1740,6 +1898,91 @@ class StableBrowser {
1740
1898
  });
1741
1899
  }
1742
1900
  }
1901
+ async extractEmailData(emailAddress, options, world) {
1902
+ if (!emailAddress) {
1903
+ throw new Error("email address is null");
1904
+ }
1905
+ // check if address contain @
1906
+ if (emailAddress.indexOf("@") === -1) {
1907
+ emailAddress = emailAddress + "@blinq-mail.io";
1908
+ }
1909
+ else {
1910
+ if (!emailAddress.toLowerCase().endsWith("@blinq-mail.io")) {
1911
+ throw new Error("email address should end with @blinq-mail.io");
1912
+ }
1913
+ }
1914
+ const startTime = Date.now();
1915
+ let timeout = 60000;
1916
+ if (options && options.timeout) {
1917
+ timeout = options.timeout;
1918
+ }
1919
+ const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1920
+ const request = {
1921
+ method: "POST",
1922
+ url: serviceUrl,
1923
+ headers: {
1924
+ "Content-Type": "application/json",
1925
+ Authorization: `Bearer ${process.env.TOKEN}`,
1926
+ },
1927
+ data: JSON.stringify({
1928
+ email: emailAddress,
1929
+ }),
1930
+ };
1931
+ let errorCount = 0;
1932
+ while (true) {
1933
+ try {
1934
+ let result = await this.context.api.request(request);
1935
+ // the response body expected to be the following:
1936
+ // {
1937
+ // "status": true,
1938
+ // "content": {
1939
+ // "url": "",
1940
+ // "code": "112112",
1941
+ // "name": "generate_link_or_code"
1942
+ // }
1943
+ //}
1944
+ if ((result && result.data, result.data.status === true)) {
1945
+ let codeOrUrlFound = false;
1946
+ let emailCode = null;
1947
+ let emailUrl = null;
1948
+ // check if a code is returned
1949
+ if (result.data.content && result.data.content.code) {
1950
+ let code = result.data.content.code;
1951
+ this.setTestData({ emailCode: code }, world);
1952
+ this.logger.info("set test data: emailCode = " + code);
1953
+ emailCode = code;
1954
+ codeOrUrlFound = true;
1955
+ }
1956
+ // check if a url is returned
1957
+ if (result.data.content && result.data.content.url) {
1958
+ let url = result.data.content.url;
1959
+ this.setTestData({ emailUrl: url }, world);
1960
+ this.logger.info("set test data: emailUrl = " + url);
1961
+ emailUrl = url;
1962
+ codeOrUrlFound = true;
1963
+ }
1964
+ if (codeOrUrlFound) {
1965
+ return { emailUrl, emailCode };
1966
+ }
1967
+ else {
1968
+ this.logger.info("an email received but no code or url found");
1969
+ }
1970
+ }
1971
+ }
1972
+ catch (e) {
1973
+ errorCount++;
1974
+ if (errorCount > 3) {
1975
+ throw e;
1976
+ }
1977
+ // ignore
1978
+ }
1979
+ // check if the timeout is reached
1980
+ if (Date.now() - startTime > timeout) {
1981
+ throw new Error("timeout reached");
1982
+ }
1983
+ await new Promise((resolve) => setTimeout(resolve, 5000));
1984
+ }
1985
+ }
1743
1986
  async _highlightElements(scope, css) {
1744
1987
  try {
1745
1988
  if (!scope) {
@@ -1921,8 +2164,10 @@ class StableBrowser {
1921
2164
  const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
1922
2165
  await this._highlightElements(frame, dataAttribute);
1923
2166
  const element = await frame.$(dataAttribute);
1924
- await this.scrollIfNeeded(element, info);
1925
- await element.dispatchEvent("bvt_verify_page_contains_text");
2167
+ if (element) {
2168
+ await this.scrollIfNeeded(element, info);
2169
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2170
+ }
1926
2171
  }
1927
2172
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1928
2173
  return info;
@@ -1960,6 +2205,16 @@ class StableBrowser {
1960
2205
  });
1961
2206
  }
1962
2207
  }
2208
+ _getServerUrl() {
2209
+ let serviceUrl = "https://api.blinq.io";
2210
+ if (process.env.NODE_ENV_BLINQ === "dev") {
2211
+ serviceUrl = "https://dev.api.blinq.io";
2212
+ }
2213
+ else if (process.env.NODE_ENV_BLINQ === "stage") {
2214
+ serviceUrl = "https://stage.api.blinq.io";
2215
+ }
2216
+ return serviceUrl;
2217
+ }
1963
2218
  async visualVerification(text, options = {}, world = null) {
1964
2219
  const startTime = Date.now();
1965
2220
  let error = null;
@@ -1974,13 +2229,7 @@ class StableBrowser {
1974
2229
  throw new Error("TOKEN is not set");
1975
2230
  }
1976
2231
  try {
1977
- let serviceUrl = "https://api.blinq.io";
1978
- if (process.env.NODE_ENV_BLINQ === "dev") {
1979
- serviceUrl = "https://dev.api.blinq.io";
1980
- }
1981
- else if (process.env.NODE_ENV_BLINQ === "stage") {
1982
- serviceUrl = "https://stage.api.blinq.io";
1983
- }
2232
+ let serviceUrl = this._getServerUrl();
1984
2233
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1985
2234
  info.screenshotPath = screenshotPath;
1986
2235
  const screenshot = await this.takeScreenshot();
@@ -2129,7 +2378,16 @@ class StableBrowser {
2129
2378
  let screenshotId = null;
2130
2379
  let screenshotPath = null;
2131
2380
  const info = {};
2132
- info.log = "***** analyze table " + selectors.element_name + " query " + query + " operator " + operator + " value " + value + " *****\n";
2381
+ info.log =
2382
+ "***** analyze table " +
2383
+ selectors.element_name +
2384
+ " query " +
2385
+ query +
2386
+ " operator " +
2387
+ operator +
2388
+ " value " +
2389
+ value +
2390
+ " *****\n";
2133
2391
  info.operation = "analyzeTable";
2134
2392
  info.selectors = selectors;
2135
2393
  info.query = query;
@@ -2363,13 +2621,6 @@ class StableBrowser {
2363
2621
  const info = {};
2364
2622
  try {
2365
2623
  await this.page.close();
2366
- if (this.context && this.context.pages && this.context.pages.length > 0) {
2367
- this.context.pages.pop();
2368
- this.page = this.context.pages[this.context.pages.length - 1];
2369
- this.context.page = this.page;
2370
- let title = await this.page.title();
2371
- console.log("Switched to page " + title);
2372
- }
2373
2624
  }
2374
2625
  catch (e) {
2375
2626
  console.log(".");