automation_model 1.0.364-dev.0 → 1.0.364-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,7 @@ 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";
15
16
  const Types = {
16
17
  CLICK: "click_element",
17
18
  NAVIGATE: "navigate",
@@ -27,6 +28,7 @@ const Types = {
27
28
  SELECT: "select_combobox",
28
29
  VERIFY_PAGE_PATH: "verify_page_path",
29
30
  TYPE_PRESS: "type_press",
31
+ PRESS: "press_key",
30
32
  HOVER: "hover_element",
31
33
  CHECK: "check_element",
32
34
  UNCHECK: "uncheck_element",
@@ -80,9 +82,15 @@ class StableBrowser {
80
82
  this.page = page;
81
83
  context.page = page;
82
84
  context.pages.push(page);
83
- this.webLogFile = this.getWebLogFile(logFolder);
84
- this.registerConsoleLogListener(page, context, this.webLogFile);
85
- this.registerRequestListener();
85
+ page.on("close", async () => {
86
+ if (this.context && this.context.pages && this.context.pages.length > 0) {
87
+ this.context.pages.pop();
88
+ this.page = this.context.pages[this.context.pages.length - 1];
89
+ this.context.page = this.page;
90
+ let title = await this.page.title();
91
+ console.log("Switched to page " + title);
92
+ }
93
+ });
86
94
  try {
87
95
  await this.waitForPageLoad();
88
96
  console.log("Switch page: " + (await page.title()));
@@ -91,7 +99,7 @@ class StableBrowser {
91
99
  this.logger.error("error on page load " + e);
92
100
  }
93
101
  context.pageLoading.status = false;
94
- });
102
+ }.bind(this));
95
103
  }
96
104
  getWebLogFile(logFolder) {
97
105
  if (!fs.existsSync(logFolder)) {
@@ -168,23 +176,65 @@ class StableBrowser {
168
176
  return text;
169
177
  }
170
178
  for (let key in _params) {
171
- text = text.replaceAll(new RegExp("{" + key + "}", "g"), _params[key]);
179
+ let regValue = key;
180
+ if (key.startsWith("_")) {
181
+ // remove the _ prefix
182
+ regValue = key.substring(1);
183
+ }
184
+ text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
172
185
  }
173
186
  return text;
174
187
  }
188
+ _fixLocatorUsingParams(locator, _params) {
189
+ // check if not null
190
+ if (!locator) {
191
+ return locator;
192
+ }
193
+ // clone the locator
194
+ locator = JSON.parse(JSON.stringify(locator));
195
+ this.scanAndManipulate(locator, _params);
196
+ return locator;
197
+ }
198
+ _isObject(value) {
199
+ return value && typeof value === "object" && value.constructor === Object;
200
+ }
201
+ scanAndManipulate(currentObj, _params) {
202
+ for (const key in currentObj) {
203
+ if (typeof currentObj[key] === "string") {
204
+ // Perform string manipulation
205
+ currentObj[key] = this._fixUsingParams(currentObj[key], _params);
206
+ }
207
+ else if (this._isObject(currentObj[key])) {
208
+ // Recursively scan nested objects
209
+ this.scanAndManipulate(currentObj[key], _params);
210
+ }
211
+ }
212
+ }
175
213
  _getLocator(locator, scope, _params) {
214
+ locator = this._fixLocatorUsingParams(locator, _params);
215
+ if (locator.type === "pw_selector") {
216
+ return scope.locator(locator.selector);
217
+ }
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
+ if ((locator === null || locator === void 0 ? void 0 : locator.engine) && (locator === null || locator === void 0 ? void 0 : locator.score) <= 520) {
232
+ let selector = locator.selector.replace(/"/g, '\\"');
233
+ if (locator.engine === "internal:att") {
234
+ selector = `[${selector}]`;
235
+ }
236
+ const locator = scope.locator(`${locator.engine}="${selector}"`);
237
+ return locator;
188
238
  }
189
239
  throw new Error("unknown locator type");
190
240
  }
@@ -309,10 +359,13 @@ class StableBrowser {
309
359
  }
310
360
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
311
361
  let locatorSearch = selectorHierarchy[index];
312
- info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
362
+ //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
313
363
  let locator = null;
314
364
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
315
365
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
366
+ if (!locatorString) {
367
+ return;
368
+ }
316
369
  locator = this._getLocator({ css: locatorString }, scope, _params);
317
370
  }
318
371
  else if (locatorSearch.text) {
@@ -334,7 +387,7 @@ class StableBrowser {
334
387
  // cssHref = true;
335
388
  // }
336
389
  let count = await locator.count();
337
- info.log += "total elements found " + count + "\n";
390
+ //info.log += "total elements found " + count + "\n";
338
391
  //let visibleCount = 0;
339
392
  let visibleLocator = null;
340
393
  if (locatorSearch.index && locatorSearch.index < count) {
@@ -344,16 +397,20 @@ class StableBrowser {
344
397
  for (let j = 0; j < count; j++) {
345
398
  let visible = await locator.nth(j).isVisible();
346
399
  const enabled = await locator.nth(j).isEnabled();
347
- info.log += "element " + j + " visible " + visible + " enabled " + enabled + "\n";
348
400
  if (!visibleOnly) {
349
401
  visible = true;
350
402
  }
351
403
  if (visible && enabled) {
352
404
  foundLocators.push(locator.nth(j));
353
- // if (cssHref) {
354
- // info.log += "css href locator found, will ignore all others" + "\n";
355
- // break;
356
- // }
405
+ }
406
+ else {
407
+ if (!info.printMessages) {
408
+ info.printMessages = {};
409
+ }
410
+ if (!info.printMessages[j.toString()]) {
411
+ info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
412
+ info.printMessages[j.toString()] = true;
413
+ }
357
414
  }
358
415
  }
359
416
  }
@@ -379,15 +436,30 @@ class StableBrowser {
379
436
  }
380
437
  if (result.foundElements.length > 0) {
381
438
  // need to handle popup
382
- let dialogCloseLocator = this._getLocator(this.configuration.popupHandlers[result.locatorIndex].close_dialog_locator, scope, _params);
383
- await dialogCloseLocator.click();
384
- return { rerun: true };
439
+ const closeHandlerGroup = [];
440
+ closeHandlerGroup.push(this.configuration.popupHandlers[result.locatorIndex].close_dialog_locator);
441
+ for (let i = 0; i < scopes.length; i++) {
442
+ result = await this._scanLocatorsGroup(closeHandlerGroup, scopes[i], _params, info, true);
443
+ if (result.foundElements.length > 0) {
444
+ break;
445
+ }
446
+ }
447
+ if (result.foundElements.length > 0) {
448
+ let dialogCloseLocator = result.foundElements[0].locator;
449
+ await dialogCloseLocator.click();
450
+ return { rerun: true };
451
+ }
385
452
  }
386
453
  }
387
454
  return { rerun: false };
388
455
  }
389
456
  async _locate(selectors, info, _params, timeout = 30000) {
390
457
  for (let i = 0; i < 3; i++) {
458
+ info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
459
+ for (let j = 0; j < selectors.locators.length; j++) {
460
+ let selector = selectors.locators[j];
461
+ info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
462
+ }
391
463
  let element = await this._locate_internal(selectors, info, _params, timeout);
392
464
  if (!element.rerun) {
393
465
  return element;
@@ -403,6 +475,7 @@ class StableBrowser {
403
475
  //let arrayMode = Array.isArray(selectors);
404
476
  let scope = this.page;
405
477
  if (selectors.iframe_src || selectors.frameLocators) {
478
+ info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
406
479
  while (true) {
407
480
  let frameFound = false;
408
481
  if (selectors.frameLocators) {
@@ -463,19 +536,25 @@ class StableBrowser {
463
536
  if (popupResult.rerun) {
464
537
  return popupResult;
465
538
  }
466
- info.log += "scanning locators in priority 1" + "\n";
539
+ // info.log += "scanning locators in priority 1" + "\n";
540
+ let onlyPriority3 = selectorsLocators[0].priority === 3;
467
541
  result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
468
542
  if (result.foundElements.length === 0) {
469
- info.log += "scanning locators in priority 2" + "\n";
543
+ // info.log += "scanning locators in priority 2" + "\n";
470
544
  result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
471
545
  }
472
- if (result.foundElements.length === 0 && !highPriorityOnly) {
473
- info.log += "scanning locators in priority 3" + "\n";
546
+ if (result.foundElements.length === 0 && onlyPriority3) {
474
547
  result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
475
548
  }
549
+ else {
550
+ if (result.foundElements.length === 0 && !highPriorityOnly) {
551
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
552
+ }
553
+ }
476
554
  let foundElements = result.foundElements;
477
555
  if (foundElements.length === 1 && foundElements[0].unique) {
478
556
  info.box = foundElements[0].box;
557
+ info.log += "unique element was found, locator: " + foundElements[0].locator + "\n";
479
558
  return foundElements[0].locator;
480
559
  }
481
560
  //info.log += "total elements found " + foundElements.length);
@@ -505,6 +584,7 @@ class StableBrowser {
505
584
  }
506
585
  }
507
586
  if (maxCountElement) {
587
+ info.log += "unique element was found, locator: " + maxCountElement.locator + "\n";
508
588
  info.box = await maxCountElement.locator.boundingBox();
509
589
  return maxCountElement.locator;
510
590
  }
@@ -513,9 +593,11 @@ class StableBrowser {
513
593
  break;
514
594
  }
515
595
  if (performance.now() - startTime > highPriorityTimeout) {
596
+ info.log += "high priority timeout, will try all elements" + "\n";
516
597
  highPriorityOnly = false;
517
598
  }
518
599
  if (performance.now() - startTime > visibleOnlyTimeout) {
600
+ info.log += "visible only timeout, will try all elements" + "\n";
519
601
  visibleOnly = false;
520
602
  }
521
603
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -560,7 +642,7 @@ class StableBrowser {
560
642
  this._validateSelectors(selectors);
561
643
  const startTime = Date.now();
562
644
  const info = {};
563
- info.log = "";
645
+ info.log = "***** click on " + selectors.element_name + " *****\n";
564
646
  info.operation = "click";
565
647
  info.selectors = selectors;
566
648
  let error = null;
@@ -905,12 +987,12 @@ class StableBrowser {
905
987
  await this.waitForPageLoad();
906
988
  }
907
989
  }
908
- catch (error) {
990
+ catch (err) {
909
991
  //await this.closeUnexpectedPopups();
910
992
  this.logger.error("setting date time input failed " + JSON.stringify(info));
911
993
  this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
912
994
  info.screenshotPath = screenshotPath;
913
- Object.assign(error, { info: info });
995
+ Object.assign(err, { info: info });
914
996
  await element.click();
915
997
  await new Promise((resolve) => setTimeout(resolve, 500));
916
998
  if (format) {
@@ -931,76 +1013,7 @@ class StableBrowser {
931
1013
  }
932
1014
  }
933
1015
  }
934
- catch (error) {
935
- error = e;
936
- throw e;
937
- }
938
- finally {
939
- const endTime = Date.now();
940
- this._reportToWorld(world, {
941
- element_name: selectors.element_name,
942
- type: Types.SET_DATE_TIME,
943
- screenshotId,
944
- value: value,
945
- text: `setDateTime input with value: ${value}`,
946
- result: error
947
- ? {
948
- status: "FAILED",
949
- startTime,
950
- endTime,
951
- message: error === null || error === void 0 ? void 0 : error.message,
952
- }
953
- : {
954
- status: "PASSED",
955
- startTime,
956
- endTime,
957
- },
958
- info: info,
959
- });
960
- }
961
- }
962
- async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
963
- this._validateSelectors(selectors);
964
- const startTime = Date.now();
965
- let error = null;
966
- let screenshotId = null;
967
- let screenshotPath = null;
968
- const info = {};
969
- info.log = "";
970
- info.operation = Types.SET_DATE_TIME;
971
- info.selectors = selectors;
972
- info.value = value;
973
- try {
974
- let element = await this._locate(selectors, info, _params);
975
- //insert red border around the element
976
- await this.scrollIfNeeded(element, info);
977
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
978
- await this._highlightElements(element);
979
- try {
980
- await element.click();
981
- await new Promise((resolve) => setTimeout(resolve, 500));
982
- const dateTimeValue = await getDateTimeValue({ value, element });
983
- await element.evaluateHandle((el, dateTimeValue) => {
984
- el.value = ""; // clear input
985
- el.value = dateTimeValue;
986
- }, dateTimeValue);
987
- }
988
- catch (error) {
989
- //await this.closeUnexpectedPopups();
990
- this.logger.error("setting date time input failed " + JSON.stringify(info));
991
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
992
- info.screenshotPath = screenshotPath;
993
- Object.assign(error, { info: info });
994
- await element.click();
995
- 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);
1001
- }
1002
- }
1003
- catch (error) {
1016
+ catch (e) {
1004
1017
  error = e;
1005
1018
  throw e;
1006
1019
  }
@@ -1035,7 +1048,7 @@ class StableBrowser {
1035
1048
  let screenshotId = null;
1036
1049
  let screenshotPath = null;
1037
1050
  const info = {};
1038
- info.log = "";
1051
+ info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
1039
1052
  info.operation = "clickType";
1040
1053
  info.selectors = selectors;
1041
1054
  const newValue = await this._replaceWithLocalData(_value, world);
@@ -1050,20 +1063,32 @@ class StableBrowser {
1050
1063
  await this.scrollIfNeeded(element, info);
1051
1064
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1052
1065
  await this._highlightElements(element);
1053
- try {
1054
- let currentValue = await element.inputValue();
1055
- if (currentValue) {
1056
- await element.fill("");
1066
+ if (options === null || options === undefined || !options.press) {
1067
+ try {
1068
+ let currentValue = await element.inputValue();
1069
+ if (currentValue) {
1070
+ await element.fill("");
1071
+ }
1072
+ }
1073
+ catch (e) {
1074
+ this.logger.info("unable to clear input value");
1057
1075
  }
1058
1076
  }
1059
- catch (e) {
1060
- this.logger.info("unable to clear input value");
1061
- }
1062
- try {
1063
- await element.click({ timeout: 5000 });
1077
+ if (options === null || options === undefined || options.press) {
1078
+ try {
1079
+ await element.click({ timeout: 5000 });
1080
+ }
1081
+ catch (e) {
1082
+ await element.dispatchEvent("click");
1083
+ }
1064
1084
  }
1065
- catch (e) {
1066
- await element.dispatchEvent("click");
1085
+ else {
1086
+ try {
1087
+ await element.focus();
1088
+ }
1089
+ catch (e) {
1090
+ await element.dispatchEvent("focus");
1091
+ }
1067
1092
  }
1068
1093
  await new Promise((resolve) => setTimeout(resolve, 500));
1069
1094
  const valueSegment = _value.split("&&");
@@ -1143,7 +1168,7 @@ class StableBrowser {
1143
1168
  let screenshotId = null;
1144
1169
  let screenshotPath = null;
1145
1170
  const info = {};
1146
- info.log = "";
1171
+ info.log = "***** fill on " + selectors.element_name + " with value " + value + "*****\n";
1147
1172
  info.operation = "fill";
1148
1173
  info.selectors = selectors;
1149
1174
  info.value = value;
@@ -1259,7 +1284,8 @@ class StableBrowser {
1259
1284
  let screenshotId = null;
1260
1285
  let screenshotPath = null;
1261
1286
  const info = {};
1262
- info.log = "";
1287
+ info.log =
1288
+ "***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
1263
1289
  info.operation = "containsPattern";
1264
1290
  info.selectors = selectors;
1265
1291
  info.value = text;
@@ -1325,7 +1351,7 @@ class StableBrowser {
1325
1351
  let screenshotId = null;
1326
1352
  let screenshotPath = null;
1327
1353
  const info = {};
1328
- info.log = "";
1354
+ info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
1329
1355
  info.operation = "containsText";
1330
1356
  info.selectors = selectors;
1331
1357
  const newValue = await this._replaceWithLocalData(text, world);
@@ -1429,15 +1455,62 @@ class StableBrowser {
1429
1455
  // save the data to the file
1430
1456
  fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
1431
1457
  }
1458
+ _getDataFilePath(fileName) {
1459
+ let dataFile = path.join(this.project_path, "data", fileName);
1460
+ if (fs.existsSync(dataFile)) {
1461
+ return dataFile;
1462
+ }
1463
+ dataFile = path.join(this.project_path, fileName);
1464
+ if (fs.existsSync(dataFile)) {
1465
+ return dataFile;
1466
+ }
1467
+ throw new Error("data file not found " + fileName);
1468
+ }
1469
+ _parseCSVSync(filePath) {
1470
+ const data = fs.readFileSync(filePath, "utf8");
1471
+ const results = [];
1472
+ return new Promise((resolve, reject) => {
1473
+ const readableStream = new stream.Readable();
1474
+ readableStream._read = () => { }; // _read is required but you can noop it
1475
+ readableStream.push(data);
1476
+ readableStream.push(null);
1477
+ readableStream
1478
+ .pipe(csv())
1479
+ .on("data", (data) => results.push(data))
1480
+ .on("end", () => resolve(results))
1481
+ .on("error", (error) => reject(error));
1482
+ });
1483
+ }
1432
1484
  loadTestData(type, dataSelector, world = null) {
1433
1485
  switch (type) {
1434
1486
  case "users":
1435
- // check if file users.json exists
1436
- if (!fs.existsSync(path.join(this.project_path, "users.json"))) {
1437
- throw new Error("users.json file not found");
1487
+ // get the users.json file path
1488
+ let dataFile = this._getDataFilePath("users.json");
1489
+ // read the file and return the data
1490
+ const users = JSON.parse(fs.readFileSync(dataFile, "utf8"));
1491
+ for (let i = 0; i < users.length; i++) {
1492
+ if (users[i].username === dataSelector) {
1493
+ const userObj = {
1494
+ username: users[i].username,
1495
+ password: "secret:" + users[i].password,
1496
+ totp: users[i].secretKey ? "totp:" + users[i].secretKey : null,
1497
+ };
1498
+ this.setTestData(userObj, world);
1499
+ return userObj;
1500
+ }
1438
1501
  }
1502
+ throw new Error("user not found " + dataSelector);
1503
+ default:
1504
+ throw new Error("unknown type " + type);
1505
+ }
1506
+ }
1507
+ async loadTestDataAsync(type, dataSelector, world = null) {
1508
+ switch (type) {
1509
+ case "users": {
1510
+ // get the users.json file path
1511
+ let dataFile = this._getDataFilePath("users.json");
1439
1512
  // read the file and return the data
1440
- const users = JSON.parse(fs.readFileSync(path.join(this.project_path, "users.json"), "utf8"));
1513
+ const users = JSON.parse(fs.readFileSync(dataFile, "utf8"));
1441
1514
  for (let i = 0; i < users.length; i++) {
1442
1515
  if (users[i].username === dataSelector) {
1443
1516
  const userObj = {
@@ -1450,6 +1523,29 @@ class StableBrowser {
1450
1523
  }
1451
1524
  }
1452
1525
  throw new Error("user not found " + dataSelector);
1526
+ }
1527
+ case "csv": {
1528
+ // 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
1529
+ const parts = dataSelector.split(":");
1530
+ let rowNumber = 0;
1531
+ if (parts.length > 1) {
1532
+ rowNumber = parseInt(parts[1]);
1533
+ }
1534
+ let dataFile = this._getDataFilePath(parts[0]);
1535
+ const results = await this._parseCSVSync(dataFile);
1536
+ // result stracture:
1537
+ // [
1538
+ // { NAME: 'Daffy Duck', AGE: '24' },
1539
+ // { NAME: 'Bugs Bunny', AGE: '22' }
1540
+ // ]
1541
+ // verify the row number is within the range
1542
+ if (rowNumber >= results.length) {
1543
+ throw new Error("row number is out of range " + rowNumber);
1544
+ }
1545
+ const data = results[rowNumber];
1546
+ this.setTestData(data, world);
1547
+ return data;
1548
+ }
1453
1549
  default:
1454
1550
  throw new Error("unknown type " + type);
1455
1551
  }
@@ -1588,7 +1684,7 @@ class StableBrowser {
1588
1684
  let screenshotPath = null;
1589
1685
  await new Promise((resolve) => setTimeout(resolve, 2000));
1590
1686
  const info = {};
1591
- info.log = "";
1687
+ info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
1592
1688
  info.operation = "verify";
1593
1689
  info.selectors = selectors;
1594
1690
  try {
@@ -1641,7 +1737,7 @@ class StableBrowser {
1641
1737
  let screenshotPath = null;
1642
1738
  await new Promise((resolve) => setTimeout(resolve, 2000));
1643
1739
  const info = {};
1644
- info.log = "";
1740
+ info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1645
1741
  info.operation = "extract";
1646
1742
  info.selectors = selectors;
1647
1743
  try {
@@ -1666,7 +1762,8 @@ class StableBrowser {
1666
1762
  if (world) {
1667
1763
  world[variable] = info.value;
1668
1764
  }
1669
- this.logger.info("world." + variable + "=" + info.value);
1765
+ this.setTestData({ [variable]: info.value }, world);
1766
+ this.logger.info("set test data: " + variable + "=" + info.value);
1670
1767
  return info;
1671
1768
  }
1672
1769
  catch (e) {
@@ -1703,6 +1800,91 @@ class StableBrowser {
1703
1800
  });
1704
1801
  }
1705
1802
  }
1803
+ async extractEmailData(emailAddress, options, world) {
1804
+ if (!emailAddress) {
1805
+ throw new Error("email address is null");
1806
+ }
1807
+ // check if address contain @
1808
+ if (emailAddress.indexOf("@") === -1) {
1809
+ emailAddress = emailAddress + "@blinq-mail.io";
1810
+ }
1811
+ else {
1812
+ if (!emailAddress.toLowerCase().endsWith("@blinq-mail.io")) {
1813
+ throw new Error("email address should end with @blinq-mail.io");
1814
+ }
1815
+ }
1816
+ const startTime = Date.now();
1817
+ let timeout = 60000;
1818
+ if (options && options.timeout) {
1819
+ timeout = options.timeout;
1820
+ }
1821
+ const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1822
+ const request = {
1823
+ method: "POST",
1824
+ url: serviceUrl,
1825
+ headers: {
1826
+ "Content-Type": "application/json",
1827
+ Authorization: `Bearer ${process.env.TOKEN}`,
1828
+ },
1829
+ data: JSON.stringify({
1830
+ email: emailAddress,
1831
+ }),
1832
+ };
1833
+ let errorCount = 0;
1834
+ while (true) {
1835
+ try {
1836
+ let result = await this.context.api.request(request);
1837
+ // the response body expected to be the following:
1838
+ // {
1839
+ // "status": true,
1840
+ // "content": {
1841
+ // "url": "",
1842
+ // "code": "112112",
1843
+ // "name": "generate_link_or_code"
1844
+ // }
1845
+ //}
1846
+ if ((result && result.data, result.data.status === true)) {
1847
+ let codeOrUrlFound = false;
1848
+ let emailCode = null;
1849
+ let emailUrl = null;
1850
+ // check if a code is returned
1851
+ if (result.data.content && result.data.content.code) {
1852
+ let code = result.data.content.code;
1853
+ this.setTestData({ emailCode: code }, world);
1854
+ this.logger.info("set test data: emailCode = " + code);
1855
+ emailCode = code;
1856
+ codeOrUrlFound = true;
1857
+ }
1858
+ // check if a url is returned
1859
+ if (result.data.content && result.data.content.url) {
1860
+ let url = result.data.content.url;
1861
+ this.setTestData({ emailUrl: url }, world);
1862
+ this.logger.info("set test data: emailUrl = " + url);
1863
+ emailUrl = url;
1864
+ codeOrUrlFound = true;
1865
+ }
1866
+ if (codeOrUrlFound) {
1867
+ return { emailUrl, emailCode };
1868
+ }
1869
+ else {
1870
+ this.logger.info("an email received but no code or url found");
1871
+ }
1872
+ }
1873
+ }
1874
+ catch (e) {
1875
+ errorCount++;
1876
+ if (errorCount > 3) {
1877
+ throw e;
1878
+ }
1879
+ // ignore
1880
+ }
1881
+ // check if the timeout is reached
1882
+ if (Date.now() - startTime > timeout) {
1883
+ throw new Error("timeout reached");
1884
+ }
1885
+ await new Promise((resolve) => setTimeout(resolve, 5000));
1886
+ }
1887
+ }
1706
1888
  async _highlightElements(scope, css) {
1707
1889
  try {
1708
1890
  if (!scope) {
@@ -1774,7 +1956,7 @@ class StableBrowser {
1774
1956
  let screenshotPath = null;
1775
1957
  await new Promise((resolve) => setTimeout(resolve, 2000));
1776
1958
  const info = {};
1777
- info.log = "";
1959
+ info.log = "***** verify page path " + pathPart + " *****\n";
1778
1960
  info.operation = "verifyPagePath";
1779
1961
  const newValue = await this._replaceWithLocalData(pathPart, world);
1780
1962
  if (newValue !== pathPart) {
@@ -1835,7 +2017,7 @@ class StableBrowser {
1835
2017
  let screenshotPath = null;
1836
2018
  await new Promise((resolve) => setTimeout(resolve, 2000));
1837
2019
  const info = {};
1838
- info.log = "";
2020
+ info.log = "***** verify text " + text + " exists in page *****\n";
1839
2021
  info.operation = "verifyTextExistInPage";
1840
2022
  const newValue = await this._replaceWithLocalData(text, world);
1841
2023
  if (newValue !== text) {
@@ -1884,8 +2066,10 @@ class StableBrowser {
1884
2066
  const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
1885
2067
  await this._highlightElements(frame, dataAttribute);
1886
2068
  const element = await frame.$(dataAttribute);
1887
- await this.scrollIfNeeded(element, info);
1888
- await element.dispatchEvent("bvt_verify_page_contains_text");
2069
+ if (element) {
2070
+ await this.scrollIfNeeded(element, info);
2071
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2072
+ }
1889
2073
  }
1890
2074
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1891
2075
  return info;
@@ -1923,6 +2107,16 @@ class StableBrowser {
1923
2107
  });
1924
2108
  }
1925
2109
  }
2110
+ _getServerUrl() {
2111
+ let serviceUrl = "https://api.blinq.io";
2112
+ if (process.env.NODE_ENV_BLINQ === "dev") {
2113
+ serviceUrl = "https://dev.api.blinq.io";
2114
+ }
2115
+ else if (process.env.NODE_ENV_BLINQ === "stage") {
2116
+ serviceUrl = "https://stage.api.blinq.io";
2117
+ }
2118
+ return serviceUrl;
2119
+ }
1926
2120
  async visualVerification(text, options = {}, world = null) {
1927
2121
  const startTime = Date.now();
1928
2122
  let error = null;
@@ -1937,13 +2131,7 @@ class StableBrowser {
1937
2131
  throw new Error("TOKEN is not set");
1938
2132
  }
1939
2133
  try {
1940
- let serviceUrl = "https://api.blinq.io";
1941
- if (process.env.NODE_ENV_BLINQ === "dev") {
1942
- serviceUrl = "https://dev.api.blinq.io";
1943
- }
1944
- else if (process.env.NODE_ENV_BLINQ === "stage") {
1945
- serviceUrl = "https://stage.api.blinq.io";
1946
- }
2134
+ let serviceUrl = this._getServerUrl();
1947
2135
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1948
2136
  info.screenshotPath = screenshotPath;
1949
2137
  const screenshot = await this.takeScreenshot();
@@ -2092,7 +2280,16 @@ class StableBrowser {
2092
2280
  let screenshotId = null;
2093
2281
  let screenshotPath = null;
2094
2282
  const info = {};
2095
- info.log = "";
2283
+ info.log =
2284
+ "***** analyze table " +
2285
+ selectors.element_name +
2286
+ " query " +
2287
+ query +
2288
+ " operator " +
2289
+ operator +
2290
+ " value " +
2291
+ value +
2292
+ " *****\n";
2096
2293
  info.operation = "analyzeTable";
2097
2294
  info.selectors = selectors;
2098
2295
  info.query = query;
@@ -2326,13 +2523,6 @@ class StableBrowser {
2326
2523
  const info = {};
2327
2524
  try {
2328
2525
  await this.page.close();
2329
- if (this.context && this.context.pages && this.context.pages.length > 0) {
2330
- this.context.pages.pop();
2331
- this.page = this.context.pages[this.context.pages.length - 1];
2332
- this.context.page = this.page;
2333
- let title = await this.page.title();
2334
- console.log("Switched to page " + title);
2335
- }
2336
2526
  }
2337
2527
  catch (e) {
2338
2528
  console.log(".");