automation_model 1.0.522-dev → 1.0.522-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;
@@ -58,6 +67,7 @@ class StableBrowser {
58
67
  networkLogger = null;
59
68
  configuration = null;
60
69
  appName = "main";
70
+ tags = null;
61
71
  constructor(browser, page, logger = null, context = null, world = null) {
62
72
  this.browser = browser;
63
73
  this.page = page;
@@ -88,17 +98,17 @@ class StableBrowser {
88
98
  catch (e) {
89
99
  this.logger.error("unable to read ai_config.json");
90
100
  }
91
- context.pageLoading = { status: false };
92
- context.pages = [this.page];
93
101
  const logFolder = path.join(this.project_path, "logs", "web");
94
102
  this.world = world;
103
+ context.pages = [this.page];
104
+ context.pageLoading = { status: false };
95
105
  this.registerEventListeners(this.context);
96
106
  registerNetworkEvents(this.world, this, this.context, this.page);
97
107
  registerDownloadEvent(this.page, this.world, this.context);
98
108
  }
99
109
  registerEventListeners(context) {
100
110
  this.registerConsoleLogListener(this.page, context);
101
- this.registerRequestListener(this.page, context, this.webLogFile);
111
+ // this.registerRequestListener(this.page, context, this.webLogFile);
102
112
  if (!context.pageLoading) {
103
113
  context.pageLoading = { status: false };
104
114
  }
@@ -143,7 +153,7 @@ class StableBrowser {
143
153
  if (this.appName === appName) {
144
154
  return;
145
155
  }
146
- let newContextCreated = false;
156
+ let navigate = false;
147
157
  if (!apps[appName]) {
148
158
  let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
149
159
  newContextCreated = true;
@@ -154,32 +164,15 @@ class StableBrowser {
154
164
  };
155
165
  }
156
166
  const tempContext = {};
157
- this._copyContext(this, tempContext);
158
- this._copyContext(apps[appName], this);
167
+ _copyContext(this, tempContext);
168
+ _copyContext(apps[appName], this);
159
169
  apps[this.appName] = tempContext;
160
170
  this.appName = appName;
161
- if (newContextCreated) {
162
- this.registerEventListeners(this.context);
171
+ if (navigate) {
163
172
  await this.goto(this.context.environment.baseUrl);
164
173
  await this.waitForPageLoad();
165
174
  }
166
175
  }
167
- _copyContext(from, to) {
168
- to.browser = from.browser;
169
- to.page = from.page;
170
- to.context = from.context;
171
- }
172
- getWebLogFile(logFolder) {
173
- if (!fs.existsSync(logFolder)) {
174
- fs.mkdirSync(logFolder, { recursive: true });
175
- }
176
- let nextIndex = 1;
177
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
178
- nextIndex++;
179
- }
180
- const fileName = nextIndex + ".json";
181
- return path.join(logFolder, fileName);
182
- }
183
176
  registerConsoleLogListener(page, context) {
184
177
  if (!this.context.webLogger) {
185
178
  this.context.webLogger = [];
@@ -243,55 +236,48 @@ class StableBrowser {
243
236
  // async closeUnexpectedPopups() {
244
237
  // await closeUnexpectedPopups(this.page);
245
238
  // }
246
- async goto(url) {
239
+ async goto(url, world = null) {
247
240
  if (!url.startsWith("http")) {
248
241
  url = "https://" + url;
249
242
  }
250
- await this.page.goto(url, {
251
- timeout: 60000,
252
- });
253
- }
254
- _fixUsingParams(text, _params) {
255
- if (!_params || typeof text !== "string") {
256
- 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);
257
262
  }
258
- for (let key in _params) {
259
- let regValue = key;
260
- if (key.startsWith("_")) {
261
- // remove the _ prefix
262
- regValue = key.substring(1);
263
- }
264
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
263
+ catch (error) {
264
+ console.error("Error on goto", error);
265
+ _commandError(state, error, this);
265
266
  }
266
- return text;
267
- }
268
- _fixLocatorUsingParams(locator, _params) {
269
- // check if not null
270
- if (!locator) {
271
- return locator;
267
+ finally {
268
+ _commandFinally(state, this);
272
269
  }
273
- // clone the locator
274
- locator = JSON.parse(JSON.stringify(locator));
275
- this.scanAndManipulate(locator, _params);
276
- return locator;
277
270
  }
278
- _isObject(value) {
279
- return value && typeof value === "object" && value.constructor === Object;
280
- }
281
- scanAndManipulate(currentObj, _params) {
282
- for (const key in currentObj) {
283
- if (typeof currentObj[key] === "string") {
284
- // Perform string manipulation
285
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
286
- }
287
- else if (this._isObject(currentObj[key])) {
288
- // Recursively scan nested objects
289
- 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);
290
279
  }
291
280
  }
292
- }
293
- _getLocator(locator, scope, _params) {
294
- locator = this._fixLocatorUsingParams(locator, _params);
295
281
  let locatorReturn;
296
282
  if (locator.role) {
297
283
  if (locator.role[1].nameReg) {
@@ -299,7 +285,7 @@ class StableBrowser {
299
285
  delete locator.role[1].nameReg;
300
286
  }
301
287
  // if (locator.role[1].name) {
302
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
288
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
303
289
  // }
304
290
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
305
291
  }
@@ -342,140 +328,54 @@ class StableBrowser {
342
328
  if (css && css.locator) {
343
329
  css = css.locator;
344
330
  }
345
- 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);
346
332
  if (result.elementCount === 0) {
347
333
  return;
348
334
  }
349
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
335
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
350
336
  // css climb to parent element
351
337
  const climbArray = [];
352
338
  for (let i = 0; i < climb; i++) {
353
339
  climbArray.push("..");
354
340
  }
355
341
  let climbXpath = "xpath=" + climbArray.join("/");
356
- return textElementCss + " >> " + climbXpath + " >> " + css;
357
- }
358
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
359
- //const stringifyText = JSON.stringify(text);
360
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
361
- function isParent(parent, child) {
362
- let currentNode = child.parentNode;
363
- while (currentNode !== null) {
364
- if (currentNode === parent) {
365
- return true;
366
- }
367
- currentNode = currentNode.parentNode;
368
- }
369
- return false;
370
- }
371
- document.isParent = isParent;
372
- function getRegex(str) {
373
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
374
- if (!match) {
375
- return null;
376
- }
377
- let [_, pattern, flags] = match;
378
- return new RegExp(pattern, flags);
379
- }
380
- document.getRegex = getRegex;
381
- function collectAllShadowDomElements(element, result = []) {
382
- // Check and add the element if it has a shadow root
383
- if (element.shadowRoot) {
384
- result.push(element);
385
- // Also search within the shadow root
386
- document.collectAllShadowDomElements(element.shadowRoot, result);
387
- }
388
- // Iterate over child nodes
389
- element.childNodes.forEach((child) => {
390
- // Recursively call the function for each child node
391
- document.collectAllShadowDomElements(child, result);
392
- });
393
- return result;
394
- }
395
- document.collectAllShadowDomElements = collectAllShadowDomElements;
396
- if (!tag) {
397
- tag = "*:not(script, style, head)";
398
- }
399
- let regexpSearch = document.getRegex(text);
400
- if (regexpSearch) {
401
- regex = true;
402
- }
403
- let elements = Array.from(document.querySelectorAll(tag));
404
- let shadowHosts = [];
405
- document.collectAllShadowDomElements(document, shadowHosts);
406
- for (let i = 0; i < shadowHosts.length; i++) {
407
- let shadowElement = shadowHosts[i].shadowRoot;
408
- if (!shadowElement) {
409
- console.log("shadowElement is null, for host " + shadowHosts[i]);
410
- continue;
411
- }
412
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
413
- elements = elements.concat(shadowElements);
414
- }
415
- let randomToken = null;
416
- const foundElements = [];
417
- if (regex) {
418
- if (!regexpSearch) {
419
- regexpSearch = new RegExp(text, "im");
420
- }
421
- for (let i = 0; i < elements.length; i++) {
422
- const element = elements[i];
423
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
424
- (element.value && regexpSearch.test(element.value))) {
425
- foundElements.push(element);
426
- }
427
- }
428
- }
429
- else {
430
- text = text.trim();
431
- for (let i = 0; i < elements.length; i++) {
432
- const element = elements[i];
433
- if (partial) {
434
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
435
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
436
- foundElements.push(element);
437
- }
438
- }
439
- else {
440
- if ((element.innerText && element.innerText.trim() === text) ||
441
- (element.value && element.value === text)) {
442
- foundElements.push(element);
443
- }
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;
444
364
  }
445
365
  }
446
- }
447
- let noChildElements = [];
448
- for (let i = 0; i < foundElements.length; i++) {
449
- let element = foundElements[i];
450
- let hasChild = false;
451
- for (let j = 0; j < foundElements.length; j++) {
452
- if (i === j) {
453
- continue;
454
- }
455
- if (isParent(element, foundElements[j])) {
456
- hasChild = true;
457
- break;
458
- }
459
- }
460
- if (!hasChild) {
461
- noChildElements.push(element);
462
- }
463
- }
464
- let elementCount = 0;
465
- if (noChildElements.length > 0) {
466
- for (let i = 0; i < noChildElements.length; i++) {
467
- if (randomToken === null) {
468
- randomToken = Math.random().toString(36).substring(7);
469
- }
470
- let element = noChildElements[i];
471
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
472
- elementCount++;
366
+ if (!el.setAttribute) {
367
+ el = el.parentElement;
473
368
  }
369
+ el.setAttribute("data-blinq-id-" + randomToken, "");
370
+ return true;
371
+ }, [tag1, randomToken]))) {
372
+ continue;
474
373
  }
475
- return { elementCount: elementCount, randomToken: randomToken };
476
- }, [text1, tag1, regex1, partial1]);
374
+ tagCount++;
375
+ }
376
+ return { elementCount: tagCount, randomToken };
477
377
  }
478
- 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) {
479
379
  if (!info) {
480
380
  info = {};
481
381
  }
@@ -484,10 +384,13 @@ class StableBrowser {
484
384
  }
485
385
  if (!info.log) {
486
386
  info.log = "";
387
+ info.locatorLog = new LocatorLog(selectorHierarchy);
487
388
  }
488
389
  let locatorSearch = selectorHierarchy[index];
390
+ let originalLocatorSearch = "";
489
391
  try {
490
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
392
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
393
+ locatorSearch = JSON.parse(originalLocatorSearch);
491
394
  }
492
395
  catch (e) {
493
396
  console.error(e);
@@ -495,30 +398,31 @@ class StableBrowser {
495
398
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
496
399
  let locator = null;
497
400
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
498
- 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);
499
403
  if (!locatorString) {
500
404
  info.failCause.textNotFound = true;
501
- 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}`;
502
406
  return;
503
407
  }
504
- locator = this._getLocator({ css: locatorString }, scope, _params);
408
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
505
409
  }
506
410
  else if (locatorSearch.text) {
507
- let text = this._fixUsingParams(locatorSearch.text, _params);
508
- 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);
509
413
  if (result.elementCount === 0) {
510
414
  info.failCause.textNotFound = true;
511
- info.failCause.lastError = "failed to locate element by text: " + text;
415
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
512
416
  return;
513
417
  }
514
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
418
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
515
419
  if (locatorSearch.childCss) {
516
420
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
517
421
  }
518
- locator = this._getLocator(locatorSearch, scope, _params);
422
+ locator = await this._getLocator(locatorSearch, scope, _params);
519
423
  }
520
424
  else {
521
- locator = this._getLocator(locatorSearch, scope, _params);
425
+ locator = await this._getLocator(locatorSearch, scope, _params);
522
426
  }
523
427
  // let cssHref = false;
524
428
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -531,18 +435,27 @@ class StableBrowser {
531
435
  //info.log += "total elements found " + count + "\n";
532
436
  //let visibleCount = 0;
533
437
  let visibleLocator = null;
534
- if (locatorSearch.index && locatorSearch.index < count) {
438
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
535
439
  foundLocators.push(locator.nth(locatorSearch.index));
440
+ if (info.locatorLog) {
441
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
442
+ }
536
443
  return;
537
444
  }
445
+ if (info.locatorLog && count === 0) {
446
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
447
+ }
538
448
  for (let j = 0; j < count; j++) {
539
449
  let visible = await locator.nth(j).isVisible();
540
450
  const enabled = await locator.nth(j).isEnabled();
541
451
  if (!visibleOnly) {
542
452
  visible = true;
543
453
  }
544
- if (visible && enabled) {
454
+ if (visible && (allowDisabled || enabled)) {
545
455
  foundLocators.push(locator.nth(j));
456
+ if (info.locatorLog) {
457
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
458
+ }
546
459
  }
547
460
  else {
548
461
  info.failCause.visible = visible;
@@ -550,8 +463,16 @@ class StableBrowser {
550
463
  if (!info.printMessages) {
551
464
  info.printMessages = {};
552
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
+ }
553
474
  if (!info.printMessages[j.toString()]) {
554
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
475
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
555
476
  info.printMessages[j.toString()] = true;
556
477
  }
557
478
  }
@@ -567,7 +488,7 @@ class StableBrowser {
567
488
  if (!info) {
568
489
  info = {};
569
490
  }
570
- info.log += "scan for popup handlers" + "\n";
491
+ //info.log += "scan for popup handlers" + "\n";
571
492
  const handlerGroup = [];
572
493
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
573
494
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -594,16 +515,28 @@ class StableBrowser {
594
515
  }
595
516
  if (result.foundElements.length > 0) {
596
517
  let dialogCloseLocator = result.foundElements[0].locator;
597
- await dialogCloseLocator.click();
598
- // wait for the dialog to close
599
- 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
+ }
600
533
  return { rerun: true };
601
534
  }
602
535
  }
603
536
  }
604
537
  return { rerun: false };
605
538
  }
606
- async _locate(selectors, info, _params, timeout) {
539
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
607
540
  if (!timeout) {
608
541
  timeout = 30000;
609
542
  }
@@ -613,7 +546,7 @@ class StableBrowser {
613
546
  let selector = selectors.locators[j];
614
547
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
615
548
  }
616
- let element = await this._locate_internal(selectors, info, _params, timeout);
549
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
617
550
  if (!element.rerun) {
618
551
  return element;
619
552
  }
@@ -626,6 +559,7 @@ class StableBrowser {
626
559
  info.failCause = {};
627
560
  info.log = "";
628
561
  }
562
+ let startTime = Date.now();
629
563
  let scope = this.page;
630
564
  if (selectors.frame) {
631
565
  return selectors.frame;
@@ -656,9 +590,11 @@ class StableBrowser {
656
590
  }
657
591
  return framescope;
658
592
  };
593
+ let fLocator = null;
659
594
  while (true) {
660
595
  let frameFound = false;
661
596
  if (selectors.nestFrmLoc) {
597
+ fLocator = selectors.nestFrmLoc;
662
598
  scope = await findFrame(selectors.nestFrmLoc, scope);
663
599
  frameFound = true;
664
600
  break;
@@ -667,6 +603,7 @@ class StableBrowser {
667
603
  for (let i = 0; i < selectors.frameLocators.length; i++) {
668
604
  let frameLocator = selectors.frameLocators[i];
669
605
  if (frameLocator.css) {
606
+ fLocator = frameLocator.css;
670
607
  scope = scope.frameLocator(frameLocator.css);
671
608
  frameFound = true;
672
609
  break;
@@ -674,18 +611,25 @@ class StableBrowser {
674
611
  }
675
612
  }
676
613
  if (!frameFound && selectors.iframe_src) {
614
+ fLocator = selectors.iframe_src;
677
615
  scope = this.page.frame({ url: selectors.iframe_src });
678
616
  }
679
617
  if (!scope) {
680
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
681
- 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) {
682
623
  info.failCause.iframeNotFound = true;
683
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
624
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
684
625
  throw new Error("unable to locate iframe " + selectors.iframe_src);
685
626
  }
686
627
  await new Promise((resolve) => setTimeout(resolve, 1000));
687
628
  }
688
629
  else {
630
+ if (info && info.locatorLog) {
631
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
632
+ }
689
633
  break;
690
634
  }
691
635
  }
@@ -702,16 +646,18 @@ class StableBrowser {
702
646
  return bodyContent;
703
647
  });
704
648
  }
705
- async _locate_internal(selectors, info, _params, timeout = 30000) {
649
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
706
650
  if (!info) {
707
651
  info = {};
708
652
  info.failCause = {};
709
653
  info.log = "";
654
+ info.locatorLog = new LocatorLog(selectors);
710
655
  }
711
656
  let highPriorityTimeout = 5000;
712
657
  let visibleOnlyTimeout = 6000;
713
- let startTime = performance.now();
658
+ let startTime = Date.now();
714
659
  let locatorsCount = 0;
660
+ let lazy_scroll = false;
715
661
  //let arrayMode = Array.isArray(selectors);
716
662
  let scope = await this._findFrameScope(selectors, timeout, info);
717
663
  let selectorsLocators = null;
@@ -749,17 +695,17 @@ class StableBrowser {
749
695
  }
750
696
  // info.log += "scanning locators in priority 1" + "\n";
751
697
  let onlyPriority3 = selectorsLocators[0].priority === 3;
752
- 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);
753
699
  if (result.foundElements.length === 0) {
754
700
  // info.log += "scanning locators in priority 2" + "\n";
755
- 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);
756
702
  }
757
703
  if (result.foundElements.length === 0 && onlyPriority3) {
758
- 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);
759
705
  }
760
706
  else {
761
707
  if (result.foundElements.length === 0 && !highPriorityOnly) {
762
- 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);
763
709
  }
764
710
  }
765
711
  let foundElements = result.foundElements;
@@ -800,26 +746,38 @@ class StableBrowser {
800
746
  return maxCountElement.locator;
801
747
  }
802
748
  }
803
- if (performance.now() - startTime > timeout) {
749
+ if (Date.now() - startTime > timeout) {
804
750
  break;
805
751
  }
806
- if (performance.now() - startTime > highPriorityTimeout) {
807
- 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";
808
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
+ }
809
759
  }
810
- if (performance.now() - startTime > visibleOnlyTimeout) {
811
- 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";
812
762
  visibleOnly = false;
813
763
  }
814
764
  await new Promise((resolve) => setTimeout(resolve, 1000));
815
765
  }
816
766
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
817
- 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";
818
774
  info.failCause.locatorNotFound = true;
819
- 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
+ }
820
778
  throw new Error("failed to locate first element no elements found, " + info.log);
821
779
  }
822
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
780
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
823
781
  let foundElements = [];
824
782
  const result = {
825
783
  foundElements: foundElements,
@@ -827,14 +785,15 @@ class StableBrowser {
827
785
  for (let i = 0; i < locatorsGroup.length; i++) {
828
786
  let foundLocators = [];
829
787
  try {
830
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
788
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
831
789
  }
832
790
  catch (e) {
833
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
834
- 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);
835
794
  foundLocators = [];
836
795
  try {
837
- 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);
838
797
  }
839
798
  catch (e) {
840
799
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -850,11 +809,27 @@ class StableBrowser {
850
809
  }
851
810
  if (foundLocators.length > 1) {
852
811
  info.failCause.foundMultiple = true;
812
+ if (info.locatorLog) {
813
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
814
+ }
853
815
  }
854
816
  }
855
817
  return result;
856
818
  }
857
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);
858
833
  const startTime = Date.now();
859
834
  let timeout = 30000;
860
835
  if (options && options.timeout) {
@@ -879,13 +854,31 @@ class StableBrowser {
879
854
  catch (e) {
880
855
  if (performance.now() - startTime > timeout) {
881
856
  // throw e;
882
- 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
+ }
883
863
  }
884
864
  }
885
865
  await new Promise((resolve) => setTimeout(resolve, 3000));
886
866
  }
887
867
  }
888
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);
889
882
  const startTime = Date.now();
890
883
  let timeout = 30000;
891
884
  if (options && options.timeout) {
@@ -910,7 +903,12 @@ class StableBrowser {
910
903
  catch (e) {
911
904
  if (performance.now() - startTime > timeout) {
912
905
  // throw e;
913
- 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
+ }
914
912
  }
915
913
  }
916
914
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -929,9 +927,9 @@ class StableBrowser {
929
927
  };
930
928
  try {
931
929
  await _preCommand(state, this);
932
- if (state.options && state.options.context) {
933
- state.selectors.locators[0].text = state.options.context;
934
- }
930
+ // if (state.options && state.options.context) {
931
+ // state.selectors.locators[0].text = state.options.context;
932
+ // }
935
933
  try {
936
934
  await state.element.click();
937
935
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -969,9 +967,15 @@ class StableBrowser {
969
967
  // let element = await this._locate(selectors, info, _params);
970
968
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
971
969
  try {
972
- // await this._highlightElements(element);
970
+ // if (world && world.screenshot && !world.screenshotPath) {
971
+ // console.log(`Highlighting while running from recorder`);
972
+ await this._highlightElements(element);
973
973
  await state.element.setChecked(checked);
974
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);
975
979
  }
976
980
  catch (e) {
977
981
  if (e.message && e.message.includes("did not change its state")) {
@@ -1010,6 +1014,7 @@ class StableBrowser {
1010
1014
  await _preCommand(state, this);
1011
1015
  try {
1012
1016
  await state.element.hover();
1017
+ // await _screenshot(state, this);
1013
1018
  await new Promise((resolve) => setTimeout(resolve, 1000));
1014
1019
  }
1015
1020
  catch (e) {
@@ -1017,6 +1022,7 @@ class StableBrowser {
1017
1022
  state.info.log += "hover failed, will try again" + "\n";
1018
1023
  state.element = await this._locate(selectors, state.info, _params);
1019
1024
  await state.element.hover({ timeout: 10000 });
1025
+ // await _screenshot(state, this);
1020
1026
  await new Promise((resolve) => setTimeout(resolve, 1000));
1021
1027
  }
1022
1028
  await _screenshot(state, this);
@@ -1291,7 +1297,12 @@ class StableBrowser {
1291
1297
  await this.waitForPageLoad();
1292
1298
  }
1293
1299
  else if (enter === false) {
1294
- await state.element.dispatchEvent("change");
1300
+ try {
1301
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1302
+ }
1303
+ catch (e) {
1304
+ // ignore
1305
+ }
1295
1306
  //await this.page.keyboard.press("Tab");
1296
1307
  }
1297
1308
  else {
@@ -1348,6 +1359,7 @@ class StableBrowser {
1348
1359
  let screenshotPath = null;
1349
1360
  if (!info.log) {
1350
1361
  info.log = "";
1362
+ info.locatorLog = new LocatorLog(selectors);
1351
1363
  }
1352
1364
  info.operation = "getText";
1353
1365
  info.selectors = selectors;
@@ -1370,6 +1382,18 @@ class StableBrowser {
1370
1382
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1371
1383
  try {
1372
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
+ // }
1373
1397
  const elementText = await element.innerText();
1374
1398
  return {
1375
1399
  text: elementText,
@@ -1381,7 +1405,7 @@ class StableBrowser {
1381
1405
  }
1382
1406
  catch (e) {
1383
1407
  //await this.closeUnexpectedPopups();
1384
- this.logger.info("no innerText will use textContent");
1408
+ this.logger.info("no innerText, will use textContent");
1385
1409
  const elementText = await element.textContent();
1386
1410
  return { text: elementText, screenshotId, screenshotPath, value: value };
1387
1411
  }
@@ -1686,11 +1710,9 @@ class StableBrowser {
1686
1710
  if (!fs.existsSync(world.screenshotPath)) {
1687
1711
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1688
1712
  }
1689
- let nextIndex = 1;
1690
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1691
- nextIndex++;
1692
- }
1693
- 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");
1694
1716
  try {
1695
1717
  await this.takeScreenshot(screenshotPath);
1696
1718
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1700,15 +1722,15 @@ class StableBrowser {
1700
1722
  // this.logger.info("unable to save screenshot " + screenshotPath);
1701
1723
  // }
1702
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
+ }
1703
1730
  }
1704
1731
  catch (e) {
1705
1732
  this.logger.info("unable to take screenshot, ignored");
1706
1733
  }
1707
- result.screenshotId = nextIndex;
1708
- result.screenshotPath = screenshotPath;
1709
- if (info && info.box) {
1710
- await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1711
- }
1712
1734
  }
1713
1735
  else if (options && options.screenshot) {
1714
1736
  result.screenshotPath = options.screenshotPath;
@@ -1743,6 +1765,15 @@ class StableBrowser {
1743
1765
  document.documentElement.clientWidth,
1744
1766
  ])));
1745
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
+ // }
1746
1777
  if (this.context.browserName === "chromium") {
1747
1778
  const client = await playContext.newCDPSession(this.page);
1748
1779
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1764,6 +1795,10 @@ class StableBrowser {
1764
1795
  else {
1765
1796
  screenshotBuffer = await this.page.screenshot();
1766
1797
  }
1798
+ // if (focusedElement) {
1799
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1800
+ // await this._unhighlightElements(focusedElement);
1801
+ // }
1767
1802
  let image = await Jimp.read(screenshotBuffer);
1768
1803
  // Get the image dimensions
1769
1804
  const { width, height } = image.bitmap;
@@ -1813,6 +1848,7 @@ class StableBrowser {
1813
1848
  text: `Extract attribute from element`,
1814
1849
  operation: "extractAttribute",
1815
1850
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1851
+ allowDisabled: true,
1816
1852
  };
1817
1853
  await new Promise((resolve) => setTimeout(resolve, 2000));
1818
1854
  try {
@@ -1834,6 +1870,70 @@ class StableBrowser {
1834
1870
  state.info.value = state.value;
1835
1871
  this.setTestData({ [variable]: state.value }, world);
1836
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
+ }
1837
1937
  return state.info;
1838
1938
  }
1839
1939
  catch (e) {
@@ -1861,7 +1961,7 @@ class StableBrowser {
1861
1961
  if (options && options.timeout) {
1862
1962
  timeout = options.timeout;
1863
1963
  }
1864
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1964
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1865
1965
  const request = {
1866
1966
  method: "POST",
1867
1967
  url: serviceUrl,
@@ -1932,27 +2032,32 @@ class StableBrowser {
1932
2032
  async _highlightElements(scope, css) {
1933
2033
  try {
1934
2034
  if (!scope) {
2035
+ // console.log(`Scope is not defined`);
1935
2036
  return;
1936
2037
  }
1937
2038
  if (!css) {
1938
2039
  scope
1939
2040
  .evaluate((node) => {
1940
2041
  if (node && node.style) {
1941
- let originalBorder = node.style.border;
1942
- 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}`);
1943
2047
  if (window) {
1944
2048
  window.addEventListener("beforeunload", function (e) {
1945
- node.style.border = originalBorder;
2049
+ node.style.outline = originalOutline;
1946
2050
  });
1947
2051
  }
1948
2052
  setTimeout(function () {
1949
- node.style.border = originalBorder;
2053
+ node.style.outline = originalOutline;
1950
2054
  }, 2000);
1951
2055
  }
1952
2056
  })
1953
2057
  .then(() => { })
1954
2058
  .catch((e) => {
1955
2059
  // ignore
2060
+ // console.error(`Could not highlight node : ${e}`);
1956
2061
  });
1957
2062
  }
1958
2063
  else {
@@ -1968,17 +2073,18 @@ class StableBrowser {
1968
2073
  if (!element.style) {
1969
2074
  return;
1970
2075
  }
1971
- var originalBorder = element.style.border;
2076
+ let originalOutline = element.style.outline;
2077
+ element.__previousOutline = originalOutline;
1972
2078
  // Set the new border to be red and 2px solid
1973
- element.style.border = "2px solid red";
2079
+ element.style.outline = "2px solid red";
1974
2080
  if (window) {
1975
2081
  window.addEventListener("beforeunload", function (e) {
1976
- element.style.border = originalBorder;
2082
+ element.style.outline = originalOutline;
1977
2083
  });
1978
2084
  }
1979
2085
  // Set a timeout to revert to the original border after 2 seconds
1980
2086
  setTimeout(function () {
1981
- element.style.border = originalBorder;
2087
+ element.style.outline = originalOutline;
1982
2088
  }, 2000);
1983
2089
  }
1984
2090
  return;
@@ -1986,6 +2092,7 @@ class StableBrowser {
1986
2092
  .then(() => { })
1987
2093
  .catch((e) => {
1988
2094
  // ignore
2095
+ // console.error(`Could not highlight css: ${e}`);
1989
2096
  });
1990
2097
  }
1991
2098
  }
@@ -1993,6 +2100,54 @@ class StableBrowser {
1993
2100
  console.debug(error);
1994
2101
  }
1995
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
+ // }
1996
2151
  async verifyPagePath(pathPart, options = {}, world = null) {
1997
2152
  const startTime = Date.now();
1998
2153
  let error = null;
@@ -2054,6 +2209,35 @@ class StableBrowser {
2054
2209
  });
2055
2210
  }
2056
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
+ }
2057
2241
  async verifyTextExistInPage(text, options = {}, world = null) {
2058
2242
  text = unEscapeString(text);
2059
2243
  const state = {
@@ -2063,7 +2247,7 @@ class StableBrowser {
2063
2247
  locate: false,
2064
2248
  scroll: false,
2065
2249
  highlight: false,
2066
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2250
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2067
2251
  text: `Verify text exists in page`,
2068
2252
  operation: "verifyTextExistInPage",
2069
2253
  log: "***** verify text " + text + " exists in page *****\n",
@@ -2081,31 +2265,7 @@ class StableBrowser {
2081
2265
  await _preCommand(state, this);
2082
2266
  state.info.text = text;
2083
2267
  while (true) {
2084
- const frames = this.page.frames();
2085
- let results = [];
2086
- for (let i = 0; i < frames.length; i++) {
2087
- if (dateAlternatives.date) {
2088
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2089
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2090
- result.frame = frames[i];
2091
- results.push(result);
2092
- }
2093
- }
2094
- else if (numberAlternatives.number) {
2095
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2096
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2097
- result.frame = frames[i];
2098
- results.push(result);
2099
- }
2100
- }
2101
- else {
2102
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2103
- result.frame = frames[i];
2104
- results.push(result);
2105
- }
2106
- }
2107
- state.info.results = results;
2108
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2268
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2109
2269
  if (resultWithElementsFound.length === 0) {
2110
2270
  if (Date.now() - state.startTime > timeout) {
2111
2271
  throw new Error(`Text ${text} not found in page`);
@@ -2115,12 +2275,29 @@ class StableBrowser {
2115
2275
  }
2116
2276
  if (resultWithElementsFound[0].randomToken) {
2117
2277
  const frame = resultWithElementsFound[0].frame;
2118
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2278
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2119
2279
  await this._highlightElements(frame, dataAttribute);
2120
- 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);
2121
2297
  if (element) {
2122
2298
  await this.scrollIfNeeded(element, state.info);
2123
2299
  await element.dispatchEvent("bvt_verify_page_contains_text");
2300
+ // await _screenshot(state, this, element);
2124
2301
  }
2125
2302
  }
2126
2303
  await _screenshot(state, this);
@@ -2162,31 +2339,7 @@ class StableBrowser {
2162
2339
  await _preCommand(state, this);
2163
2340
  state.info.text = text;
2164
2341
  while (true) {
2165
- const frames = this.page.frames();
2166
- let results = [];
2167
- for (let i = 0; i < frames.length; i++) {
2168
- if (dateAlternatives.date) {
2169
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2170
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2171
- result.frame = frames[i];
2172
- results.push(result);
2173
- }
2174
- }
2175
- else if (numberAlternatives.number) {
2176
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2177
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2178
- result.frame = frames[i];
2179
- results.push(result);
2180
- }
2181
- }
2182
- else {
2183
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2184
- result.frame = frames[i];
2185
- results.push(result);
2186
- }
2187
- }
2188
- state.info.results = results;
2189
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2342
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2190
2343
  if (resultWithElementsFound.length === 0) {
2191
2344
  await _screenshot(state, this);
2192
2345
  return state.info;
@@ -2204,15 +2357,102 @@ class StableBrowser {
2204
2357
  _commandFinally(state, this);
2205
2358
  }
2206
2359
  }
2207
- _getServerUrl() {
2208
- let serviceUrl = "https://api.blinq.io";
2209
- if (process.env.NODE_ENV_BLINQ === "dev") {
2210
- 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 });
2211
2449
  }
2212
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2213
- serviceUrl = "https://stage.api.blinq.io";
2450
+ catch (e) {
2451
+ await _commandError(state, e, this);
2452
+ }
2453
+ finally {
2454
+ _commandFinally(state, this);
2214
2455
  }
2215
- return serviceUrl;
2216
2456
  }
2217
2457
  async visualVerification(text, options = {}, world = null) {
2218
2458
  const startTime = Date.now();
@@ -2228,7 +2468,7 @@ class StableBrowser {
2228
2468
  throw new Error("TOKEN is not set");
2229
2469
  }
2230
2470
  try {
2231
- let serviceUrl = this._getServerUrl();
2471
+ let serviceUrl = _getServerUrl();
2232
2472
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2233
2473
  info.screenshotPath = screenshotPath;
2234
2474
  const screenshot = await this.takeScreenshot();
@@ -2317,6 +2557,7 @@ class StableBrowser {
2317
2557
  let screenshotPath = null;
2318
2558
  const info = {};
2319
2559
  info.log = "";
2560
+ info.locatorLog = new LocatorLog(selectors);
2320
2561
  info.operation = "getTableData";
2321
2562
  info.selectors = selectors;
2322
2563
  try {
@@ -2392,7 +2633,7 @@ class StableBrowser {
2392
2633
  info.operation = "analyzeTable";
2393
2634
  info.selectors = selectors;
2394
2635
  info.query = query;
2395
- query = this._fixUsingParams(query, _params);
2636
+ query = _fixUsingParams(query, _params);
2396
2637
  info.query_fixed = query;
2397
2638
  info.operator = operator;
2398
2639
  info.value = value;
@@ -2623,6 +2864,7 @@ class StableBrowser {
2623
2864
  saveTestDataAsGlobal(options, world) {
2624
2865
  const dataFile = this._getDataFile(world);
2625
2866
  process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2867
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2626
2868
  }
2627
2869
  async setViewportSize(width, hight, options = {}, world = null) {
2628
2870
  const startTime = Date.now();
@@ -2721,19 +2963,34 @@ class StableBrowser {
2721
2963
  }
2722
2964
  }
2723
2965
  async beforeStep(world, step) {
2724
- this.stepName = step.pickleStep.text;
2725
- this.logger.info("step: " + this.stepName);
2726
2966
  if (this.stepIndex === undefined) {
2727
2967
  this.stepIndex = 0;
2728
2968
  }
2729
2969
  else {
2730
2970
  this.stepIndex++;
2731
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
+ }
2732
2982
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2733
2983
  if (this.context.browserObject.context) {
2734
2984
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2735
2985
  }
2736
2986
  }
2987
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2988
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2989
+ // check if @global_test_data tag is present
2990
+ if (this.tags.includes("@global_test_data")) {
2991
+ this.saveTestDataAsGlobal({}, world);
2992
+ }
2993
+ }
2737
2994
  }
2738
2995
  async afterStep(world, step) {
2739
2996
  this.stepName = null;
@@ -2751,156 +3008,5 @@ function createTimedPromise(promise, label) {
2751
3008
  .then((result) => ({ status: "fulfilled", label, result }))
2752
3009
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2753
3010
  }
2754
- const KEYBOARD_EVENTS = [
2755
- "ALT",
2756
- "AltGraph",
2757
- "CapsLock",
2758
- "Control",
2759
- "Fn",
2760
- "FnLock",
2761
- "Hyper",
2762
- "Meta",
2763
- "NumLock",
2764
- "ScrollLock",
2765
- "Shift",
2766
- "Super",
2767
- "Symbol",
2768
- "SymbolLock",
2769
- "Enter",
2770
- "Tab",
2771
- "ArrowDown",
2772
- "ArrowLeft",
2773
- "ArrowRight",
2774
- "ArrowUp",
2775
- "End",
2776
- "Home",
2777
- "PageDown",
2778
- "PageUp",
2779
- "Backspace",
2780
- "Clear",
2781
- "Copy",
2782
- "CrSel",
2783
- "Cut",
2784
- "Delete",
2785
- "EraseEof",
2786
- "ExSel",
2787
- "Insert",
2788
- "Paste",
2789
- "Redo",
2790
- "Undo",
2791
- "Accept",
2792
- "Again",
2793
- "Attn",
2794
- "Cancel",
2795
- "ContextMenu",
2796
- "Escape",
2797
- "Execute",
2798
- "Find",
2799
- "Finish",
2800
- "Help",
2801
- "Pause",
2802
- "Play",
2803
- "Props",
2804
- "Select",
2805
- "ZoomIn",
2806
- "ZoomOut",
2807
- "BrightnessDown",
2808
- "BrightnessUp",
2809
- "Eject",
2810
- "LogOff",
2811
- "Power",
2812
- "PowerOff",
2813
- "PrintScreen",
2814
- "Hibernate",
2815
- "Standby",
2816
- "WakeUp",
2817
- "AllCandidates",
2818
- "Alphanumeric",
2819
- "CodeInput",
2820
- "Compose",
2821
- "Convert",
2822
- "Dead",
2823
- "FinalMode",
2824
- "GroupFirst",
2825
- "GroupLast",
2826
- "GroupNext",
2827
- "GroupPrevious",
2828
- "ModeChange",
2829
- "NextCandidate",
2830
- "NonConvert",
2831
- "PreviousCandidate",
2832
- "Process",
2833
- "SingleCandidate",
2834
- "HangulMode",
2835
- "HanjaMode",
2836
- "JunjaMode",
2837
- "Eisu",
2838
- "Hankaku",
2839
- "Hiragana",
2840
- "HiraganaKatakana",
2841
- "KanaMode",
2842
- "KanjiMode",
2843
- "Katakana",
2844
- "Romaji",
2845
- "Zenkaku",
2846
- "ZenkakuHanaku",
2847
- "F1",
2848
- "F2",
2849
- "F3",
2850
- "F4",
2851
- "F5",
2852
- "F6",
2853
- "F7",
2854
- "F8",
2855
- "F9",
2856
- "F10",
2857
- "F11",
2858
- "F12",
2859
- "Soft1",
2860
- "Soft2",
2861
- "Soft3",
2862
- "Soft4",
2863
- "ChannelDown",
2864
- "ChannelUp",
2865
- "Close",
2866
- "MailForward",
2867
- "MailReply",
2868
- "MailSend",
2869
- "MediaFastForward",
2870
- "MediaPause",
2871
- "MediaPlay",
2872
- "MediaPlayPause",
2873
- "MediaRecord",
2874
- "MediaRewind",
2875
- "MediaStop",
2876
- "MediaTrackNext",
2877
- "MediaTrackPrevious",
2878
- "AudioBalanceLeft",
2879
- "AudioBalanceRight",
2880
- "AudioBassBoostDown",
2881
- "AudioBassBoostToggle",
2882
- "AudioBassBoostUp",
2883
- "AudioFaderFront",
2884
- "AudioFaderRear",
2885
- "AudioSurroundModeNext",
2886
- "AudioTrebleDown",
2887
- "AudioTrebleUp",
2888
- "AudioVolumeDown",
2889
- "AudioVolumeMute",
2890
- "AudioVolumeUp",
2891
- "MicrophoneToggle",
2892
- "MicrophoneVolumeDown",
2893
- "MicrophoneVolumeMute",
2894
- "MicrophoneVolumeUp",
2895
- "TV",
2896
- "TV3DMode",
2897
- "TVAntennaCable",
2898
- "TVAudioDescription",
2899
- ];
2900
- function unEscapeString(str) {
2901
- const placeholder = "__NEWLINE__";
2902
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2903
- return str;
2904
- }
2905
3011
  export { StableBrowser };
2906
3012
  //# sourceMappingURL=stable_browser.js.map