automation_model 1.0.565-dev → 1.0.565-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,18 +10,21 @@ 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 { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
13
+ import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } 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
22
  import { LocatorLog } from "./locator_log.js";
23
+ import axios from "axios";
24
+ import { _findCellArea, findElementsInArea } from "./table_helper.js";
23
25
  export const Types = {
24
26
  CLICK: "click_element",
27
+ WAIT_ELEMENT: "wait_element",
25
28
  NAVIGATE: "navigate",
26
29
  FILL: "fill_element",
27
30
  EXECUTE: "execute_page_method",
@@ -43,6 +46,7 @@ export const Types = {
43
46
  UNCHECK: "uncheck_element",
44
47
  EXTRACT: "extract_attribute",
45
48
  CLOSE_PAGE: "close_page",
49
+ TABLE_OPERATION: "table_operation",
46
50
  SET_DATE_TIME: "set_date_time",
47
51
  SET_VIEWPORT: "set_viewport",
48
52
  VERIFY_VISUAL: "verify_visual",
@@ -53,6 +57,9 @@ export const Types = {
53
57
  VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
54
58
  };
55
59
  export const apps = {};
60
+ const formatElementName = (elementName) => {
61
+ return elementName ? JSON.stringify(elementName) : "element";
62
+ };
56
63
  class StableBrowser {
57
64
  browser;
58
65
  page;
@@ -66,6 +73,7 @@ class StableBrowser {
66
73
  appName = "main";
67
74
  tags = null;
68
75
  isRecording = false;
76
+ initSnapshotTaken = false;
69
77
  constructor(browser, page, logger = null, context = null, world = null) {
70
78
  this.browser = browser;
71
79
  this.page = page;
@@ -235,16 +243,51 @@ class StableBrowser {
235
243
  // async closeUnexpectedPopups() {
236
244
  // await closeUnexpectedPopups(this.page);
237
245
  // }
238
- async goto(url) {
246
+ async goto(url, world = null) {
247
+ if (!url) {
248
+ throw new Error("url is null, verify that the environment file is correct");
249
+ }
239
250
  if (!url.startsWith("http")) {
240
251
  url = "https://" + url;
241
252
  }
242
- await this.page.goto(url, {
243
- timeout: 60000,
244
- });
253
+ const state = {
254
+ value: url,
255
+ world: world,
256
+ type: Types.NAVIGATE,
257
+ text: `Navigate Page to: ${url}`,
258
+ operation: "goto",
259
+ log: "***** navigate page to " + url + " *****\n",
260
+ info: {},
261
+ locate: false,
262
+ scroll: false,
263
+ screenshot: false,
264
+ highlight: false,
265
+ };
266
+ try {
267
+ await _preCommand(state, this);
268
+ await this.page.goto(url, {
269
+ timeout: 60000,
270
+ });
271
+ await _screenshot(state, this);
272
+ }
273
+ catch (error) {
274
+ console.error("Error on goto", error);
275
+ _commandError(state, error, this);
276
+ }
277
+ finally {
278
+ _commandFinally(state, this);
279
+ }
245
280
  }
246
- _getLocator(locator, scope, _params) {
281
+ async _getLocator(locator, scope, _params) {
247
282
  locator = _fixLocatorUsingParams(locator, _params);
283
+ // locator = await this._replaceWithLocalData(locator);
284
+ for (let key in locator) {
285
+ if (typeof locator[key] !== "string")
286
+ continue;
287
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
288
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
289
+ }
290
+ }
248
291
  let locatorReturn;
249
292
  if (locator.role) {
250
293
  if (locator.role[1].nameReg) {
@@ -295,52 +338,60 @@ class StableBrowser {
295
338
  if (css && css.locator) {
296
339
  css = css.locator;
297
340
  }
298
- let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
341
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
299
342
  if (result.elementCount === 0) {
300
343
  return;
301
344
  }
302
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
345
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
303
346
  // css climb to parent element
304
347
  const climbArray = [];
305
348
  for (let i = 0; i < climb; i++) {
306
349
  climbArray.push("..");
307
350
  }
308
351
  let climbXpath = "xpath=" + climbArray.join("/");
309
- return textElementCss + " >> " + climbXpath + " >> " + css;
310
- }
311
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
312
- //const stringifyText = JSON.stringify(text);
313
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
314
- const options = {
315
- innerText: true,
316
- };
317
- if (regex) {
318
- options.singleRegex = true;
319
- }
320
- if (tag) {
321
- options.tag = tag;
322
- }
323
- if (!(partial === true)) {
324
- options.exactMatch = true;
325
- }
326
- const elements = window.findMatchingElements(text, options);
327
- let randomToken = null;
328
- const foundElements = [];
329
- let elementCount = 0;
330
- if (elements.length > 0) {
331
- for (let i = 0; i < elements.length; i++) {
332
- if (randomToken === null) {
333
- randomToken = Math.random().toString(36).substring(7);
352
+ let resultCss = textElementCss + " >> " + climbXpath;
353
+ if (css) {
354
+ resultCss = resultCss + " >> " + css;
355
+ }
356
+ return resultCss;
357
+ }
358
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
359
+ const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
360
+ const locator = scope.locator(query);
361
+ const count = await locator.count();
362
+ if (!tag1) {
363
+ tag1 = "*";
364
+ }
365
+ const randomToken = Math.random().toString(36).substring(7);
366
+ let tagCount = 0;
367
+ for (let i = 0; i < count; i++) {
368
+ const element = locator.nth(i);
369
+ // check if the tag matches
370
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
371
+ if (!tag.startsWith("*")) {
372
+ if (el.tagName.toLowerCase() !== tag) {
373
+ return false;
334
374
  }
335
- let element = elements[i];
336
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
337
- elementCount++;
338
375
  }
376
+ if (!el.setAttribute) {
377
+ el = el.parentElement;
378
+ }
379
+ // remove any attributes start with data-blinq-id
380
+ // for (let i = 0; i < el.attributes.length; i++) {
381
+ // if (el.attributes[i].name.startsWith("data-blinq-id")) {
382
+ // el.removeAttribute(el.attributes[i].name);
383
+ // }
384
+ // }
385
+ el.setAttribute("data-blinq-id-" + randomToken, "");
386
+ return true;
387
+ }, [tag1, randomToken]))) {
388
+ continue;
339
389
  }
340
- return { elementCount: elementCount, randomToken: randomToken };
341
- }, [text1, tag1, regex1, partial1]);
390
+ tagCount++;
391
+ }
392
+ return { elementCount: tagCount, randomToken };
342
393
  }
343
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
394
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
344
395
  if (!info) {
345
396
  info = {};
346
397
  }
@@ -363,30 +414,31 @@ class StableBrowser {
363
414
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
364
415
  let locator = null;
365
416
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
366
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
417
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
418
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
367
419
  if (!locatorString) {
368
420
  info.failCause.textNotFound = true;
369
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
421
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
370
422
  return;
371
423
  }
372
- locator = this._getLocator({ css: locatorString }, scope, _params);
424
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
373
425
  }
374
426
  else if (locatorSearch.text) {
375
427
  let text = _fixUsingParams(locatorSearch.text, _params);
376
- let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
428
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
377
429
  if (result.elementCount === 0) {
378
430
  info.failCause.textNotFound = true;
379
- info.failCause.lastError = "failed to locate element by text: " + text;
431
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
380
432
  return;
381
433
  }
382
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
434
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
383
435
  if (locatorSearch.childCss) {
384
436
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
385
437
  }
386
- locator = this._getLocator(locatorSearch, scope, _params);
438
+ locator = await this._getLocator(locatorSearch, scope, _params);
387
439
  }
388
440
  else {
389
- locator = this._getLocator(locatorSearch, scope, _params);
441
+ locator = await this._getLocator(locatorSearch, scope, _params);
390
442
  }
391
443
  // let cssHref = false;
392
444
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -415,7 +467,7 @@ class StableBrowser {
415
467
  if (!visibleOnly) {
416
468
  visible = true;
417
469
  }
418
- if (visible && enabled) {
470
+ if (visible && (allowDisabled || enabled)) {
419
471
  foundLocators.push(locator.nth(j));
420
472
  if (info.locatorLog) {
421
473
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
@@ -428,9 +480,11 @@ class StableBrowser {
428
480
  info.printMessages = {};
429
481
  }
430
482
  if (info.locatorLog && !visible) {
483
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
431
484
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
432
485
  }
433
486
  if (info.locatorLog && !enabled) {
487
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
434
488
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
435
489
  }
436
490
  if (!info.printMessages[j.toString()]) {
@@ -498,7 +552,7 @@ class StableBrowser {
498
552
  }
499
553
  return { rerun: false };
500
554
  }
501
- async _locate(selectors, info, _params, timeout) {
555
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
502
556
  if (!timeout) {
503
557
  timeout = 30000;
504
558
  }
@@ -508,9 +562,30 @@ class StableBrowser {
508
562
  let selector = selectors.locators[j];
509
563
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
510
564
  }
511
- let element = await this._locate_internal(selectors, info, _params, timeout);
565
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
512
566
  if (!element.rerun) {
513
- return element;
567
+ const randomToken = Math.random().toString(36).substring(7);
568
+ element.evaluate((el, randomToken) => {
569
+ el.setAttribute("data-blinq-id-" + randomToken, "");
570
+ }, randomToken);
571
+ // if (element._frame) {
572
+ // return element;
573
+ // }
574
+ const scope = element._frame ?? element.page();
575
+ let newElementSelector = "[data-blinq-id-" + randomToken + "]";
576
+ let prefixSelector = "";
577
+ const frameControlSelector = " >> internal:control=enter-frame";
578
+ const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
579
+ if (frameSelectorIndex !== -1) {
580
+ // remove everything after the >> internal:control=enter-frame
581
+ const frameSelector = element._selector.substring(0, frameSelectorIndex);
582
+ prefixSelector = frameSelector + " >> internal:control=enter-frame";
583
+ }
584
+ // if (element?._frame?._selector) {
585
+ // prefixSelector = element._frame._selector + " >> " + prefixSelector;
586
+ // }
587
+ const newSelector = prefixSelector + newElementSelector;
588
+ return scope.locator(newSelector);
514
589
  }
515
590
  }
516
591
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -583,7 +658,7 @@ class StableBrowser {
583
658
  //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
584
659
  if (Date.now() - startTime > timeout) {
585
660
  info.failCause.iframeNotFound = true;
586
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
661
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
587
662
  throw new Error("unable to locate iframe " + selectors.iframe_src);
588
663
  }
589
664
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -608,7 +683,7 @@ class StableBrowser {
608
683
  return bodyContent;
609
684
  });
610
685
  }
611
- async _locate_internal(selectors, info, _params, timeout = 30000) {
686
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
612
687
  if (!info) {
613
688
  info = {};
614
689
  info.failCause = {};
@@ -657,17 +732,17 @@ class StableBrowser {
657
732
  }
658
733
  // info.log += "scanning locators in priority 1" + "\n";
659
734
  let onlyPriority3 = selectorsLocators[0].priority === 3;
660
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
735
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
661
736
  if (result.foundElements.length === 0) {
662
737
  // info.log += "scanning locators in priority 2" + "\n";
663
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
738
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
664
739
  }
665
740
  if (result.foundElements.length === 0 && onlyPriority3) {
666
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
741
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
667
742
  }
668
743
  else {
669
744
  if (result.foundElements.length === 0 && !highPriorityOnly) {
670
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
745
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
671
746
  }
672
747
  }
673
748
  let foundElements = result.foundElements;
@@ -712,7 +787,7 @@ class StableBrowser {
712
787
  break;
713
788
  }
714
789
  if (Date.now() - startTime > highPriorityTimeout) {
715
- info.log += "high priority timeout, will try all elements" + "\n";
790
+ //info.log += "high priority timeout, will try all elements" + "\n";
716
791
  highPriorityOnly = false;
717
792
  if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
718
793
  lazy_scroll = true;
@@ -720,7 +795,7 @@ class StableBrowser {
720
795
  }
721
796
  }
722
797
  if (Date.now() - startTime > visibleOnlyTimeout) {
723
- info.log += "visible only timeout, will try all elements" + "\n";
798
+ //info.log += "visible only timeout, will try all elements" + "\n";
724
799
  visibleOnly = false;
725
800
  }
726
801
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -734,10 +809,12 @@ class StableBrowser {
734
809
  // }
735
810
  //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
736
811
  info.failCause.locatorNotFound = true;
737
- info.failCause.lastError = "failed to locate unique element";
812
+ if (!info?.failCause?.lastError) {
813
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
814
+ }
738
815
  throw new Error("failed to locate first element no elements found, " + info.log);
739
816
  }
740
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
817
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
741
818
  let foundElements = [];
742
819
  const result = {
743
820
  foundElements: foundElements,
@@ -745,7 +822,7 @@ class StableBrowser {
745
822
  for (let i = 0; i < locatorsGroup.length; i++) {
746
823
  let foundLocators = [];
747
824
  try {
748
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
825
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
749
826
  }
750
827
  catch (e) {
751
828
  // this call can fail it the browser is navigating
@@ -753,7 +830,7 @@ class StableBrowser {
753
830
  // this.logger.debug(e);
754
831
  foundLocators = [];
755
832
  try {
756
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
833
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
757
834
  }
758
835
  catch (e) {
759
836
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -768,9 +845,40 @@ class StableBrowser {
768
845
  result.locatorIndex = i;
769
846
  }
770
847
  if (foundLocators.length > 1) {
771
- info.failCause.foundMultiple = true;
772
- if (info.locatorLog) {
773
- info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
848
+ // remove elements that consume the same space with 10 pixels tolerance
849
+ const boxes = [];
850
+ for (let j = 0; j < foundLocators.length; j++) {
851
+ boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
852
+ }
853
+ for (let j = 0; j < boxes.length; j++) {
854
+ for (let k = 0; k < boxes.length; k++) {
855
+ if (j === k) {
856
+ continue;
857
+ }
858
+ // check if x, y, width, height are the same with 10 pixels tolerance
859
+ if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
860
+ Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
861
+ Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
862
+ Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
863
+ // as the element is not unique, will remove it
864
+ boxes.splice(k, 1);
865
+ k--;
866
+ }
867
+ }
868
+ }
869
+ if (boxes.length === 1) {
870
+ result.foundElements.push({
871
+ locator: boxes[0].locator.first(),
872
+ box: boxes[0].box,
873
+ unique: true,
874
+ });
875
+ result.locatorIndex = i;
876
+ }
877
+ else {
878
+ info.failCause.foundMultiple = true;
879
+ if (info.locatorLog) {
880
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
881
+ }
774
882
  }
775
883
  }
776
884
  }
@@ -881,25 +989,14 @@ class StableBrowser {
881
989
  options,
882
990
  world,
883
991
  text: "Click element",
992
+ _text: "Click on " + selectors.element_name,
884
993
  type: Types.CLICK,
885
994
  operation: "click",
886
995
  log: "***** click on " + selectors.element_name + " *****\n",
887
996
  };
888
997
  try {
889
998
  await _preCommand(state, this);
890
- if (state.options && state.options.context) {
891
- state.selectors.locators[0].text = state.options.context;
892
- }
893
- try {
894
- await state.element.click();
895
- // await new Promise((resolve) => setTimeout(resolve, 1000));
896
- }
897
- catch (e) {
898
- // await this.closeUnexpectedPopups();
899
- state.element = await this._locate(selectors, state.info, _params);
900
- await state.element.dispatchEvent("click");
901
- // await new Promise((resolve) => setTimeout(resolve, 1000));
902
- }
999
+ await performAction("click", state.element, options, this, state, _params);
903
1000
  await this.waitForPageLoad();
904
1001
  return state.info;
905
1002
  }
@@ -910,6 +1007,38 @@ class StableBrowser {
910
1007
  _commandFinally(state, this);
911
1008
  }
912
1009
  }
1010
+ async waitForElement(selectors, _params, options = {}, world = null) {
1011
+ const timeout = this._getFindElementTimeout(options);
1012
+ const state = {
1013
+ selectors,
1014
+ _params,
1015
+ options,
1016
+ world,
1017
+ text: "Wait for element",
1018
+ _text: "Wait for " + selectors.element_name,
1019
+ type: Types.WAIT_ELEMENT,
1020
+ operation: "waitForElement",
1021
+ log: "***** wait for " + selectors.element_name + " *****\n",
1022
+ };
1023
+ let found = false;
1024
+ try {
1025
+ await _preCommand(state, this);
1026
+ // if (state.options && state.options.context) {
1027
+ // state.selectors.locators[0].text = state.options.context;
1028
+ // }
1029
+ await state.element.waitFor({ timeout: timeout });
1030
+ found = true;
1031
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1032
+ }
1033
+ catch (e) {
1034
+ console.error("Error on waitForElement", e);
1035
+ // await _commandError(state, e, this);
1036
+ }
1037
+ finally {
1038
+ _commandFinally(state, this);
1039
+ }
1040
+ return found;
1041
+ }
913
1042
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
914
1043
  const state = {
915
1044
  selectors,
@@ -918,6 +1047,7 @@ class StableBrowser {
918
1047
  world,
919
1048
  type: checked ? Types.CHECK : Types.UNCHECK,
920
1049
  text: checked ? `Check element` : `Uncheck element`,
1050
+ _text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
921
1051
  operation: "setCheck",
922
1052
  log: "***** check " + selectors.element_name + " *****\n",
923
1053
  };
@@ -927,9 +1057,15 @@ class StableBrowser {
927
1057
  // let element = await this._locate(selectors, info, _params);
928
1058
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
929
1059
  try {
930
- // await this._highlightElements(element);
1060
+ // if (world && world.screenshot && !world.screenshotPath) {
1061
+ // console.log(`Highlighting while running from recorder`);
1062
+ await this._highlightElements(state.element);
931
1063
  await state.element.setChecked(checked);
932
1064
  await new Promise((resolve) => setTimeout(resolve, 1000));
1065
+ // await this._unHighlightElements(element);
1066
+ // }
1067
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1068
+ // await this._unHighlightElements(element);
933
1069
  }
934
1070
  catch (e) {
935
1071
  if (e.message && e.message.includes("did not change its state")) {
@@ -961,22 +1097,13 @@ class StableBrowser {
961
1097
  world,
962
1098
  type: Types.HOVER,
963
1099
  text: `Hover element`,
1100
+ _text: `Hover on ${selectors.element_name}`,
964
1101
  operation: "hover",
965
1102
  log: "***** hover " + selectors.element_name + " *****\n",
966
1103
  };
967
1104
  try {
968
1105
  await _preCommand(state, this);
969
- try {
970
- await state.element.hover();
971
- await new Promise((resolve) => setTimeout(resolve, 1000));
972
- }
973
- catch (e) {
974
- //await this.closeUnexpectedPopups();
975
- state.info.log += "hover failed, will try again" + "\n";
976
- state.element = await this._locate(selectors, state.info, _params);
977
- await state.element.hover({ timeout: 10000 });
978
- await new Promise((resolve) => setTimeout(resolve, 1000));
979
- }
1106
+ await performAction("hover", state.element, options, this, state, _params);
980
1107
  await _screenshot(state, this);
981
1108
  await this.waitForPageLoad();
982
1109
  return state.info;
@@ -1000,6 +1127,7 @@ class StableBrowser {
1000
1127
  value: values.toString(),
1001
1128
  type: Types.SELECT,
1002
1129
  text: `Select option: ${values}`,
1130
+ _text: `Select option: ${values} on ${selectors.element_name}`,
1003
1131
  operation: "selectOption",
1004
1132
  log: "***** select option " + selectors.element_name + " *****\n",
1005
1133
  };
@@ -1034,6 +1162,7 @@ class StableBrowser {
1034
1162
  highlight: false,
1035
1163
  type: Types.TYPE_PRESS,
1036
1164
  text: `Type value: ${_value}`,
1165
+ _text: `Type value: ${_value}`,
1037
1166
  operation: "type",
1038
1167
  log: "",
1039
1168
  };
@@ -1113,6 +1242,7 @@ class StableBrowser {
1113
1242
  world,
1114
1243
  type: Types.SET_DATE_TIME,
1115
1244
  text: `Set date time value: ${value}`,
1245
+ _text: `Set date time value: ${value} on ${selectors.element_name}`,
1116
1246
  operation: "setDateTime",
1117
1247
  log: "***** set date time value " + selectors.element_name + " *****\n",
1118
1248
  throwError: false,
@@ -1120,7 +1250,7 @@ class StableBrowser {
1120
1250
  try {
1121
1251
  await _preCommand(state, this);
1122
1252
  try {
1123
- await state.element.click();
1253
+ await performAction("click", state.element, options, this, state, _params);
1124
1254
  await new Promise((resolve) => setTimeout(resolve, 500));
1125
1255
  if (format) {
1126
1256
  state.value = dayjs(state.value).format(format);
@@ -1184,9 +1314,13 @@ class StableBrowser {
1184
1314
  world,
1185
1315
  type: Types.FILL,
1186
1316
  text: `Click type input with value: ${_value}`,
1317
+ _text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
1187
1318
  operation: "clickType",
1188
1319
  log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1189
1320
  };
1321
+ if (!options) {
1322
+ options = {};
1323
+ }
1190
1324
  if (newValue !== _value) {
1191
1325
  //this.logger.info(_value + "=" + newValue);
1192
1326
  _value = newValue;
@@ -1194,7 +1328,7 @@ class StableBrowser {
1194
1328
  try {
1195
1329
  await _preCommand(state, this);
1196
1330
  state.info.value = _value;
1197
- if (options === null || options === undefined || !options.press) {
1331
+ if (!options.press) {
1198
1332
  try {
1199
1333
  let currentValue = await state.element.inputValue();
1200
1334
  if (currentValue) {
@@ -1205,13 +1339,9 @@ class StableBrowser {
1205
1339
  this.logger.info("unable to clear input value");
1206
1340
  }
1207
1341
  }
1208
- if (options === null || options === undefined || options.press) {
1209
- try {
1210
- await state.element.click({ timeout: 5000 });
1211
- }
1212
- catch (e) {
1213
- await state.element.dispatchEvent("click");
1214
- }
1342
+ if (options.press) {
1343
+ options.timeout = 5000;
1344
+ await performAction("click", state.element, options, this, state, _params);
1215
1345
  }
1216
1346
  else {
1217
1347
  try {
@@ -1249,7 +1379,12 @@ class StableBrowser {
1249
1379
  await this.waitForPageLoad();
1250
1380
  }
1251
1381
  else if (enter === false) {
1252
- await state.element.dispatchEvent("change");
1382
+ try {
1383
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1384
+ }
1385
+ catch (e) {
1386
+ // ignore
1387
+ }
1253
1388
  //await this.page.keyboard.press("Tab");
1254
1389
  }
1255
1390
  else {
@@ -1301,6 +1436,7 @@ class StableBrowser {
1301
1436
  return await this._getText(selectors, 0, _params, options, info, world);
1302
1437
  }
1303
1438
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1439
+ const timeout = this._getFindElementTimeout(options);
1304
1440
  _validateSelectors(selectors);
1305
1441
  let screenshotId = null;
1306
1442
  let screenshotPath = null;
@@ -1310,7 +1446,7 @@ class StableBrowser {
1310
1446
  }
1311
1447
  info.operation = "getText";
1312
1448
  info.selectors = selectors;
1313
- let element = await this._locate(selectors, info, _params);
1449
+ let element = await this._locate(selectors, info, _params, timeout);
1314
1450
  if (climb > 0) {
1315
1451
  const climbArray = [];
1316
1452
  for (let i = 0; i < climb; i++) {
@@ -1329,6 +1465,18 @@ class StableBrowser {
1329
1465
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1330
1466
  try {
1331
1467
  await this._highlightElements(element);
1468
+ // if (world && world.screenshot && !world.screenshotPath) {
1469
+ // // console.log(`Highlighting for get text while running from recorder`);
1470
+ // this._highlightElements(element)
1471
+ // .then(async () => {
1472
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1473
+ // this._unhighlightElements(element).then(
1474
+ // () => {}
1475
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1476
+ // );
1477
+ // })
1478
+ // .catch(e);
1479
+ // }
1332
1480
  const elementText = await element.innerText();
1333
1481
  return {
1334
1482
  text: elementText,
@@ -1340,7 +1488,7 @@ class StableBrowser {
1340
1488
  }
1341
1489
  catch (e) {
1342
1490
  //await this.closeUnexpectedPopups();
1343
- this.logger.info("no innerText will use textContent");
1491
+ this.logger.info("no innerText, will use textContent");
1344
1492
  const elementText = await element.textContent();
1345
1493
  return { text: elementText, screenshotId, screenshotPath, value: value };
1346
1494
  }
@@ -1365,6 +1513,7 @@ class StableBrowser {
1365
1513
  highlight: false,
1366
1514
  type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1367
1515
  text: `Verify element contains pattern: ${pattern}`,
1516
+ _text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
1368
1517
  operation: "containsPattern",
1369
1518
  log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1370
1519
  };
@@ -1400,6 +1549,8 @@ class StableBrowser {
1400
1549
  }
1401
1550
  }
1402
1551
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1552
+ const timeout = this._getFindElementTimeout(options);
1553
+ const startTime = Date.now();
1403
1554
  const state = {
1404
1555
  selectors,
1405
1556
  _params,
@@ -1426,62 +1577,54 @@ class StableBrowser {
1426
1577
  }
1427
1578
  let foundObj = null;
1428
1579
  try {
1429
- await _preCommand(state, this);
1430
- foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1431
- if (foundObj && foundObj.element) {
1432
- await this.scrollIfNeeded(foundObj.element, state.info);
1433
- }
1434
- await _screenshot(state, this);
1435
- const dateAlternatives = findDateAlternatives(text);
1436
- const numberAlternatives = findNumberAlternatives(text);
1437
- if (dateAlternatives.date) {
1438
- for (let i = 0; i < dateAlternatives.dates.length; i++) {
1439
- if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1440
- foundObj?.value?.includes(dateAlternatives.dates[i])) {
1441
- return state.info;
1580
+ while (Date.now() - startTime < timeout) {
1581
+ try {
1582
+ await _preCommand(state, this);
1583
+ foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
1584
+ if (foundObj && foundObj.element) {
1585
+ await this.scrollIfNeeded(foundObj.element, state.info);
1442
1586
  }
1443
- }
1444
- throw new Error("element doesn't contain text " + text);
1445
- }
1446
- else if (numberAlternatives.number) {
1447
- for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1448
- if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1449
- foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1587
+ await _screenshot(state, this);
1588
+ const dateAlternatives = findDateAlternatives(text);
1589
+ const numberAlternatives = findNumberAlternatives(text);
1590
+ if (dateAlternatives.date) {
1591
+ for (let i = 0; i < dateAlternatives.dates.length; i++) {
1592
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1593
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1594
+ return state.info;
1595
+ }
1596
+ }
1597
+ }
1598
+ else if (numberAlternatives.number) {
1599
+ for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1600
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1601
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1602
+ return state.info;
1603
+ }
1604
+ }
1605
+ }
1606
+ else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
1450
1607
  return state.info;
1451
1608
  }
1452
1609
  }
1453
- throw new Error("element doesn't contain text " + text);
1454
- }
1455
- else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1456
- state.info.foundText = foundObj?.text;
1457
- state.info.value = foundObj?.value;
1458
- throw new Error("element doesn't contain text " + text);
1610
+ catch (e) {
1611
+ // Log error but continue retrying until timeout is reached
1612
+ this.logger.warn("Retrying containsText due to: " + e.message);
1613
+ }
1614
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
1459
1615
  }
1460
- return state.info;
1616
+ state.info.foundText = foundObj?.text;
1617
+ state.info.value = foundObj?.value;
1618
+ throw new Error("element doesn't contain text " + text);
1461
1619
  }
1462
1620
  catch (e) {
1463
1621
  await _commandError(state, e, this);
1622
+ throw e;
1464
1623
  }
1465
1624
  finally {
1466
1625
  _commandFinally(state, this);
1467
1626
  }
1468
1627
  }
1469
- _getDataFile(world = null) {
1470
- let dataFile = null;
1471
- if (world && world.reportFolder) {
1472
- dataFile = path.join(world.reportFolder, "data.json");
1473
- }
1474
- else if (this.reportFolder) {
1475
- dataFile = path.join(this.reportFolder, "data.json");
1476
- }
1477
- else if (this.context && this.context.reportFolder) {
1478
- dataFile = path.join(this.context.reportFolder, "data.json");
1479
- }
1480
- else {
1481
- dataFile = "data.json";
1482
- }
1483
- return dataFile;
1484
- }
1485
1628
  async waitForUserInput(message, world = null) {
1486
1629
  if (!message) {
1487
1630
  message = "# Wait for user input. Press any key to continue";
@@ -1510,13 +1653,22 @@ class StableBrowser {
1510
1653
  return;
1511
1654
  }
1512
1655
  // if data file exists, load it
1513
- const dataFile = this._getDataFile(world);
1656
+ const dataFile = _getDataFile(world, this.context, this);
1514
1657
  let data = this.getTestData(world);
1515
1658
  // merge the testData with the existing data
1516
1659
  Object.assign(data, testData);
1517
1660
  // save the data to the file
1518
1661
  fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
1519
1662
  }
1663
+ overwriteTestData(testData, world = null) {
1664
+ if (!testData) {
1665
+ return;
1666
+ }
1667
+ // if data file exists, load it
1668
+ const dataFile = _getDataFile(world, this.context, this);
1669
+ // save the data to the file
1670
+ fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
1671
+ }
1520
1672
  _getDataFilePath(fileName) {
1521
1673
  let dataFile = path.join(this.project_path, "data", fileName);
1522
1674
  if (fs.existsSync(dataFile)) {
@@ -1613,7 +1765,7 @@ class StableBrowser {
1613
1765
  }
1614
1766
  }
1615
1767
  getTestData(world = null) {
1616
- const dataFile = this._getDataFile(world);
1768
+ const dataFile = _getDataFile(world, this.context, this);
1617
1769
  let data = {};
1618
1770
  if (fs.existsSync(dataFile)) {
1619
1771
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1700,6 +1852,15 @@ class StableBrowser {
1700
1852
  document.documentElement.clientWidth,
1701
1853
  ])));
1702
1854
  let screenshotBuffer = null;
1855
+ // if (focusedElement) {
1856
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1857
+ // await this._unhighlightElements(focusedElement);
1858
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1859
+ // console.log(`Unhighlighted previous element`);
1860
+ // }
1861
+ // if (focusedElement) {
1862
+ // await this._highlightElements(focusedElement);
1863
+ // }
1703
1864
  if (this.context.browserName === "chromium") {
1704
1865
  const client = await playContext.newCDPSession(this.page);
1705
1866
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1721,6 +1882,10 @@ class StableBrowser {
1721
1882
  else {
1722
1883
  screenshotBuffer = await this.page.screenshot();
1723
1884
  }
1885
+ // if (focusedElement) {
1886
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1887
+ // await this._unhighlightElements(focusedElement);
1888
+ // }
1724
1889
  let image = await Jimp.read(screenshotBuffer);
1725
1890
  // Get the image dimensions
1726
1891
  const { width, height } = image.bitmap;
@@ -1733,6 +1898,7 @@ class StableBrowser {
1733
1898
  else {
1734
1899
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1735
1900
  }
1901
+ return screenshotBuffer;
1736
1902
  }
1737
1903
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1738
1904
  const state = {
@@ -1768,8 +1934,10 @@ class StableBrowser {
1768
1934
  world,
1769
1935
  type: Types.EXTRACT,
1770
1936
  text: `Extract attribute from element`,
1937
+ _text: `Extract attribute ${attribute} from ${selectors.element_name}`,
1771
1938
  operation: "extractAttribute",
1772
1939
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1940
+ allowDisabled: true,
1773
1941
  };
1774
1942
  await new Promise((resolve) => setTimeout(resolve, 2000));
1775
1943
  try {
@@ -1791,6 +1959,7 @@ class StableBrowser {
1791
1959
  state.info.value = state.value;
1792
1960
  this.setTestData({ [variable]: state.value }, world);
1793
1961
  this.logger.info("set test data: " + variable + "=" + state.value);
1962
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1794
1963
  return state.info;
1795
1964
  }
1796
1965
  catch (e) {
@@ -1809,14 +1978,21 @@ class StableBrowser {
1809
1978
  options,
1810
1979
  world,
1811
1980
  type: Types.VERIFY_ATTRIBUTE,
1981
+ highlight: true,
1982
+ screenshot: true,
1812
1983
  text: `Verify element attribute`,
1984
+ _text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
1813
1985
  operation: "verifyAttribute",
1814
1986
  log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1987
+ allowDisabled: true,
1815
1988
  };
1816
1989
  await new Promise((resolve) => setTimeout(resolve, 2000));
1817
1990
  let val;
1991
+ let expectedValue;
1818
1992
  try {
1819
1993
  await _preCommand(state, this);
1994
+ expectedValue = state.value;
1995
+ state.info.expectedValue = expectedValue;
1820
1996
  switch (attribute) {
1821
1997
  case "innerText":
1822
1998
  val = String(await state.element.innerText());
@@ -1830,23 +2006,30 @@ class StableBrowser {
1830
2006
  case "disabled":
1831
2007
  val = String(await state.element.isDisabled());
1832
2008
  break;
2009
+ case "readOnly":
2010
+ const isEditable = await state.element.isEditable();
2011
+ val = String(!isEditable);
2012
+ break;
1833
2013
  default:
1834
2014
  val = String(await state.element.getAttribute(attribute));
1835
2015
  break;
1836
2016
  }
2017
+ state.info.value = val;
1837
2018
  let regex;
1838
- if (value.startsWith("/") && value.endsWith("/")) {
1839
- const patternBody = value.slice(1, -1);
2019
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
2020
+ const patternBody = expectedValue.slice(1, -1);
1840
2021
  regex = new RegExp(patternBody, "g");
1841
2022
  }
1842
2023
  else {
1843
- const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2024
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1844
2025
  regex = new RegExp(escapedPattern, "g");
1845
2026
  }
1846
2027
  if (!val.match(regex)) {
1847
- throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
2028
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
2029
+ state.info.failCause.assertionFailed = true;
2030
+ state.info.failCause.lastError = errorMessage;
2031
+ throw new Error(errorMessage);
1848
2032
  }
1849
- state.info.value = val;
1850
2033
  return state.info;
1851
2034
  }
1852
2035
  catch (e) {
@@ -1945,27 +2128,32 @@ class StableBrowser {
1945
2128
  async _highlightElements(scope, css) {
1946
2129
  try {
1947
2130
  if (!scope) {
2131
+ // console.log(`Scope is not defined`);
1948
2132
  return;
1949
2133
  }
1950
2134
  if (!css) {
1951
2135
  scope
1952
2136
  .evaluate((node) => {
1953
2137
  if (node && node.style) {
1954
- let originalBorder = node.style.border;
1955
- node.style.border = "2px solid red";
2138
+ let originalOutline = node.style.outline;
2139
+ // console.log(`Original outline was: ${originalOutline}`);
2140
+ // node.__previousOutline = originalOutline;
2141
+ node.style.outline = "2px solid red";
2142
+ // console.log(`New outline is: ${node.style.outline}`);
1956
2143
  if (window) {
1957
2144
  window.addEventListener("beforeunload", function (e) {
1958
- node.style.border = originalBorder;
2145
+ node.style.outline = originalOutline;
1959
2146
  });
1960
2147
  }
1961
2148
  setTimeout(function () {
1962
- node.style.border = originalBorder;
2149
+ node.style.outline = originalOutline;
1963
2150
  }, 2000);
1964
2151
  }
1965
2152
  })
1966
2153
  .then(() => { })
1967
2154
  .catch((e) => {
1968
2155
  // ignore
2156
+ // console.error(`Could not highlight node : ${e}`);
1969
2157
  });
1970
2158
  }
1971
2159
  else {
@@ -1981,17 +2169,18 @@ class StableBrowser {
1981
2169
  if (!element.style) {
1982
2170
  return;
1983
2171
  }
1984
- var originalBorder = element.style.border;
2172
+ let originalOutline = element.style.outline;
2173
+ element.__previousOutline = originalOutline;
1985
2174
  // Set the new border to be red and 2px solid
1986
- element.style.border = "2px solid red";
2175
+ element.style.outline = "2px solid red";
1987
2176
  if (window) {
1988
2177
  window.addEventListener("beforeunload", function (e) {
1989
- element.style.border = originalBorder;
2178
+ element.style.outline = originalOutline;
1990
2179
  });
1991
2180
  }
1992
2181
  // Set a timeout to revert to the original border after 2 seconds
1993
2182
  setTimeout(function () {
1994
- element.style.border = originalBorder;
2183
+ element.style.outline = originalOutline;
1995
2184
  }, 2000);
1996
2185
  }
1997
2186
  return;
@@ -1999,6 +2188,7 @@ class StableBrowser {
1999
2188
  .then(() => { })
2000
2189
  .catch((e) => {
2001
2190
  // ignore
2191
+ // console.error(`Could not highlight css: ${e}`);
2002
2192
  });
2003
2193
  }
2004
2194
  }
@@ -2006,6 +2196,54 @@ class StableBrowser {
2006
2196
  console.debug(error);
2007
2197
  }
2008
2198
  }
2199
+ // async _unhighlightElements(scope, css) {
2200
+ // try {
2201
+ // if (!scope) {
2202
+ // return;
2203
+ // }
2204
+ // if (!css) {
2205
+ // scope
2206
+ // .evaluate((node) => {
2207
+ // if (node && node.style) {
2208
+ // if (!node.__previousOutline) {
2209
+ // node.style.outline = "";
2210
+ // } else {
2211
+ // node.style.outline = node.__previousOutline;
2212
+ // }
2213
+ // }
2214
+ // })
2215
+ // .then(() => {})
2216
+ // .catch((e) => {
2217
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2218
+ // });
2219
+ // } else {
2220
+ // scope
2221
+ // .evaluate(([css]) => {
2222
+ // if (!css) {
2223
+ // return;
2224
+ // }
2225
+ // let elements = Array.from(document.querySelectorAll(css));
2226
+ // for (i = 0; i < elements.length; i++) {
2227
+ // let element = elements[i];
2228
+ // if (!element.style) {
2229
+ // return;
2230
+ // }
2231
+ // if (!element.__previousOutline) {
2232
+ // element.style.outline = "";
2233
+ // } else {
2234
+ // element.style.outline = element.__previousOutline;
2235
+ // }
2236
+ // }
2237
+ // })
2238
+ // .then(() => {})
2239
+ // .catch((e) => {
2240
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2241
+ // });
2242
+ // }
2243
+ // } catch (error) {
2244
+ // // console.debug(error);
2245
+ // }
2246
+ // }
2009
2247
  async verifyPagePath(pathPart, options = {}, world = null) {
2010
2248
  const startTime = Date.now();
2011
2249
  let error = null;
@@ -2050,6 +2288,7 @@ class StableBrowser {
2050
2288
  _reportToWorld(world, {
2051
2289
  type: Types.VERIFY_PAGE_PATH,
2052
2290
  text: "Verify page path",
2291
+ _text: "Verify the page path contains " + pathPart,
2053
2292
  screenshotId,
2054
2293
  result: error
2055
2294
  ? {
@@ -2067,26 +2306,27 @@ class StableBrowser {
2067
2306
  });
2068
2307
  }
2069
2308
  }
2070
- async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2309
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
2071
2310
  const frames = this.page.frames();
2072
2311
  let results = [];
2312
+ // let ignoreCase = false;
2073
2313
  for (let i = 0; i < frames.length; i++) {
2074
2314
  if (dateAlternatives.date) {
2075
2315
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2076
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
2316
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2077
2317
  result.frame = frames[i];
2078
2318
  results.push(result);
2079
2319
  }
2080
2320
  }
2081
2321
  else if (numberAlternatives.number) {
2082
2322
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2083
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
2323
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2084
2324
  result.frame = frames[i];
2085
2325
  results.push(result);
2086
2326
  }
2087
2327
  }
2088
2328
  else {
2089
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
2329
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
2090
2330
  result.frame = frames[i];
2091
2331
  results.push(result);
2092
2332
  }
@@ -2105,11 +2345,15 @@ class StableBrowser {
2105
2345
  scroll: false,
2106
2346
  highlight: false,
2107
2347
  type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2108
- text: `Verify text exists in page`,
2348
+ text: `Verify the text '${text}' exists in page`,
2349
+ _text: `Verify the text '${text}' exists in page`,
2109
2350
  operation: "verifyTextExistInPage",
2110
2351
  log: "***** verify text " + text + " exists in page *****\n",
2111
2352
  };
2112
- const timeout = this._getLoadTimeout(options);
2353
+ if (testForRegex(text)) {
2354
+ text = text.replace(/\\"/g, '"');
2355
+ }
2356
+ const timeout = this._getFindElementTimeout(options);
2113
2357
  await new Promise((resolve) => setTimeout(resolve, 2000));
2114
2358
  const newValue = await this._replaceWithLocalData(text, world);
2115
2359
  if (newValue !== text) {
@@ -2122,7 +2366,15 @@ class StableBrowser {
2122
2366
  await _preCommand(state, this);
2123
2367
  state.info.text = text;
2124
2368
  while (true) {
2125
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2369
+ let resultWithElementsFound = {
2370
+ length: 0,
2371
+ };
2372
+ try {
2373
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2374
+ }
2375
+ catch (error) {
2376
+ // ignore
2377
+ }
2126
2378
  if (resultWithElementsFound.length === 0) {
2127
2379
  if (Date.now() - state.startTime > timeout) {
2128
2380
  throw new Error(`Text ${text} not found in page`);
@@ -2130,18 +2382,40 @@ class StableBrowser {
2130
2382
  await new Promise((resolve) => setTimeout(resolve, 1000));
2131
2383
  continue;
2132
2384
  }
2133
- if (resultWithElementsFound[0].randomToken) {
2134
- const frame = resultWithElementsFound[0].frame;
2135
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2136
- await this._highlightElements(frame, dataAttribute);
2137
- const element = await frame.$(dataAttribute);
2138
- if (element) {
2139
- await this.scrollIfNeeded(element, state.info);
2140
- await element.dispatchEvent("bvt_verify_page_contains_text");
2385
+ try {
2386
+ if (resultWithElementsFound[0].randomToken) {
2387
+ const frame = resultWithElementsFound[0].frame;
2388
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2389
+ await this._highlightElements(frame, dataAttribute);
2390
+ // if (world && world.screenshot && !world.screenshotPath) {
2391
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2392
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2393
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2394
+ // this._unhighlightElements(frame, dataAttribute)
2395
+ // .then(async () => {
2396
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2397
+ // })
2398
+ // .catch(
2399
+ // (e) => {}
2400
+ // console.error(e)
2401
+ // );
2402
+ // });
2403
+ // }
2404
+ const element = await frame.locator(dataAttribute).first();
2405
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2406
+ // await this._unhighlightElements(frame, dataAttribute);
2407
+ if (element) {
2408
+ await this.scrollIfNeeded(element, state.info);
2409
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2410
+ // await _screenshot(state, this, element);
2411
+ }
2141
2412
  }
2413
+ await _screenshot(state, this);
2414
+ return state.info;
2415
+ }
2416
+ catch (error) {
2417
+ console.error(error);
2142
2418
  }
2143
- await _screenshot(state, this);
2144
- return state.info;
2145
2419
  }
2146
2420
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2147
2421
  }
@@ -2162,11 +2436,15 @@ class StableBrowser {
2162
2436
  scroll: false,
2163
2437
  highlight: false,
2164
2438
  type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2165
- text: `Verify text does not exist in page`,
2439
+ text: `Verify the text '${text}' does not exist in page`,
2440
+ _text: `Verify the text '${text}' does not exist in page`,
2166
2441
  operation: "verifyTextNotExistInPage",
2167
2442
  log: "***** verify text " + text + " does not exist in page *****\n",
2168
2443
  };
2169
- const timeout = this._getLoadTimeout(options);
2444
+ if (testForRegex(text)) {
2445
+ text = text.replace(/\\"/g, '"');
2446
+ }
2447
+ const timeout = this._getFindElementTimeout(options);
2170
2448
  await new Promise((resolve) => setTimeout(resolve, 2000));
2171
2449
  const newValue = await this._replaceWithLocalData(text, world);
2172
2450
  if (newValue !== text) {
@@ -2178,8 +2456,16 @@ class StableBrowser {
2178
2456
  try {
2179
2457
  await _preCommand(state, this);
2180
2458
  state.info.text = text;
2459
+ let resultWithElementsFound = {
2460
+ length: null, // initial cannot be 0
2461
+ };
2181
2462
  while (true) {
2182
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2463
+ try {
2464
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2465
+ }
2466
+ catch (error) {
2467
+ // ignore
2468
+ }
2183
2469
  if (resultWithElementsFound.length === 0) {
2184
2470
  await _screenshot(state, this);
2185
2471
  return state.info;
@@ -2209,10 +2495,11 @@ class StableBrowser {
2209
2495
  highlight: false,
2210
2496
  type: Types.VERIFY_TEXT_WITH_RELATION,
2211
2497
  text: `Verify text with relation to another text`,
2498
+ _text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
2212
2499
  operation: "verify_text_with_relation",
2213
2500
  log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2214
2501
  };
2215
- const timeout = this._getLoadTimeout(options);
2502
+ const timeout = this._getFindElementTimeout(options);
2216
2503
  await new Promise((resolve) => setTimeout(resolve, 2000));
2217
2504
  let newValue = await this._replaceWithLocalData(textAnchor, world);
2218
2505
  if (newValue !== textAnchor) {
@@ -2230,8 +2517,16 @@ class StableBrowser {
2230
2517
  try {
2231
2518
  await _preCommand(state, this);
2232
2519
  state.info.text = textToVerify;
2520
+ let resultWithElementsFound = {
2521
+ length: 0,
2522
+ };
2233
2523
  while (true) {
2234
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2524
+ try {
2525
+ resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
2526
+ }
2527
+ catch (error) {
2528
+ // ignore
2529
+ }
2235
2530
  if (resultWithElementsFound.length === 0) {
2236
2531
  if (Date.now() - state.startTime > timeout) {
2237
2532
  throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
@@ -2239,51 +2534,56 @@ class StableBrowser {
2239
2534
  await new Promise((resolve) => setTimeout(resolve, 1000));
2240
2535
  continue;
2241
2536
  }
2242
- for (let i = 0; i < resultWithElementsFound.length; i++) {
2243
- foundAncore = true;
2244
- const result = resultWithElementsFound[i];
2245
- const token = result.randomToken;
2246
- const frame = result.frame;
2247
- const css = `[data-blinq-id="blinq-id-${token}"]`;
2248
- const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
2249
- const elements = Array.from(document.querySelectorAll(css));
2250
- for (let i = 0; i < elements.length; i++) {
2251
- const element = elements[i];
2252
- let climbParent = element;
2253
- for (let j = 0; j < climb; j++) {
2254
- climbParent = climbParent.parentElement;
2255
- if (!climbParent) {
2256
- break;
2537
+ try {
2538
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2539
+ foundAncore = true;
2540
+ const result = resultWithElementsFound[i];
2541
+ const token = result.randomToken;
2542
+ const frame = result.frame;
2543
+ let css = `[data-blinq-id-${token}]`;
2544
+ const climbArray1 = [];
2545
+ for (let i = 0; i < climb; i++) {
2546
+ climbArray1.push("..");
2547
+ }
2548
+ let climbXpath = "xpath=" + climbArray1.join("/");
2549
+ css = css + " >> " + climbXpath;
2550
+ const count = await frame.locator(css).count();
2551
+ for (let j = 0; j < count; j++) {
2552
+ const continer = await frame.locator(css).nth(j);
2553
+ const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, false, true, {});
2554
+ if (result.elementCount > 0) {
2555
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2556
+ await this._highlightElements(frame, dataAttribute);
2557
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2558
+ // if (world && world.screenshot && !world.screenshotPath) {
2559
+ // console.log(`Highlighting for vtrt while running from recorder`);
2560
+ // this._highlightElements(frame, dataAttribute)
2561
+ // .then(async () => {
2562
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2563
+ // this._unhighlightElements(frame, dataAttribute).then(
2564
+ // () => {}
2565
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2566
+ // );
2567
+ // })
2568
+ // .catch(e);
2569
+ // }
2570
+ //await this._highlightElements(frame, cssAnchor);
2571
+ const element = await frame.locator(dataAttribute).first();
2572
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2573
+ // await this._unhighlightElements(frame, dataAttribute);
2574
+ if (element) {
2575
+ await this.scrollIfNeeded(element, state.info);
2576
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2257
2577
  }
2578
+ await _screenshot(state, this);
2579
+ return state.info;
2258
2580
  }
2259
- if (!climbParent) {
2260
- continue;
2261
- }
2262
- const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
2263
- if (foundElements.length > 0) {
2264
- // set the container element attribute
2265
- element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
2266
- climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
2267
- foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
2268
- return { found: true };
2269
- }
2270
- }
2271
- return { found: false };
2272
- }, [css, climb, textToVerify, result.randomToken]);
2273
- if (findResult.found === true) {
2274
- const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
2275
- const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2276
- await this._highlightElements(frame, dataAttribute);
2277
- await this._highlightElements(frame, cssAnchor);
2278
- const element = await frame.$(dataAttribute);
2279
- if (element) {
2280
- await this.scrollIfNeeded(element, state.info);
2281
- await element.dispatchEvent("bvt_verify_page_contains_text");
2282
2581
  }
2283
- await _screenshot(state, this);
2284
- return state.info;
2285
2582
  }
2286
2583
  }
2584
+ catch (error) {
2585
+ console.error(error);
2586
+ }
2287
2587
  }
2288
2588
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2289
2589
  }
@@ -2294,6 +2594,30 @@ class StableBrowser {
2294
2594
  _commandFinally(state, this);
2295
2595
  }
2296
2596
  }
2597
+ async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
2598
+ const frames = this.page.frames();
2599
+ let results = [];
2600
+ let ignoreCase = false;
2601
+ for (let i = 0; i < frames.length; i++) {
2602
+ const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
2603
+ result.frame = frames[i];
2604
+ const climbArray = [];
2605
+ for (let i = 0; i < climb; i++) {
2606
+ climbArray.push("..");
2607
+ }
2608
+ let climbXpath = "xpath=" + climbArray.join("/");
2609
+ const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
2610
+ const count = await frames[i].locator(newLocator).count();
2611
+ if (count > 0) {
2612
+ result.elementCount = count;
2613
+ result.locator = newLocator;
2614
+ results.push(result);
2615
+ }
2616
+ }
2617
+ // state.info.results = results;
2618
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2619
+ return resultWithElementsFound;
2620
+ }
2297
2621
  async visualVerification(text, options = {}, world = null) {
2298
2622
  const startTime = Date.now();
2299
2623
  let error = null;
@@ -2312,10 +2636,13 @@ class StableBrowser {
2312
2636
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2313
2637
  info.screenshotPath = screenshotPath;
2314
2638
  const screenshot = await this.takeScreenshot();
2315
- const request = {
2316
- method: "POST",
2639
+ let request = {
2640
+ method: "post",
2641
+ maxBodyLength: Infinity,
2317
2642
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2318
2643
  headers: {
2644
+ "x-bvt-project-id": path.basename(this.project_path),
2645
+ "x-source": "aaa",
2319
2646
  "Content-Type": "application/json",
2320
2647
  Authorization: `Bearer ${process.env.TOKEN}`,
2321
2648
  },
@@ -2324,7 +2651,7 @@ class StableBrowser {
2324
2651
  screenshot: screenshot,
2325
2652
  }),
2326
2653
  };
2327
- let result = await this.context.api.request(request);
2654
+ const result = await axios.request(request);
2328
2655
  if (result.data.status !== true) {
2329
2656
  throw new Error("Visual validation failed");
2330
2657
  }
@@ -2352,6 +2679,7 @@ class StableBrowser {
2352
2679
  _reportToWorld(world, {
2353
2680
  type: Types.VERIFY_VISUAL,
2354
2681
  text: "Visual verification",
2682
+ _text: "Visual verification of " + text,
2355
2683
  screenshotId,
2356
2684
  result: error
2357
2685
  ? {
@@ -2618,6 +2946,32 @@ class StableBrowser {
2618
2946
  }
2619
2947
  return timeout;
2620
2948
  }
2949
+ _getFindElementTimeout(options) {
2950
+ if (options && options.timeout) {
2951
+ return options.timeout;
2952
+ }
2953
+ if (this.configuration.find_element_timeout) {
2954
+ return this.configuration.find_element_timeout;
2955
+ }
2956
+ return 30000;
2957
+ }
2958
+ async saveStoreState(path = null, world = null) {
2959
+ const storageState = await this.page.context().storageState();
2960
+ //const testDataFile = _getDataFile(world, this.context, this);
2961
+ if (path) {
2962
+ // save { storageState: storageState } into the path
2963
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
2964
+ }
2965
+ else {
2966
+ await this.setTestData({ storageState: storageState }, world);
2967
+ }
2968
+ }
2969
+ async restoreSaveState(path = null, world = null) {
2970
+ await refreshBrowser(this, path, world);
2971
+ this.registerEventListeners(this.context);
2972
+ registerNetworkEvents(this.world, this, this.context, this.page);
2973
+ registerDownloadEvent(this.page, this.world, this.context);
2974
+ }
2621
2975
  async waitForPageLoad(options = {}, world = null) {
2622
2976
  let timeout = this._getLoadTimeout(options);
2623
2977
  const promiseArray = [];
@@ -2685,6 +3039,7 @@ class StableBrowser {
2685
3039
  highlight: false,
2686
3040
  type: Types.CLOSE_PAGE,
2687
3041
  text: `Close page`,
3042
+ _text: `Close the page`,
2688
3043
  operation: "closePage",
2689
3044
  log: "***** close page *****\n",
2690
3045
  throwError: false,
@@ -2701,8 +3056,95 @@ class StableBrowser {
2701
3056
  _commandFinally(state, this);
2702
3057
  }
2703
3058
  }
3059
+ async tableCellOperation(headerText, rowText, options, _params, world = null) {
3060
+ let operation = null;
3061
+ if (!options || !options.operation) {
3062
+ throw new Error("operation is not defined");
3063
+ }
3064
+ operation = options.operation;
3065
+ // validate operation is one of the supported operations
3066
+ if (operation != "click" && operation != "hover+click") {
3067
+ throw new Error("operation is not supported");
3068
+ }
3069
+ const state = {
3070
+ options,
3071
+ world,
3072
+ locate: false,
3073
+ scroll: false,
3074
+ highlight: false,
3075
+ type: Types.TABLE_OPERATION,
3076
+ text: `Table operation`,
3077
+ _text: `Table ${operation} operation`,
3078
+ operation: operation,
3079
+ log: "***** Table operation *****\n",
3080
+ };
3081
+ const timeout = this._getFindElementTimeout(options);
3082
+ try {
3083
+ await _preCommand(state, this);
3084
+ const start = Date.now();
3085
+ let cellArea = null;
3086
+ while (true) {
3087
+ try {
3088
+ cellArea = await _findCellArea(headerText, rowText, this, state);
3089
+ if (cellArea) {
3090
+ break;
3091
+ }
3092
+ }
3093
+ catch (e) {
3094
+ // ignore
3095
+ }
3096
+ if (Date.now() - start > timeout) {
3097
+ throw new Error(`Cell not found in table`);
3098
+ }
3099
+ await new Promise((resolve) => setTimeout(resolve, 1000));
3100
+ }
3101
+ switch (operation) {
3102
+ case "click":
3103
+ if (!options.css) {
3104
+ // will click in the center of the cell
3105
+ let xOffset = 0;
3106
+ let yOffset = 0;
3107
+ if (options.xOffset) {
3108
+ xOffset = options.xOffset;
3109
+ }
3110
+ if (options.yOffset) {
3111
+ yOffset = options.yOffset;
3112
+ }
3113
+ await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
3114
+ }
3115
+ else {
3116
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3117
+ if (results.length === 0) {
3118
+ throw new Error(`Element not found in cell area`);
3119
+ }
3120
+ state.element = results[0];
3121
+ await performAction("click", state.element, options, this, state, _params);
3122
+ }
3123
+ break;
3124
+ case "hover+click":
3125
+ if (!options.css) {
3126
+ throw new Error("css is not defined");
3127
+ }
3128
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3129
+ if (results.length === 0) {
3130
+ throw new Error(`Element not found in cell area`);
3131
+ }
3132
+ state.element = results[0];
3133
+ await performAction("hover+click", state.element, options, this, state, _params);
3134
+ break;
3135
+ default:
3136
+ throw new Error("operation is not supported");
3137
+ }
3138
+ }
3139
+ catch (e) {
3140
+ await _commandError(state, e, this);
3141
+ }
3142
+ finally {
3143
+ _commandFinally(state, this);
3144
+ }
3145
+ }
2704
3146
  saveTestDataAsGlobal(options, world) {
2705
- const dataFile = this._getDataFile(world);
3147
+ const dataFile = _getDataFile(world, this.context, this);
2706
3148
  process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2707
3149
  this.logger.info("Save the scenario test data as global for the following scenarios.");
2708
3150
  }
@@ -2732,6 +3174,7 @@ class StableBrowser {
2732
3174
  _reportToWorld(world, {
2733
3175
  type: Types.SET_VIEWPORT,
2734
3176
  text: "set viewport size to " + width + "x" + hight,
3177
+ _text: "Set the viewport size to " + width + "x" + hight,
2735
3178
  screenshotId,
2736
3179
  result: error
2737
3180
  ? {
@@ -2803,14 +3246,25 @@ class StableBrowser {
2803
3246
  }
2804
3247
  }
2805
3248
  async beforeStep(world, step) {
2806
- this.stepName = step.pickleStep.text;
2807
- this.logger.info("step: " + this.stepName);
2808
3249
  if (this.stepIndex === undefined) {
2809
3250
  this.stepIndex = 0;
2810
3251
  }
2811
3252
  else {
2812
3253
  this.stepIndex++;
2813
3254
  }
3255
+ if (step && step.pickleStep && step.pickleStep.text) {
3256
+ this.stepName = step.pickleStep.text;
3257
+ this.logger.info("step: " + this.stepName);
3258
+ }
3259
+ else if (step && step.text) {
3260
+ this.stepName = step.text;
3261
+ }
3262
+ else {
3263
+ this.stepName = "step " + this.stepIndex;
3264
+ }
3265
+ if (this.context) {
3266
+ this.context.examplesRow = extractStepExampleParameters(step);
3267
+ }
2814
3268
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2815
3269
  if (this.context.browserObject.context) {
2816
3270
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2823,6 +3277,41 @@ class StableBrowser {
2823
3277
  this.saveTestDataAsGlobal({}, world);
2824
3278
  }
2825
3279
  }
3280
+ if (this.initSnapshotTaken === false) {
3281
+ this.initSnapshotTaken = true;
3282
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3283
+ const snapshot = await this.getAriaSnapshot();
3284
+ if (snapshot) {
3285
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
3286
+ }
3287
+ }
3288
+ }
3289
+ }
3290
+ async getAriaSnapshot() {
3291
+ try {
3292
+ // find the page url
3293
+ const url = await this.page.url();
3294
+ // extract the path from the url
3295
+ const path = new URL(url).pathname;
3296
+ // get the page title
3297
+ const title = await this.page.title();
3298
+ // go over other frams
3299
+ const frames = this.page.frames();
3300
+ const snapshots = [];
3301
+ const content = [`- path: ${path}`, `- title: ${title}`];
3302
+ const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
3303
+ for (let i = 0; i < frames.length; i++) {
3304
+ content.push(`- frame: ${i}`);
3305
+ const frame = frames[i];
3306
+ const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
3307
+ content.push(snapshot);
3308
+ }
3309
+ return content.join("\n");
3310
+ }
3311
+ catch (e) {
3312
+ console.error(e);
3313
+ }
3314
+ return null;
2826
3315
  }
2827
3316
  async afterStep(world, step) {
2828
3317
  this.stepName = null;
@@ -2833,6 +3322,16 @@ class StableBrowser {
2833
3322
  });
2834
3323
  }
2835
3324
  }
3325
+ if (this.context) {
3326
+ this.context.examplesRow = null;
3327
+ }
3328
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3329
+ const snapshot = await this.getAriaSnapshot();
3330
+ if (snapshot) {
3331
+ const obj = {};
3332
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
3333
+ }
3334
+ }
2836
3335
  }
2837
3336
  }
2838
3337
  function createTimedPromise(promise, label) {