automation_model 1.0.524-dev → 1.0.524-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,9 +546,15 @@ 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
- return element;
551
+ const randomToken = Math.random().toString(36).substring(7);
552
+ element.evaluate((el, randomToken) => {
553
+ el.setAttribute("data-blinq-id-" + randomToken, "");
554
+ }, randomToken);
555
+ const scope = element._frame ?? element.page();
556
+ const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
557
+ return newSelector;
620
558
  }
621
559
  }
622
560
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -627,6 +565,7 @@ class StableBrowser {
627
565
  info.failCause = {};
628
566
  info.log = "";
629
567
  }
568
+ let startTime = Date.now();
630
569
  let scope = this.page;
631
570
  if (selectors.frame) {
632
571
  return selectors.frame;
@@ -657,9 +596,11 @@ class StableBrowser {
657
596
  }
658
597
  return framescope;
659
598
  };
599
+ let fLocator = null;
660
600
  while (true) {
661
601
  let frameFound = false;
662
602
  if (selectors.nestFrmLoc) {
603
+ fLocator = selectors.nestFrmLoc;
663
604
  scope = await findFrame(selectors.nestFrmLoc, scope);
664
605
  frameFound = true;
665
606
  break;
@@ -668,6 +609,7 @@ class StableBrowser {
668
609
  for (let i = 0; i < selectors.frameLocators.length; i++) {
669
610
  let frameLocator = selectors.frameLocators[i];
670
611
  if (frameLocator.css) {
612
+ fLocator = frameLocator.css;
671
613
  scope = scope.frameLocator(frameLocator.css);
672
614
  frameFound = true;
673
615
  break;
@@ -675,18 +617,25 @@ class StableBrowser {
675
617
  }
676
618
  }
677
619
  if (!frameFound && selectors.iframe_src) {
620
+ fLocator = selectors.iframe_src;
678
621
  scope = this.page.frame({ url: selectors.iframe_src });
679
622
  }
680
623
  if (!scope) {
681
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
682
- if (performance.now() - startTime > timeout) {
624
+ if (info && info.locatorLog) {
625
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
626
+ }
627
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
628
+ if (Date.now() - startTime > timeout) {
683
629
  info.failCause.iframeNotFound = true;
684
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
630
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
685
631
  throw new Error("unable to locate iframe " + selectors.iframe_src);
686
632
  }
687
633
  await new Promise((resolve) => setTimeout(resolve, 1000));
688
634
  }
689
635
  else {
636
+ if (info && info.locatorLog) {
637
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
638
+ }
690
639
  break;
691
640
  }
692
641
  }
@@ -703,16 +652,18 @@ class StableBrowser {
703
652
  return bodyContent;
704
653
  });
705
654
  }
706
- async _locate_internal(selectors, info, _params, timeout = 30000) {
655
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
707
656
  if (!info) {
708
657
  info = {};
709
658
  info.failCause = {};
710
659
  info.log = "";
660
+ info.locatorLog = new LocatorLog(selectors);
711
661
  }
712
662
  let highPriorityTimeout = 5000;
713
663
  let visibleOnlyTimeout = 6000;
714
- let startTime = performance.now();
664
+ let startTime = Date.now();
715
665
  let locatorsCount = 0;
666
+ let lazy_scroll = false;
716
667
  //let arrayMode = Array.isArray(selectors);
717
668
  let scope = await this._findFrameScope(selectors, timeout, info);
718
669
  let selectorsLocators = null;
@@ -750,17 +701,17 @@ class StableBrowser {
750
701
  }
751
702
  // info.log += "scanning locators in priority 1" + "\n";
752
703
  let onlyPriority3 = selectorsLocators[0].priority === 3;
753
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
704
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
754
705
  if (result.foundElements.length === 0) {
755
706
  // info.log += "scanning locators in priority 2" + "\n";
756
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
707
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
757
708
  }
758
709
  if (result.foundElements.length === 0 && onlyPriority3) {
759
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
710
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
760
711
  }
761
712
  else {
762
713
  if (result.foundElements.length === 0 && !highPriorityOnly) {
763
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
714
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
764
715
  }
765
716
  }
766
717
  let foundElements = result.foundElements;
@@ -801,26 +752,38 @@ class StableBrowser {
801
752
  return maxCountElement.locator;
802
753
  }
803
754
  }
804
- if (performance.now() - startTime > timeout) {
755
+ if (Date.now() - startTime > timeout) {
805
756
  break;
806
757
  }
807
- if (performance.now() - startTime > highPriorityTimeout) {
808
- info.log += "high priority timeout, will try all elements" + "\n";
758
+ if (Date.now() - startTime > highPriorityTimeout) {
759
+ //info.log += "high priority timeout, will try all elements" + "\n";
809
760
  highPriorityOnly = false;
761
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
762
+ lazy_scroll = true;
763
+ await scrollPageToLoadLazyElements(this.page);
764
+ }
810
765
  }
811
- if (performance.now() - startTime > visibleOnlyTimeout) {
812
- info.log += "visible only timeout, will try all elements" + "\n";
766
+ if (Date.now() - startTime > visibleOnlyTimeout) {
767
+ //info.log += "visible only timeout, will try all elements" + "\n";
813
768
  visibleOnly = false;
814
769
  }
815
770
  await new Promise((resolve) => setTimeout(resolve, 1000));
816
771
  }
817
772
  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";
773
+ // if (info.locatorLog) {
774
+ // const lines = info.locatorLog.toString().split("\n");
775
+ // for (let line of lines) {
776
+ // this.logger.debug(line);
777
+ // }
778
+ // }
779
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
819
780
  info.failCause.locatorNotFound = true;
820
- info.failCause.lastError = "failed to locate unique element";
781
+ if (!info?.failCause?.lastError) {
782
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
783
+ }
821
784
  throw new Error("failed to locate first element no elements found, " + info.log);
822
785
  }
823
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
786
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
824
787
  let foundElements = [];
825
788
  const result = {
826
789
  foundElements: foundElements,
@@ -828,14 +791,15 @@ class StableBrowser {
828
791
  for (let i = 0; i < locatorsGroup.length; i++) {
829
792
  let foundLocators = [];
830
793
  try {
831
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
794
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
832
795
  }
833
796
  catch (e) {
834
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
835
- this.logger.debug(e);
797
+ // this call can fail it the browser is navigating
798
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
799
+ // this.logger.debug(e);
836
800
  foundLocators = [];
837
801
  try {
838
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
802
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
839
803
  }
840
804
  catch (e) {
841
805
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -851,11 +815,27 @@ class StableBrowser {
851
815
  }
852
816
  if (foundLocators.length > 1) {
853
817
  info.failCause.foundMultiple = true;
818
+ if (info.locatorLog) {
819
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
820
+ }
854
821
  }
855
822
  }
856
823
  return result;
857
824
  }
858
825
  async simpleClick(elementDescription, _params, options = {}, world = null) {
826
+ const state = {
827
+ locate: false,
828
+ scroll: false,
829
+ highlight: false,
830
+ _params,
831
+ options,
832
+ world,
833
+ type: Types.CLICK,
834
+ text: "Click element",
835
+ operation: "simpleClick",
836
+ log: "***** click on " + elementDescription + " *****\n",
837
+ };
838
+ _preCommand(state, this);
859
839
  const startTime = Date.now();
860
840
  let timeout = 30000;
861
841
  if (options && options.timeout) {
@@ -880,13 +860,31 @@ class StableBrowser {
880
860
  catch (e) {
881
861
  if (performance.now() - startTime > timeout) {
882
862
  // throw e;
883
- await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
863
+ try {
864
+ await _commandError(state, "timeout looking for " + elementDescription, this);
865
+ }
866
+ finally {
867
+ _commandFinally(state, this);
868
+ }
884
869
  }
885
870
  }
886
871
  await new Promise((resolve) => setTimeout(resolve, 3000));
887
872
  }
888
873
  }
889
874
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
875
+ const state = {
876
+ locate: false,
877
+ scroll: false,
878
+ highlight: false,
879
+ _params,
880
+ options,
881
+ world,
882
+ type: Types.FILL,
883
+ text: "Fill element",
884
+ operation: "simpleClickType",
885
+ log: "***** click type on " + elementDescription + " *****\n",
886
+ };
887
+ _preCommand(state, this);
890
888
  const startTime = Date.now();
891
889
  let timeout = 30000;
892
890
  if (options && options.timeout) {
@@ -911,7 +909,12 @@ class StableBrowser {
911
909
  catch (e) {
912
910
  if (performance.now() - startTime > timeout) {
913
911
  // throw e;
914
- await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
912
+ try {
913
+ await _commandError(state, "timeout looking for " + elementDescription, this);
914
+ }
915
+ finally {
916
+ _commandFinally(state, this);
917
+ }
915
918
  }
916
919
  }
917
920
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -930,9 +933,9 @@ class StableBrowser {
930
933
  };
931
934
  try {
932
935
  await _preCommand(state, this);
933
- if (state.options && state.options.context) {
934
- state.selectors.locators[0].text = state.options.context;
935
- }
936
+ // if (state.options && state.options.context) {
937
+ // state.selectors.locators[0].text = state.options.context;
938
+ // }
936
939
  try {
937
940
  await state.element.click();
938
941
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -970,9 +973,15 @@ class StableBrowser {
970
973
  // let element = await this._locate(selectors, info, _params);
971
974
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
972
975
  try {
973
- // await this._highlightElements(element);
976
+ // if (world && world.screenshot && !world.screenshotPath) {
977
+ // console.log(`Highlighting while running from recorder`);
978
+ await this._highlightElements(element);
974
979
  await state.element.setChecked(checked);
975
980
  await new Promise((resolve) => setTimeout(resolve, 1000));
981
+ // await this._unHighlightElements(element);
982
+ // }
983
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
984
+ // await this._unHighlightElements(element);
976
985
  }
977
986
  catch (e) {
978
987
  if (e.message && e.message.includes("did not change its state")) {
@@ -1011,6 +1020,7 @@ class StableBrowser {
1011
1020
  await _preCommand(state, this);
1012
1021
  try {
1013
1022
  await state.element.hover();
1023
+ // await _screenshot(state, this);
1014
1024
  await new Promise((resolve) => setTimeout(resolve, 1000));
1015
1025
  }
1016
1026
  catch (e) {
@@ -1018,6 +1028,7 @@ class StableBrowser {
1018
1028
  state.info.log += "hover failed, will try again" + "\n";
1019
1029
  state.element = await this._locate(selectors, state.info, _params);
1020
1030
  await state.element.hover({ timeout: 10000 });
1031
+ // await _screenshot(state, this);
1021
1032
  await new Promise((resolve) => setTimeout(resolve, 1000));
1022
1033
  }
1023
1034
  await _screenshot(state, this);
@@ -1292,7 +1303,12 @@ class StableBrowser {
1292
1303
  await this.waitForPageLoad();
1293
1304
  }
1294
1305
  else if (enter === false) {
1295
- await state.element.dispatchEvent("change");
1306
+ try {
1307
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1308
+ }
1309
+ catch (e) {
1310
+ // ignore
1311
+ }
1296
1312
  //await this.page.keyboard.press("Tab");
1297
1313
  }
1298
1314
  else {
@@ -1349,6 +1365,7 @@ class StableBrowser {
1349
1365
  let screenshotPath = null;
1350
1366
  if (!info.log) {
1351
1367
  info.log = "";
1368
+ info.locatorLog = new LocatorLog(selectors);
1352
1369
  }
1353
1370
  info.operation = "getText";
1354
1371
  info.selectors = selectors;
@@ -1371,6 +1388,18 @@ class StableBrowser {
1371
1388
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1372
1389
  try {
1373
1390
  await this._highlightElements(element);
1391
+ // if (world && world.screenshot && !world.screenshotPath) {
1392
+ // // console.log(`Highlighting for get text while running from recorder`);
1393
+ // this._highlightElements(element)
1394
+ // .then(async () => {
1395
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1396
+ // this._unhighlightElements(element).then(
1397
+ // () => {}
1398
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1399
+ // );
1400
+ // })
1401
+ // .catch(e);
1402
+ // }
1374
1403
  const elementText = await element.innerText();
1375
1404
  return {
1376
1405
  text: elementText,
@@ -1382,7 +1411,7 @@ class StableBrowser {
1382
1411
  }
1383
1412
  catch (e) {
1384
1413
  //await this.closeUnexpectedPopups();
1385
- this.logger.info("no innerText will use textContent");
1414
+ this.logger.info("no innerText, will use textContent");
1386
1415
  const elementText = await element.textContent();
1387
1416
  return { text: elementText, screenshotId, screenshotPath, value: value };
1388
1417
  }
@@ -1687,11 +1716,9 @@ class StableBrowser {
1687
1716
  if (!fs.existsSync(world.screenshotPath)) {
1688
1717
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1689
1718
  }
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");
1719
+ // to make sure the path doesn't start with -
1720
+ const uuidStr = "id_" + randomUUID();
1721
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1695
1722
  try {
1696
1723
  await this.takeScreenshot(screenshotPath);
1697
1724
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1701,15 +1728,15 @@ class StableBrowser {
1701
1728
  // this.logger.info("unable to save screenshot " + screenshotPath);
1702
1729
  // }
1703
1730
  // });
1731
+ result.screenshotId = uuidStr;
1732
+ result.screenshotPath = screenshotPath;
1733
+ if (info && info.box) {
1734
+ await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1735
+ }
1704
1736
  }
1705
1737
  catch (e) {
1706
1738
  this.logger.info("unable to take screenshot, ignored");
1707
1739
  }
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
1740
  }
1714
1741
  else if (options && options.screenshot) {
1715
1742
  result.screenshotPath = options.screenshotPath;
@@ -1744,6 +1771,15 @@ class StableBrowser {
1744
1771
  document.documentElement.clientWidth,
1745
1772
  ])));
1746
1773
  let screenshotBuffer = null;
1774
+ // if (focusedElement) {
1775
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1776
+ // await this._unhighlightElements(focusedElement);
1777
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1778
+ // console.log(`Unhighlighted previous element`);
1779
+ // }
1780
+ // if (focusedElement) {
1781
+ // await this._highlightElements(focusedElement);
1782
+ // }
1747
1783
  if (this.context.browserName === "chromium") {
1748
1784
  const client = await playContext.newCDPSession(this.page);
1749
1785
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1765,6 +1801,10 @@ class StableBrowser {
1765
1801
  else {
1766
1802
  screenshotBuffer = await this.page.screenshot();
1767
1803
  }
1804
+ // if (focusedElement) {
1805
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1806
+ // await this._unhighlightElements(focusedElement);
1807
+ // }
1768
1808
  let image = await Jimp.read(screenshotBuffer);
1769
1809
  // Get the image dimensions
1770
1810
  const { width, height } = image.bitmap;
@@ -1814,6 +1854,7 @@ class StableBrowser {
1814
1854
  text: `Extract attribute from element`,
1815
1855
  operation: "extractAttribute",
1816
1856
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1857
+ allowDisabled: true,
1817
1858
  };
1818
1859
  await new Promise((resolve) => setTimeout(resolve, 2000));
1819
1860
  try {
@@ -1835,6 +1876,70 @@ class StableBrowser {
1835
1876
  state.info.value = state.value;
1836
1877
  this.setTestData({ [variable]: state.value }, world);
1837
1878
  this.logger.info("set test data: " + variable + "=" + state.value);
1879
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1880
+ return state.info;
1881
+ }
1882
+ catch (e) {
1883
+ await _commandError(state, e, this);
1884
+ }
1885
+ finally {
1886
+ _commandFinally(state, this);
1887
+ }
1888
+ }
1889
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1890
+ const state = {
1891
+ selectors,
1892
+ _params,
1893
+ attribute,
1894
+ value,
1895
+ options,
1896
+ world,
1897
+ type: Types.VERIFY_ATTRIBUTE,
1898
+ highlight: true,
1899
+ screenshot: true,
1900
+ text: `Verify element attribute`,
1901
+ operation: "verifyAttribute",
1902
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1903
+ allowDisabled: true,
1904
+ };
1905
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1906
+ let val;
1907
+ try {
1908
+ await _preCommand(state, this);
1909
+ switch (attribute) {
1910
+ case "innerText":
1911
+ val = String(await state.element.innerText());
1912
+ break;
1913
+ case "value":
1914
+ val = String(await state.element.inputValue());
1915
+ break;
1916
+ case "checked":
1917
+ val = String(await state.element.isChecked());
1918
+ break;
1919
+ case "disabled":
1920
+ val = String(await state.element.isDisabled());
1921
+ break;
1922
+ case "readOnly":
1923
+ const isEditable = await state.element.isEditable();
1924
+ val = String(!isEditable);
1925
+ break;
1926
+ default:
1927
+ val = String(await state.element.getAttribute(attribute));
1928
+ break;
1929
+ }
1930
+ state.info.expectedValue = val;
1931
+ let regex;
1932
+ if (value.startsWith("/") && value.endsWith("/")) {
1933
+ const patternBody = value.slice(1, -1);
1934
+ regex = new RegExp(patternBody, "g");
1935
+ }
1936
+ else {
1937
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1938
+ regex = new RegExp(escapedPattern, "g");
1939
+ }
1940
+ if (!val.match(regex)) {
1941
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1942
+ }
1838
1943
  return state.info;
1839
1944
  }
1840
1945
  catch (e) {
@@ -1862,7 +1967,7 @@ class StableBrowser {
1862
1967
  if (options && options.timeout) {
1863
1968
  timeout = options.timeout;
1864
1969
  }
1865
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1970
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1866
1971
  const request = {
1867
1972
  method: "POST",
1868
1973
  url: serviceUrl,
@@ -1933,27 +2038,32 @@ class StableBrowser {
1933
2038
  async _highlightElements(scope, css) {
1934
2039
  try {
1935
2040
  if (!scope) {
2041
+ // console.log(`Scope is not defined`);
1936
2042
  return;
1937
2043
  }
1938
2044
  if (!css) {
1939
2045
  scope
1940
2046
  .evaluate((node) => {
1941
2047
  if (node && node.style) {
1942
- let originalBorder = node.style.border;
1943
- node.style.border = "2px solid red";
2048
+ let originalOutline = node.style.outline;
2049
+ // console.log(`Original outline was: ${originalOutline}`);
2050
+ // node.__previousOutline = originalOutline;
2051
+ node.style.outline = "2px solid red";
2052
+ // console.log(`New outline is: ${node.style.outline}`);
1944
2053
  if (window) {
1945
2054
  window.addEventListener("beforeunload", function (e) {
1946
- node.style.border = originalBorder;
2055
+ node.style.outline = originalOutline;
1947
2056
  });
1948
2057
  }
1949
2058
  setTimeout(function () {
1950
- node.style.border = originalBorder;
2059
+ node.style.outline = originalOutline;
1951
2060
  }, 2000);
1952
2061
  }
1953
2062
  })
1954
2063
  .then(() => { })
1955
2064
  .catch((e) => {
1956
2065
  // ignore
2066
+ // console.error(`Could not highlight node : ${e}`);
1957
2067
  });
1958
2068
  }
1959
2069
  else {
@@ -1969,17 +2079,18 @@ class StableBrowser {
1969
2079
  if (!element.style) {
1970
2080
  return;
1971
2081
  }
1972
- var originalBorder = element.style.border;
2082
+ let originalOutline = element.style.outline;
2083
+ element.__previousOutline = originalOutline;
1973
2084
  // Set the new border to be red and 2px solid
1974
- element.style.border = "2px solid red";
2085
+ element.style.outline = "2px solid red";
1975
2086
  if (window) {
1976
2087
  window.addEventListener("beforeunload", function (e) {
1977
- element.style.border = originalBorder;
2088
+ element.style.outline = originalOutline;
1978
2089
  });
1979
2090
  }
1980
2091
  // Set a timeout to revert to the original border after 2 seconds
1981
2092
  setTimeout(function () {
1982
- element.style.border = originalBorder;
2093
+ element.style.outline = originalOutline;
1983
2094
  }, 2000);
1984
2095
  }
1985
2096
  return;
@@ -1987,6 +2098,7 @@ class StableBrowser {
1987
2098
  .then(() => { })
1988
2099
  .catch((e) => {
1989
2100
  // ignore
2101
+ // console.error(`Could not highlight css: ${e}`);
1990
2102
  });
1991
2103
  }
1992
2104
  }
@@ -1994,6 +2106,54 @@ class StableBrowser {
1994
2106
  console.debug(error);
1995
2107
  }
1996
2108
  }
2109
+ // async _unhighlightElements(scope, css) {
2110
+ // try {
2111
+ // if (!scope) {
2112
+ // return;
2113
+ // }
2114
+ // if (!css) {
2115
+ // scope
2116
+ // .evaluate((node) => {
2117
+ // if (node && node.style) {
2118
+ // if (!node.__previousOutline) {
2119
+ // node.style.outline = "";
2120
+ // } else {
2121
+ // node.style.outline = node.__previousOutline;
2122
+ // }
2123
+ // }
2124
+ // })
2125
+ // .then(() => {})
2126
+ // .catch((e) => {
2127
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2128
+ // });
2129
+ // } else {
2130
+ // scope
2131
+ // .evaluate(([css]) => {
2132
+ // if (!css) {
2133
+ // return;
2134
+ // }
2135
+ // let elements = Array.from(document.querySelectorAll(css));
2136
+ // for (i = 0; i < elements.length; i++) {
2137
+ // let element = elements[i];
2138
+ // if (!element.style) {
2139
+ // return;
2140
+ // }
2141
+ // if (!element.__previousOutline) {
2142
+ // element.style.outline = "";
2143
+ // } else {
2144
+ // element.style.outline = element.__previousOutline;
2145
+ // }
2146
+ // }
2147
+ // })
2148
+ // .then(() => {})
2149
+ // .catch((e) => {
2150
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2151
+ // });
2152
+ // }
2153
+ // } catch (error) {
2154
+ // // console.debug(error);
2155
+ // }
2156
+ // }
1997
2157
  async verifyPagePath(pathPart, options = {}, world = null) {
1998
2158
  const startTime = Date.now();
1999
2159
  let error = null;
@@ -2055,6 +2215,35 @@ class StableBrowser {
2055
2215
  });
2056
2216
  }
2057
2217
  }
2218
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2219
+ const frames = this.page.frames();
2220
+ let results = [];
2221
+ let ignoreCase = false;
2222
+ for (let i = 0; i < frames.length; i++) {
2223
+ if (dateAlternatives.date) {
2224
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2225
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2226
+ result.frame = frames[i];
2227
+ results.push(result);
2228
+ }
2229
+ }
2230
+ else if (numberAlternatives.number) {
2231
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2232
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2233
+ result.frame = frames[i];
2234
+ results.push(result);
2235
+ }
2236
+ }
2237
+ else {
2238
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2239
+ result.frame = frames[i];
2240
+ results.push(result);
2241
+ }
2242
+ }
2243
+ state.info.results = results;
2244
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2245
+ return resultWithElementsFound;
2246
+ }
2058
2247
  async verifyTextExistInPage(text, options = {}, world = null) {
2059
2248
  text = unEscapeString(text);
2060
2249
  const state = {
@@ -2064,7 +2253,7 @@ class StableBrowser {
2064
2253
  locate: false,
2065
2254
  scroll: false,
2066
2255
  highlight: false,
2067
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2256
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2068
2257
  text: `Verify text exists in page`,
2069
2258
  operation: "verifyTextExistInPage",
2070
2259
  log: "***** verify text " + text + " exists in page *****\n",
@@ -2082,31 +2271,7 @@ class StableBrowser {
2082
2271
  await _preCommand(state, this);
2083
2272
  state.info.text = text;
2084
2273
  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);
2274
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2110
2275
  if (resultWithElementsFound.length === 0) {
2111
2276
  if (Date.now() - state.startTime > timeout) {
2112
2277
  throw new Error(`Text ${text} not found in page`);
@@ -2116,12 +2281,29 @@ class StableBrowser {
2116
2281
  }
2117
2282
  if (resultWithElementsFound[0].randomToken) {
2118
2283
  const frame = resultWithElementsFound[0].frame;
2119
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2284
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2120
2285
  await this._highlightElements(frame, dataAttribute);
2121
- const element = await frame.$(dataAttribute);
2286
+ // if (world && world.screenshot && !world.screenshotPath) {
2287
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2288
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2289
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2290
+ // this._unhighlightElements(frame, dataAttribute)
2291
+ // .then(async () => {
2292
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2293
+ // })
2294
+ // .catch(
2295
+ // (e) => {}
2296
+ // console.error(e)
2297
+ // );
2298
+ // });
2299
+ // }
2300
+ const element = await frame.locator(dataAttribute).first();
2301
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2302
+ // await this._unhighlightElements(frame, dataAttribute);
2122
2303
  if (element) {
2123
2304
  await this.scrollIfNeeded(element, state.info);
2124
2305
  await element.dispatchEvent("bvt_verify_page_contains_text");
2306
+ // await _screenshot(state, this, element);
2125
2307
  }
2126
2308
  }
2127
2309
  await _screenshot(state, this);
@@ -2163,31 +2345,7 @@ class StableBrowser {
2163
2345
  await _preCommand(state, this);
2164
2346
  state.info.text = text;
2165
2347
  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);
2348
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2191
2349
  if (resultWithElementsFound.length === 0) {
2192
2350
  await _screenshot(state, this);
2193
2351
  return state.info;
@@ -2205,15 +2363,102 @@ class StableBrowser {
2205
2363
  _commandFinally(state, this);
2206
2364
  }
2207
2365
  }
2208
- _getServerUrl() {
2209
- let serviceUrl = "https://api.blinq.io";
2210
- if (process.env.NODE_ENV_BLINQ === "dev") {
2211
- serviceUrl = "https://dev.api.blinq.io";
2366
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2367
+ textAnchor = unEscapeString(textAnchor);
2368
+ textToVerify = unEscapeString(textToVerify);
2369
+ const state = {
2370
+ text_search: textToVerify,
2371
+ options,
2372
+ world,
2373
+ locate: false,
2374
+ scroll: false,
2375
+ highlight: false,
2376
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2377
+ text: `Verify text with relation to another text`,
2378
+ operation: "verify_text_with_relation",
2379
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2380
+ };
2381
+ const timeout = this._getLoadTimeout(options);
2382
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2383
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2384
+ if (newValue !== textAnchor) {
2385
+ this.logger.info(textAnchor + "=" + newValue);
2386
+ textAnchor = newValue;
2387
+ }
2388
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2389
+ if (newValue !== textToVerify) {
2390
+ this.logger.info(textToVerify + "=" + newValue);
2391
+ textToVerify = newValue;
2392
+ }
2393
+ let dateAlternatives = findDateAlternatives(textToVerify);
2394
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2395
+ let foundAncore = false;
2396
+ try {
2397
+ await _preCommand(state, this);
2398
+ state.info.text = textToVerify;
2399
+ while (true) {
2400
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2401
+ if (resultWithElementsFound.length === 0) {
2402
+ if (Date.now() - state.startTime > timeout) {
2403
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2404
+ }
2405
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2406
+ continue;
2407
+ }
2408
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2409
+ foundAncore = true;
2410
+ const result = resultWithElementsFound[i];
2411
+ const token = result.randomToken;
2412
+ const frame = result.frame;
2413
+ let css = `[data-blinq-id-${token}]`;
2414
+ const climbArray1 = [];
2415
+ for (let i = 0; i < climb; i++) {
2416
+ climbArray1.push("..");
2417
+ }
2418
+ let climbXpath = "xpath=" + climbArray1.join("/");
2419
+ css = css + " >> " + climbXpath;
2420
+ const count = await frame.locator(css).count();
2421
+ for (let j = 0; j < count; j++) {
2422
+ const continer = await frame.locator(css).nth(j);
2423
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2424
+ if (result.elementCount > 0) {
2425
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2426
+ await this._highlightElements(frame, dataAttribute);
2427
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2428
+ // if (world && world.screenshot && !world.screenshotPath) {
2429
+ // console.log(`Highlighting for vtrt while running from recorder`);
2430
+ // this._highlightElements(frame, dataAttribute)
2431
+ // .then(async () => {
2432
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2433
+ // this._unhighlightElements(frame, dataAttribute).then(
2434
+ // () => {}
2435
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2436
+ // );
2437
+ // })
2438
+ // .catch(e);
2439
+ // }
2440
+ //await this._highlightElements(frame, cssAnchor);
2441
+ const element = await frame.locator(dataAttribute).first();
2442
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2443
+ // await this._unhighlightElements(frame, dataAttribute);
2444
+ if (element) {
2445
+ await this.scrollIfNeeded(element, state.info);
2446
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2447
+ }
2448
+ await _screenshot(state, this);
2449
+ return state.info;
2450
+ }
2451
+ }
2452
+ }
2453
+ }
2454
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2455
+ }
2456
+ catch (e) {
2457
+ await _commandError(state, e, this);
2212
2458
  }
2213
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2214
- serviceUrl = "https://stage.api.blinq.io";
2459
+ finally {
2460
+ _commandFinally(state, this);
2215
2461
  }
2216
- return serviceUrl;
2217
2462
  }
2218
2463
  async visualVerification(text, options = {}, world = null) {
2219
2464
  const startTime = Date.now();
@@ -2229,7 +2474,7 @@ class StableBrowser {
2229
2474
  throw new Error("TOKEN is not set");
2230
2475
  }
2231
2476
  try {
2232
- let serviceUrl = this._getServerUrl();
2477
+ let serviceUrl = _getServerUrl();
2233
2478
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2234
2479
  info.screenshotPath = screenshotPath;
2235
2480
  const screenshot = await this.takeScreenshot();
@@ -2318,6 +2563,7 @@ class StableBrowser {
2318
2563
  let screenshotPath = null;
2319
2564
  const info = {};
2320
2565
  info.log = "";
2566
+ info.locatorLog = new LocatorLog(selectors);
2321
2567
  info.operation = "getTableData";
2322
2568
  info.selectors = selectors;
2323
2569
  try {
@@ -2393,7 +2639,7 @@ class StableBrowser {
2393
2639
  info.operation = "analyzeTable";
2394
2640
  info.selectors = selectors;
2395
2641
  info.query = query;
2396
- query = this._fixUsingParams(query, _params);
2642
+ query = _fixUsingParams(query, _params);
2397
2643
  info.query_fixed = query;
2398
2644
  info.operator = operator;
2399
2645
  info.value = value;
@@ -2723,14 +2969,22 @@ class StableBrowser {
2723
2969
  }
2724
2970
  }
2725
2971
  async beforeStep(world, step) {
2726
- this.stepName = step.pickleStep.text;
2727
- this.logger.info("step: " + this.stepName);
2728
2972
  if (this.stepIndex === undefined) {
2729
2973
  this.stepIndex = 0;
2730
2974
  }
2731
2975
  else {
2732
2976
  this.stepIndex++;
2733
2977
  }
2978
+ if (step && step.pickleStep && step.pickleStep.text) {
2979
+ this.stepName = step.pickleStep.text;
2980
+ this.logger.info("step: " + this.stepName);
2981
+ }
2982
+ else if (step && step.text) {
2983
+ this.stepName = step.text;
2984
+ }
2985
+ else {
2986
+ this.stepName = "step " + this.stepIndex;
2987
+ }
2734
2988
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2735
2989
  if (this.context.browserObject.context) {
2736
2990
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2760,156 +3014,5 @@ function createTimedPromise(promise, label) {
2760
3014
  .then((result) => ({ status: "fulfilled", label, result }))
2761
3015
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2762
3016
  }
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
3017
  export { StableBrowser };
2915
3018
  //# sourceMappingURL=stable_browser.js.map