automation_model 1.0.523-dev → 1.0.523-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,15 +10,17 @@ 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, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
14
14
  import csv from "csv-parser";
15
15
  import { Readable } from "node:stream";
16
16
  import readline from "readline";
17
17
  import { getContext } from "./init_browser.js";
18
18
  import { locate_element } from "./locate_element.js";
19
+ import { randomUUID } from "crypto";
19
20
  import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
20
21
  import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
21
- const Types = {
22
+ import { LocatorLog } from "./locator_log.js";
23
+ export const Types = {
22
24
  CLICK: "click_element",
23
25
  NAVIGATE: "navigate",
24
26
  FILL: "fill_element",
@@ -29,6 +31,8 @@ const Types = {
29
31
  GET_PAGE_STATUS: "get_page_status",
30
32
  CLICK_ROW_ACTION: "click_row_action",
31
33
  VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
34
+ VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
35
+ VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
32
36
  ANALYZE_TABLE: "analyze_table",
33
37
  SELECT: "select_combobox",
34
38
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -45,8 +49,13 @@ const Types = {
45
49
  LOAD_DATA: "load_data",
46
50
  SET_INPUT: "set_input",
47
51
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
52
+ VERIFY_ATTRIBUTE: "verify_element_attribute",
53
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
48
54
  };
49
55
  export const apps = {};
56
+ const formatElementName = (elementName) => {
57
+ return elementName ? JSON.stringify(elementName) : "element";
58
+ };
50
59
  class StableBrowser {
51
60
  browser;
52
61
  page;
@@ -89,17 +98,17 @@ class StableBrowser {
89
98
  catch (e) {
90
99
  this.logger.error("unable to read ai_config.json");
91
100
  }
92
- context.pageLoading = { status: false };
93
- context.pages = [this.page];
94
101
  const logFolder = path.join(this.project_path, "logs", "web");
95
102
  this.world = world;
103
+ context.pages = [this.page];
104
+ context.pageLoading = { status: false };
96
105
  this.registerEventListeners(this.context);
97
106
  registerNetworkEvents(this.world, this, this.context, this.page);
98
107
  registerDownloadEvent(this.page, this.world, this.context);
99
108
  }
100
109
  registerEventListeners(context) {
101
110
  this.registerConsoleLogListener(this.page, context);
102
- this.registerRequestListener(this.page, context, this.webLogFile);
111
+ // this.registerRequestListener(this.page, context, this.webLogFile);
103
112
  if (!context.pageLoading) {
104
113
  context.pageLoading = { status: false };
105
114
  }
@@ -144,7 +153,7 @@ class StableBrowser {
144
153
  if (this.appName === appName) {
145
154
  return;
146
155
  }
147
- let newContextCreated = false;
156
+ let navigate = false;
148
157
  if (!apps[appName]) {
149
158
  let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
150
159
  newContextCreated = true;
@@ -155,32 +164,15 @@ class StableBrowser {
155
164
  };
156
165
  }
157
166
  const tempContext = {};
158
- this._copyContext(this, tempContext);
159
- this._copyContext(apps[appName], this);
167
+ _copyContext(this, tempContext);
168
+ _copyContext(apps[appName], this);
160
169
  apps[this.appName] = tempContext;
161
170
  this.appName = appName;
162
- if (newContextCreated) {
163
- this.registerEventListeners(this.context);
171
+ if (navigate) {
164
172
  await this.goto(this.context.environment.baseUrl);
165
173
  await this.waitForPageLoad();
166
174
  }
167
175
  }
168
- _copyContext(from, to) {
169
- to.browser = from.browser;
170
- to.page = from.page;
171
- to.context = from.context;
172
- }
173
- getWebLogFile(logFolder) {
174
- if (!fs.existsSync(logFolder)) {
175
- fs.mkdirSync(logFolder, { recursive: true });
176
- }
177
- let nextIndex = 1;
178
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
179
- nextIndex++;
180
- }
181
- const fileName = nextIndex + ".json";
182
- return path.join(logFolder, fileName);
183
- }
184
176
  registerConsoleLogListener(page, context) {
185
177
  if (!this.context.webLogger) {
186
178
  this.context.webLogger = [];
@@ -244,55 +236,48 @@ class StableBrowser {
244
236
  // async closeUnexpectedPopups() {
245
237
  // await closeUnexpectedPopups(this.page);
246
238
  // }
247
- async goto(url) {
239
+ async goto(url, world = null) {
248
240
  if (!url.startsWith("http")) {
249
241
  url = "https://" + url;
250
242
  }
251
- await this.page.goto(url, {
252
- timeout: 60000,
253
- });
254
- }
255
- _fixUsingParams(text, _params) {
256
- if (!_params || typeof text !== "string") {
257
- return text;
243
+ const state = {
244
+ value: url,
245
+ world: world,
246
+ type: Types.NAVIGATE,
247
+ text: `Navigate Page to: ${url}`,
248
+ operation: "goto",
249
+ log: "***** navigate page to " + url + " *****\n",
250
+ info: {},
251
+ locate: false,
252
+ scroll: false,
253
+ screenshot: false,
254
+ highlight: false,
255
+ };
256
+ try {
257
+ await _preCommand(state, this);
258
+ await this.page.goto(url, {
259
+ timeout: 60000,
260
+ });
261
+ await _screenshot(state, this);
258
262
  }
259
- for (let key in _params) {
260
- let regValue = key;
261
- if (key.startsWith("_")) {
262
- // remove the _ prefix
263
- regValue = key.substring(1);
264
- }
265
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
263
+ catch (error) {
264
+ console.error("Error on goto", error);
265
+ _commandError(state, error, this);
266
266
  }
267
- return text;
268
- }
269
- _fixLocatorUsingParams(locator, _params) {
270
- // check if not null
271
- if (!locator) {
272
- return locator;
267
+ finally {
268
+ _commandFinally(state, this);
273
269
  }
274
- // clone the locator
275
- locator = JSON.parse(JSON.stringify(locator));
276
- this.scanAndManipulate(locator, _params);
277
- return locator;
278
270
  }
279
- _isObject(value) {
280
- return value && typeof value === "object" && value.constructor === Object;
281
- }
282
- scanAndManipulate(currentObj, _params) {
283
- for (const key in currentObj) {
284
- if (typeof currentObj[key] === "string") {
285
- // Perform string manipulation
286
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
287
- }
288
- else if (this._isObject(currentObj[key])) {
289
- // Recursively scan nested objects
290
- this.scanAndManipulate(currentObj[key], _params);
271
+ async _getLocator(locator, scope, _params) {
272
+ locator = _fixLocatorUsingParams(locator, _params);
273
+ // locator = await this._replaceWithLocalData(locator);
274
+ for (let key in locator) {
275
+ if (typeof locator[key] !== "string")
276
+ continue;
277
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
278
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
291
279
  }
292
280
  }
293
- }
294
- _getLocator(locator, scope, _params) {
295
- locator = this._fixLocatorUsingParams(locator, _params);
296
281
  let locatorReturn;
297
282
  if (locator.role) {
298
283
  if (locator.role[1].nameReg) {
@@ -300,7 +285,7 @@ class StableBrowser {
300
285
  delete locator.role[1].nameReg;
301
286
  }
302
287
  // if (locator.role[1].name) {
303
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
288
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
304
289
  // }
305
290
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
306
291
  }
@@ -343,140 +328,54 @@ class StableBrowser {
343
328
  if (css && css.locator) {
344
329
  css = css.locator;
345
330
  }
346
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
331
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
347
332
  if (result.elementCount === 0) {
348
333
  return;
349
334
  }
350
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
335
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
351
336
  // css climb to parent element
352
337
  const climbArray = [];
353
338
  for (let i = 0; i < climb; i++) {
354
339
  climbArray.push("..");
355
340
  }
356
341
  let climbXpath = "xpath=" + climbArray.join("/");
357
- return textElementCss + " >> " + climbXpath + " >> " + css;
358
- }
359
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
360
- //const stringifyText = JSON.stringify(text);
361
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
362
- function isParent(parent, child) {
363
- let currentNode = child.parentNode;
364
- while (currentNode !== null) {
365
- if (currentNode === parent) {
366
- return true;
342
+ let resultCss = textElementCss + " >> " + climbXpath;
343
+ if (css) {
344
+ resultCss = resultCss + " >> " + css;
345
+ }
346
+ return resultCss;
347
+ }
348
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
349
+ const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
350
+ const locator = scope.locator(query);
351
+ const count = await locator.count();
352
+ if (!tag1) {
353
+ tag1 = "*";
354
+ }
355
+ const randomToken = Math.random().toString(36).substring(7);
356
+ let tagCount = 0;
357
+ for (let i = 0; i < count; i++) {
358
+ const element = locator.nth(i);
359
+ // check if the tag matches
360
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
361
+ if (!tag.startsWith("*")) {
362
+ if (el.tagName.toLowerCase() !== tag) {
363
+ return false;
367
364
  }
368
- currentNode = currentNode.parentNode;
369
- }
370
- return false;
371
- }
372
- document.isParent = isParent;
373
- function getRegex(str) {
374
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
375
- if (!match) {
376
- return null;
377
- }
378
- let [_, pattern, flags] = match;
379
- return new RegExp(pattern, flags);
380
- }
381
- document.getRegex = getRegex;
382
- function collectAllShadowDomElements(element, result = []) {
383
- // Check and add the element if it has a shadow root
384
- if (element.shadowRoot) {
385
- result.push(element);
386
- // Also search within the shadow root
387
- document.collectAllShadowDomElements(element.shadowRoot, result);
388
- }
389
- // Iterate over child nodes
390
- element.childNodes.forEach((child) => {
391
- // Recursively call the function for each child node
392
- document.collectAllShadowDomElements(child, result);
393
- });
394
- return result;
395
- }
396
- document.collectAllShadowDomElements = collectAllShadowDomElements;
397
- if (!tag) {
398
- tag = "*:not(script, style, head)";
399
- }
400
- let regexpSearch = document.getRegex(text);
401
- if (regexpSearch) {
402
- regex = true;
403
- }
404
- let elements = Array.from(document.querySelectorAll(tag));
405
- let shadowHosts = [];
406
- document.collectAllShadowDomElements(document, shadowHosts);
407
- for (let i = 0; i < shadowHosts.length; i++) {
408
- let shadowElement = shadowHosts[i].shadowRoot;
409
- if (!shadowElement) {
410
- console.log("shadowElement is null, for host " + shadowHosts[i]);
411
- continue;
412
- }
413
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
414
- elements = elements.concat(shadowElements);
415
- }
416
- let randomToken = null;
417
- const foundElements = [];
418
- if (regex) {
419
- if (!regexpSearch) {
420
- regexpSearch = new RegExp(text, "im");
421
365
  }
422
- for (let i = 0; i < elements.length; i++) {
423
- const element = elements[i];
424
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
425
- (element.value && regexpSearch.test(element.value))) {
426
- foundElements.push(element);
427
- }
428
- }
429
- }
430
- else {
431
- text = text.trim();
432
- for (let i = 0; i < elements.length; i++) {
433
- const element = elements[i];
434
- if (partial) {
435
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
436
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
437
- foundElements.push(element);
438
- }
439
- }
440
- else {
441
- if ((element.innerText && element.innerText.trim() === text) ||
442
- (element.value && element.value === text)) {
443
- foundElements.push(element);
444
- }
445
- }
446
- }
447
- }
448
- let noChildElements = [];
449
- for (let i = 0; i < foundElements.length; i++) {
450
- let element = foundElements[i];
451
- let hasChild = false;
452
- for (let j = 0; j < foundElements.length; j++) {
453
- if (i === j) {
454
- continue;
455
- }
456
- if (isParent(element, foundElements[j])) {
457
- hasChild = true;
458
- break;
459
- }
460
- }
461
- if (!hasChild) {
462
- noChildElements.push(element);
463
- }
464
- }
465
- let elementCount = 0;
466
- if (noChildElements.length > 0) {
467
- for (let i = 0; i < noChildElements.length; i++) {
468
- if (randomToken === null) {
469
- randomToken = Math.random().toString(36).substring(7);
470
- }
471
- let element = noChildElements[i];
472
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
473
- elementCount++;
366
+ if (!el.setAttribute) {
367
+ el = el.parentElement;
474
368
  }
369
+ el.setAttribute("data-blinq-id-" + randomToken, "");
370
+ return true;
371
+ }, [tag1, randomToken]))) {
372
+ continue;
475
373
  }
476
- return { elementCount: elementCount, randomToken: randomToken };
477
- }, [text1, tag1, regex1, partial1]);
374
+ tagCount++;
375
+ }
376
+ return { elementCount: tagCount, randomToken };
478
377
  }
479
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
378
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
480
379
  if (!info) {
481
380
  info = {};
482
381
  }
@@ -485,10 +384,13 @@ class StableBrowser {
485
384
  }
486
385
  if (!info.log) {
487
386
  info.log = "";
387
+ info.locatorLog = new LocatorLog(selectorHierarchy);
488
388
  }
489
389
  let locatorSearch = selectorHierarchy[index];
390
+ let originalLocatorSearch = "";
490
391
  try {
491
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
392
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
393
+ locatorSearch = JSON.parse(originalLocatorSearch);
492
394
  }
493
395
  catch (e) {
494
396
  console.error(e);
@@ -496,30 +398,31 @@ class StableBrowser {
496
398
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
497
399
  let locator = null;
498
400
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
499
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
401
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
402
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
500
403
  if (!locatorString) {
501
404
  info.failCause.textNotFound = true;
502
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
405
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
503
406
  return;
504
407
  }
505
- locator = this._getLocator({ css: locatorString }, scope, _params);
408
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
506
409
  }
507
410
  else if (locatorSearch.text) {
508
- let text = this._fixUsingParams(locatorSearch.text, _params);
509
- let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
411
+ let text = _fixUsingParams(locatorSearch.text, _params);
412
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
510
413
  if (result.elementCount === 0) {
511
414
  info.failCause.textNotFound = true;
512
- info.failCause.lastError = "failed to locate element by text: " + text;
415
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
513
416
  return;
514
417
  }
515
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
418
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
516
419
  if (locatorSearch.childCss) {
517
420
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
518
421
  }
519
- locator = this._getLocator(locatorSearch, scope, _params);
422
+ locator = await this._getLocator(locatorSearch, scope, _params);
520
423
  }
521
424
  else {
522
- locator = this._getLocator(locatorSearch, scope, _params);
425
+ locator = await this._getLocator(locatorSearch, scope, _params);
523
426
  }
524
427
  // let cssHref = false;
525
428
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -532,18 +435,27 @@ class StableBrowser {
532
435
  //info.log += "total elements found " + count + "\n";
533
436
  //let visibleCount = 0;
534
437
  let visibleLocator = null;
535
- if (locatorSearch.index && locatorSearch.index < count) {
438
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
536
439
  foundLocators.push(locator.nth(locatorSearch.index));
440
+ if (info.locatorLog) {
441
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
442
+ }
537
443
  return;
538
444
  }
445
+ if (info.locatorLog && count === 0) {
446
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
447
+ }
539
448
  for (let j = 0; j < count; j++) {
540
449
  let visible = await locator.nth(j).isVisible();
541
450
  const enabled = await locator.nth(j).isEnabled();
542
451
  if (!visibleOnly) {
543
452
  visible = true;
544
453
  }
545
- if (visible && enabled) {
454
+ if (visible && (allowDisabled || enabled)) {
546
455
  foundLocators.push(locator.nth(j));
456
+ if (info.locatorLog) {
457
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
458
+ }
547
459
  }
548
460
  else {
549
461
  info.failCause.visible = visible;
@@ -551,8 +463,16 @@ class StableBrowser {
551
463
  if (!info.printMessages) {
552
464
  info.printMessages = {};
553
465
  }
466
+ if (info.locatorLog && !visible) {
467
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
468
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
469
+ }
470
+ if (info.locatorLog && !enabled) {
471
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
472
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
473
+ }
554
474
  if (!info.printMessages[j.toString()]) {
555
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
475
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
556
476
  info.printMessages[j.toString()] = true;
557
477
  }
558
478
  }
@@ -568,7 +488,7 @@ class StableBrowser {
568
488
  if (!info) {
569
489
  info = {};
570
490
  }
571
- info.log += "scan for popup handlers" + "\n";
491
+ //info.log += "scan for popup handlers" + "\n";
572
492
  const handlerGroup = [];
573
493
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
574
494
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -595,16 +515,28 @@ class StableBrowser {
595
515
  }
596
516
  if (result.foundElements.length > 0) {
597
517
  let dialogCloseLocator = result.foundElements[0].locator;
598
- await dialogCloseLocator.click();
599
- // wait for the dialog to close
600
- await dialogCloseLocator.waitFor({ state: "hidden" });
518
+ try {
519
+ await scope?.evaluate(() => {
520
+ window.__isClosingPopups = true;
521
+ });
522
+ await dialogCloseLocator.click();
523
+ // wait for the dialog to close
524
+ await dialogCloseLocator.waitFor({ state: "hidden" });
525
+ }
526
+ catch (e) {
527
+ }
528
+ finally {
529
+ await scope?.evaluate(() => {
530
+ window.__isClosingPopups = false;
531
+ });
532
+ }
601
533
  return { rerun: true };
602
534
  }
603
535
  }
604
536
  }
605
537
  return { rerun: false };
606
538
  }
607
- async _locate(selectors, info, _params, timeout) {
539
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
608
540
  if (!timeout) {
609
541
  timeout = 30000;
610
542
  }
@@ -614,7 +546,7 @@ class StableBrowser {
614
546
  let selector = selectors.locators[j];
615
547
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
616
548
  }
617
- let element = await this._locate_internal(selectors, info, _params, timeout);
549
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
618
550
  if (!element.rerun) {
619
551
  return element;
620
552
  }
@@ -627,6 +559,7 @@ class StableBrowser {
627
559
  info.failCause = {};
628
560
  info.log = "";
629
561
  }
562
+ let startTime = Date.now();
630
563
  let scope = this.page;
631
564
  if (selectors.frame) {
632
565
  return selectors.frame;
@@ -657,9 +590,11 @@ class StableBrowser {
657
590
  }
658
591
  return framescope;
659
592
  };
593
+ let fLocator = null;
660
594
  while (true) {
661
595
  let frameFound = false;
662
596
  if (selectors.nestFrmLoc) {
597
+ fLocator = selectors.nestFrmLoc;
663
598
  scope = await findFrame(selectors.nestFrmLoc, scope);
664
599
  frameFound = true;
665
600
  break;
@@ -668,6 +603,7 @@ class StableBrowser {
668
603
  for (let i = 0; i < selectors.frameLocators.length; i++) {
669
604
  let frameLocator = selectors.frameLocators[i];
670
605
  if (frameLocator.css) {
606
+ fLocator = frameLocator.css;
671
607
  scope = scope.frameLocator(frameLocator.css);
672
608
  frameFound = true;
673
609
  break;
@@ -675,18 +611,25 @@ class StableBrowser {
675
611
  }
676
612
  }
677
613
  if (!frameFound && selectors.iframe_src) {
614
+ fLocator = selectors.iframe_src;
678
615
  scope = this.page.frame({ url: selectors.iframe_src });
679
616
  }
680
617
  if (!scope) {
681
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
682
- if (performance.now() - startTime > timeout) {
618
+ if (info && info.locatorLog) {
619
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
620
+ }
621
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
622
+ if (Date.now() - startTime > timeout) {
683
623
  info.failCause.iframeNotFound = true;
684
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
624
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
685
625
  throw new Error("unable to locate iframe " + selectors.iframe_src);
686
626
  }
687
627
  await new Promise((resolve) => setTimeout(resolve, 1000));
688
628
  }
689
629
  else {
630
+ if (info && info.locatorLog) {
631
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
632
+ }
690
633
  break;
691
634
  }
692
635
  }
@@ -703,16 +646,18 @@ class StableBrowser {
703
646
  return bodyContent;
704
647
  });
705
648
  }
706
- async _locate_internal(selectors, info, _params, timeout = 30000) {
649
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
707
650
  if (!info) {
708
651
  info = {};
709
652
  info.failCause = {};
710
653
  info.log = "";
654
+ info.locatorLog = new LocatorLog(selectors);
711
655
  }
712
656
  let highPriorityTimeout = 5000;
713
657
  let visibleOnlyTimeout = 6000;
714
- let startTime = performance.now();
658
+ let startTime = Date.now();
715
659
  let locatorsCount = 0;
660
+ let lazy_scroll = false;
716
661
  //let arrayMode = Array.isArray(selectors);
717
662
  let scope = await this._findFrameScope(selectors, timeout, info);
718
663
  let selectorsLocators = null;
@@ -750,17 +695,17 @@ class StableBrowser {
750
695
  }
751
696
  // info.log += "scanning locators in priority 1" + "\n";
752
697
  let onlyPriority3 = selectorsLocators[0].priority === 3;
753
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
698
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
754
699
  if (result.foundElements.length === 0) {
755
700
  // info.log += "scanning locators in priority 2" + "\n";
756
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
701
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
757
702
  }
758
703
  if (result.foundElements.length === 0 && onlyPriority3) {
759
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
704
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
760
705
  }
761
706
  else {
762
707
  if (result.foundElements.length === 0 && !highPriorityOnly) {
763
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
708
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
764
709
  }
765
710
  }
766
711
  let foundElements = result.foundElements;
@@ -801,26 +746,38 @@ class StableBrowser {
801
746
  return maxCountElement.locator;
802
747
  }
803
748
  }
804
- if (performance.now() - startTime > timeout) {
749
+ if (Date.now() - startTime > timeout) {
805
750
  break;
806
751
  }
807
- if (performance.now() - startTime > highPriorityTimeout) {
808
- info.log += "high priority timeout, will try all elements" + "\n";
752
+ if (Date.now() - startTime > highPriorityTimeout) {
753
+ //info.log += "high priority timeout, will try all elements" + "\n";
809
754
  highPriorityOnly = false;
755
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
756
+ lazy_scroll = true;
757
+ await scrollPageToLoadLazyElements(this.page);
758
+ }
810
759
  }
811
- if (performance.now() - startTime > visibleOnlyTimeout) {
812
- info.log += "visible only timeout, will try all elements" + "\n";
760
+ if (Date.now() - startTime > visibleOnlyTimeout) {
761
+ //info.log += "visible only timeout, will try all elements" + "\n";
813
762
  visibleOnly = false;
814
763
  }
815
764
  await new Promise((resolve) => setTimeout(resolve, 1000));
816
765
  }
817
766
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
818
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
767
+ // if (info.locatorLog) {
768
+ // const lines = info.locatorLog.toString().split("\n");
769
+ // for (let line of lines) {
770
+ // this.logger.debug(line);
771
+ // }
772
+ // }
773
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
819
774
  info.failCause.locatorNotFound = true;
820
- info.failCause.lastError = "failed to locate unique element";
775
+ if (!info?.failCause?.lastError) {
776
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
777
+ }
821
778
  throw new Error("failed to locate first element no elements found, " + info.log);
822
779
  }
823
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
780
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
824
781
  let foundElements = [];
825
782
  const result = {
826
783
  foundElements: foundElements,
@@ -828,14 +785,15 @@ class StableBrowser {
828
785
  for (let i = 0; i < locatorsGroup.length; i++) {
829
786
  let foundLocators = [];
830
787
  try {
831
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
788
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
832
789
  }
833
790
  catch (e) {
834
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
835
- this.logger.debug(e);
791
+ // this call can fail it the browser is navigating
792
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
793
+ // this.logger.debug(e);
836
794
  foundLocators = [];
837
795
  try {
838
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
796
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
839
797
  }
840
798
  catch (e) {
841
799
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -851,11 +809,27 @@ class StableBrowser {
851
809
  }
852
810
  if (foundLocators.length > 1) {
853
811
  info.failCause.foundMultiple = true;
812
+ if (info.locatorLog) {
813
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
814
+ }
854
815
  }
855
816
  }
856
817
  return result;
857
818
  }
858
819
  async simpleClick(elementDescription, _params, options = {}, world = null) {
820
+ const state = {
821
+ locate: false,
822
+ scroll: false,
823
+ highlight: false,
824
+ _params,
825
+ options,
826
+ world,
827
+ type: Types.CLICK,
828
+ text: "Click element",
829
+ operation: "simpleClick",
830
+ log: "***** click on " + elementDescription + " *****\n",
831
+ };
832
+ _preCommand(state, this);
859
833
  const startTime = Date.now();
860
834
  let timeout = 30000;
861
835
  if (options && options.timeout) {
@@ -880,13 +854,31 @@ class StableBrowser {
880
854
  catch (e) {
881
855
  if (performance.now() - startTime > timeout) {
882
856
  // throw e;
883
- await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
857
+ try {
858
+ await _commandError(state, "timeout looking for " + elementDescription, this);
859
+ }
860
+ finally {
861
+ _commandFinally(state, this);
862
+ }
884
863
  }
885
864
  }
886
865
  await new Promise((resolve) => setTimeout(resolve, 3000));
887
866
  }
888
867
  }
889
868
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
869
+ const state = {
870
+ locate: false,
871
+ scroll: false,
872
+ highlight: false,
873
+ _params,
874
+ options,
875
+ world,
876
+ type: Types.FILL,
877
+ text: "Fill element",
878
+ operation: "simpleClickType",
879
+ log: "***** click type on " + elementDescription + " *****\n",
880
+ };
881
+ _preCommand(state, this);
890
882
  const startTime = Date.now();
891
883
  let timeout = 30000;
892
884
  if (options && options.timeout) {
@@ -911,7 +903,12 @@ class StableBrowser {
911
903
  catch (e) {
912
904
  if (performance.now() - startTime > timeout) {
913
905
  // throw e;
914
- await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
906
+ try {
907
+ await _commandError(state, "timeout looking for " + elementDescription, this);
908
+ }
909
+ finally {
910
+ _commandFinally(state, this);
911
+ }
915
912
  }
916
913
  }
917
914
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -930,9 +927,9 @@ class StableBrowser {
930
927
  };
931
928
  try {
932
929
  await _preCommand(state, this);
933
- if (state.options && state.options.context) {
934
- state.selectors.locators[0].text = state.options.context;
935
- }
930
+ // if (state.options && state.options.context) {
931
+ // state.selectors.locators[0].text = state.options.context;
932
+ // }
936
933
  try {
937
934
  await state.element.click();
938
935
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -970,9 +967,15 @@ class StableBrowser {
970
967
  // let element = await this._locate(selectors, info, _params);
971
968
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
972
969
  try {
973
- // await this._highlightElements(element);
970
+ // if (world && world.screenshot && !world.screenshotPath) {
971
+ // console.log(`Highlighting while running from recorder`);
972
+ await this._highlightElements(element);
974
973
  await state.element.setChecked(checked);
975
974
  await new Promise((resolve) => setTimeout(resolve, 1000));
975
+ // await this._unHighlightElements(element);
976
+ // }
977
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
978
+ // await this._unHighlightElements(element);
976
979
  }
977
980
  catch (e) {
978
981
  if (e.message && e.message.includes("did not change its state")) {
@@ -1011,6 +1014,7 @@ class StableBrowser {
1011
1014
  await _preCommand(state, this);
1012
1015
  try {
1013
1016
  await state.element.hover();
1017
+ // await _screenshot(state, this);
1014
1018
  await new Promise((resolve) => setTimeout(resolve, 1000));
1015
1019
  }
1016
1020
  catch (e) {
@@ -1018,6 +1022,7 @@ class StableBrowser {
1018
1022
  state.info.log += "hover failed, will try again" + "\n";
1019
1023
  state.element = await this._locate(selectors, state.info, _params);
1020
1024
  await state.element.hover({ timeout: 10000 });
1025
+ // await _screenshot(state, this);
1021
1026
  await new Promise((resolve) => setTimeout(resolve, 1000));
1022
1027
  }
1023
1028
  await _screenshot(state, this);
@@ -1292,7 +1297,12 @@ class StableBrowser {
1292
1297
  await this.waitForPageLoad();
1293
1298
  }
1294
1299
  else if (enter === false) {
1295
- await state.element.dispatchEvent("change");
1300
+ try {
1301
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1302
+ }
1303
+ catch (e) {
1304
+ // ignore
1305
+ }
1296
1306
  //await this.page.keyboard.press("Tab");
1297
1307
  }
1298
1308
  else {
@@ -1349,6 +1359,7 @@ class StableBrowser {
1349
1359
  let screenshotPath = null;
1350
1360
  if (!info.log) {
1351
1361
  info.log = "";
1362
+ info.locatorLog = new LocatorLog(selectors);
1352
1363
  }
1353
1364
  info.operation = "getText";
1354
1365
  info.selectors = selectors;
@@ -1371,6 +1382,18 @@ class StableBrowser {
1371
1382
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1372
1383
  try {
1373
1384
  await this._highlightElements(element);
1385
+ // if (world && world.screenshot && !world.screenshotPath) {
1386
+ // // console.log(`Highlighting for get text while running from recorder`);
1387
+ // this._highlightElements(element)
1388
+ // .then(async () => {
1389
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1390
+ // this._unhighlightElements(element).then(
1391
+ // () => {}
1392
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1393
+ // );
1394
+ // })
1395
+ // .catch(e);
1396
+ // }
1374
1397
  const elementText = await element.innerText();
1375
1398
  return {
1376
1399
  text: elementText,
@@ -1382,7 +1405,7 @@ class StableBrowser {
1382
1405
  }
1383
1406
  catch (e) {
1384
1407
  //await this.closeUnexpectedPopups();
1385
- this.logger.info("no innerText will use textContent");
1408
+ this.logger.info("no innerText, will use textContent");
1386
1409
  const elementText = await element.textContent();
1387
1410
  return { text: elementText, screenshotId, screenshotPath, value: value };
1388
1411
  }
@@ -1687,11 +1710,9 @@ class StableBrowser {
1687
1710
  if (!fs.existsSync(world.screenshotPath)) {
1688
1711
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1689
1712
  }
1690
- let nextIndex = 1;
1691
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1692
- nextIndex++;
1693
- }
1694
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1713
+ // to make sure the path doesn't start with -
1714
+ const uuidStr = "id_" + randomUUID();
1715
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1695
1716
  try {
1696
1717
  await this.takeScreenshot(screenshotPath);
1697
1718
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1701,15 +1722,15 @@ class StableBrowser {
1701
1722
  // this.logger.info("unable to save screenshot " + screenshotPath);
1702
1723
  // }
1703
1724
  // });
1725
+ result.screenshotId = uuidStr;
1726
+ result.screenshotPath = screenshotPath;
1727
+ if (info && info.box) {
1728
+ await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1729
+ }
1704
1730
  }
1705
1731
  catch (e) {
1706
1732
  this.logger.info("unable to take screenshot, ignored");
1707
1733
  }
1708
- result.screenshotId = nextIndex;
1709
- result.screenshotPath = screenshotPath;
1710
- if (info && info.box) {
1711
- await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1712
- }
1713
1734
  }
1714
1735
  else if (options && options.screenshot) {
1715
1736
  result.screenshotPath = options.screenshotPath;
@@ -1744,6 +1765,15 @@ class StableBrowser {
1744
1765
  document.documentElement.clientWidth,
1745
1766
  ])));
1746
1767
  let screenshotBuffer = null;
1768
+ // if (focusedElement) {
1769
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1770
+ // await this._unhighlightElements(focusedElement);
1771
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1772
+ // console.log(`Unhighlighted previous element`);
1773
+ // }
1774
+ // if (focusedElement) {
1775
+ // await this._highlightElements(focusedElement);
1776
+ // }
1747
1777
  if (this.context.browserName === "chromium") {
1748
1778
  const client = await playContext.newCDPSession(this.page);
1749
1779
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1765,6 +1795,10 @@ class StableBrowser {
1765
1795
  else {
1766
1796
  screenshotBuffer = await this.page.screenshot();
1767
1797
  }
1798
+ // if (focusedElement) {
1799
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1800
+ // await this._unhighlightElements(focusedElement);
1801
+ // }
1768
1802
  let image = await Jimp.read(screenshotBuffer);
1769
1803
  // Get the image dimensions
1770
1804
  const { width, height } = image.bitmap;
@@ -1814,6 +1848,7 @@ class StableBrowser {
1814
1848
  text: `Extract attribute from element`,
1815
1849
  operation: "extractAttribute",
1816
1850
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1851
+ allowDisabled: true,
1817
1852
  };
1818
1853
  await new Promise((resolve) => setTimeout(resolve, 2000));
1819
1854
  try {
@@ -1835,6 +1870,70 @@ class StableBrowser {
1835
1870
  state.info.value = state.value;
1836
1871
  this.setTestData({ [variable]: state.value }, world);
1837
1872
  this.logger.info("set test data: " + variable + "=" + state.value);
1873
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1874
+ return state.info;
1875
+ }
1876
+ catch (e) {
1877
+ await _commandError(state, e, this);
1878
+ }
1879
+ finally {
1880
+ _commandFinally(state, this);
1881
+ }
1882
+ }
1883
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1884
+ const state = {
1885
+ selectors,
1886
+ _params,
1887
+ attribute,
1888
+ value,
1889
+ options,
1890
+ world,
1891
+ type: Types.VERIFY_ATTRIBUTE,
1892
+ highlight: true,
1893
+ screenshot: true,
1894
+ text: `Verify element attribute`,
1895
+ operation: "verifyAttribute",
1896
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1897
+ allowDisabled: true,
1898
+ };
1899
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1900
+ let val;
1901
+ try {
1902
+ await _preCommand(state, this);
1903
+ switch (attribute) {
1904
+ case "innerText":
1905
+ val = String(await state.element.innerText());
1906
+ break;
1907
+ case "value":
1908
+ val = String(await state.element.inputValue());
1909
+ break;
1910
+ case "checked":
1911
+ val = String(await state.element.isChecked());
1912
+ break;
1913
+ case "disabled":
1914
+ val = String(await state.element.isDisabled());
1915
+ break;
1916
+ case "readOnly":
1917
+ const isEditable = await state.element.isEditable();
1918
+ val = String(!isEditable);
1919
+ break;
1920
+ default:
1921
+ val = String(await state.element.getAttribute(attribute));
1922
+ break;
1923
+ }
1924
+ state.info.expectedValue = val;
1925
+ let regex;
1926
+ if (value.startsWith("/") && value.endsWith("/")) {
1927
+ const patternBody = value.slice(1, -1);
1928
+ regex = new RegExp(patternBody, "g");
1929
+ }
1930
+ else {
1931
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1932
+ regex = new RegExp(escapedPattern, "g");
1933
+ }
1934
+ if (!val.match(regex)) {
1935
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1936
+ }
1838
1937
  return state.info;
1839
1938
  }
1840
1939
  catch (e) {
@@ -1862,7 +1961,7 @@ class StableBrowser {
1862
1961
  if (options && options.timeout) {
1863
1962
  timeout = options.timeout;
1864
1963
  }
1865
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1964
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1866
1965
  const request = {
1867
1966
  method: "POST",
1868
1967
  url: serviceUrl,
@@ -1933,27 +2032,32 @@ class StableBrowser {
1933
2032
  async _highlightElements(scope, css) {
1934
2033
  try {
1935
2034
  if (!scope) {
2035
+ // console.log(`Scope is not defined`);
1936
2036
  return;
1937
2037
  }
1938
2038
  if (!css) {
1939
2039
  scope
1940
2040
  .evaluate((node) => {
1941
2041
  if (node && node.style) {
1942
- let originalBorder = node.style.border;
1943
- node.style.border = "2px solid red";
2042
+ let originalOutline = node.style.outline;
2043
+ // console.log(`Original outline was: ${originalOutline}`);
2044
+ // node.__previousOutline = originalOutline;
2045
+ node.style.outline = "2px solid red";
2046
+ // console.log(`New outline is: ${node.style.outline}`);
1944
2047
  if (window) {
1945
2048
  window.addEventListener("beforeunload", function (e) {
1946
- node.style.border = originalBorder;
2049
+ node.style.outline = originalOutline;
1947
2050
  });
1948
2051
  }
1949
2052
  setTimeout(function () {
1950
- node.style.border = originalBorder;
2053
+ node.style.outline = originalOutline;
1951
2054
  }, 2000);
1952
2055
  }
1953
2056
  })
1954
2057
  .then(() => { })
1955
2058
  .catch((e) => {
1956
2059
  // ignore
2060
+ // console.error(`Could not highlight node : ${e}`);
1957
2061
  });
1958
2062
  }
1959
2063
  else {
@@ -1969,17 +2073,18 @@ class StableBrowser {
1969
2073
  if (!element.style) {
1970
2074
  return;
1971
2075
  }
1972
- var originalBorder = element.style.border;
2076
+ let originalOutline = element.style.outline;
2077
+ element.__previousOutline = originalOutline;
1973
2078
  // Set the new border to be red and 2px solid
1974
- element.style.border = "2px solid red";
2079
+ element.style.outline = "2px solid red";
1975
2080
  if (window) {
1976
2081
  window.addEventListener("beforeunload", function (e) {
1977
- element.style.border = originalBorder;
2082
+ element.style.outline = originalOutline;
1978
2083
  });
1979
2084
  }
1980
2085
  // Set a timeout to revert to the original border after 2 seconds
1981
2086
  setTimeout(function () {
1982
- element.style.border = originalBorder;
2087
+ element.style.outline = originalOutline;
1983
2088
  }, 2000);
1984
2089
  }
1985
2090
  return;
@@ -1987,6 +2092,7 @@ class StableBrowser {
1987
2092
  .then(() => { })
1988
2093
  .catch((e) => {
1989
2094
  // ignore
2095
+ // console.error(`Could not highlight css: ${e}`);
1990
2096
  });
1991
2097
  }
1992
2098
  }
@@ -1994,6 +2100,54 @@ class StableBrowser {
1994
2100
  console.debug(error);
1995
2101
  }
1996
2102
  }
2103
+ // async _unhighlightElements(scope, css) {
2104
+ // try {
2105
+ // if (!scope) {
2106
+ // return;
2107
+ // }
2108
+ // if (!css) {
2109
+ // scope
2110
+ // .evaluate((node) => {
2111
+ // if (node && node.style) {
2112
+ // if (!node.__previousOutline) {
2113
+ // node.style.outline = "";
2114
+ // } else {
2115
+ // node.style.outline = node.__previousOutline;
2116
+ // }
2117
+ // }
2118
+ // })
2119
+ // .then(() => {})
2120
+ // .catch((e) => {
2121
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2122
+ // });
2123
+ // } else {
2124
+ // scope
2125
+ // .evaluate(([css]) => {
2126
+ // if (!css) {
2127
+ // return;
2128
+ // }
2129
+ // let elements = Array.from(document.querySelectorAll(css));
2130
+ // for (i = 0; i < elements.length; i++) {
2131
+ // let element = elements[i];
2132
+ // if (!element.style) {
2133
+ // return;
2134
+ // }
2135
+ // if (!element.__previousOutline) {
2136
+ // element.style.outline = "";
2137
+ // } else {
2138
+ // element.style.outline = element.__previousOutline;
2139
+ // }
2140
+ // }
2141
+ // })
2142
+ // .then(() => {})
2143
+ // .catch((e) => {
2144
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2145
+ // });
2146
+ // }
2147
+ // } catch (error) {
2148
+ // // console.debug(error);
2149
+ // }
2150
+ // }
1997
2151
  async verifyPagePath(pathPart, options = {}, world = null) {
1998
2152
  const startTime = Date.now();
1999
2153
  let error = null;
@@ -2055,6 +2209,35 @@ class StableBrowser {
2055
2209
  });
2056
2210
  }
2057
2211
  }
2212
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2213
+ const frames = this.page.frames();
2214
+ let results = [];
2215
+ let ignoreCase = false;
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)", false, true, ignoreCase, {});
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)", false, true, ignoreCase, {});
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)", false, true, ignoreCase, {});
2233
+ result.frame = frames[i];
2234
+ results.push(result);
2235
+ }
2236
+ }
2237
+ state.info.results = results;
2238
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2239
+ return resultWithElementsFound;
2240
+ }
2058
2241
  async verifyTextExistInPage(text, options = {}, world = null) {
2059
2242
  text = unEscapeString(text);
2060
2243
  const state = {
@@ -2064,7 +2247,7 @@ class StableBrowser {
2064
2247
  locate: false,
2065
2248
  scroll: false,
2066
2249
  highlight: false,
2067
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2250
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2068
2251
  text: `Verify text exists in page`,
2069
2252
  operation: "verifyTextExistInPage",
2070
2253
  log: "***** verify text " + text + " exists in page *****\n",
@@ -2082,31 +2265,7 @@ class StableBrowser {
2082
2265
  await _preCommand(state, this);
2083
2266
  state.info.text = text;
2084
2267
  while (true) {
2085
- const frames = this.page.frames();
2086
- let results = [];
2087
- for (let i = 0; i < frames.length; i++) {
2088
- if (dateAlternatives.date) {
2089
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2090
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2091
- result.frame = frames[i];
2092
- results.push(result);
2093
- }
2094
- }
2095
- else if (numberAlternatives.number) {
2096
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2097
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2098
- result.frame = frames[i];
2099
- results.push(result);
2100
- }
2101
- }
2102
- else {
2103
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2104
- result.frame = frames[i];
2105
- results.push(result);
2106
- }
2107
- }
2108
- state.info.results = results;
2109
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2268
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2110
2269
  if (resultWithElementsFound.length === 0) {
2111
2270
  if (Date.now() - state.startTime > timeout) {
2112
2271
  throw new Error(`Text ${text} not found in page`);
@@ -2116,12 +2275,29 @@ class StableBrowser {
2116
2275
  }
2117
2276
  if (resultWithElementsFound[0].randomToken) {
2118
2277
  const frame = resultWithElementsFound[0].frame;
2119
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2278
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2120
2279
  await this._highlightElements(frame, dataAttribute);
2121
- const element = await frame.$(dataAttribute);
2280
+ // if (world && world.screenshot && !world.screenshotPath) {
2281
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2282
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2283
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2284
+ // this._unhighlightElements(frame, dataAttribute)
2285
+ // .then(async () => {
2286
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2287
+ // })
2288
+ // .catch(
2289
+ // (e) => {}
2290
+ // console.error(e)
2291
+ // );
2292
+ // });
2293
+ // }
2294
+ const element = await frame.locator(dataAttribute).first();
2295
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2296
+ // await this._unhighlightElements(frame, dataAttribute);
2122
2297
  if (element) {
2123
2298
  await this.scrollIfNeeded(element, state.info);
2124
2299
  await element.dispatchEvent("bvt_verify_page_contains_text");
2300
+ // await _screenshot(state, this, element);
2125
2301
  }
2126
2302
  }
2127
2303
  await _screenshot(state, this);
@@ -2163,31 +2339,7 @@ class StableBrowser {
2163
2339
  await _preCommand(state, this);
2164
2340
  state.info.text = text;
2165
2341
  while (true) {
2166
- const frames = this.page.frames();
2167
- let results = [];
2168
- for (let i = 0; i < frames.length; i++) {
2169
- if (dateAlternatives.date) {
2170
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2171
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2172
- result.frame = frames[i];
2173
- results.push(result);
2174
- }
2175
- }
2176
- else if (numberAlternatives.number) {
2177
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2178
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2179
- result.frame = frames[i];
2180
- results.push(result);
2181
- }
2182
- }
2183
- else {
2184
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2185
- result.frame = frames[i];
2186
- results.push(result);
2187
- }
2188
- }
2189
- state.info.results = results;
2190
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2342
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2191
2343
  if (resultWithElementsFound.length === 0) {
2192
2344
  await _screenshot(state, this);
2193
2345
  return state.info;
@@ -2205,15 +2357,102 @@ class StableBrowser {
2205
2357
  _commandFinally(state, this);
2206
2358
  }
2207
2359
  }
2208
- _getServerUrl() {
2209
- let serviceUrl = "https://api.blinq.io";
2210
- if (process.env.NODE_ENV_BLINQ === "dev") {
2211
- serviceUrl = "https://dev.api.blinq.io";
2360
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2361
+ textAnchor = unEscapeString(textAnchor);
2362
+ textToVerify = unEscapeString(textToVerify);
2363
+ const state = {
2364
+ text_search: textToVerify,
2365
+ options,
2366
+ world,
2367
+ locate: false,
2368
+ scroll: false,
2369
+ highlight: false,
2370
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2371
+ text: `Verify text with relation to another text`,
2372
+ operation: "verify_text_with_relation",
2373
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2374
+ };
2375
+ const timeout = this._getLoadTimeout(options);
2376
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2377
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2378
+ if (newValue !== textAnchor) {
2379
+ this.logger.info(textAnchor + "=" + newValue);
2380
+ textAnchor = newValue;
2381
+ }
2382
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2383
+ if (newValue !== textToVerify) {
2384
+ this.logger.info(textToVerify + "=" + newValue);
2385
+ textToVerify = newValue;
2386
+ }
2387
+ let dateAlternatives = findDateAlternatives(textToVerify);
2388
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2389
+ let foundAncore = false;
2390
+ try {
2391
+ await _preCommand(state, this);
2392
+ state.info.text = textToVerify;
2393
+ while (true) {
2394
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2395
+ if (resultWithElementsFound.length === 0) {
2396
+ if (Date.now() - state.startTime > timeout) {
2397
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2398
+ }
2399
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2400
+ continue;
2401
+ }
2402
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2403
+ foundAncore = true;
2404
+ const result = resultWithElementsFound[i];
2405
+ const token = result.randomToken;
2406
+ const frame = result.frame;
2407
+ let css = `[data-blinq-id-${token}]`;
2408
+ const climbArray1 = [];
2409
+ for (let i = 0; i < climb; i++) {
2410
+ climbArray1.push("..");
2411
+ }
2412
+ let climbXpath = "xpath=" + climbArray1.join("/");
2413
+ css = css + " >> " + climbXpath;
2414
+ const count = await frame.locator(css).count();
2415
+ for (let j = 0; j < count; j++) {
2416
+ const continer = await frame.locator(css).nth(j);
2417
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2418
+ if (result.elementCount > 0) {
2419
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2420
+ await this._highlightElements(frame, dataAttribute);
2421
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2422
+ // if (world && world.screenshot && !world.screenshotPath) {
2423
+ // console.log(`Highlighting for vtrt while running from recorder`);
2424
+ // this._highlightElements(frame, dataAttribute)
2425
+ // .then(async () => {
2426
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2427
+ // this._unhighlightElements(frame, dataAttribute).then(
2428
+ // () => {}
2429
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2430
+ // );
2431
+ // })
2432
+ // .catch(e);
2433
+ // }
2434
+ //await this._highlightElements(frame, cssAnchor);
2435
+ const element = await frame.locator(dataAttribute).first();
2436
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2437
+ // await this._unhighlightElements(frame, dataAttribute);
2438
+ if (element) {
2439
+ await this.scrollIfNeeded(element, state.info);
2440
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2441
+ }
2442
+ await _screenshot(state, this);
2443
+ return state.info;
2444
+ }
2445
+ }
2446
+ }
2447
+ }
2448
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2449
+ }
2450
+ catch (e) {
2451
+ await _commandError(state, e, this);
2212
2452
  }
2213
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2214
- serviceUrl = "https://stage.api.blinq.io";
2453
+ finally {
2454
+ _commandFinally(state, this);
2215
2455
  }
2216
- return serviceUrl;
2217
2456
  }
2218
2457
  async visualVerification(text, options = {}, world = null) {
2219
2458
  const startTime = Date.now();
@@ -2229,7 +2468,7 @@ class StableBrowser {
2229
2468
  throw new Error("TOKEN is not set");
2230
2469
  }
2231
2470
  try {
2232
- let serviceUrl = this._getServerUrl();
2471
+ let serviceUrl = _getServerUrl();
2233
2472
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2234
2473
  info.screenshotPath = screenshotPath;
2235
2474
  const screenshot = await this.takeScreenshot();
@@ -2318,6 +2557,7 @@ class StableBrowser {
2318
2557
  let screenshotPath = null;
2319
2558
  const info = {};
2320
2559
  info.log = "";
2560
+ info.locatorLog = new LocatorLog(selectors);
2321
2561
  info.operation = "getTableData";
2322
2562
  info.selectors = selectors;
2323
2563
  try {
@@ -2393,7 +2633,7 @@ class StableBrowser {
2393
2633
  info.operation = "analyzeTable";
2394
2634
  info.selectors = selectors;
2395
2635
  info.query = query;
2396
- query = this._fixUsingParams(query, _params);
2636
+ query = _fixUsingParams(query, _params);
2397
2637
  info.query_fixed = query;
2398
2638
  info.operator = operator;
2399
2639
  info.value = value;
@@ -2723,14 +2963,22 @@ class StableBrowser {
2723
2963
  }
2724
2964
  }
2725
2965
  async beforeStep(world, step) {
2726
- this.stepName = step.pickleStep.text;
2727
- this.logger.info("step: " + this.stepName);
2728
2966
  if (this.stepIndex === undefined) {
2729
2967
  this.stepIndex = 0;
2730
2968
  }
2731
2969
  else {
2732
2970
  this.stepIndex++;
2733
2971
  }
2972
+ if (step && step.pickleStep && step.pickleStep.text) {
2973
+ this.stepName = step.pickleStep.text;
2974
+ this.logger.info("step: " + this.stepName);
2975
+ }
2976
+ else if (step && step.text) {
2977
+ this.stepName = step.text;
2978
+ }
2979
+ else {
2980
+ this.stepName = "step " + this.stepIndex;
2981
+ }
2734
2982
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2735
2983
  if (this.context.browserObject.context) {
2736
2984
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2760,156 +3008,5 @@ function createTimedPromise(promise, label) {
2760
3008
  .then((result) => ({ status: "fulfilled", label, result }))
2761
3009
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2762
3010
  }
2763
- const KEYBOARD_EVENTS = [
2764
- "ALT",
2765
- "AltGraph",
2766
- "CapsLock",
2767
- "Control",
2768
- "Fn",
2769
- "FnLock",
2770
- "Hyper",
2771
- "Meta",
2772
- "NumLock",
2773
- "ScrollLock",
2774
- "Shift",
2775
- "Super",
2776
- "Symbol",
2777
- "SymbolLock",
2778
- "Enter",
2779
- "Tab",
2780
- "ArrowDown",
2781
- "ArrowLeft",
2782
- "ArrowRight",
2783
- "ArrowUp",
2784
- "End",
2785
- "Home",
2786
- "PageDown",
2787
- "PageUp",
2788
- "Backspace",
2789
- "Clear",
2790
- "Copy",
2791
- "CrSel",
2792
- "Cut",
2793
- "Delete",
2794
- "EraseEof",
2795
- "ExSel",
2796
- "Insert",
2797
- "Paste",
2798
- "Redo",
2799
- "Undo",
2800
- "Accept",
2801
- "Again",
2802
- "Attn",
2803
- "Cancel",
2804
- "ContextMenu",
2805
- "Escape",
2806
- "Execute",
2807
- "Find",
2808
- "Finish",
2809
- "Help",
2810
- "Pause",
2811
- "Play",
2812
- "Props",
2813
- "Select",
2814
- "ZoomIn",
2815
- "ZoomOut",
2816
- "BrightnessDown",
2817
- "BrightnessUp",
2818
- "Eject",
2819
- "LogOff",
2820
- "Power",
2821
- "PowerOff",
2822
- "PrintScreen",
2823
- "Hibernate",
2824
- "Standby",
2825
- "WakeUp",
2826
- "AllCandidates",
2827
- "Alphanumeric",
2828
- "CodeInput",
2829
- "Compose",
2830
- "Convert",
2831
- "Dead",
2832
- "FinalMode",
2833
- "GroupFirst",
2834
- "GroupLast",
2835
- "GroupNext",
2836
- "GroupPrevious",
2837
- "ModeChange",
2838
- "NextCandidate",
2839
- "NonConvert",
2840
- "PreviousCandidate",
2841
- "Process",
2842
- "SingleCandidate",
2843
- "HangulMode",
2844
- "HanjaMode",
2845
- "JunjaMode",
2846
- "Eisu",
2847
- "Hankaku",
2848
- "Hiragana",
2849
- "HiraganaKatakana",
2850
- "KanaMode",
2851
- "KanjiMode",
2852
- "Katakana",
2853
- "Romaji",
2854
- "Zenkaku",
2855
- "ZenkakuHanaku",
2856
- "F1",
2857
- "F2",
2858
- "F3",
2859
- "F4",
2860
- "F5",
2861
- "F6",
2862
- "F7",
2863
- "F8",
2864
- "F9",
2865
- "F10",
2866
- "F11",
2867
- "F12",
2868
- "Soft1",
2869
- "Soft2",
2870
- "Soft3",
2871
- "Soft4",
2872
- "ChannelDown",
2873
- "ChannelUp",
2874
- "Close",
2875
- "MailForward",
2876
- "MailReply",
2877
- "MailSend",
2878
- "MediaFastForward",
2879
- "MediaPause",
2880
- "MediaPlay",
2881
- "MediaPlayPause",
2882
- "MediaRecord",
2883
- "MediaRewind",
2884
- "MediaStop",
2885
- "MediaTrackNext",
2886
- "MediaTrackPrevious",
2887
- "AudioBalanceLeft",
2888
- "AudioBalanceRight",
2889
- "AudioBassBoostDown",
2890
- "AudioBassBoostToggle",
2891
- "AudioBassBoostUp",
2892
- "AudioFaderFront",
2893
- "AudioFaderRear",
2894
- "AudioSurroundModeNext",
2895
- "AudioTrebleDown",
2896
- "AudioTrebleUp",
2897
- "AudioVolumeDown",
2898
- "AudioVolumeMute",
2899
- "AudioVolumeUp",
2900
- "MicrophoneToggle",
2901
- "MicrophoneVolumeDown",
2902
- "MicrophoneVolumeMute",
2903
- "MicrophoneVolumeUp",
2904
- "TV",
2905
- "TV3DMode",
2906
- "TVAntennaCable",
2907
- "TVAudioDescription",
2908
- ];
2909
- function unEscapeString(str) {
2910
- const placeholder = "__NEWLINE__";
2911
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2912
- return str;
2913
- }
2914
3011
  export { StableBrowser };
2915
3012
  //# sourceMappingURL=stable_browser.js.map