automation_model 1.0.548-dev → 1.0.548-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.
@@ -10,17 +10,20 @@ import { getDateTimeValue } from "./date_time.js";
10
10
  import drawRectangle from "./drawRect.js";
11
11
  //import { closeUnexpectedPopups } from "./popups.js";
12
12
  import { getTableCells, getTableData } from "./table_analyze.js";
13
- import { maskValue, replaceWithLocalTestData } from "./utils.js";
13
+ import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, } from "./utils.js";
14
14
  import csv from "csv-parser";
15
15
  import { Readable } from "node:stream";
16
16
  import readline from "readline";
17
- import { getContext } from "./init_browser.js";
17
+ import { getContext, refreshBrowser } from "./init_browser.js";
18
18
  import { locate_element } from "./locate_element.js";
19
19
  import { randomUUID } from "crypto";
20
20
  import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
21
21
  import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
22
- const Types = {
22
+ import { LocatorLog } from "./locator_log.js";
23
+ import axios from "axios";
24
+ export const Types = {
23
25
  CLICK: "click_element",
26
+ WAIT_ELEMENT: "wait_element",
24
27
  NAVIGATE: "navigate",
25
28
  FILL: "fill_element",
26
29
  EXECUTE: "execute_page_method",
@@ -30,6 +33,8 @@ const Types = {
30
33
  GET_PAGE_STATUS: "get_page_status",
31
34
  CLICK_ROW_ACTION: "click_row_action",
32
35
  VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
36
+ VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
37
+ VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
33
38
  ANALYZE_TABLE: "analyze_table",
34
39
  SELECT: "select_combobox",
35
40
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -47,8 +52,12 @@ const Types = {
47
52
  SET_INPUT: "set_input",
48
53
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
49
54
  VERIFY_ATTRIBUTE: "verify_element_attribute",
55
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
50
56
  };
51
57
  export const apps = {};
58
+ const formatElementName = (elementName) => {
59
+ return elementName ? JSON.stringify(elementName) : "element";
60
+ };
52
61
  class StableBrowser {
53
62
  browser;
54
63
  page;
@@ -62,6 +71,7 @@ class StableBrowser {
62
71
  appName = "main";
63
72
  tags = null;
64
73
  isRecording = false;
74
+ initSnapshotTaken = false;
65
75
  constructor(browser, page, logger = null, context = null, world = null) {
66
76
  this.browser = browser;
67
77
  this.page = page;
@@ -100,27 +110,9 @@ class StableBrowser {
100
110
  registerNetworkEvents(this.world, this, this.context, this.page);
101
111
  registerDownloadEvent(this.page, this.world, this.context);
102
112
  }
103
- async scrollPageToLoadLazyElements() {
104
- let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
105
- let retry = 0;
106
- while (true) {
107
- await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
108
- await new Promise((resolve) => setTimeout(resolve, 1000));
109
- let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
110
- if (newHeight === lastHeight) {
111
- break;
112
- }
113
- lastHeight = newHeight;
114
- retry++;
115
- if (retry > 10) {
116
- break;
117
- }
118
- }
119
- await this.page.evaluate(() => window.scrollTo(0, 0));
120
- }
121
113
  registerEventListeners(context) {
122
114
  this.registerConsoleLogListener(this.page, context);
123
- this.registerRequestListener(this.page, context, this.webLogFile);
115
+ // this.registerRequestListener(this.page, context, this.webLogFile);
124
116
  if (!context.pageLoading) {
125
117
  context.pageLoading = { status: false };
126
118
  }
@@ -176,8 +168,8 @@ class StableBrowser {
176
168
  };
177
169
  }
178
170
  const tempContext = {};
179
- this._copyContext(this, tempContext);
180
- this._copyContext(apps[appName], this);
171
+ _copyContext(this, tempContext);
172
+ _copyContext(apps[appName], this);
181
173
  apps[this.appName] = tempContext;
182
174
  this.appName = appName;
183
175
  if (newContextCreated) {
@@ -186,22 +178,6 @@ class StableBrowser {
186
178
  await this.waitForPageLoad();
187
179
  }
188
180
  }
189
- _copyContext(from, to) {
190
- to.browser = from.browser;
191
- to.page = from.page;
192
- to.context = from.context;
193
- }
194
- getWebLogFile(logFolder) {
195
- if (!fs.existsSync(logFolder)) {
196
- fs.mkdirSync(logFolder, { recursive: true });
197
- }
198
- let nextIndex = 1;
199
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
200
- nextIndex++;
201
- }
202
- const fileName = nextIndex + ".json";
203
- return path.join(logFolder, fileName);
204
- }
205
181
  registerConsoleLogListener(page, context) {
206
182
  if (!this.context.webLogger) {
207
183
  this.context.webLogger = [];
@@ -265,55 +241,51 @@ class StableBrowser {
265
241
  // async closeUnexpectedPopups() {
266
242
  // await closeUnexpectedPopups(this.page);
267
243
  // }
268
- async goto(url) {
244
+ async goto(url, world = null) {
245
+ if (!url) {
246
+ throw new Error("url is null, verify that the environment file is correct");
247
+ }
269
248
  if (!url.startsWith("http")) {
270
249
  url = "https://" + url;
271
250
  }
272
- await this.page.goto(url, {
273
- timeout: 60000,
274
- });
275
- }
276
- _fixUsingParams(text, _params) {
277
- if (!_params || typeof text !== "string") {
278
- return text;
251
+ const state = {
252
+ value: url,
253
+ world: world,
254
+ type: Types.NAVIGATE,
255
+ text: `Navigate Page to: ${url}`,
256
+ operation: "goto",
257
+ log: "***** navigate page to " + url + " *****\n",
258
+ info: {},
259
+ locate: false,
260
+ scroll: false,
261
+ screenshot: false,
262
+ highlight: false,
263
+ };
264
+ try {
265
+ await _preCommand(state, this);
266
+ await this.page.goto(url, {
267
+ timeout: 60000,
268
+ });
269
+ await _screenshot(state, this);
279
270
  }
280
- for (let key in _params) {
281
- let regValue = key;
282
- if (key.startsWith("_")) {
283
- // remove the _ prefix
284
- regValue = key.substring(1);
285
- }
286
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
271
+ catch (error) {
272
+ console.error("Error on goto", error);
273
+ _commandError(state, error, this);
287
274
  }
288
- return text;
289
- }
290
- _fixLocatorUsingParams(locator, _params) {
291
- // check if not null
292
- if (!locator) {
293
- return locator;
275
+ finally {
276
+ _commandFinally(state, this);
294
277
  }
295
- // clone the locator
296
- locator = JSON.parse(JSON.stringify(locator));
297
- this.scanAndManipulate(locator, _params);
298
- return locator;
299
278
  }
300
- _isObject(value) {
301
- return value && typeof value === "object" && value.constructor === Object;
302
- }
303
- scanAndManipulate(currentObj, _params) {
304
- for (const key in currentObj) {
305
- if (typeof currentObj[key] === "string") {
306
- // Perform string manipulation
307
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
308
- }
309
- else if (this._isObject(currentObj[key])) {
310
- // Recursively scan nested objects
311
- this.scanAndManipulate(currentObj[key], _params);
279
+ async _getLocator(locator, scope, _params) {
280
+ locator = _fixLocatorUsingParams(locator, _params);
281
+ // locator = await this._replaceWithLocalData(locator);
282
+ for (let key in locator) {
283
+ if (typeof locator[key] !== "string")
284
+ continue;
285
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
286
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
312
287
  }
313
288
  }
314
- }
315
- _getLocator(locator, scope, _params) {
316
- locator = this._fixLocatorUsingParams(locator, _params);
317
289
  let locatorReturn;
318
290
  if (locator.role) {
319
291
  if (locator.role[1].nameReg) {
@@ -321,7 +293,7 @@ class StableBrowser {
321
293
  delete locator.role[1].nameReg;
322
294
  }
323
295
  // if (locator.role[1].name) {
324
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
296
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
325
297
  // }
326
298
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
327
299
  }
@@ -364,140 +336,54 @@ class StableBrowser {
364
336
  if (css && css.locator) {
365
337
  css = css.locator;
366
338
  }
367
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
339
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
368
340
  if (result.elementCount === 0) {
369
341
  return;
370
342
  }
371
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
343
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
372
344
  // css climb to parent element
373
345
  const climbArray = [];
374
346
  for (let i = 0; i < climb; i++) {
375
347
  climbArray.push("..");
376
348
  }
377
349
  let climbXpath = "xpath=" + climbArray.join("/");
378
- return textElementCss + " >> " + climbXpath + " >> " + css;
379
- }
380
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
381
- //const stringifyText = JSON.stringify(text);
382
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
383
- function isParent(parent, child) {
384
- let currentNode = child.parentNode;
385
- while (currentNode !== null) {
386
- if (currentNode === parent) {
387
- return true;
388
- }
389
- currentNode = currentNode.parentNode;
390
- }
391
- return false;
392
- }
393
- document.isParent = isParent;
394
- function getRegex(str) {
395
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
396
- if (!match) {
397
- return null;
398
- }
399
- let [_, pattern, flags] = match;
400
- return new RegExp(pattern, flags);
401
- }
402
- document.getRegex = getRegex;
403
- function collectAllShadowDomElements(element, result = []) {
404
- // Check and add the element if it has a shadow root
405
- if (element.shadowRoot) {
406
- result.push(element);
407
- // Also search within the shadow root
408
- document.collectAllShadowDomElements(element.shadowRoot, result);
409
- }
410
- // Iterate over child nodes
411
- element.childNodes.forEach((child) => {
412
- // Recursively call the function for each child node
413
- document.collectAllShadowDomElements(child, result);
414
- });
415
- return result;
416
- }
417
- document.collectAllShadowDomElements = collectAllShadowDomElements;
418
- if (!tag) {
419
- tag = "*:not(script, style, head)";
420
- }
421
- let regexpSearch = document.getRegex(text);
422
- if (regexpSearch) {
423
- regex = true;
424
- }
425
- let elements = Array.from(document.querySelectorAll(tag));
426
- let shadowHosts = [];
427
- document.collectAllShadowDomElements(document, shadowHosts);
428
- for (let i = 0; i < shadowHosts.length; i++) {
429
- let shadowElement = shadowHosts[i].shadowRoot;
430
- if (!shadowElement) {
431
- console.log("shadowElement is null, for host " + shadowHosts[i]);
432
- continue;
433
- }
434
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
435
- elements = elements.concat(shadowElements);
436
- }
437
- let randomToken = null;
438
- const foundElements = [];
439
- if (regex) {
440
- if (!regexpSearch) {
441
- regexpSearch = new RegExp(text, "im");
442
- }
443
- for (let i = 0; i < elements.length; i++) {
444
- const element = elements[i];
445
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
446
- (element.value && regexpSearch.test(element.value))) {
447
- foundElements.push(element);
448
- }
449
- }
450
- }
451
- else {
452
- text = text.trim();
453
- for (let i = 0; i < elements.length; i++) {
454
- const element = elements[i];
455
- if (partial) {
456
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
457
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
458
- foundElements.push(element);
459
- }
460
- }
461
- else {
462
- if ((element.innerText && element.innerText.trim() === text) ||
463
- (element.value && element.value === text)) {
464
- foundElements.push(element);
465
- }
466
- }
467
- }
468
- }
469
- let noChildElements = [];
470
- for (let i = 0; i < foundElements.length; i++) {
471
- let element = foundElements[i];
472
- let hasChild = false;
473
- for (let j = 0; j < foundElements.length; j++) {
474
- if (i === j) {
475
- continue;
476
- }
477
- if (isParent(element, foundElements[j])) {
478
- hasChild = true;
479
- break;
350
+ let resultCss = textElementCss + " >> " + climbXpath;
351
+ if (css) {
352
+ resultCss = resultCss + " >> " + css;
353
+ }
354
+ return resultCss;
355
+ }
356
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
357
+ const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
358
+ const locator = scope.locator(query);
359
+ const count = await locator.count();
360
+ if (!tag1) {
361
+ tag1 = "*";
362
+ }
363
+ const randomToken = Math.random().toString(36).substring(7);
364
+ let tagCount = 0;
365
+ for (let i = 0; i < count; i++) {
366
+ const element = locator.nth(i);
367
+ // check if the tag matches
368
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
369
+ if (!tag.startsWith("*")) {
370
+ if (el.tagName.toLowerCase() !== tag) {
371
+ return false;
480
372
  }
481
373
  }
482
- if (!hasChild) {
483
- noChildElements.push(element);
374
+ if (!el.setAttribute) {
375
+ el = el.parentElement;
484
376
  }
377
+ el.setAttribute("data-blinq-id-" + randomToken, "");
378
+ return true;
379
+ }, [tag1, randomToken]))) {
380
+ continue;
485
381
  }
486
- let elementCount = 0;
487
- if (noChildElements.length > 0) {
488
- for (let i = 0; i < noChildElements.length; i++) {
489
- if (randomToken === null) {
490
- randomToken = Math.random().toString(36).substring(7);
491
- }
492
- let element = noChildElements[i];
493
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
494
- elementCount++;
495
- }
496
- }
497
- return { elementCount: elementCount, randomToken: randomToken };
498
- }, [text1, tag1, regex1, partial1]);
382
+ tagCount++;
383
+ }
384
+ return { elementCount: tagCount, randomToken };
499
385
  }
500
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
386
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
501
387
  if (!info) {
502
388
  info = {};
503
389
  }
@@ -506,10 +392,13 @@ class StableBrowser {
506
392
  }
507
393
  if (!info.log) {
508
394
  info.log = "";
395
+ info.locatorLog = new LocatorLog(selectorHierarchy);
509
396
  }
510
397
  let locatorSearch = selectorHierarchy[index];
398
+ let originalLocatorSearch = "";
511
399
  try {
512
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
400
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
401
+ locatorSearch = JSON.parse(originalLocatorSearch);
513
402
  }
514
403
  catch (e) {
515
404
  console.error(e);
@@ -517,30 +406,31 @@ class StableBrowser {
517
406
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
518
407
  let locator = null;
519
408
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
520
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
409
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
410
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
521
411
  if (!locatorString) {
522
412
  info.failCause.textNotFound = true;
523
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
413
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
524
414
  return;
525
415
  }
526
- locator = this._getLocator({ css: locatorString }, scope, _params);
416
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
527
417
  }
528
418
  else if (locatorSearch.text) {
529
- let text = this._fixUsingParams(locatorSearch.text, _params);
530
- let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
419
+ let text = _fixUsingParams(locatorSearch.text, _params);
420
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
531
421
  if (result.elementCount === 0) {
532
422
  info.failCause.textNotFound = true;
533
- info.failCause.lastError = "failed to locate element by text: " + text;
423
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
534
424
  return;
535
425
  }
536
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
426
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
537
427
  if (locatorSearch.childCss) {
538
428
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
539
429
  }
540
- locator = this._getLocator(locatorSearch, scope, _params);
430
+ locator = await this._getLocator(locatorSearch, scope, _params);
541
431
  }
542
432
  else {
543
- locator = this._getLocator(locatorSearch, scope, _params);
433
+ locator = await this._getLocator(locatorSearch, scope, _params);
544
434
  }
545
435
  // let cssHref = false;
546
436
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -555,16 +445,25 @@ class StableBrowser {
555
445
  let visibleLocator = null;
556
446
  if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
557
447
  foundLocators.push(locator.nth(locatorSearch.index));
448
+ if (info.locatorLog) {
449
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
450
+ }
558
451
  return;
559
452
  }
453
+ if (info.locatorLog && count === 0) {
454
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
455
+ }
560
456
  for (let j = 0; j < count; j++) {
561
457
  let visible = await locator.nth(j).isVisible();
562
458
  const enabled = await locator.nth(j).isEnabled();
563
459
  if (!visibleOnly) {
564
460
  visible = true;
565
461
  }
566
- if (visible && enabled) {
462
+ if (visible && (allowDisabled || enabled)) {
567
463
  foundLocators.push(locator.nth(j));
464
+ if (info.locatorLog) {
465
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
466
+ }
568
467
  }
569
468
  else {
570
469
  info.failCause.visible = visible;
@@ -572,8 +471,16 @@ class StableBrowser {
572
471
  if (!info.printMessages) {
573
472
  info.printMessages = {};
574
473
  }
474
+ if (info.locatorLog && !visible) {
475
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
476
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
477
+ }
478
+ if (info.locatorLog && !enabled) {
479
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
480
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
481
+ }
575
482
  if (!info.printMessages[j.toString()]) {
576
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
483
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
577
484
  info.printMessages[j.toString()] = true;
578
485
  }
579
486
  }
@@ -589,7 +496,7 @@ class StableBrowser {
589
496
  if (!info) {
590
497
  info = {};
591
498
  }
592
- info.log += "scan for popup handlers" + "\n";
499
+ //info.log += "scan for popup handlers" + "\n";
593
500
  const handlerGroup = [];
594
501
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
595
502
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -637,7 +544,7 @@ class StableBrowser {
637
544
  }
638
545
  return { rerun: false };
639
546
  }
640
- async _locate(selectors, info, _params, timeout) {
547
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
641
548
  if (!timeout) {
642
549
  timeout = 30000;
643
550
  }
@@ -647,9 +554,18 @@ class StableBrowser {
647
554
  let selector = selectors.locators[j];
648
555
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
649
556
  }
650
- let element = await this._locate_internal(selectors, info, _params, timeout);
557
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
651
558
  if (!element.rerun) {
652
- return element;
559
+ const randomToken = Math.random().toString(36).substring(7);
560
+ element.evaluate((el, randomToken) => {
561
+ el.setAttribute("data-blinq-id-" + randomToken, "");
562
+ }, randomToken);
563
+ if (element._frame) {
564
+ return element;
565
+ }
566
+ const scope = element.page();
567
+ const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
568
+ return newSelector;
653
569
  }
654
570
  }
655
571
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -691,9 +607,11 @@ class StableBrowser {
691
607
  }
692
608
  return framescope;
693
609
  };
610
+ let fLocator = null;
694
611
  while (true) {
695
612
  let frameFound = false;
696
613
  if (selectors.nestFrmLoc) {
614
+ fLocator = selectors.nestFrmLoc;
697
615
  scope = await findFrame(selectors.nestFrmLoc, scope);
698
616
  frameFound = true;
699
617
  break;
@@ -702,6 +620,7 @@ class StableBrowser {
702
620
  for (let i = 0; i < selectors.frameLocators.length; i++) {
703
621
  let frameLocator = selectors.frameLocators[i];
704
622
  if (frameLocator.css) {
623
+ fLocator = frameLocator.css;
705
624
  scope = scope.frameLocator(frameLocator.css);
706
625
  frameFound = true;
707
626
  break;
@@ -709,18 +628,25 @@ class StableBrowser {
709
628
  }
710
629
  }
711
630
  if (!frameFound && selectors.iframe_src) {
631
+ fLocator = selectors.iframe_src;
712
632
  scope = this.page.frame({ url: selectors.iframe_src });
713
633
  }
714
634
  if (!scope) {
715
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
635
+ if (info && info.locatorLog) {
636
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
637
+ }
638
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
716
639
  if (Date.now() - startTime > timeout) {
717
640
  info.failCause.iframeNotFound = true;
718
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
641
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
719
642
  throw new Error("unable to locate iframe " + selectors.iframe_src);
720
643
  }
721
644
  await new Promise((resolve) => setTimeout(resolve, 1000));
722
645
  }
723
646
  else {
647
+ if (info && info.locatorLog) {
648
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
649
+ }
724
650
  break;
725
651
  }
726
652
  }
@@ -737,11 +663,12 @@ class StableBrowser {
737
663
  return bodyContent;
738
664
  });
739
665
  }
740
- async _locate_internal(selectors, info, _params, timeout = 30000) {
666
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
741
667
  if (!info) {
742
668
  info = {};
743
669
  info.failCause = {};
744
670
  info.log = "";
671
+ info.locatorLog = new LocatorLog(selectors);
745
672
  }
746
673
  let highPriorityTimeout = 5000;
747
674
  let visibleOnlyTimeout = 6000;
@@ -785,17 +712,17 @@ class StableBrowser {
785
712
  }
786
713
  // info.log += "scanning locators in priority 1" + "\n";
787
714
  let onlyPriority3 = selectorsLocators[0].priority === 3;
788
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
715
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
789
716
  if (result.foundElements.length === 0) {
790
717
  // info.log += "scanning locators in priority 2" + "\n";
791
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
718
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
792
719
  }
793
720
  if (result.foundElements.length === 0 && onlyPriority3) {
794
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
721
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
795
722
  }
796
723
  else {
797
724
  if (result.foundElements.length === 0 && !highPriorityOnly) {
798
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
725
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
799
726
  }
800
727
  }
801
728
  let foundElements = result.foundElements;
@@ -840,26 +767,34 @@ class StableBrowser {
840
767
  break;
841
768
  }
842
769
  if (Date.now() - startTime > highPriorityTimeout) {
843
- info.log += "high priority timeout, will try all elements" + "\n";
770
+ //info.log += "high priority timeout, will try all elements" + "\n";
844
771
  highPriorityOnly = false;
845
772
  if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
846
773
  lazy_scroll = true;
847
- await this.scrollPageToLoadLazyElements();
774
+ await scrollPageToLoadLazyElements(this.page);
848
775
  }
849
776
  }
850
777
  if (Date.now() - startTime > visibleOnlyTimeout) {
851
- info.log += "visible only timeout, will try all elements" + "\n";
778
+ //info.log += "visible only timeout, will try all elements" + "\n";
852
779
  visibleOnly = false;
853
780
  }
854
781
  await new Promise((resolve) => setTimeout(resolve, 1000));
855
782
  }
856
783
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
857
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
784
+ // if (info.locatorLog) {
785
+ // const lines = info.locatorLog.toString().split("\n");
786
+ // for (let line of lines) {
787
+ // this.logger.debug(line);
788
+ // }
789
+ // }
790
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
858
791
  info.failCause.locatorNotFound = true;
859
- info.failCause.lastError = "failed to locate unique element";
792
+ if (!info?.failCause?.lastError) {
793
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
794
+ }
860
795
  throw new Error("failed to locate first element no elements found, " + info.log);
861
796
  }
862
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
797
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
863
798
  let foundElements = [];
864
799
  const result = {
865
800
  foundElements: foundElements,
@@ -867,14 +802,15 @@ class StableBrowser {
867
802
  for (let i = 0; i < locatorsGroup.length; i++) {
868
803
  let foundLocators = [];
869
804
  try {
870
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
805
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
871
806
  }
872
807
  catch (e) {
873
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
874
- this.logger.debug(e);
808
+ // this call can fail it the browser is navigating
809
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
810
+ // this.logger.debug(e);
875
811
  foundLocators = [];
876
812
  try {
877
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
813
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
878
814
  }
879
815
  catch (e) {
880
816
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -890,6 +826,9 @@ class StableBrowser {
890
826
  }
891
827
  if (foundLocators.length > 1) {
892
828
  info.failCause.foundMultiple = true;
829
+ if (info.locatorLog) {
830
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
831
+ }
893
832
  }
894
833
  }
895
834
  return result;
@@ -999,15 +938,16 @@ class StableBrowser {
999
938
  options,
1000
939
  world,
1001
940
  text: "Click element",
941
+ _text: "Click on " + selectors.element_name,
1002
942
  type: Types.CLICK,
1003
943
  operation: "click",
1004
944
  log: "***** click on " + selectors.element_name + " *****\n",
1005
945
  };
1006
946
  try {
1007
947
  await _preCommand(state, this);
1008
- if (state.options && state.options.context) {
1009
- state.selectors.locators[0].text = state.options.context;
1010
- }
948
+ // if (state.options && state.options.context) {
949
+ // state.selectors.locators[0].text = state.options.context;
950
+ // }
1011
951
  try {
1012
952
  await state.element.click();
1013
953
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1028,6 +968,38 @@ class StableBrowser {
1028
968
  _commandFinally(state, this);
1029
969
  }
1030
970
  }
971
+ async waitForElement(selectors, _params, options = {}, world = null) {
972
+ const timeout = this._getFindElementTimeout(options);
973
+ const state = {
974
+ selectors,
975
+ _params,
976
+ options,
977
+ world,
978
+ text: "Wait for element",
979
+ _text: "Wait for " + selectors.element_name,
980
+ type: Types.WAIT_ELEMENT,
981
+ operation: "waitForElement",
982
+ log: "***** wait for " + selectors.element_name + " *****\n",
983
+ };
984
+ let found = false;
985
+ try {
986
+ await _preCommand(state, this);
987
+ // if (state.options && state.options.context) {
988
+ // state.selectors.locators[0].text = state.options.context;
989
+ // }
990
+ await state.element.waitFor({ timeout: timeout });
991
+ found = true;
992
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
993
+ }
994
+ catch (e) {
995
+ console.error("Error on waitForElement", e);
996
+ // await _commandError(state, e, this);
997
+ }
998
+ finally {
999
+ _commandFinally(state, this);
1000
+ }
1001
+ return found;
1002
+ }
1031
1003
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
1032
1004
  const state = {
1033
1005
  selectors,
@@ -1036,6 +1008,7 @@ class StableBrowser {
1036
1008
  world,
1037
1009
  type: checked ? Types.CHECK : Types.UNCHECK,
1038
1010
  text: checked ? `Check element` : `Uncheck element`,
1011
+ _text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
1039
1012
  operation: "setCheck",
1040
1013
  log: "***** check " + selectors.element_name + " *****\n",
1041
1014
  };
@@ -1045,9 +1018,15 @@ class StableBrowser {
1045
1018
  // let element = await this._locate(selectors, info, _params);
1046
1019
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1047
1020
  try {
1048
- // await this._highlightElements(element);
1021
+ // if (world && world.screenshot && !world.screenshotPath) {
1022
+ // console.log(`Highlighting while running from recorder`);
1023
+ await this._highlightElements(element);
1049
1024
  await state.element.setChecked(checked);
1050
1025
  await new Promise((resolve) => setTimeout(resolve, 1000));
1026
+ // await this._unHighlightElements(element);
1027
+ // }
1028
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1029
+ // await this._unHighlightElements(element);
1051
1030
  }
1052
1031
  catch (e) {
1053
1032
  if (e.message && e.message.includes("did not change its state")) {
@@ -1079,6 +1058,7 @@ class StableBrowser {
1079
1058
  world,
1080
1059
  type: Types.HOVER,
1081
1060
  text: `Hover element`,
1061
+ _text: `Hover on ${selectors.element_name}`,
1082
1062
  operation: "hover",
1083
1063
  log: "***** hover " + selectors.element_name + " *****\n",
1084
1064
  };
@@ -1086,6 +1066,7 @@ class StableBrowser {
1086
1066
  await _preCommand(state, this);
1087
1067
  try {
1088
1068
  await state.element.hover();
1069
+ // await _screenshot(state, this);
1089
1070
  await new Promise((resolve) => setTimeout(resolve, 1000));
1090
1071
  }
1091
1072
  catch (e) {
@@ -1093,6 +1074,7 @@ class StableBrowser {
1093
1074
  state.info.log += "hover failed, will try again" + "\n";
1094
1075
  state.element = await this._locate(selectors, state.info, _params);
1095
1076
  await state.element.hover({ timeout: 10000 });
1077
+ // await _screenshot(state, this);
1096
1078
  await new Promise((resolve) => setTimeout(resolve, 1000));
1097
1079
  }
1098
1080
  await _screenshot(state, this);
@@ -1118,6 +1100,7 @@ class StableBrowser {
1118
1100
  value: values.toString(),
1119
1101
  type: Types.SELECT,
1120
1102
  text: `Select option: ${values}`,
1103
+ _text: `Select option: ${values} on ${selectors.element_name}`,
1121
1104
  operation: "selectOption",
1122
1105
  log: "***** select option " + selectors.element_name + " *****\n",
1123
1106
  };
@@ -1152,6 +1135,7 @@ class StableBrowser {
1152
1135
  highlight: false,
1153
1136
  type: Types.TYPE_PRESS,
1154
1137
  text: `Type value: ${_value}`,
1138
+ _text: `Type value: ${_value}`,
1155
1139
  operation: "type",
1156
1140
  log: "",
1157
1141
  };
@@ -1231,6 +1215,7 @@ class StableBrowser {
1231
1215
  world,
1232
1216
  type: Types.SET_DATE_TIME,
1233
1217
  text: `Set date time value: ${value}`,
1218
+ _text: `Set date time value: ${value} on ${selectors.element_name}`,
1234
1219
  operation: "setDateTime",
1235
1220
  log: "***** set date time value " + selectors.element_name + " *****\n",
1236
1221
  throwError: false,
@@ -1302,6 +1287,7 @@ class StableBrowser {
1302
1287
  world,
1303
1288
  type: Types.FILL,
1304
1289
  text: `Click type input with value: ${_value}`,
1290
+ _text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
1305
1291
  operation: "clickType",
1306
1292
  log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1307
1293
  };
@@ -1367,7 +1353,12 @@ class StableBrowser {
1367
1353
  await this.waitForPageLoad();
1368
1354
  }
1369
1355
  else if (enter === false) {
1370
- await state.element.dispatchEvent("change");
1356
+ try {
1357
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1358
+ }
1359
+ catch (e) {
1360
+ // ignore
1361
+ }
1371
1362
  //await this.page.keyboard.press("Tab");
1372
1363
  }
1373
1364
  else {
@@ -1419,15 +1410,17 @@ class StableBrowser {
1419
1410
  return await this._getText(selectors, 0, _params, options, info, world);
1420
1411
  }
1421
1412
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1413
+ const timeout = this._getFindElementTimeout(options);
1422
1414
  _validateSelectors(selectors);
1423
1415
  let screenshotId = null;
1424
1416
  let screenshotPath = null;
1425
1417
  if (!info.log) {
1426
1418
  info.log = "";
1419
+ info.locatorLog = new LocatorLog(selectors);
1427
1420
  }
1428
1421
  info.operation = "getText";
1429
1422
  info.selectors = selectors;
1430
- let element = await this._locate(selectors, info, _params);
1423
+ let element = await this._locate(selectors, info, _params, timeout);
1431
1424
  if (climb > 0) {
1432
1425
  const climbArray = [];
1433
1426
  for (let i = 0; i < climb; i++) {
@@ -1446,6 +1439,18 @@ class StableBrowser {
1446
1439
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1447
1440
  try {
1448
1441
  await this._highlightElements(element);
1442
+ // if (world && world.screenshot && !world.screenshotPath) {
1443
+ // // console.log(`Highlighting for get text while running from recorder`);
1444
+ // this._highlightElements(element)
1445
+ // .then(async () => {
1446
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1447
+ // this._unhighlightElements(element).then(
1448
+ // () => {}
1449
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1450
+ // );
1451
+ // })
1452
+ // .catch(e);
1453
+ // }
1449
1454
  const elementText = await element.innerText();
1450
1455
  return {
1451
1456
  text: elementText,
@@ -1457,7 +1462,7 @@ class StableBrowser {
1457
1462
  }
1458
1463
  catch (e) {
1459
1464
  //await this.closeUnexpectedPopups();
1460
- this.logger.info("no innerText will use textContent");
1465
+ this.logger.info("no innerText, will use textContent");
1461
1466
  const elementText = await element.textContent();
1462
1467
  return { text: elementText, screenshotId, screenshotPath, value: value };
1463
1468
  }
@@ -1482,6 +1487,7 @@ class StableBrowser {
1482
1487
  highlight: false,
1483
1488
  type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1484
1489
  text: `Verify element contains pattern: ${pattern}`,
1490
+ _text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
1485
1491
  operation: "containsPattern",
1486
1492
  log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1487
1493
  };
@@ -1517,6 +1523,8 @@ class StableBrowser {
1517
1523
  }
1518
1524
  }
1519
1525
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1526
+ const timeout = this._getFindElementTimeout(options);
1527
+ const startTime = Date.now();
1520
1528
  const state = {
1521
1529
  selectors,
1522
1530
  _params,
@@ -1543,62 +1551,54 @@ class StableBrowser {
1543
1551
  }
1544
1552
  let foundObj = null;
1545
1553
  try {
1546
- await _preCommand(state, this);
1547
- foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1548
- if (foundObj && foundObj.element) {
1549
- await this.scrollIfNeeded(foundObj.element, state.info);
1550
- }
1551
- await _screenshot(state, this);
1552
- const dateAlternatives = findDateAlternatives(text);
1553
- const numberAlternatives = findNumberAlternatives(text);
1554
- if (dateAlternatives.date) {
1555
- for (let i = 0; i < dateAlternatives.dates.length; i++) {
1556
- if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1557
- foundObj?.value?.includes(dateAlternatives.dates[i])) {
1558
- return state.info;
1554
+ while (Date.now() - startTime < timeout) {
1555
+ try {
1556
+ await _preCommand(state, this);
1557
+ foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
1558
+ if (foundObj && foundObj.element) {
1559
+ await this.scrollIfNeeded(foundObj.element, state.info);
1559
1560
  }
1560
- }
1561
- throw new Error("element doesn't contain text " + text);
1562
- }
1563
- else if (numberAlternatives.number) {
1564
- for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1565
- if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1566
- foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1561
+ await _screenshot(state, this);
1562
+ const dateAlternatives = findDateAlternatives(text);
1563
+ const numberAlternatives = findNumberAlternatives(text);
1564
+ if (dateAlternatives.date) {
1565
+ for (let i = 0; i < dateAlternatives.dates.length; i++) {
1566
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1567
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1568
+ return state.info;
1569
+ }
1570
+ }
1571
+ }
1572
+ else if (numberAlternatives.number) {
1573
+ for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1574
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1575
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1576
+ return state.info;
1577
+ }
1578
+ }
1579
+ }
1580
+ else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
1567
1581
  return state.info;
1568
1582
  }
1569
1583
  }
1570
- throw new Error("element doesn't contain text " + text);
1571
- }
1572
- else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1573
- state.info.foundText = foundObj?.text;
1574
- state.info.value = foundObj?.value;
1575
- throw new Error("element doesn't contain text " + text);
1584
+ catch (e) {
1585
+ // Log error but continue retrying until timeout is reached
1586
+ this.logger.warn("Retrying containsText due to: " + e.message);
1587
+ }
1588
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
1576
1589
  }
1577
- return state.info;
1590
+ state.info.foundText = foundObj?.text;
1591
+ state.info.value = foundObj?.value;
1592
+ throw new Error("element doesn't contain text " + text);
1578
1593
  }
1579
1594
  catch (e) {
1580
1595
  await _commandError(state, e, this);
1596
+ throw e;
1581
1597
  }
1582
1598
  finally {
1583
1599
  _commandFinally(state, this);
1584
1600
  }
1585
1601
  }
1586
- _getDataFile(world = null) {
1587
- let dataFile = null;
1588
- if (world && world.reportFolder) {
1589
- dataFile = path.join(world.reportFolder, "data.json");
1590
- }
1591
- else if (this.reportFolder) {
1592
- dataFile = path.join(this.reportFolder, "data.json");
1593
- }
1594
- else if (this.context && this.context.reportFolder) {
1595
- dataFile = path.join(this.context.reportFolder, "data.json");
1596
- }
1597
- else {
1598
- dataFile = "data.json";
1599
- }
1600
- return dataFile;
1601
- }
1602
1602
  async waitForUserInput(message, world = null) {
1603
1603
  if (!message) {
1604
1604
  message = "# Wait for user input. Press any key to continue";
@@ -1627,7 +1627,7 @@ class StableBrowser {
1627
1627
  return;
1628
1628
  }
1629
1629
  // if data file exists, load it
1630
- const dataFile = this._getDataFile(world);
1630
+ const dataFile = _getDataFile(world, this.context, this);
1631
1631
  let data = this.getTestData(world);
1632
1632
  // merge the testData with the existing data
1633
1633
  Object.assign(data, testData);
@@ -1730,7 +1730,7 @@ class StableBrowser {
1730
1730
  }
1731
1731
  }
1732
1732
  getTestData(world = null) {
1733
- const dataFile = this._getDataFile(world);
1733
+ const dataFile = _getDataFile(world, this.context, this);
1734
1734
  let data = {};
1735
1735
  if (fs.existsSync(dataFile)) {
1736
1736
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1817,6 +1817,15 @@ class StableBrowser {
1817
1817
  document.documentElement.clientWidth,
1818
1818
  ])));
1819
1819
  let screenshotBuffer = null;
1820
+ // if (focusedElement) {
1821
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1822
+ // await this._unhighlightElements(focusedElement);
1823
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1824
+ // console.log(`Unhighlighted previous element`);
1825
+ // }
1826
+ // if (focusedElement) {
1827
+ // await this._highlightElements(focusedElement);
1828
+ // }
1820
1829
  if (this.context.browserName === "chromium") {
1821
1830
  const client = await playContext.newCDPSession(this.page);
1822
1831
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1838,6 +1847,10 @@ class StableBrowser {
1838
1847
  else {
1839
1848
  screenshotBuffer = await this.page.screenshot();
1840
1849
  }
1850
+ // if (focusedElement) {
1851
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1852
+ // await this._unhighlightElements(focusedElement);
1853
+ // }
1841
1854
  let image = await Jimp.read(screenshotBuffer);
1842
1855
  // Get the image dimensions
1843
1856
  const { width, height } = image.bitmap;
@@ -1850,6 +1863,7 @@ class StableBrowser {
1850
1863
  else {
1851
1864
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1852
1865
  }
1866
+ return screenshotBuffer;
1853
1867
  }
1854
1868
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1855
1869
  const state = {
@@ -1885,8 +1899,10 @@ class StableBrowser {
1885
1899
  world,
1886
1900
  type: Types.EXTRACT,
1887
1901
  text: `Extract attribute from element`,
1902
+ _text: `Extract attribute ${attribute} from ${selectors.element_name}`,
1888
1903
  operation: "extractAttribute",
1889
1904
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1905
+ allowDisabled: true,
1890
1906
  };
1891
1907
  await new Promise((resolve) => setTimeout(resolve, 2000));
1892
1908
  try {
@@ -1908,6 +1924,7 @@ class StableBrowser {
1908
1924
  state.info.value = state.value;
1909
1925
  this.setTestData({ [variable]: state.value }, world);
1910
1926
  this.logger.info("set test data: " + variable + "=" + state.value);
1927
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1911
1928
  return state.info;
1912
1929
  }
1913
1930
  catch (e) {
@@ -1926,14 +1943,21 @@ class StableBrowser {
1926
1943
  options,
1927
1944
  world,
1928
1945
  type: Types.VERIFY_ATTRIBUTE,
1946
+ highlight: true,
1947
+ screenshot: true,
1929
1948
  text: `Verify element attribute`,
1949
+ _text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
1930
1950
  operation: "verifyAttribute",
1931
1951
  log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1952
+ allowDisabled: true,
1932
1953
  };
1933
1954
  await new Promise((resolve) => setTimeout(resolve, 2000));
1934
1955
  let val;
1956
+ let expectedValue;
1935
1957
  try {
1936
1958
  await _preCommand(state, this);
1959
+ expectedValue = state.value;
1960
+ state.info.expectedValue = expectedValue;
1937
1961
  switch (attribute) {
1938
1962
  case "innerText":
1939
1963
  val = String(await state.element.innerText());
@@ -1947,23 +1971,30 @@ class StableBrowser {
1947
1971
  case "disabled":
1948
1972
  val = String(await state.element.isDisabled());
1949
1973
  break;
1974
+ case "readOnly":
1975
+ const isEditable = await state.element.isEditable();
1976
+ val = String(!isEditable);
1977
+ break;
1950
1978
  default:
1951
1979
  val = String(await state.element.getAttribute(attribute));
1952
1980
  break;
1953
1981
  }
1982
+ state.info.value = val;
1954
1983
  let regex;
1955
- if (value.startsWith("/") && value.endsWith("/")) {
1956
- const patternBody = value.slice(1, -1);
1984
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
1985
+ const patternBody = expectedValue.slice(1, -1);
1957
1986
  regex = new RegExp(patternBody, "g");
1958
1987
  }
1959
1988
  else {
1960
- const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1989
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1961
1990
  regex = new RegExp(escapedPattern, "g");
1962
1991
  }
1963
1992
  if (!val.match(regex)) {
1964
- throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1993
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
1994
+ state.info.failCause.assertionFailed = true;
1995
+ state.info.failCause.lastError = errorMessage;
1996
+ throw new Error(errorMessage);
1965
1997
  }
1966
- state.info.value = val;
1967
1998
  return state.info;
1968
1999
  }
1969
2000
  catch (e) {
@@ -1991,7 +2022,7 @@ class StableBrowser {
1991
2022
  if (options && options.timeout) {
1992
2023
  timeout = options.timeout;
1993
2024
  }
1994
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
2025
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1995
2026
  const request = {
1996
2027
  method: "POST",
1997
2028
  url: serviceUrl,
@@ -2062,27 +2093,32 @@ class StableBrowser {
2062
2093
  async _highlightElements(scope, css) {
2063
2094
  try {
2064
2095
  if (!scope) {
2096
+ // console.log(`Scope is not defined`);
2065
2097
  return;
2066
2098
  }
2067
2099
  if (!css) {
2068
2100
  scope
2069
2101
  .evaluate((node) => {
2070
2102
  if (node && node.style) {
2071
- let originalBorder = node.style.border;
2072
- node.style.border = "2px solid red";
2103
+ let originalOutline = node.style.outline;
2104
+ // console.log(`Original outline was: ${originalOutline}`);
2105
+ // node.__previousOutline = originalOutline;
2106
+ node.style.outline = "2px solid red";
2107
+ // console.log(`New outline is: ${node.style.outline}`);
2073
2108
  if (window) {
2074
2109
  window.addEventListener("beforeunload", function (e) {
2075
- node.style.border = originalBorder;
2110
+ node.style.outline = originalOutline;
2076
2111
  });
2077
2112
  }
2078
2113
  setTimeout(function () {
2079
- node.style.border = originalBorder;
2114
+ node.style.outline = originalOutline;
2080
2115
  }, 2000);
2081
2116
  }
2082
2117
  })
2083
2118
  .then(() => { })
2084
2119
  .catch((e) => {
2085
2120
  // ignore
2121
+ // console.error(`Could not highlight node : ${e}`);
2086
2122
  });
2087
2123
  }
2088
2124
  else {
@@ -2098,17 +2134,18 @@ class StableBrowser {
2098
2134
  if (!element.style) {
2099
2135
  return;
2100
2136
  }
2101
- var originalBorder = element.style.border;
2137
+ let originalOutline = element.style.outline;
2138
+ element.__previousOutline = originalOutline;
2102
2139
  // Set the new border to be red and 2px solid
2103
- element.style.border = "2px solid red";
2140
+ element.style.outline = "2px solid red";
2104
2141
  if (window) {
2105
2142
  window.addEventListener("beforeunload", function (e) {
2106
- element.style.border = originalBorder;
2143
+ element.style.outline = originalOutline;
2107
2144
  });
2108
2145
  }
2109
2146
  // Set a timeout to revert to the original border after 2 seconds
2110
2147
  setTimeout(function () {
2111
- element.style.border = originalBorder;
2148
+ element.style.outline = originalOutline;
2112
2149
  }, 2000);
2113
2150
  }
2114
2151
  return;
@@ -2116,6 +2153,7 @@ class StableBrowser {
2116
2153
  .then(() => { })
2117
2154
  .catch((e) => {
2118
2155
  // ignore
2156
+ // console.error(`Could not highlight css: ${e}`);
2119
2157
  });
2120
2158
  }
2121
2159
  }
@@ -2123,6 +2161,54 @@ class StableBrowser {
2123
2161
  console.debug(error);
2124
2162
  }
2125
2163
  }
2164
+ // async _unhighlightElements(scope, css) {
2165
+ // try {
2166
+ // if (!scope) {
2167
+ // return;
2168
+ // }
2169
+ // if (!css) {
2170
+ // scope
2171
+ // .evaluate((node) => {
2172
+ // if (node && node.style) {
2173
+ // if (!node.__previousOutline) {
2174
+ // node.style.outline = "";
2175
+ // } else {
2176
+ // node.style.outline = node.__previousOutline;
2177
+ // }
2178
+ // }
2179
+ // })
2180
+ // .then(() => {})
2181
+ // .catch((e) => {
2182
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2183
+ // });
2184
+ // } else {
2185
+ // scope
2186
+ // .evaluate(([css]) => {
2187
+ // if (!css) {
2188
+ // return;
2189
+ // }
2190
+ // let elements = Array.from(document.querySelectorAll(css));
2191
+ // for (i = 0; i < elements.length; i++) {
2192
+ // let element = elements[i];
2193
+ // if (!element.style) {
2194
+ // return;
2195
+ // }
2196
+ // if (!element.__previousOutline) {
2197
+ // element.style.outline = "";
2198
+ // } else {
2199
+ // element.style.outline = element.__previousOutline;
2200
+ // }
2201
+ // }
2202
+ // })
2203
+ // .then(() => {})
2204
+ // .catch((e) => {
2205
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2206
+ // });
2207
+ // }
2208
+ // } catch (error) {
2209
+ // // console.debug(error);
2210
+ // }
2211
+ // }
2126
2212
  async verifyPagePath(pathPart, options = {}, world = null) {
2127
2213
  const startTime = Date.now();
2128
2214
  let error = null;
@@ -2167,6 +2253,7 @@ class StableBrowser {
2167
2253
  _reportToWorld(world, {
2168
2254
  type: Types.VERIFY_PAGE_PATH,
2169
2255
  text: "Verify page path",
2256
+ _text: "Verify the page path contains " + pathPart,
2170
2257
  screenshotId,
2171
2258
  result: error
2172
2259
  ? {
@@ -2184,6 +2271,35 @@ class StableBrowser {
2184
2271
  });
2185
2272
  }
2186
2273
  }
2274
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2275
+ const frames = this.page.frames();
2276
+ let results = [];
2277
+ let ignoreCase = false;
2278
+ for (let i = 0; i < frames.length; i++) {
2279
+ if (dateAlternatives.date) {
2280
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2281
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2282
+ result.frame = frames[i];
2283
+ results.push(result);
2284
+ }
2285
+ }
2286
+ else if (numberAlternatives.number) {
2287
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2288
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2289
+ result.frame = frames[i];
2290
+ results.push(result);
2291
+ }
2292
+ }
2293
+ else {
2294
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2295
+ result.frame = frames[i];
2296
+ results.push(result);
2297
+ }
2298
+ }
2299
+ state.info.results = results;
2300
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2301
+ return resultWithElementsFound;
2302
+ }
2187
2303
  async verifyTextExistInPage(text, options = {}, world = null) {
2188
2304
  text = unEscapeString(text);
2189
2305
  const state = {
@@ -2193,12 +2309,16 @@ class StableBrowser {
2193
2309
  locate: false,
2194
2310
  scroll: false,
2195
2311
  highlight: false,
2196
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2312
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2197
2313
  text: `Verify text exists in page`,
2314
+ _text: `Verify the text '${text}' exists in page`,
2198
2315
  operation: "verifyTextExistInPage",
2199
2316
  log: "***** verify text " + text + " exists in page *****\n",
2200
2317
  };
2201
- const timeout = this._getLoadTimeout(options);
2318
+ if (testForRegex(text)) {
2319
+ text = text.replace(/\\"/g, '"');
2320
+ }
2321
+ const timeout = this._getFindElementTimeout(options);
2202
2322
  await new Promise((resolve) => setTimeout(resolve, 2000));
2203
2323
  const newValue = await this._replaceWithLocalData(text, world);
2204
2324
  if (newValue !== text) {
@@ -2211,31 +2331,15 @@ class StableBrowser {
2211
2331
  await _preCommand(state, this);
2212
2332
  state.info.text = text;
2213
2333
  while (true) {
2214
- const frames = this.page.frames();
2215
- let results = [];
2216
- for (let i = 0; i < frames.length; i++) {
2217
- if (dateAlternatives.date) {
2218
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2219
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2220
- result.frame = frames[i];
2221
- results.push(result);
2222
- }
2223
- }
2224
- else if (numberAlternatives.number) {
2225
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2226
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2227
- result.frame = frames[i];
2228
- results.push(result);
2229
- }
2230
- }
2231
- else {
2232
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2233
- result.frame = frames[i];
2234
- results.push(result);
2235
- }
2334
+ let resultWithElementsFound = {
2335
+ length: 0,
2336
+ };
2337
+ try {
2338
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2339
+ }
2340
+ catch (error) {
2341
+ // ignore
2236
2342
  }
2237
- state.info.results = results;
2238
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2239
2343
  if (resultWithElementsFound.length === 0) {
2240
2344
  if (Date.now() - state.startTime > timeout) {
2241
2345
  throw new Error(`Text ${text} not found in page`);
@@ -2243,18 +2347,40 @@ class StableBrowser {
2243
2347
  await new Promise((resolve) => setTimeout(resolve, 1000));
2244
2348
  continue;
2245
2349
  }
2246
- if (resultWithElementsFound[0].randomToken) {
2247
- const frame = resultWithElementsFound[0].frame;
2248
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2249
- await this._highlightElements(frame, dataAttribute);
2250
- const element = await frame.$(dataAttribute);
2251
- if (element) {
2252
- await this.scrollIfNeeded(element, state.info);
2253
- await element.dispatchEvent("bvt_verify_page_contains_text");
2350
+ try {
2351
+ if (resultWithElementsFound[0].randomToken) {
2352
+ const frame = resultWithElementsFound[0].frame;
2353
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2354
+ await this._highlightElements(frame, dataAttribute);
2355
+ // if (world && world.screenshot && !world.screenshotPath) {
2356
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2357
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2358
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2359
+ // this._unhighlightElements(frame, dataAttribute)
2360
+ // .then(async () => {
2361
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2362
+ // })
2363
+ // .catch(
2364
+ // (e) => {}
2365
+ // console.error(e)
2366
+ // );
2367
+ // });
2368
+ // }
2369
+ const element = await frame.locator(dataAttribute).first();
2370
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2371
+ // await this._unhighlightElements(frame, dataAttribute);
2372
+ if (element) {
2373
+ await this.scrollIfNeeded(element, state.info);
2374
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2375
+ // await _screenshot(state, this, element);
2376
+ }
2254
2377
  }
2378
+ await _screenshot(state, this);
2379
+ return state.info;
2380
+ }
2381
+ catch (error) {
2382
+ console.error(error);
2255
2383
  }
2256
- await _screenshot(state, this);
2257
- return state.info;
2258
2384
  }
2259
2385
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2260
2386
  }
@@ -2276,10 +2402,14 @@ class StableBrowser {
2276
2402
  highlight: false,
2277
2403
  type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2278
2404
  text: `Verify text does not exist in page`,
2405
+ _text: `Verify the text '${text}' does not exist in page`,
2279
2406
  operation: "verifyTextNotExistInPage",
2280
2407
  log: "***** verify text " + text + " does not exist in page *****\n",
2281
2408
  };
2282
- const timeout = this._getLoadTimeout(options);
2409
+ if (testForRegex(text)) {
2410
+ text = text.replace(/\\"/g, '"');
2411
+ }
2412
+ const timeout = this._getFindElementTimeout(options);
2283
2413
  await new Promise((resolve) => setTimeout(resolve, 2000));
2284
2414
  const newValue = await this._replaceWithLocalData(text, world);
2285
2415
  if (newValue !== text) {
@@ -2291,32 +2421,16 @@ class StableBrowser {
2291
2421
  try {
2292
2422
  await _preCommand(state, this);
2293
2423
  state.info.text = text;
2424
+ let resultWithElementsFound = {
2425
+ length: null, // initial cannot be 0
2426
+ };
2294
2427
  while (true) {
2295
- const frames = this.page.frames();
2296
- let results = [];
2297
- for (let i = 0; i < frames.length; i++) {
2298
- if (dateAlternatives.date) {
2299
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2300
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2301
- result.frame = frames[i];
2302
- results.push(result);
2303
- }
2304
- }
2305
- else if (numberAlternatives.number) {
2306
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2307
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2308
- result.frame = frames[i];
2309
- results.push(result);
2310
- }
2311
- }
2312
- else {
2313
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2314
- result.frame = frames[i];
2315
- results.push(result);
2316
- }
2428
+ try {
2429
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2430
+ }
2431
+ catch (error) {
2432
+ // ignore
2317
2433
  }
2318
- state.info.results = results;
2319
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2320
2434
  if (resultWithElementsFound.length === 0) {
2321
2435
  await _screenshot(state, this);
2322
2436
  return state.info;
@@ -2334,15 +2448,116 @@ class StableBrowser {
2334
2448
  _commandFinally(state, this);
2335
2449
  }
2336
2450
  }
2337
- _getServerUrl() {
2338
- let serviceUrl = "https://api.blinq.io";
2339
- if (process.env.NODE_ENV_BLINQ === "dev") {
2340
- serviceUrl = "https://dev.api.blinq.io";
2451
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2452
+ textAnchor = unEscapeString(textAnchor);
2453
+ textToVerify = unEscapeString(textToVerify);
2454
+ const state = {
2455
+ text_search: textToVerify,
2456
+ options,
2457
+ world,
2458
+ locate: false,
2459
+ scroll: false,
2460
+ highlight: false,
2461
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2462
+ text: `Verify text with relation to another text`,
2463
+ _text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
2464
+ operation: "verify_text_with_relation",
2465
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2466
+ };
2467
+ const timeout = this._getFindElementTimeout(options);
2468
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2469
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2470
+ if (newValue !== textAnchor) {
2471
+ this.logger.info(textAnchor + "=" + newValue);
2472
+ textAnchor = newValue;
2473
+ }
2474
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2475
+ if (newValue !== textToVerify) {
2476
+ this.logger.info(textToVerify + "=" + newValue);
2477
+ textToVerify = newValue;
2478
+ }
2479
+ let dateAlternatives = findDateAlternatives(textToVerify);
2480
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2481
+ let foundAncore = false;
2482
+ try {
2483
+ await _preCommand(state, this);
2484
+ state.info.text = textToVerify;
2485
+ let resultWithElementsFound = {
2486
+ length: 0,
2487
+ };
2488
+ while (true) {
2489
+ try {
2490
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2491
+ }
2492
+ catch (error) {
2493
+ // ignore
2494
+ }
2495
+ if (resultWithElementsFound.length === 0) {
2496
+ if (Date.now() - state.startTime > timeout) {
2497
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2498
+ }
2499
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2500
+ continue;
2501
+ }
2502
+ try {
2503
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2504
+ foundAncore = true;
2505
+ const result = resultWithElementsFound[i];
2506
+ const token = result.randomToken;
2507
+ const frame = result.frame;
2508
+ let css = `[data-blinq-id-${token}]`;
2509
+ const climbArray1 = [];
2510
+ for (let i = 0; i < climb; i++) {
2511
+ climbArray1.push("..");
2512
+ }
2513
+ let climbXpath = "xpath=" + climbArray1.join("/");
2514
+ css = css + " >> " + climbXpath;
2515
+ const count = await frame.locator(css).count();
2516
+ for (let j = 0; j < count; j++) {
2517
+ const continer = await frame.locator(css).nth(j);
2518
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2519
+ if (result.elementCount > 0) {
2520
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2521
+ await this._highlightElements(frame, dataAttribute);
2522
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2523
+ // if (world && world.screenshot && !world.screenshotPath) {
2524
+ // console.log(`Highlighting for vtrt while running from recorder`);
2525
+ // this._highlightElements(frame, dataAttribute)
2526
+ // .then(async () => {
2527
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2528
+ // this._unhighlightElements(frame, dataAttribute).then(
2529
+ // () => {}
2530
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2531
+ // );
2532
+ // })
2533
+ // .catch(e);
2534
+ // }
2535
+ //await this._highlightElements(frame, cssAnchor);
2536
+ const element = await frame.locator(dataAttribute).first();
2537
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2538
+ // await this._unhighlightElements(frame, dataAttribute);
2539
+ if (element) {
2540
+ await this.scrollIfNeeded(element, state.info);
2541
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2542
+ }
2543
+ await _screenshot(state, this);
2544
+ return state.info;
2545
+ }
2546
+ }
2547
+ }
2548
+ }
2549
+ catch (error) {
2550
+ console.error(error);
2551
+ }
2552
+ }
2553
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2341
2554
  }
2342
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2343
- serviceUrl = "https://stage.api.blinq.io";
2555
+ catch (e) {
2556
+ await _commandError(state, e, this);
2557
+ }
2558
+ finally {
2559
+ _commandFinally(state, this);
2344
2560
  }
2345
- return serviceUrl;
2346
2561
  }
2347
2562
  async visualVerification(text, options = {}, world = null) {
2348
2563
  const startTime = Date.now();
@@ -2358,14 +2573,17 @@ class StableBrowser {
2358
2573
  throw new Error("TOKEN is not set");
2359
2574
  }
2360
2575
  try {
2361
- let serviceUrl = this._getServerUrl();
2576
+ let serviceUrl = _getServerUrl();
2362
2577
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2363
2578
  info.screenshotPath = screenshotPath;
2364
2579
  const screenshot = await this.takeScreenshot();
2365
- const request = {
2366
- method: "POST",
2580
+ let request = {
2581
+ method: "post",
2582
+ maxBodyLength: Infinity,
2367
2583
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2368
2584
  headers: {
2585
+ "x-bvt-project-id": path.basename(this.project_path),
2586
+ "x-source": "aaa",
2369
2587
  "Content-Type": "application/json",
2370
2588
  Authorization: `Bearer ${process.env.TOKEN}`,
2371
2589
  },
@@ -2374,7 +2592,7 @@ class StableBrowser {
2374
2592
  screenshot: screenshot,
2375
2593
  }),
2376
2594
  };
2377
- let result = await this.context.api.request(request);
2595
+ const result = await axios.request(request);
2378
2596
  if (result.data.status !== true) {
2379
2597
  throw new Error("Visual validation failed");
2380
2598
  }
@@ -2402,6 +2620,7 @@ class StableBrowser {
2402
2620
  _reportToWorld(world, {
2403
2621
  type: Types.VERIFY_VISUAL,
2404
2622
  text: "Visual verification",
2623
+ _text: "Visual verification of " + text,
2405
2624
  screenshotId,
2406
2625
  result: error
2407
2626
  ? {
@@ -2447,6 +2666,7 @@ class StableBrowser {
2447
2666
  let screenshotPath = null;
2448
2667
  const info = {};
2449
2668
  info.log = "";
2669
+ info.locatorLog = new LocatorLog(selectors);
2450
2670
  info.operation = "getTableData";
2451
2671
  info.selectors = selectors;
2452
2672
  try {
@@ -2522,7 +2742,7 @@ class StableBrowser {
2522
2742
  info.operation = "analyzeTable";
2523
2743
  info.selectors = selectors;
2524
2744
  info.query = query;
2525
- query = this._fixUsingParams(query, _params);
2745
+ query = _fixUsingParams(query, _params);
2526
2746
  info.query_fixed = query;
2527
2747
  info.operator = operator;
2528
2748
  info.value = value;
@@ -2667,6 +2887,32 @@ class StableBrowser {
2667
2887
  }
2668
2888
  return timeout;
2669
2889
  }
2890
+ _getFindElementTimeout(options) {
2891
+ if (options && options.timeout) {
2892
+ return options.timeout;
2893
+ }
2894
+ if (this.configuration.find_element_timeout) {
2895
+ return this.configuration.find_element_timeout;
2896
+ }
2897
+ return 30000;
2898
+ }
2899
+ async saveStoreState(path = null, world = null) {
2900
+ const storageState = await this.page.context().storageState();
2901
+ //const testDataFile = _getDataFile(world, this.context, this);
2902
+ if (path) {
2903
+ // save { storageState: storageState } into the path
2904
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
2905
+ }
2906
+ else {
2907
+ await this.setTestData({ storageState: storageState }, world);
2908
+ }
2909
+ }
2910
+ async restoreSaveState(path = null, world = null) {
2911
+ await refreshBrowser(this, path, world);
2912
+ this.registerEventListeners(this.context);
2913
+ registerNetworkEvents(this.world, this, this.context, this.page);
2914
+ registerDownloadEvent(this.page, this.world, this.context);
2915
+ }
2670
2916
  async waitForPageLoad(options = {}, world = null) {
2671
2917
  let timeout = this._getLoadTimeout(options);
2672
2918
  const promiseArray = [];
@@ -2734,6 +2980,7 @@ class StableBrowser {
2734
2980
  highlight: false,
2735
2981
  type: Types.CLOSE_PAGE,
2736
2982
  text: `Close page`,
2983
+ _text: `Close the page`,
2737
2984
  operation: "closePage",
2738
2985
  log: "***** close page *****\n",
2739
2986
  throwError: false,
@@ -2751,7 +2998,7 @@ class StableBrowser {
2751
2998
  }
2752
2999
  }
2753
3000
  saveTestDataAsGlobal(options, world) {
2754
- const dataFile = this._getDataFile(world);
3001
+ const dataFile = _getDataFile(world, this.context, this);
2755
3002
  process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2756
3003
  this.logger.info("Save the scenario test data as global for the following scenarios.");
2757
3004
  }
@@ -2781,6 +3028,7 @@ class StableBrowser {
2781
3028
  _reportToWorld(world, {
2782
3029
  type: Types.SET_VIEWPORT,
2783
3030
  text: "set viewport size to " + width + "x" + hight,
3031
+ _text: "Set the viewport size to " + width + "x" + hight,
2784
3032
  screenshotId,
2785
3033
  result: error
2786
3034
  ? {
@@ -2852,14 +3100,25 @@ class StableBrowser {
2852
3100
  }
2853
3101
  }
2854
3102
  async beforeStep(world, step) {
2855
- this.stepName = step.pickleStep.text;
2856
- this.logger.info("step: " + this.stepName);
2857
3103
  if (this.stepIndex === undefined) {
2858
3104
  this.stepIndex = 0;
2859
3105
  }
2860
3106
  else {
2861
3107
  this.stepIndex++;
2862
3108
  }
3109
+ if (step && step.pickleStep && step.pickleStep.text) {
3110
+ this.stepName = step.pickleStep.text;
3111
+ this.logger.info("step: " + this.stepName);
3112
+ }
3113
+ else if (step && step.text) {
3114
+ this.stepName = step.text;
3115
+ }
3116
+ else {
3117
+ this.stepName = "step " + this.stepIndex;
3118
+ }
3119
+ if (this.context) {
3120
+ this.context.examplesRow = extractStepExampleParameters(step);
3121
+ }
2863
3122
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2864
3123
  if (this.context.browserObject.context) {
2865
3124
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2872,6 +3131,41 @@ class StableBrowser {
2872
3131
  this.saveTestDataAsGlobal({}, world);
2873
3132
  }
2874
3133
  }
3134
+ if (this.initSnapshotTaken === false) {
3135
+ this.initSnapshotTaken = true;
3136
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3137
+ const snapshot = await this.getAriaSnapshot();
3138
+ if (snapshot) {
3139
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
3140
+ }
3141
+ }
3142
+ }
3143
+ }
3144
+ async getAriaSnapshot() {
3145
+ try {
3146
+ // find the page url
3147
+ const url = await this.page.url();
3148
+ // extract the path from the url
3149
+ const path = new URL(url).pathname;
3150
+ // get the page title
3151
+ const title = await this.page.title();
3152
+ // go over other frams
3153
+ const frames = this.page.frames();
3154
+ const snapshots = [];
3155
+ const content = [`- path: ${path}`, `- title: ${title}`];
3156
+ const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
3157
+ for (let i = 0; i < frames.length; i++) {
3158
+ content.push(`- frame: ${i}`);
3159
+ const frame = frames[i];
3160
+ const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
3161
+ content.push(snapshot);
3162
+ }
3163
+ return content.join("\n");
3164
+ }
3165
+ catch (e) {
3166
+ console.error(e);
3167
+ }
3168
+ return null;
2875
3169
  }
2876
3170
  async afterStep(world, step) {
2877
3171
  this.stepName = null;
@@ -2882,6 +3176,16 @@ class StableBrowser {
2882
3176
  });
2883
3177
  }
2884
3178
  }
3179
+ if (this.context) {
3180
+ this.context.examplesRow = null;
3181
+ }
3182
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3183
+ const snapshot = await this.getAriaSnapshot();
3184
+ if (snapshot) {
3185
+ const obj = {};
3186
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
3187
+ }
3188
+ }
2885
3189
  }
2886
3190
  }
2887
3191
  function createTimedPromise(promise, label) {
@@ -2889,156 +3193,5 @@ function createTimedPromise(promise, label) {
2889
3193
  .then((result) => ({ status: "fulfilled", label, result }))
2890
3194
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2891
3195
  }
2892
- const KEYBOARD_EVENTS = [
2893
- "ALT",
2894
- "AltGraph",
2895
- "CapsLock",
2896
- "Control",
2897
- "Fn",
2898
- "FnLock",
2899
- "Hyper",
2900
- "Meta",
2901
- "NumLock",
2902
- "ScrollLock",
2903
- "Shift",
2904
- "Super",
2905
- "Symbol",
2906
- "SymbolLock",
2907
- "Enter",
2908
- "Tab",
2909
- "ArrowDown",
2910
- "ArrowLeft",
2911
- "ArrowRight",
2912
- "ArrowUp",
2913
- "End",
2914
- "Home",
2915
- "PageDown",
2916
- "PageUp",
2917
- "Backspace",
2918
- "Clear",
2919
- "Copy",
2920
- "CrSel",
2921
- "Cut",
2922
- "Delete",
2923
- "EraseEof",
2924
- "ExSel",
2925
- "Insert",
2926
- "Paste",
2927
- "Redo",
2928
- "Undo",
2929
- "Accept",
2930
- "Again",
2931
- "Attn",
2932
- "Cancel",
2933
- "ContextMenu",
2934
- "Escape",
2935
- "Execute",
2936
- "Find",
2937
- "Finish",
2938
- "Help",
2939
- "Pause",
2940
- "Play",
2941
- "Props",
2942
- "Select",
2943
- "ZoomIn",
2944
- "ZoomOut",
2945
- "BrightnessDown",
2946
- "BrightnessUp",
2947
- "Eject",
2948
- "LogOff",
2949
- "Power",
2950
- "PowerOff",
2951
- "PrintScreen",
2952
- "Hibernate",
2953
- "Standby",
2954
- "WakeUp",
2955
- "AllCandidates",
2956
- "Alphanumeric",
2957
- "CodeInput",
2958
- "Compose",
2959
- "Convert",
2960
- "Dead",
2961
- "FinalMode",
2962
- "GroupFirst",
2963
- "GroupLast",
2964
- "GroupNext",
2965
- "GroupPrevious",
2966
- "ModeChange",
2967
- "NextCandidate",
2968
- "NonConvert",
2969
- "PreviousCandidate",
2970
- "Process",
2971
- "SingleCandidate",
2972
- "HangulMode",
2973
- "HanjaMode",
2974
- "JunjaMode",
2975
- "Eisu",
2976
- "Hankaku",
2977
- "Hiragana",
2978
- "HiraganaKatakana",
2979
- "KanaMode",
2980
- "KanjiMode",
2981
- "Katakana",
2982
- "Romaji",
2983
- "Zenkaku",
2984
- "ZenkakuHanaku",
2985
- "F1",
2986
- "F2",
2987
- "F3",
2988
- "F4",
2989
- "F5",
2990
- "F6",
2991
- "F7",
2992
- "F8",
2993
- "F9",
2994
- "F10",
2995
- "F11",
2996
- "F12",
2997
- "Soft1",
2998
- "Soft2",
2999
- "Soft3",
3000
- "Soft4",
3001
- "ChannelDown",
3002
- "ChannelUp",
3003
- "Close",
3004
- "MailForward",
3005
- "MailReply",
3006
- "MailSend",
3007
- "MediaFastForward",
3008
- "MediaPause",
3009
- "MediaPlay",
3010
- "MediaPlayPause",
3011
- "MediaRecord",
3012
- "MediaRewind",
3013
- "MediaStop",
3014
- "MediaTrackNext",
3015
- "MediaTrackPrevious",
3016
- "AudioBalanceLeft",
3017
- "AudioBalanceRight",
3018
- "AudioBassBoostDown",
3019
- "AudioBassBoostToggle",
3020
- "AudioBassBoostUp",
3021
- "AudioFaderFront",
3022
- "AudioFaderRear",
3023
- "AudioSurroundModeNext",
3024
- "AudioTrebleDown",
3025
- "AudioTrebleUp",
3026
- "AudioVolumeDown",
3027
- "AudioVolumeMute",
3028
- "AudioVolumeUp",
3029
- "MicrophoneToggle",
3030
- "MicrophoneVolumeDown",
3031
- "MicrophoneVolumeMute",
3032
- "MicrophoneVolumeUp",
3033
- "TV",
3034
- "TV3DMode",
3035
- "TVAntennaCable",
3036
- "TVAudioDescription",
3037
- ];
3038
- function unEscapeString(str) {
3039
- const placeholder = "__NEWLINE__";
3040
- str = str.replace(new RegExp(placeholder, "g"), "\n");
3041
- return str;
3042
- }
3043
3196
  export { StableBrowser };
3044
3197
  //# sourceMappingURL=stable_browser.js.map