automation_model 1.0.392-dev → 1.0.392-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,9 @@ 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";
17
+ import readline from "readline";
15
18
  const Types = {
16
19
  CLICK: "click_element",
17
20
  NAVIGATE: "navigate",
@@ -27,6 +30,7 @@ const Types = {
27
30
  SELECT: "select_combobox",
28
31
  VERIFY_PAGE_PATH: "verify_page_path",
29
32
  TYPE_PRESS: "type_press",
33
+ PRESS: "press_key",
30
34
  HOVER: "hover_element",
31
35
  CHECK: "check_element",
32
36
  UNCHECK: "uncheck_element",
@@ -35,6 +39,8 @@ const Types = {
35
39
  SET_DATE_TIME: "set_date_time",
36
40
  SET_VIEWPORT: "set_viewport",
37
41
  VERIFY_VISUAL: "verify_visual",
42
+ LOAD_DATA: "load_data",
43
+ SET_INPUT: "set_input",
38
44
  };
39
45
  class StableBrowser {
40
46
  constructor(browser, page, logger = null, context = null) {
@@ -80,9 +86,20 @@ class StableBrowser {
80
86
  this.page = page;
81
87
  context.page = page;
82
88
  context.pages.push(page);
83
- this.webLogFile = this.getWebLogFile(logFolder);
84
- this.registerConsoleLogListener(page, context, this.webLogFile);
85
- this.registerRequestListener();
89
+ page.on("close", async () => {
90
+ if (this.context && this.context.pages && this.context.pages.length > 1) {
91
+ this.context.pages.pop();
92
+ this.page = this.context.pages[this.context.pages.length - 1];
93
+ this.context.page = this.page;
94
+ try {
95
+ let title = await this.page.title();
96
+ console.log("Switched to page " + title);
97
+ }
98
+ catch (error) {
99
+ console.error("Error on page close", error);
100
+ }
101
+ }
102
+ });
86
103
  try {
87
104
  await this.waitForPageLoad();
88
105
  console.log("Switch page: " + (await page.title()));
@@ -177,24 +194,78 @@ class StableBrowser {
177
194
  }
178
195
  return text;
179
196
  }
180
- _getLocator(locator, scope, _params) {
181
- if (locator.type === "pw_selector") {
182
- return scope.locator(locator.selector);
197
+ _fixLocatorUsingParams(locator, _params) {
198
+ // check if not null
199
+ if (!locator) {
200
+ return locator;
201
+ }
202
+ // clone the locator
203
+ locator = JSON.parse(JSON.stringify(locator));
204
+ this.scanAndManipulate(locator, _params);
205
+ return locator;
206
+ }
207
+ _isObject(value) {
208
+ return value && typeof value === "object" && value.constructor === Object;
209
+ }
210
+ scanAndManipulate(currentObj, _params) {
211
+ for (const key in currentObj) {
212
+ if (typeof currentObj[key] === "string") {
213
+ // Perform string manipulation
214
+ currentObj[key] = this._fixUsingParams(currentObj[key], _params);
215
+ }
216
+ else if (this._isObject(currentObj[key])) {
217
+ // Recursively scan nested objects
218
+ this.scanAndManipulate(currentObj[key], _params);
219
+ }
183
220
  }
221
+ }
222
+ _getLocator(locator, scope, _params) {
223
+ locator = this._fixLocatorUsingParams(locator, _params);
224
+ let locatorReturn;
184
225
  if (locator.role) {
185
226
  if (locator.role[1].nameReg) {
186
227
  locator.role[1].name = reg_parser(locator.role[1].nameReg);
187
228
  delete locator.role[1].nameReg;
188
229
  }
189
- if (locator.role[1].name) {
190
- locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
191
- }
192
- return scope.getByRole(locator.role[0], locator.role[1]);
230
+ // if (locator.role[1].name) {
231
+ // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
232
+ // }
233
+ locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
193
234
  }
194
235
  if (locator.css) {
195
- return scope.locator(this._fixUsingParams(locator.css, _params));
236
+ locatorReturn = scope.locator(locator.css);
237
+ }
238
+ // handle role/name locators
239
+ // locator.selector will be something like: textbox[name="Username"i]
240
+ if (locator.engine === "internal:role") {
241
+ // extract the role, name and the i/s flags using regex
242
+ const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
243
+ if (match) {
244
+ const role = match[1];
245
+ const name = match[3];
246
+ const flags = match[4];
247
+ locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
248
+ }
196
249
  }
197
- throw new Error("unknown locator type");
250
+ if (locator === null || locator === void 0 ? void 0 : locator.engine) {
251
+ if (locator.engine === "css") {
252
+ locatorReturn = scope.locator(locator.selector);
253
+ }
254
+ else {
255
+ let selector = locator.selector;
256
+ if (locator.engine === "internal:attr") {
257
+ if (!selector.startsWith("[")) {
258
+ selector = `[${selector}]`;
259
+ }
260
+ }
261
+ locatorReturn = scope.locator(`${locator.engine}=${selector}`);
262
+ }
263
+ }
264
+ if (!locatorReturn) {
265
+ console.error(locator);
266
+ throw new Error("Locator undefined");
267
+ }
268
+ return locatorReturn;
198
269
  }
199
270
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
200
271
  let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
@@ -413,7 +484,7 @@ class StableBrowser {
413
484
  }
414
485
  async _locate(selectors, info, _params, timeout = 30000) {
415
486
  for (let i = 0; i < 3; i++) {
416
- info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
487
+ info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
417
488
  for (let j = 0; j < selectors.locators.length; j++) {
418
489
  let selector = selectors.locators[j];
419
490
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
@@ -599,6 +670,9 @@ class StableBrowser {
599
670
  async click(selectors, _params, options = {}, world = null) {
600
671
  this._validateSelectors(selectors);
601
672
  const startTime = Date.now();
673
+ if (options && options.context) {
674
+ selectors.locators[0].text = options.context;
675
+ }
602
676
  const info = {};
603
677
  info.log = "***** click on " + selectors.element_name + " *****\n";
604
678
  info.operation = "click";
@@ -612,14 +686,14 @@ class StableBrowser {
612
686
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
613
687
  try {
614
688
  await this._highlightElements(element);
615
- await element.click({ timeout: 5000 });
689
+ await element.click();
616
690
  await new Promise((resolve) => setTimeout(resolve, 1000));
617
691
  }
618
692
  catch (e) {
619
693
  // await this.closeUnexpectedPopups();
620
694
  info.log += "click failed, will try again" + "\n";
621
695
  element = await this._locate(selectors, info, _params);
622
- await element.click({ timeout: 10000, force: true });
696
+ await element.dispatchEvent("click");
623
697
  await new Promise((resolve) => setTimeout(resolve, 1000));
624
698
  }
625
699
  await this.waitForPageLoad();
@@ -672,7 +746,7 @@ class StableBrowser {
672
746
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
673
747
  try {
674
748
  await this._highlightElements(element);
675
- await element.setChecked(checked, { timeout: 5000 });
749
+ await element.setChecked(checked);
676
750
  await new Promise((resolve) => setTimeout(resolve, 1000));
677
751
  }
678
752
  catch (e) {
@@ -736,7 +810,7 @@ class StableBrowser {
736
810
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
737
811
  try {
738
812
  await this._highlightElements(element);
739
- await element.hover({ timeout: 10000 });
813
+ await element.hover();
740
814
  await new Promise((resolve) => setTimeout(resolve, 1000));
741
815
  }
742
816
  catch (e) {
@@ -798,7 +872,7 @@ class StableBrowser {
798
872
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
799
873
  try {
800
874
  await this._highlightElements(element);
801
- await element.selectOption(values, { timeout: 5000 });
875
+ await element.selectOption(values);
802
876
  }
803
877
  catch (e) {
804
878
  //await this.closeUnexpectedPopups();
@@ -907,71 +981,45 @@ class StableBrowser {
907
981
  });
908
982
  }
909
983
  }
910
- async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
984
+ async setInputValue(selectors, value, _params = null, options = {}, world = null) {
985
+ // set input value for non fillable inputs like date, time, range, color, etc.
911
986
  this._validateSelectors(selectors);
912
987
  const startTime = Date.now();
913
- let error = null;
914
- let screenshotId = null;
915
- let screenshotPath = null;
916
988
  const info = {};
917
- info.log = "";
918
- info.operation = Types.SET_DATE_TIME;
989
+ info.log = "***** set input value " + selectors.element_name + " *****\n";
990
+ info.operation = "setInputValue";
919
991
  info.selectors = selectors;
992
+ value = this._fixUsingParams(value, _params);
920
993
  info.value = value;
994
+ let error = null;
995
+ let screenshotId = null;
996
+ let screenshotPath = null;
921
997
  try {
922
998
  value = await this._replaceWithLocalData(value, this);
923
999
  let element = await this._locate(selectors, info, _params);
924
- //insert red border around the element
925
1000
  await this.scrollIfNeeded(element, info);
926
1001
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
927
1002
  await this._highlightElements(element);
928
1003
  try {
929
- await element.click();
930
- await new Promise((resolve) => setTimeout(resolve, 500));
931
- if (format) {
932
- value = dayjs(value).format(format);
933
- await element.fill(value);
934
- }
935
- else {
936
- const dateTimeValue = await getDateTimeValue({ value, element });
937
- await element.evaluateHandle((el, dateTimeValue) => {
938
- el.value = ""; // clear input
939
- el.value = dateTimeValue;
940
- }, dateTimeValue);
941
- }
942
- if (enter) {
943
- await new Promise((resolve) => setTimeout(resolve, 2000));
944
- await this.page.keyboard.press("Enter");
945
- await this.waitForPageLoad();
946
- }
1004
+ await element.evaluateHandle((el, value) => {
1005
+ el.value = value;
1006
+ }, value);
947
1007
  }
948
1008
  catch (error) {
949
- //await this.closeUnexpectedPopups();
950
- this.logger.error("setting date time input failed " + JSON.stringify(info));
951
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1009
+ this.logger.error("setInputValue failed, will try again");
1010
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
952
1011
  info.screenshotPath = screenshotPath;
953
1012
  Object.assign(error, { info: info });
954
- await element.click();
955
- await new Promise((resolve) => setTimeout(resolve, 500));
956
- if (format) {
957
- value = dayjs(value).format(format);
958
- await element.fill(value);
959
- }
960
- else {
961
- const dateTimeValue = await getDateTimeValue({ value, element });
962
- await element.evaluateHandle((el, dateTimeValue) => {
963
- el.value = ""; // clear input
964
- el.value = dateTimeValue;
965
- }, dateTimeValue);
966
- }
967
- if (enter) {
968
- await new Promise((resolve) => setTimeout(resolve, 2000));
969
- await this.page.keyboard.press("Enter");
970
- await this.waitForPageLoad();
971
- }
1013
+ await element.evaluateHandle((el, value) => {
1014
+ el.value = value;
1015
+ });
972
1016
  }
973
1017
  }
974
- catch (error) {
1018
+ catch (e) {
1019
+ this.logger.error("setInputValue failed " + info.log);
1020
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1021
+ info.screenshotPath = screenshotPath;
1022
+ Object.assign(e, { info: info });
975
1023
  error = e;
976
1024
  throw e;
977
1025
  }
@@ -979,10 +1027,10 @@ class StableBrowser {
979
1027
  const endTime = Date.now();
980
1028
  this._reportToWorld(world, {
981
1029
  element_name: selectors.element_name,
982
- type: Types.SET_DATE_TIME,
983
- screenshotId,
1030
+ type: Types.SET_INPUT,
1031
+ text: `Set input value`,
984
1032
  value: value,
985
- text: `setDateTime input with value: ${value}`,
1033
+ screenshotId,
986
1034
  result: error
987
1035
  ? {
988
1036
  status: "FAILED",
@@ -999,7 +1047,7 @@ class StableBrowser {
999
1047
  });
1000
1048
  }
1001
1049
  }
1002
- async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
1050
+ async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1003
1051
  this._validateSelectors(selectors);
1004
1052
  const startTime = Date.now();
1005
1053
  let error = null;
@@ -1011,6 +1059,7 @@ class StableBrowser {
1011
1059
  info.selectors = selectors;
1012
1060
  info.value = value;
1013
1061
  try {
1062
+ value = await this._replaceWithLocalData(value, this);
1014
1063
  let element = await this._locate(selectors, info, _params);
1015
1064
  //insert red border around the element
1016
1065
  await this.scrollIfNeeded(element, info);
@@ -1019,28 +1068,51 @@ class StableBrowser {
1019
1068
  try {
1020
1069
  await element.click();
1021
1070
  await new Promise((resolve) => setTimeout(resolve, 500));
1022
- const dateTimeValue = await getDateTimeValue({ value, element });
1023
- await element.evaluateHandle((el, dateTimeValue) => {
1024
- el.value = ""; // clear input
1025
- el.value = dateTimeValue;
1026
- }, dateTimeValue);
1071
+ if (format) {
1072
+ value = dayjs(value).format(format);
1073
+ await element.fill(value);
1074
+ }
1075
+ else {
1076
+ const dateTimeValue = await getDateTimeValue({ value, element });
1077
+ await element.evaluateHandle((el, dateTimeValue) => {
1078
+ el.value = ""; // clear input
1079
+ el.value = dateTimeValue;
1080
+ }, dateTimeValue);
1081
+ }
1082
+ if (enter) {
1083
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1084
+ await this.page.keyboard.press("Enter");
1085
+ await this.waitForPageLoad();
1086
+ }
1027
1087
  }
1028
- catch (error) {
1088
+ catch (err) {
1029
1089
  //await this.closeUnexpectedPopups();
1030
1090
  this.logger.error("setting date time input failed " + JSON.stringify(info));
1031
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1091
+ this.logger.info("Trying again");
1092
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1032
1093
  info.screenshotPath = screenshotPath;
1033
- Object.assign(error, { info: info });
1094
+ Object.assign(err, { info: info });
1034
1095
  await element.click();
1035
1096
  await new Promise((resolve) => setTimeout(resolve, 500));
1036
- const dateTimeValue = await getDateTimeValue({ value, element });
1037
- await element.evaluateHandle((el, dateTimeValue) => {
1038
- el.value = ""; // clear input
1039
- el.value = dateTimeValue;
1040
- }, dateTimeValue);
1097
+ if (format) {
1098
+ value = dayjs(value).format(format);
1099
+ await element.fill(value);
1100
+ }
1101
+ else {
1102
+ const dateTimeValue = await getDateTimeValue({ value, element });
1103
+ await element.evaluateHandle((el, dateTimeValue) => {
1104
+ el.value = ""; // clear input
1105
+ el.value = dateTimeValue;
1106
+ }, dateTimeValue);
1107
+ }
1108
+ if (enter) {
1109
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1110
+ await this.page.keyboard.press("Enter");
1111
+ await this.waitForPageLoad();
1112
+ }
1041
1113
  }
1042
1114
  }
1043
- catch (error) {
1115
+ catch (e) {
1044
1116
  error = e;
1045
1117
  throw e;
1046
1118
  }
@@ -1090,20 +1162,32 @@ class StableBrowser {
1090
1162
  await this.scrollIfNeeded(element, info);
1091
1163
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1092
1164
  await this._highlightElements(element);
1093
- try {
1094
- let currentValue = await element.inputValue();
1095
- if (currentValue) {
1096
- await element.fill("");
1165
+ if (options === null || options === undefined || !options.press) {
1166
+ try {
1167
+ let currentValue = await element.inputValue();
1168
+ if (currentValue) {
1169
+ await element.fill("");
1170
+ }
1171
+ }
1172
+ catch (e) {
1173
+ this.logger.info("unable to clear input value");
1097
1174
  }
1098
1175
  }
1099
- catch (e) {
1100
- this.logger.info("unable to clear input value");
1101
- }
1102
- try {
1103
- await element.click({ timeout: 5000 });
1176
+ if (options === null || options === undefined || options.press) {
1177
+ try {
1178
+ await element.click({ timeout: 5000 });
1179
+ }
1180
+ catch (e) {
1181
+ await element.dispatchEvent("click");
1182
+ }
1104
1183
  }
1105
- catch (e) {
1106
- await element.dispatchEvent("click");
1184
+ else {
1185
+ try {
1186
+ await element.focus();
1187
+ }
1188
+ catch (e) {
1189
+ await element.dispatchEvent("focus");
1190
+ }
1107
1191
  }
1108
1192
  await new Promise((resolve) => setTimeout(resolve, 500));
1109
1193
  const valueSegment = _value.split("&&");
@@ -1191,7 +1275,7 @@ class StableBrowser {
1191
1275
  let element = await this._locate(selectors, info, _params);
1192
1276
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1193
1277
  await this._highlightElements(element);
1194
- await element.fill(value, { timeout: 10000 });
1278
+ await element.fill(value);
1195
1279
  await element.dispatchEvent("change");
1196
1280
  if (enter) {
1197
1281
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -1410,7 +1494,7 @@ class StableBrowser {
1410
1494
  return info;
1411
1495
  }
1412
1496
  catch (e) {
1413
- //await this.closeUnexpectedPopups();
1497
+ await this.closeUnexpectedPopups();
1414
1498
  this.logger.error("verify element contains text failed " + info.log);
1415
1499
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1416
1500
  info.screenshotPath = screenshotPath;
@@ -1458,6 +1542,29 @@ class StableBrowser {
1458
1542
  }
1459
1543
  return dataFile;
1460
1544
  }
1545
+ async waitForUserInput(message, world = null) {
1546
+ if (!message) {
1547
+ message = "# Wait for user input. Press any key to continue";
1548
+ }
1549
+ else {
1550
+ message = "# Wait for user input. " + message;
1551
+ }
1552
+ message += "\n";
1553
+ const value = await new Promise((resolve) => {
1554
+ const rl = readline.createInterface({
1555
+ input: process.stdin,
1556
+ output: process.stdout,
1557
+ });
1558
+ rl.question(message, (answer) => {
1559
+ rl.close();
1560
+ resolve(answer);
1561
+ });
1562
+ });
1563
+ if (value) {
1564
+ this.logger.info(`{{userInput}} was set to: ${value}`);
1565
+ }
1566
+ this.setTestData({ userInput: value }, world);
1567
+ }
1461
1568
  setTestData(testData, world = null) {
1462
1569
  if (!testData) {
1463
1570
  return;
@@ -1470,15 +1577,62 @@ class StableBrowser {
1470
1577
  // save the data to the file
1471
1578
  fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
1472
1579
  }
1580
+ _getDataFilePath(fileName) {
1581
+ let dataFile = path.join(this.project_path, "data", fileName);
1582
+ if (fs.existsSync(dataFile)) {
1583
+ return dataFile;
1584
+ }
1585
+ dataFile = path.join(this.project_path, fileName);
1586
+ if (fs.existsSync(dataFile)) {
1587
+ return dataFile;
1588
+ }
1589
+ throw new Error("data file not found " + fileName);
1590
+ }
1591
+ _parseCSVSync(filePath) {
1592
+ const data = fs.readFileSync(filePath, "utf8");
1593
+ const results = [];
1594
+ return new Promise((resolve, reject) => {
1595
+ const readableStream = new Readable();
1596
+ readableStream._read = () => { }; // _read is required but you can noop it
1597
+ readableStream.push(data);
1598
+ readableStream.push(null);
1599
+ readableStream
1600
+ .pipe(csv())
1601
+ .on("data", (data) => results.push(data))
1602
+ .on("end", () => resolve(results))
1603
+ .on("error", (error) => reject(error));
1604
+ });
1605
+ }
1473
1606
  loadTestData(type, dataSelector, world = null) {
1474
1607
  switch (type) {
1475
1608
  case "users":
1476
- // check if file users.json exists
1477
- if (!fs.existsSync(path.join(this.project_path, "users.json"))) {
1478
- throw new Error("users.json file not found");
1609
+ // get the users.json file path
1610
+ let dataFile = this._getDataFilePath("users.json");
1611
+ // read the file and return the data
1612
+ const users = JSON.parse(fs.readFileSync(dataFile, "utf8"));
1613
+ for (let i = 0; i < users.length; i++) {
1614
+ if (users[i].username === dataSelector) {
1615
+ const userObj = {
1616
+ username: users[i].username,
1617
+ password: "secret:" + users[i].password,
1618
+ totp: users[i].secretKey ? "totp:" + users[i].secretKey : null,
1619
+ };
1620
+ this.setTestData(userObj, world);
1621
+ return userObj;
1622
+ }
1479
1623
  }
1624
+ throw new Error("user not found " + dataSelector);
1625
+ default:
1626
+ throw new Error("unknown type " + type);
1627
+ }
1628
+ }
1629
+ async loadTestDataAsync(type, dataSelector, world = null) {
1630
+ switch (type) {
1631
+ case "users": {
1632
+ // get the users.json file path
1633
+ let dataFile = this._getDataFilePath("users.json");
1480
1634
  // read the file and return the data
1481
- const users = JSON.parse(fs.readFileSync(path.join(this.project_path, "users.json"), "utf8"));
1635
+ const users = JSON.parse(fs.readFileSync(dataFile, "utf8"));
1482
1636
  for (let i = 0; i < users.length; i++) {
1483
1637
  if (users[i].username === dataSelector) {
1484
1638
  const userObj = {
@@ -1491,6 +1645,29 @@ class StableBrowser {
1491
1645
  }
1492
1646
  }
1493
1647
  throw new Error("user not found " + dataSelector);
1648
+ }
1649
+ case "csv": {
1650
+ // 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
1651
+ const parts = dataSelector.split(":");
1652
+ let rowNumber = 0;
1653
+ if (parts.length > 1) {
1654
+ rowNumber = parseInt(parts[1]);
1655
+ }
1656
+ let dataFile = this._getDataFilePath(parts[0]);
1657
+ const results = await this._parseCSVSync(dataFile);
1658
+ // result stracture:
1659
+ // [
1660
+ // { NAME: 'Daffy Duck', AGE: '24' },
1661
+ // { NAME: 'Bugs Bunny', AGE: '22' }
1662
+ // ]
1663
+ // verify the row number is within the range
1664
+ if (rowNumber >= results.length) {
1665
+ throw new Error("row number is out of range " + rowNumber);
1666
+ }
1667
+ const data = results[rowNumber];
1668
+ this.setTestData(data, world);
1669
+ return data;
1670
+ }
1494
1671
  default:
1495
1672
  throw new Error("unknown type " + type);
1496
1673
  }
@@ -1595,13 +1772,13 @@ class StableBrowser {
1595
1772
  ])));
1596
1773
  const { data } = await client.send("Page.captureScreenshot", {
1597
1774
  format: "png",
1598
- clip: {
1599
- x: 0,
1600
- y: 0,
1601
- width: viewportWidth,
1602
- height: viewportHeight,
1603
- scale: 1,
1604
- },
1775
+ // clip: {
1776
+ // x: 0,
1777
+ // y: 0,
1778
+ // width: viewportWidth,
1779
+ // height: viewportHeight,
1780
+ // scale: 1,
1781
+ // },
1605
1782
  });
1606
1783
  if (!screenshotPath) {
1607
1784
  return data;
@@ -1707,7 +1884,8 @@ class StableBrowser {
1707
1884
  if (world) {
1708
1885
  world[variable] = info.value;
1709
1886
  }
1710
- this.logger.info("world." + variable + "=" + info.value);
1887
+ this.setTestData({ [variable]: info.value }, world);
1888
+ this.logger.info("set test data: " + variable + "=" + info.value);
1711
1889
  return info;
1712
1890
  }
1713
1891
  catch (e) {
@@ -1744,6 +1922,91 @@ class StableBrowser {
1744
1922
  });
1745
1923
  }
1746
1924
  }
1925
+ async extractEmailData(emailAddress, options, world) {
1926
+ if (!emailAddress) {
1927
+ throw new Error("email address is null");
1928
+ }
1929
+ // check if address contain @
1930
+ if (emailAddress.indexOf("@") === -1) {
1931
+ emailAddress = emailAddress + "@blinq-mail.io";
1932
+ }
1933
+ else {
1934
+ if (!emailAddress.toLowerCase().endsWith("@blinq-mail.io")) {
1935
+ throw new Error("email address should end with @blinq-mail.io");
1936
+ }
1937
+ }
1938
+ const startTime = Date.now();
1939
+ let timeout = 60000;
1940
+ if (options && options.timeout) {
1941
+ timeout = options.timeout;
1942
+ }
1943
+ const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1944
+ const request = {
1945
+ method: "POST",
1946
+ url: serviceUrl,
1947
+ headers: {
1948
+ "Content-Type": "application/json",
1949
+ Authorization: `Bearer ${process.env.TOKEN}`,
1950
+ },
1951
+ data: JSON.stringify({
1952
+ email: emailAddress,
1953
+ }),
1954
+ };
1955
+ let errorCount = 0;
1956
+ while (true) {
1957
+ try {
1958
+ let result = await this.context.api.request(request);
1959
+ // the response body expected to be the following:
1960
+ // {
1961
+ // "status": true,
1962
+ // "content": {
1963
+ // "url": "",
1964
+ // "code": "112112",
1965
+ // "name": "generate_link_or_code"
1966
+ // }
1967
+ //}
1968
+ if ((result && result.data, result.data.status === true)) {
1969
+ let codeOrUrlFound = false;
1970
+ let emailCode = null;
1971
+ let emailUrl = null;
1972
+ // check if a code is returned
1973
+ if (result.data.content && result.data.content.code) {
1974
+ let code = result.data.content.code;
1975
+ this.setTestData({ emailCode: code }, world);
1976
+ this.logger.info("set test data: emailCode = " + code);
1977
+ emailCode = code;
1978
+ codeOrUrlFound = true;
1979
+ }
1980
+ // check if a url is returned
1981
+ if (result.data.content && result.data.content.url) {
1982
+ let url = result.data.content.url;
1983
+ this.setTestData({ emailUrl: url }, world);
1984
+ this.logger.info("set test data: emailUrl = " + url);
1985
+ emailUrl = url;
1986
+ codeOrUrlFound = true;
1987
+ }
1988
+ if (codeOrUrlFound) {
1989
+ return { emailUrl, emailCode };
1990
+ }
1991
+ else {
1992
+ this.logger.info("an email received but no code or url found");
1993
+ }
1994
+ }
1995
+ }
1996
+ catch (e) {
1997
+ errorCount++;
1998
+ if (errorCount > 3) {
1999
+ throw e;
2000
+ }
2001
+ // ignore
2002
+ }
2003
+ // check if the timeout is reached
2004
+ if (Date.now() - startTime > timeout) {
2005
+ throw new Error("timeout reached");
2006
+ }
2007
+ await new Promise((resolve) => setTimeout(resolve, 5000));
2008
+ }
2009
+ }
1747
2010
  async _highlightElements(scope, css) {
1748
2011
  try {
1749
2012
  if (!scope) {
@@ -1966,6 +2229,16 @@ class StableBrowser {
1966
2229
  });
1967
2230
  }
1968
2231
  }
2232
+ _getServerUrl() {
2233
+ let serviceUrl = "https://api.blinq.io";
2234
+ if (process.env.NODE_ENV_BLINQ === "dev") {
2235
+ serviceUrl = "https://dev.api.blinq.io";
2236
+ }
2237
+ else if (process.env.NODE_ENV_BLINQ === "stage") {
2238
+ serviceUrl = "https://stage.api.blinq.io";
2239
+ }
2240
+ return serviceUrl;
2241
+ }
1969
2242
  async visualVerification(text, options = {}, world = null) {
1970
2243
  const startTime = Date.now();
1971
2244
  let error = null;
@@ -1980,13 +2253,7 @@ class StableBrowser {
1980
2253
  throw new Error("TOKEN is not set");
1981
2254
  }
1982
2255
  try {
1983
- let serviceUrl = "https://api.blinq.io";
1984
- if (process.env.NODE_ENV_BLINQ === "dev") {
1985
- serviceUrl = "https://dev.api.blinq.io";
1986
- }
1987
- else if (process.env.NODE_ENV_BLINQ === "stage") {
1988
- serviceUrl = "https://stage.api.blinq.io";
1989
- }
2256
+ let serviceUrl = this._getServerUrl();
1990
2257
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1991
2258
  info.screenshotPath = screenshotPath;
1992
2259
  const screenshot = await this.takeScreenshot();
@@ -2337,13 +2604,13 @@ class StableBrowser {
2337
2604
  }
2338
2605
  catch (e) {
2339
2606
  if (e.label === "networkidle") {
2340
- console.log("waitted for the network to be idle timeout");
2607
+ console.log("waited for the network to be idle timeout");
2341
2608
  }
2342
2609
  else if (e.label === "load") {
2343
- console.log("waitted for the load timeout");
2610
+ console.log("waited for the load timeout");
2344
2611
  }
2345
2612
  else if (e.label === "domcontentloaded") {
2346
- console.log("waitted for the domcontent loaded timeout");
2613
+ console.log("waited for the domcontent loaded timeout");
2347
2614
  }
2348
2615
  console.log(".");
2349
2616
  }
@@ -2378,13 +2645,6 @@ class StableBrowser {
2378
2645
  const info = {};
2379
2646
  try {
2380
2647
  await this.page.close();
2381
- if (this.context && this.context.pages && this.context.pages.length > 0) {
2382
- this.context.pages.pop();
2383
- this.page = this.context.pages[this.context.pages.length - 1];
2384
- this.context.page = this.page;
2385
- let title = await this.page.title();
2386
- console.log("Switched to page " + title);
2387
- }
2388
2648
  }
2389
2649
  catch (e) {
2390
2650
  console.log(".");
@@ -2493,33 +2753,18 @@ class StableBrowser {
2493
2753
  }
2494
2754
  async scrollIfNeeded(element, info) {
2495
2755
  try {
2496
- let didScroll = await element.evaluate((node) => {
2497
- const rect = node.getBoundingClientRect();
2498
- if (rect &&
2499
- rect.top >= 0 &&
2500
- rect.left >= 0 &&
2501
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
2502
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
2503
- return false;
2504
- }
2505
- else {
2506
- node.scrollIntoView({
2507
- behavior: "smooth",
2508
- block: "center",
2509
- inline: "center",
2510
- });
2511
- return true;
2512
- }
2756
+ await element.scrollIntoViewIfNeeded({
2757
+ timeout: 2000,
2513
2758
  });
2514
- if (didScroll) {
2515
- await new Promise((resolve) => setTimeout(resolve, 500));
2516
- if (info) {
2517
- info.box = await element.boundingBox();
2518
- }
2759
+ await new Promise((resolve) => setTimeout(resolve, 500));
2760
+ if (info) {
2761
+ info.box = await element.boundingBox({
2762
+ timeout: 1000,
2763
+ });
2519
2764
  }
2520
2765
  }
2521
2766
  catch (e) {
2522
- console.log("scroll failed");
2767
+ console.log("#-#");
2523
2768
  }
2524
2769
  }
2525
2770
  _reportToWorld(world, properties) {