automation_model 1.0.520-dev → 1.0.520-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.
Files changed (44) hide show
  1. package/lib/api.d.ts +2 -1
  2. package/lib/api.js +90 -88
  3. package/lib/api.js.map +1 -1
  4. package/lib/auto_page.d.ts +2 -1
  5. package/lib/auto_page.js +19 -2
  6. package/lib/auto_page.js.map +1 -1
  7. package/lib/browser_manager.d.ts +3 -2
  8. package/lib/browser_manager.js +70 -27
  9. package/lib/browser_manager.js.map +1 -1
  10. package/lib/command_common.d.ts +2 -2
  11. package/lib/command_common.js +77 -17
  12. package/lib/command_common.js.map +1 -1
  13. package/lib/error-messages.js +20 -2
  14. package/lib/error-messages.js.map +1 -1
  15. package/lib/generation_scripts.d.ts +4 -0
  16. package/lib/generation_scripts.js +2 -0
  17. package/lib/generation_scripts.js.map +1 -0
  18. package/lib/index.d.ts +1 -0
  19. package/lib/index.js +1 -0
  20. package/lib/index.js.map +1 -1
  21. package/lib/init_browser.d.ts +2 -1
  22. package/lib/init_browser.js +15 -4
  23. package/lib/init_browser.js.map +1 -1
  24. package/lib/locate_element.js +15 -13
  25. package/lib/locate_element.js.map +1 -1
  26. package/lib/locator.d.ts +36 -0
  27. package/lib/locator.js +165 -0
  28. package/lib/locator.js.map +1 -1
  29. package/lib/locator_log.d.ts +26 -0
  30. package/lib/locator_log.js +69 -0
  31. package/lib/locator_log.js.map +1 -0
  32. package/lib/network.js +51 -12
  33. package/lib/network.js.map +1 -1
  34. package/lib/stable_browser.d.ts +54 -17
  35. package/lib/stable_browser.js +624 -513
  36. package/lib/stable_browser.js.map +1 -1
  37. package/lib/table.d.ts +13 -0
  38. package/lib/table.js +187 -0
  39. package/lib/table.js.map +1 -0
  40. package/lib/utils.d.ts +13 -1
  41. package/lib/utils.js +344 -6
  42. package/lib/utils.js.map +1 -1
  43. package/package.json +6 -5
  44. /package/lib/{axe → scripts}/axe.mini.js +0 -0
@@ -7,18 +7,19 @@ import path from "path";
7
7
  import reg_parser from "regex-parser";
8
8
  import { findDateAlternatives, findNumberAlternatives } from "./analyze_helper.js";
9
9
  import { getDateTimeValue } from "./date_time.js";
10
- import drawRectangle from "./drawRect.js";
11
10
  //import { closeUnexpectedPopups } from "./popups.js";
12
11
  import { getTableCells, getTableData } from "./table_analyze.js";
13
- import { maskValue, replaceWithLocalTestData } from "./utils.js";
12
+ import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
14
13
  import csv from "csv-parser";
15
14
  import { Readable } from "node:stream";
16
15
  import readline from "readline";
17
16
  import { getContext } from "./init_browser.js";
18
17
  import { locate_element } from "./locate_element.js";
18
+ import { randomUUID } from "crypto";
19
19
  import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
20
20
  import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
21
- const Types = {
21
+ import { LocatorLog } from "./locator_log.js";
22
+ export const Types = {
22
23
  CLICK: "click_element",
23
24
  NAVIGATE: "navigate",
24
25
  FILL: "fill_element",
@@ -29,6 +30,8 @@ const Types = {
29
30
  GET_PAGE_STATUS: "get_page_status",
30
31
  CLICK_ROW_ACTION: "click_row_action",
31
32
  VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
33
+ VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
34
+ VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
32
35
  ANALYZE_TABLE: "analyze_table",
33
36
  SELECT: "select_combobox",
34
37
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -45,8 +48,13 @@ const Types = {
45
48
  LOAD_DATA: "load_data",
46
49
  SET_INPUT: "set_input",
47
50
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
51
+ VERIFY_ATTRIBUTE: "verify_element_attribute",
52
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
48
53
  };
49
54
  export const apps = {};
55
+ const formatElementName = (elementName) => {
56
+ return elementName ? JSON.stringify(elementName) : "element";
57
+ };
50
58
  class StableBrowser {
51
59
  browser;
52
60
  page;
@@ -58,6 +66,7 @@ class StableBrowser {
58
66
  networkLogger = null;
59
67
  configuration = null;
60
68
  appName = "main";
69
+ tags = null;
61
70
  constructor(browser, page, logger = null, context = null, world = null) {
62
71
  this.browser = browser;
63
72
  this.page = page;
@@ -88,17 +97,17 @@ class StableBrowser {
88
97
  catch (e) {
89
98
  this.logger.error("unable to read ai_config.json");
90
99
  }
91
- context.pageLoading = { status: false };
92
- context.pages = [this.page];
93
100
  const logFolder = path.join(this.project_path, "logs", "web");
94
101
  this.world = world;
102
+ context.pages = [this.page];
103
+ context.pageLoading = { status: false };
95
104
  this.registerEventListeners(this.context);
96
105
  registerNetworkEvents(this.world, this, this.context, this.page);
97
106
  registerDownloadEvent(this.page, this.world, this.context);
98
107
  }
99
108
  registerEventListeners(context) {
100
109
  this.registerConsoleLogListener(this.page, context);
101
- this.registerRequestListener(this.page, context, this.webLogFile);
110
+ // this.registerRequestListener(this.page, context, this.webLogFile);
102
111
  if (!context.pageLoading) {
103
112
  context.pageLoading = { status: false };
104
113
  }
@@ -143,7 +152,7 @@ class StableBrowser {
143
152
  if (this.appName === appName) {
144
153
  return;
145
154
  }
146
- let newContextCreated = false;
155
+ let navigate = false;
147
156
  if (!apps[appName]) {
148
157
  let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
149
158
  newContextCreated = true;
@@ -154,32 +163,15 @@ class StableBrowser {
154
163
  };
155
164
  }
156
165
  const tempContext = {};
157
- this._copyContext(this, tempContext);
158
- this._copyContext(apps[appName], this);
166
+ _copyContext(this, tempContext);
167
+ _copyContext(apps[appName], this);
159
168
  apps[this.appName] = tempContext;
160
169
  this.appName = appName;
161
- if (newContextCreated) {
162
- this.registerEventListeners(this.context);
170
+ if (navigate) {
163
171
  await this.goto(this.context.environment.baseUrl);
164
172
  await this.waitForPageLoad();
165
173
  }
166
174
  }
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
175
  registerConsoleLogListener(page, context) {
184
176
  if (!this.context.webLogger) {
185
177
  this.context.webLogger = [];
@@ -243,55 +235,48 @@ class StableBrowser {
243
235
  // async closeUnexpectedPopups() {
244
236
  // await closeUnexpectedPopups(this.page);
245
237
  // }
246
- async goto(url) {
238
+ async goto(url, world = null) {
247
239
  if (!url.startsWith("http")) {
248
240
  url = "https://" + url;
249
241
  }
250
- await this.page.goto(url, {
251
- timeout: 60000,
252
- });
253
- }
254
- _fixUsingParams(text, _params) {
255
- if (!_params || typeof text !== "string") {
256
- return text;
242
+ const state = {
243
+ value: url,
244
+ world: world,
245
+ type: Types.NAVIGATE,
246
+ text: `Navigate Page to: ${url}`,
247
+ operation: "goto",
248
+ log: "***** navigate page to " + url + " *****\n",
249
+ info: {},
250
+ locate: false,
251
+ scroll: false,
252
+ screenshot: false,
253
+ highlight: false,
254
+ };
255
+ try {
256
+ await _preCommand(state, this, world);
257
+ await this.page.goto(url, {
258
+ timeout: 60000,
259
+ });
260
+ await _screenshot(state, this);
257
261
  }
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]);
262
+ catch (error) {
263
+ console.error("Error on goto", error);
264
+ _commandError(state, error, this);
265
265
  }
266
- return text;
267
- }
268
- _fixLocatorUsingParams(locator, _params) {
269
- // check if not null
270
- if (!locator) {
271
- return locator;
266
+ finally {
267
+ _commandFinally(state, this);
272
268
  }
273
- // clone the locator
274
- locator = JSON.parse(JSON.stringify(locator));
275
- this.scanAndManipulate(locator, _params);
276
- return locator;
277
269
  }
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);
270
+ async _getLocator(locator, scope, _params) {
271
+ locator = _fixLocatorUsingParams(locator, _params);
272
+ // locator = await this._replaceWithLocalData(locator);
273
+ for (let key in locator) {
274
+ if (typeof locator[key] !== "string")
275
+ continue;
276
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
277
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
290
278
  }
291
279
  }
292
- }
293
- _getLocator(locator, scope, _params) {
294
- locator = this._fixLocatorUsingParams(locator, _params);
295
280
  let locatorReturn;
296
281
  if (locator.role) {
297
282
  if (locator.role[1].nameReg) {
@@ -299,7 +284,7 @@ class StableBrowser {
299
284
  delete locator.role[1].nameReg;
300
285
  }
301
286
  // if (locator.role[1].name) {
302
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
287
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
303
288
  // }
304
289
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
305
290
  }
@@ -342,140 +327,54 @@ class StableBrowser {
342
327
  if (css && css.locator) {
343
328
  css = css.locator;
344
329
  }
345
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
330
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
346
331
  if (result.elementCount === 0) {
347
332
  return;
348
333
  }
349
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
334
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
350
335
  // css climb to parent element
351
336
  const climbArray = [];
352
337
  for (let i = 0; i < climb; i++) {
353
338
  climbArray.push("..");
354
339
  }
355
340
  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
- }
341
+ let resultCss = textElementCss + " >> " + climbXpath;
342
+ if (css) {
343
+ resultCss = resultCss + " >> " + css;
344
+ }
345
+ return resultCss;
346
+ }
347
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
348
+ const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
349
+ const locator = scope.locator(query);
350
+ const count = await locator.count();
351
+ if (!tag1) {
352
+ tag1 = "*";
353
+ }
354
+ const randomToken = Math.random().toString(36).substring(7);
355
+ let tagCount = 0;
356
+ for (let i = 0; i < count; i++) {
357
+ const element = locator.nth(i);
358
+ // check if the tag matches
359
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
360
+ if (!tag.startsWith("*")) {
361
+ if (el.tagName.toLowerCase() !== tag) {
362
+ return false;
444
363
  }
445
364
  }
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++;
365
+ if (!el.setAttribute) {
366
+ el = el.parentElement;
473
367
  }
368
+ el.setAttribute("data-blinq-id-" + randomToken, "");
369
+ return true;
370
+ }, [tag1, randomToken]))) {
371
+ continue;
474
372
  }
475
- return { elementCount: elementCount, randomToken: randomToken };
476
- }, [text1, tag1, regex1, partial1]);
373
+ tagCount++;
374
+ }
375
+ return { elementCount: tagCount, randomToken };
477
376
  }
478
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
377
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
479
378
  if (!info) {
480
379
  info = {};
481
380
  }
@@ -484,10 +383,13 @@ class StableBrowser {
484
383
  }
485
384
  if (!info.log) {
486
385
  info.log = "";
386
+ info.locatorLog = new LocatorLog(selectorHierarchy);
487
387
  }
488
388
  let locatorSearch = selectorHierarchy[index];
389
+ let originalLocatorSearch = "";
489
390
  try {
490
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
391
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
392
+ locatorSearch = JSON.parse(originalLocatorSearch);
491
393
  }
492
394
  catch (e) {
493
395
  console.error(e);
@@ -495,30 +397,31 @@ class StableBrowser {
495
397
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
496
398
  let locator = null;
497
399
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
498
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
400
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
401
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
499
402
  if (!locatorString) {
500
403
  info.failCause.textNotFound = true;
501
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
404
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
502
405
  return;
503
406
  }
504
- locator = this._getLocator({ css: locatorString }, scope, _params);
407
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
505
408
  }
506
409
  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);
410
+ let text = _fixUsingParams(locatorSearch.text, _params);
411
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
509
412
  if (result.elementCount === 0) {
510
413
  info.failCause.textNotFound = true;
511
- info.failCause.lastError = "failed to locate element by text: " + text;
414
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
512
415
  return;
513
416
  }
514
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
417
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
515
418
  if (locatorSearch.childCss) {
516
419
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
517
420
  }
518
- locator = this._getLocator(locatorSearch, scope, _params);
421
+ locator = await this._getLocator(locatorSearch, scope, _params);
519
422
  }
520
423
  else {
521
- locator = this._getLocator(locatorSearch, scope, _params);
424
+ locator = await this._getLocator(locatorSearch, scope, _params);
522
425
  }
523
426
  // let cssHref = false;
524
427
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -531,18 +434,27 @@ class StableBrowser {
531
434
  //info.log += "total elements found " + count + "\n";
532
435
  //let visibleCount = 0;
533
436
  let visibleLocator = null;
534
- if (locatorSearch.index && locatorSearch.index < count) {
437
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
535
438
  foundLocators.push(locator.nth(locatorSearch.index));
439
+ if (info.locatorLog) {
440
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
441
+ }
536
442
  return;
537
443
  }
444
+ if (info.locatorLog && count === 0) {
445
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
446
+ }
538
447
  for (let j = 0; j < count; j++) {
539
448
  let visible = await locator.nth(j).isVisible();
540
449
  const enabled = await locator.nth(j).isEnabled();
541
450
  if (!visibleOnly) {
542
451
  visible = true;
543
452
  }
544
- if (visible && enabled) {
453
+ if (visible && (allowDisabled || enabled)) {
545
454
  foundLocators.push(locator.nth(j));
455
+ if (info.locatorLog) {
456
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
457
+ }
546
458
  }
547
459
  else {
548
460
  info.failCause.visible = visible;
@@ -550,8 +462,16 @@ class StableBrowser {
550
462
  if (!info.printMessages) {
551
463
  info.printMessages = {};
552
464
  }
465
+ if (info.locatorLog && !visible) {
466
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
467
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
468
+ }
469
+ if (info.locatorLog && !enabled) {
470
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
471
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
472
+ }
553
473
  if (!info.printMessages[j.toString()]) {
554
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
474
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
555
475
  info.printMessages[j.toString()] = true;
556
476
  }
557
477
  }
@@ -567,7 +487,7 @@ class StableBrowser {
567
487
  if (!info) {
568
488
  info = {};
569
489
  }
570
- info.log += "scan for popup handlers" + "\n";
490
+ //info.log += "scan for popup handlers" + "\n";
571
491
  const handlerGroup = [];
572
492
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
573
493
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -594,16 +514,28 @@ class StableBrowser {
594
514
  }
595
515
  if (result.foundElements.length > 0) {
596
516
  let dialogCloseLocator = result.foundElements[0].locator;
597
- await dialogCloseLocator.click();
598
- // wait for the dialog to close
599
- await dialogCloseLocator.waitFor({ state: "hidden" });
517
+ try {
518
+ await scope?.evaluate(() => {
519
+ window.__isClosingPopups = true;
520
+ });
521
+ await dialogCloseLocator.click();
522
+ // wait for the dialog to close
523
+ await dialogCloseLocator.waitFor({ state: "hidden" });
524
+ }
525
+ catch (e) {
526
+ }
527
+ finally {
528
+ await scope?.evaluate(() => {
529
+ window.__isClosingPopups = false;
530
+ });
531
+ }
600
532
  return { rerun: true };
601
533
  }
602
534
  }
603
535
  }
604
536
  return { rerun: false };
605
537
  }
606
- async _locate(selectors, info, _params, timeout) {
538
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
607
539
  if (!timeout) {
608
540
  timeout = 30000;
609
541
  }
@@ -613,7 +545,7 @@ class StableBrowser {
613
545
  let selector = selectors.locators[j];
614
546
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
615
547
  }
616
- let element = await this._locate_internal(selectors, info, _params, timeout);
548
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
617
549
  if (!element.rerun) {
618
550
  return element;
619
551
  }
@@ -626,6 +558,7 @@ class StableBrowser {
626
558
  info.failCause = {};
627
559
  info.log = "";
628
560
  }
561
+ let startTime = Date.now();
629
562
  let scope = this.page;
630
563
  if (selectors.frame) {
631
564
  return selectors.frame;
@@ -656,9 +589,11 @@ class StableBrowser {
656
589
  }
657
590
  return framescope;
658
591
  };
592
+ let fLocator = null;
659
593
  while (true) {
660
594
  let frameFound = false;
661
595
  if (selectors.nestFrmLoc) {
596
+ fLocator = selectors.nestFrmLoc;
662
597
  scope = await findFrame(selectors.nestFrmLoc, scope);
663
598
  frameFound = true;
664
599
  break;
@@ -667,6 +602,7 @@ class StableBrowser {
667
602
  for (let i = 0; i < selectors.frameLocators.length; i++) {
668
603
  let frameLocator = selectors.frameLocators[i];
669
604
  if (frameLocator.css) {
605
+ fLocator = frameLocator.css;
670
606
  scope = scope.frameLocator(frameLocator.css);
671
607
  frameFound = true;
672
608
  break;
@@ -674,18 +610,25 @@ class StableBrowser {
674
610
  }
675
611
  }
676
612
  if (!frameFound && selectors.iframe_src) {
613
+ fLocator = selectors.iframe_src;
677
614
  scope = this.page.frame({ url: selectors.iframe_src });
678
615
  }
679
616
  if (!scope) {
680
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
681
- if (performance.now() - startTime > timeout) {
617
+ if (info && info.locatorLog) {
618
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
619
+ }
620
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
621
+ if (Date.now() - startTime > timeout) {
682
622
  info.failCause.iframeNotFound = true;
683
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
623
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
684
624
  throw new Error("unable to locate iframe " + selectors.iframe_src);
685
625
  }
686
626
  await new Promise((resolve) => setTimeout(resolve, 1000));
687
627
  }
688
628
  else {
629
+ if (info && info.locatorLog) {
630
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
631
+ }
689
632
  break;
690
633
  }
691
634
  }
@@ -702,16 +645,18 @@ class StableBrowser {
702
645
  return bodyContent;
703
646
  });
704
647
  }
705
- async _locate_internal(selectors, info, _params, timeout = 30000) {
648
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
706
649
  if (!info) {
707
650
  info = {};
708
651
  info.failCause = {};
709
652
  info.log = "";
653
+ info.locatorLog = new LocatorLog(selectors);
710
654
  }
711
655
  let highPriorityTimeout = 5000;
712
656
  let visibleOnlyTimeout = 6000;
713
- let startTime = performance.now();
657
+ let startTime = Date.now();
714
658
  let locatorsCount = 0;
659
+ let lazy_scroll = false;
715
660
  //let arrayMode = Array.isArray(selectors);
716
661
  let scope = await this._findFrameScope(selectors, timeout, info);
717
662
  let selectorsLocators = null;
@@ -749,17 +694,17 @@ class StableBrowser {
749
694
  }
750
695
  // info.log += "scanning locators in priority 1" + "\n";
751
696
  let onlyPriority3 = selectorsLocators[0].priority === 3;
752
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
697
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
753
698
  if (result.foundElements.length === 0) {
754
699
  // info.log += "scanning locators in priority 2" + "\n";
755
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
700
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
756
701
  }
757
702
  if (result.foundElements.length === 0 && onlyPriority3) {
758
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
703
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
759
704
  }
760
705
  else {
761
706
  if (result.foundElements.length === 0 && !highPriorityOnly) {
762
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
707
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
763
708
  }
764
709
  }
765
710
  let foundElements = result.foundElements;
@@ -800,26 +745,38 @@ class StableBrowser {
800
745
  return maxCountElement.locator;
801
746
  }
802
747
  }
803
- if (performance.now() - startTime > timeout) {
748
+ if (Date.now() - startTime > timeout) {
804
749
  break;
805
750
  }
806
- if (performance.now() - startTime > highPriorityTimeout) {
807
- info.log += "high priority timeout, will try all elements" + "\n";
751
+ if (Date.now() - startTime > highPriorityTimeout) {
752
+ //info.log += "high priority timeout, will try all elements" + "\n";
808
753
  highPriorityOnly = false;
754
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
755
+ lazy_scroll = true;
756
+ await scrollPageToLoadLazyElements(this.page);
757
+ }
809
758
  }
810
- if (performance.now() - startTime > visibleOnlyTimeout) {
811
- info.log += "visible only timeout, will try all elements" + "\n";
759
+ if (Date.now() - startTime > visibleOnlyTimeout) {
760
+ //info.log += "visible only timeout, will try all elements" + "\n";
812
761
  visibleOnly = false;
813
762
  }
814
763
  await new Promise((resolve) => setTimeout(resolve, 1000));
815
764
  }
816
765
  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";
766
+ // if (info.locatorLog) {
767
+ // const lines = info.locatorLog.toString().split("\n");
768
+ // for (let line of lines) {
769
+ // this.logger.debug(line);
770
+ // }
771
+ // }
772
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
818
773
  info.failCause.locatorNotFound = true;
819
- info.failCause.lastError = "failed to locate unique element";
774
+ if (!info?.failCause?.lastError) {
775
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
776
+ }
820
777
  throw new Error("failed to locate first element no elements found, " + info.log);
821
778
  }
822
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
779
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
823
780
  let foundElements = [];
824
781
  const result = {
825
782
  foundElements: foundElements,
@@ -827,14 +784,15 @@ class StableBrowser {
827
784
  for (let i = 0; i < locatorsGroup.length; i++) {
828
785
  let foundLocators = [];
829
786
  try {
830
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
787
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
831
788
  }
832
789
  catch (e) {
833
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
834
- this.logger.debug(e);
790
+ // this call can fail it the browser is navigating
791
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
792
+ // this.logger.debug(e);
835
793
  foundLocators = [];
836
794
  try {
837
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
795
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
838
796
  }
839
797
  catch (e) {
840
798
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -850,11 +808,27 @@ class StableBrowser {
850
808
  }
851
809
  if (foundLocators.length > 1) {
852
810
  info.failCause.foundMultiple = true;
811
+ if (info.locatorLog) {
812
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
813
+ }
853
814
  }
854
815
  }
855
816
  return result;
856
817
  }
857
818
  async simpleClick(elementDescription, _params, options = {}, world = null) {
819
+ const state = {
820
+ locate: false,
821
+ scroll: false,
822
+ highlight: false,
823
+ _params,
824
+ options,
825
+ world,
826
+ type: Types.CLICK,
827
+ text: "Click element",
828
+ operation: "simpleClick",
829
+ log: "***** click on " + elementDescription + " *****\n",
830
+ };
831
+ _preCommand(state, this, world);
858
832
  const startTime = Date.now();
859
833
  let timeout = 30000;
860
834
  if (options && options.timeout) {
@@ -879,13 +853,31 @@ class StableBrowser {
879
853
  catch (e) {
880
854
  if (performance.now() - startTime > timeout) {
881
855
  // throw e;
882
- await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
856
+ try {
857
+ await _commandError(state, "timeout looking for " + elementDescription, this);
858
+ }
859
+ finally {
860
+ _commandFinally(state, this);
861
+ }
883
862
  }
884
863
  }
885
864
  await new Promise((resolve) => setTimeout(resolve, 3000));
886
865
  }
887
866
  }
888
867
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
868
+ const state = {
869
+ locate: false,
870
+ scroll: false,
871
+ highlight: false,
872
+ _params,
873
+ options,
874
+ world,
875
+ type: Types.FILL,
876
+ text: "Fill element",
877
+ operation: "simpleClickType",
878
+ log: "***** click type on " + elementDescription + " *****\n",
879
+ };
880
+ _preCommand(state, this, world);
889
881
  const startTime = Date.now();
890
882
  let timeout = 30000;
891
883
  if (options && options.timeout) {
@@ -910,7 +902,12 @@ class StableBrowser {
910
902
  catch (e) {
911
903
  if (performance.now() - startTime > timeout) {
912
904
  // throw e;
913
- await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
905
+ try {
906
+ await _commandError(state, "timeout looking for " + elementDescription, this);
907
+ }
908
+ finally {
909
+ _commandFinally(state, this);
910
+ }
914
911
  }
915
912
  }
916
913
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -928,10 +925,10 @@ class StableBrowser {
928
925
  log: "***** click on " + selectors.element_name + " *****\n",
929
926
  };
930
927
  try {
931
- await _preCommand(state, this);
932
- if (state.options && state.options.context) {
933
- state.selectors.locators[0].text = state.options.context;
934
- }
928
+ await _preCommand(state, this, world);
929
+ // if (state.options && state.options.context) {
930
+ // state.selectors.locators[0].text = state.options.context;
931
+ // }
935
932
  try {
936
933
  await state.element.click();
937
934
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -964,14 +961,20 @@ class StableBrowser {
964
961
  log: "***** check " + selectors.element_name + " *****\n",
965
962
  };
966
963
  try {
967
- await _preCommand(state, this);
964
+ await _preCommand(state, this, world);
968
965
  state.info.checked = checked;
969
966
  // let element = await this._locate(selectors, info, _params);
970
967
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
971
968
  try {
972
- // await this._highlightElements(element);
969
+ if (world && world.screenshot && !world.screenshotPath) {
970
+ // console.log(`Highlighting while running from recorder`);
971
+ await this._highlightElements(element);
972
+ await new Promise((resolve) => setTimeout(resolve, 100));
973
+ await this._unHighlightElements(element);
974
+ }
973
975
  await state.element.setChecked(checked);
974
- await new Promise((resolve) => setTimeout(resolve, 1000));
976
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
977
+ // await this._unHighlightElements(element);
975
978
  }
976
979
  catch (e) {
977
980
  if (e.message && e.message.includes("did not change its state")) {
@@ -1007,9 +1010,10 @@ class StableBrowser {
1007
1010
  log: "***** hover " + selectors.element_name + " *****\n",
1008
1011
  };
1009
1012
  try {
1010
- await _preCommand(state, this);
1013
+ await _preCommand(state, this, world);
1011
1014
  try {
1012
1015
  await state.element.hover();
1016
+ await _screenshot(state, this);
1013
1017
  await new Promise((resolve) => setTimeout(resolve, 1000));
1014
1018
  }
1015
1019
  catch (e) {
@@ -1017,9 +1021,10 @@ class StableBrowser {
1017
1021
  state.info.log += "hover failed, will try again" + "\n";
1018
1022
  state.element = await this._locate(selectors, state.info, _params);
1019
1023
  await state.element.hover({ timeout: 10000 });
1024
+ await _screenshot(state, this);
1020
1025
  await new Promise((resolve) => setTimeout(resolve, 1000));
1021
1026
  }
1022
- await _screenshot(state, this);
1027
+ // await _screenshot(state, this);
1023
1028
  await this.waitForPageLoad();
1024
1029
  return state.info;
1025
1030
  }
@@ -1046,7 +1051,7 @@ class StableBrowser {
1046
1051
  log: "***** select option " + selectors.element_name + " *****\n",
1047
1052
  };
1048
1053
  try {
1049
- await _preCommand(state, this);
1054
+ await _preCommand(state, this, world);
1050
1055
  try {
1051
1056
  await state.element.selectOption(values);
1052
1057
  }
@@ -1080,7 +1085,7 @@ class StableBrowser {
1080
1085
  log: "",
1081
1086
  };
1082
1087
  try {
1083
- await _preCommand(state, this);
1088
+ await _preCommand(state, this, world);
1084
1089
  const valueSegment = state.value.split("&&");
1085
1090
  for (let i = 0; i < valueSegment.length; i++) {
1086
1091
  if (i > 0) {
@@ -1123,7 +1128,7 @@ class StableBrowser {
1123
1128
  log: "***** set input value " + selectors.element_name + " *****\n",
1124
1129
  };
1125
1130
  try {
1126
- await _preCommand(state, this);
1131
+ await _preCommand(state, this, world);
1127
1132
  let value = await this._replaceWithLocalData(state.value, this);
1128
1133
  try {
1129
1134
  await state.element.evaluateHandle((el, value) => {
@@ -1160,7 +1165,7 @@ class StableBrowser {
1160
1165
  throwError: false,
1161
1166
  };
1162
1167
  try {
1163
- await _preCommand(state, this);
1168
+ await _preCommand(state, this, world);
1164
1169
  try {
1165
1170
  await state.element.click();
1166
1171
  await new Promise((resolve) => setTimeout(resolve, 500));
@@ -1234,7 +1239,7 @@ class StableBrowser {
1234
1239
  _value = newValue;
1235
1240
  }
1236
1241
  try {
1237
- await _preCommand(state, this);
1242
+ await _preCommand(state, this, world);
1238
1243
  state.info.value = _value;
1239
1244
  if (options === null || options === undefined || !options.press) {
1240
1245
  try {
@@ -1291,7 +1296,12 @@ class StableBrowser {
1291
1296
  await this.waitForPageLoad();
1292
1297
  }
1293
1298
  else if (enter === false) {
1294
- await state.element.dispatchEvent("change");
1299
+ try {
1300
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1301
+ }
1302
+ catch (e) {
1303
+ // ignore
1304
+ }
1295
1305
  //await this.page.keyboard.press("Tab");
1296
1306
  }
1297
1307
  else {
@@ -1322,7 +1332,7 @@ class StableBrowser {
1322
1332
  log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
1323
1333
  };
1324
1334
  try {
1325
- await _preCommand(state, this);
1335
+ await _preCommand(state, this, world);
1326
1336
  await state.element.fill(value);
1327
1337
  await state.element.dispatchEvent("change");
1328
1338
  if (enter) {
@@ -1348,6 +1358,7 @@ class StableBrowser {
1348
1358
  let screenshotPath = null;
1349
1359
  if (!info.log) {
1350
1360
  info.log = "";
1361
+ info.locatorLog = new LocatorLog(selectors);
1351
1362
  }
1352
1363
  info.operation = "getText";
1353
1364
  info.selectors = selectors;
@@ -1367,9 +1378,19 @@ class StableBrowser {
1367
1378
  catch (e) {
1368
1379
  //ignore
1369
1380
  }
1370
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1381
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info, element));
1371
1382
  try {
1372
- await this._highlightElements(element);
1383
+ if (world && world.screenshot && !world.screenshotPath) {
1384
+ // console.log(`Highlighting for get text while running from recorder`);
1385
+ this._highlightElements(element)
1386
+ .then(async () => {
1387
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1388
+ this._unhighlightElements(element).then(() => { }
1389
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
1390
+ );
1391
+ })
1392
+ .catch(e);
1393
+ }
1373
1394
  const elementText = await element.innerText();
1374
1395
  return {
1375
1396
  text: elementText,
@@ -1381,7 +1402,7 @@ class StableBrowser {
1381
1402
  }
1382
1403
  catch (e) {
1383
1404
  //await this.closeUnexpectedPopups();
1384
- this.logger.info("no innerText will use textContent");
1405
+ this.logger.info("no innerText, will use textContent");
1385
1406
  const elementText = await element.textContent();
1386
1407
  return { text: elementText, screenshotId, screenshotPath, value: value };
1387
1408
  }
@@ -1416,13 +1437,13 @@ class StableBrowser {
1416
1437
  }
1417
1438
  let foundObj = null;
1418
1439
  try {
1419
- await _preCommand(state, this);
1440
+ await _preCommand(state, this, world);
1420
1441
  state.info.pattern = pattern;
1421
1442
  foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
1422
1443
  if (foundObj && foundObj.element) {
1423
1444
  await this.scrollIfNeeded(foundObj.element, state.info);
1424
1445
  }
1425
- await _screenshot(state, this);
1446
+ await _screenshot(state, this, foundObj.element);
1426
1447
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1427
1448
  pattern = pattern.replace("{text}", escapedText);
1428
1449
  let regex = new RegExp(pattern, "im");
@@ -1467,12 +1488,12 @@ class StableBrowser {
1467
1488
  }
1468
1489
  let foundObj = null;
1469
1490
  try {
1470
- await _preCommand(state, this);
1491
+ await _preCommand(state, this, world);
1471
1492
  foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1472
1493
  if (foundObj && foundObj.element) {
1473
1494
  await this.scrollIfNeeded(foundObj.element, state.info);
1474
1495
  }
1475
- await _screenshot(state, this);
1496
+ await _screenshot(state, this, foundObj.element);
1476
1497
  const dateAlternatives = findDateAlternatives(text);
1477
1498
  const numberAlternatives = findNumberAlternatives(text);
1478
1499
  if (dateAlternatives.date) {
@@ -1661,7 +1682,7 @@ class StableBrowser {
1661
1682
  }
1662
1683
  return data;
1663
1684
  }
1664
- async _screenShot(options = {}, world = null, info = null) {
1685
+ async _screenShot(options = {}, world = null, info = null, focusedElement = null) {
1665
1686
  // collect url/path/title
1666
1687
  if (info) {
1667
1688
  if (!info.title) {
@@ -1686,13 +1707,11 @@ class StableBrowser {
1686
1707
  if (!fs.existsSync(world.screenshotPath)) {
1687
1708
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1688
1709
  }
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");
1710
+ // to make sure the path doesn't start with -
1711
+ const uuidStr = "id_" + randomUUID();
1712
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1694
1713
  try {
1695
- await this.takeScreenshot(screenshotPath);
1714
+ await this.takeScreenshot(screenshotPath, focusedElement);
1696
1715
  // let buffer = await this.page.screenshot({ timeout: 4000 });
1697
1716
  // // save the buffer to the screenshot path asynchrously
1698
1717
  // fs.writeFile(screenshotPath, buffer, (err) => {
@@ -1700,20 +1719,20 @@ class StableBrowser {
1700
1719
  // this.logger.info("unable to save screenshot " + screenshotPath);
1701
1720
  // }
1702
1721
  // });
1722
+ result.screenshotId = uuidStr;
1723
+ result.screenshotPath = screenshotPath;
1724
+ if (info && info.box) {
1725
+ // await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1726
+ }
1703
1727
  }
1704
1728
  catch (e) {
1705
1729
  this.logger.info("unable to take screenshot, ignored");
1706
1730
  }
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
1731
  }
1713
1732
  else if (options && options.screenshot) {
1714
1733
  result.screenshotPath = options.screenshotPath;
1715
1734
  try {
1716
- await this.takeScreenshot(options.screenshotPath);
1735
+ await this.takeScreenshot(options.screenshotPath, focusedElement);
1717
1736
  // let buffer = await this.page.screenshot({ timeout: 4000 });
1718
1737
  // // save the buffer to the screenshot path asynchrously
1719
1738
  // fs.writeFile(options.screenshotPath, buffer, (err) => {
@@ -1726,12 +1745,12 @@ class StableBrowser {
1726
1745
  this.logger.info("unable to take screenshot, ignored");
1727
1746
  }
1728
1747
  if (info && info.box) {
1729
- await drawRectangle(options.screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1748
+ // await drawRectangle(options.screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1730
1749
  }
1731
1750
  }
1732
1751
  return result;
1733
1752
  }
1734
- async takeScreenshot(screenshotPath) {
1753
+ async takeScreenshot(screenshotPath, focusedElement = null) {
1735
1754
  const playContext = this.context.playContext;
1736
1755
  // Using CDP to capture the screenshot
1737
1756
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
@@ -1743,6 +1762,15 @@ class StableBrowser {
1743
1762
  document.documentElement.clientWidth,
1744
1763
  ])));
1745
1764
  let screenshotBuffer = null;
1765
+ // if (focusedElement) {
1766
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1767
+ // await this._unhighlightElements(focusedElement);
1768
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1769
+ // console.log(`Unhighlighted previous element`);
1770
+ // }
1771
+ if (focusedElement) {
1772
+ await this._highlightElements(focusedElement);
1773
+ }
1746
1774
  if (this.context.browserName === "chromium") {
1747
1775
  const client = await playContext.newCDPSession(this.page);
1748
1776
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1764,6 +1792,10 @@ class StableBrowser {
1764
1792
  else {
1765
1793
  screenshotBuffer = await this.page.screenshot();
1766
1794
  }
1795
+ if (focusedElement) {
1796
+ // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1797
+ await this._unhighlightElements(focusedElement);
1798
+ }
1767
1799
  let image = await Jimp.read(screenshotBuffer);
1768
1800
  // Get the image dimensions
1769
1801
  const { width, height } = image.bitmap;
@@ -1790,7 +1822,7 @@ class StableBrowser {
1790
1822
  };
1791
1823
  await new Promise((resolve) => setTimeout(resolve, 2000));
1792
1824
  try {
1793
- await _preCommand(state, this);
1825
+ await _preCommand(state, this, world);
1794
1826
  await expect(state.element).toHaveCount(1, { timeout: 10000 });
1795
1827
  return state.info;
1796
1828
  }
@@ -1816,7 +1848,7 @@ class StableBrowser {
1816
1848
  };
1817
1849
  await new Promise((resolve) => setTimeout(resolve, 2000));
1818
1850
  try {
1819
- await _preCommand(state, this);
1851
+ await _preCommand(state, this, world);
1820
1852
  switch (attribute) {
1821
1853
  case "inner_text":
1822
1854
  state.value = await state.element.innerText();
@@ -1834,6 +1866,70 @@ class StableBrowser {
1834
1866
  state.info.value = state.value;
1835
1867
  this.setTestData({ [variable]: state.value }, world);
1836
1868
  this.logger.info("set test data: " + variable + "=" + state.value);
1869
+ await new Promise((resolve) => setTimeout(resolve, 500));
1870
+ return state.info;
1871
+ }
1872
+ catch (e) {
1873
+ await _commandError(state, e, this);
1874
+ }
1875
+ finally {
1876
+ _commandFinally(state, this);
1877
+ }
1878
+ }
1879
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1880
+ const state = {
1881
+ selectors,
1882
+ _params,
1883
+ attribute,
1884
+ value,
1885
+ options,
1886
+ world,
1887
+ type: Types.VERIFY_ATTRIBUTE,
1888
+ highlight: true,
1889
+ screenshot: true,
1890
+ text: `Verify element attribute`,
1891
+ operation: "verifyAttribute",
1892
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1893
+ allowDisabled: true,
1894
+ };
1895
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1896
+ let val;
1897
+ try {
1898
+ await _preCommand(state, this, world);
1899
+ switch (attribute) {
1900
+ case "innerText":
1901
+ val = String(await state.element.innerText());
1902
+ break;
1903
+ case "value":
1904
+ val = String(await state.element.inputValue());
1905
+ break;
1906
+ case "checked":
1907
+ val = String(await state.element.isChecked());
1908
+ break;
1909
+ case "disabled":
1910
+ val = String(await state.element.isDisabled());
1911
+ break;
1912
+ case "readOnly":
1913
+ const isEditable = await state.element.isEditable();
1914
+ val = String(!isEditable);
1915
+ break;
1916
+ default:
1917
+ val = String(await state.element.getAttribute(attribute));
1918
+ break;
1919
+ }
1920
+ state.info.expectedValue = val;
1921
+ let regex;
1922
+ if (value.startsWith("/") && value.endsWith("/")) {
1923
+ const patternBody = value.slice(1, -1);
1924
+ regex = new RegExp(patternBody, "g");
1925
+ }
1926
+ else {
1927
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1928
+ regex = new RegExp(escapedPattern, "g");
1929
+ }
1930
+ if (!val.match(regex)) {
1931
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1932
+ }
1837
1933
  return state.info;
1838
1934
  }
1839
1935
  catch (e) {
@@ -1861,7 +1957,7 @@ class StableBrowser {
1861
1957
  if (options && options.timeout) {
1862
1958
  timeout = options.timeout;
1863
1959
  }
1864
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1960
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1865
1961
  const request = {
1866
1962
  method: "POST",
1867
1963
  url: serviceUrl,
@@ -1932,27 +2028,32 @@ class StableBrowser {
1932
2028
  async _highlightElements(scope, css) {
1933
2029
  try {
1934
2030
  if (!scope) {
2031
+ // console.log(`Scope is not defined`);
1935
2032
  return;
1936
2033
  }
1937
2034
  if (!css) {
1938
2035
  scope
1939
2036
  .evaluate((node) => {
1940
2037
  if (node && node.style) {
1941
- let originalBorder = node.style.border;
1942
- node.style.border = "2px solid red";
1943
- if (window) {
1944
- window.addEventListener("beforeunload", function (e) {
1945
- node.style.border = originalBorder;
1946
- });
1947
- }
1948
- setTimeout(function () {
1949
- node.style.border = originalBorder;
1950
- }, 2000);
2038
+ let originalOutline = node.style.outline;
2039
+ // console.log(`Original outline was: ${originalOutline}`);
2040
+ node.__previousOutline = originalOutline;
2041
+ node.style.outline = "2px solid red";
2042
+ // console.log(`New outline is: ${node.style.outline}`);
2043
+ // if (window) {
2044
+ // window.addEventListener("beforeunload", function (e) {
2045
+ // node.style.outline = originalBorder;
2046
+ // });
2047
+ // }
2048
+ // setTimeout(function () {
2049
+ // node.style.outline = originalBorder;
2050
+ //}, 2000);
1951
2051
  }
1952
2052
  })
1953
2053
  .then(() => { })
1954
2054
  .catch((e) => {
1955
2055
  // ignore
2056
+ // console.error(`Could not highlight node : ${e}`);
1956
2057
  });
1957
2058
  }
1958
2059
  else {
@@ -1968,24 +2069,26 @@ class StableBrowser {
1968
2069
  if (!element.style) {
1969
2070
  return;
1970
2071
  }
1971
- var originalBorder = element.style.border;
2072
+ let originalOutline = element.style.outline;
2073
+ element.__previousOutline = originalOutline;
1972
2074
  // Set the new border to be red and 2px solid
1973
- element.style.border = "2px solid red";
1974
- if (window) {
1975
- window.addEventListener("beforeunload", function (e) {
1976
- element.style.border = originalBorder;
1977
- });
1978
- }
2075
+ element.style.outline = "2px solid red";
2076
+ // if (window) {
2077
+ // window.addEventListener("beforeunload", function (e) {
2078
+ // element.style.outline = originalBorder;
2079
+ // });
2080
+ // }
1979
2081
  // Set a timeout to revert to the original border after 2 seconds
1980
- setTimeout(function () {
1981
- element.style.border = originalBorder;
1982
- }, 2000);
2082
+ // setTimeout(function () {
2083
+ // element.style.outline = originalBorder;
2084
+ // }, 2000);
1983
2085
  }
1984
2086
  return;
1985
2087
  }, [css])
1986
2088
  .then(() => { })
1987
2089
  .catch((e) => {
1988
2090
  // ignore
2091
+ // console.error(`Could not highlight css: ${e}`);
1989
2092
  });
1990
2093
  }
1991
2094
  }
@@ -1993,6 +2096,58 @@ class StableBrowser {
1993
2096
  console.debug(error);
1994
2097
  }
1995
2098
  }
2099
+ async _unhighlightElements(scope, css) {
2100
+ try {
2101
+ if (!scope) {
2102
+ return;
2103
+ }
2104
+ if (!css) {
2105
+ scope
2106
+ .evaluate((node) => {
2107
+ if (node && node.style) {
2108
+ if (!node.__previousOutline) {
2109
+ node.style.outline = "";
2110
+ }
2111
+ else {
2112
+ node.style.outline = node.__previousOutline;
2113
+ }
2114
+ }
2115
+ })
2116
+ .then(() => { })
2117
+ .catch((e) => {
2118
+ // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2119
+ });
2120
+ }
2121
+ else {
2122
+ scope
2123
+ .evaluate(([css]) => {
2124
+ if (!css) {
2125
+ return;
2126
+ }
2127
+ let elements = Array.from(document.querySelectorAll(css));
2128
+ for (i = 0; i < elements.length; i++) {
2129
+ let element = elements[i];
2130
+ if (!element.style) {
2131
+ return;
2132
+ }
2133
+ if (!element.__previousOutline) {
2134
+ element.style.outline = "";
2135
+ }
2136
+ else {
2137
+ element.style.outline = element.__previousOutline;
2138
+ }
2139
+ }
2140
+ })
2141
+ .then(() => { })
2142
+ .catch((e) => {
2143
+ // console.error(`Error while unhighlighting element in css: ${e}`);
2144
+ });
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",
@@ -2078,34 +2262,10 @@ class StableBrowser {
2078
2262
  let dateAlternatives = findDateAlternatives(text);
2079
2263
  let numberAlternatives = findNumberAlternatives(text);
2080
2264
  try {
2081
- await _preCommand(state, this);
2265
+ await _preCommand(state, this, world);
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,15 +2275,32 @@ 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}"]`;
2119
- await this._highlightElements(frame, dataAttribute);
2120
- const element = await frame.$(dataAttribute);
2278
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2279
+ if (world && world.screenshot && !world.screenshotPath) {
2280
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2281
+ this._highlightElements(frame, dataAttribute).then(async () => {
2282
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2283
+ this._unhighlightElements(frame, dataAttribute)
2284
+ .then(async () => {
2285
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2286
+ })
2287
+ .catch((e) => { }
2288
+ // console.error(e)
2289
+ );
2290
+ });
2291
+ }
2292
+ const element = await frame.locator(dataAttribute).first();
2293
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2294
+ // await this._unhighlightElements(frame, dataAttribute);
2121
2295
  if (element) {
2122
2296
  await this.scrollIfNeeded(element, state.info);
2123
2297
  await element.dispatchEvent("bvt_verify_page_contains_text");
2298
+ await _screenshot(state, this, element);
2124
2299
  }
2125
2300
  }
2126
- await _screenshot(state, this);
2301
+ else {
2302
+ await _screenshot(state, this);
2303
+ }
2127
2304
  return state.info;
2128
2305
  }
2129
2306
  // await expect(element).toHaveCount(1, { timeout: 10000 });
@@ -2159,34 +2336,10 @@ class StableBrowser {
2159
2336
  let dateAlternatives = findDateAlternatives(text);
2160
2337
  let numberAlternatives = findNumberAlternatives(text);
2161
2338
  try {
2162
- await _preCommand(state, this);
2339
+ await _preCommand(state, this, world);
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,103 @@ 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, world);
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
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2421
+ if (world && world.screenshot && !world.screenshotPath) {
2422
+ // console.log(`Highlighting for vtrt while running from recorder`);
2423
+ this._highlightElements(frame, dataAttribute)
2424
+ .then(async () => {
2425
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2426
+ this._unhighlightElements(frame, dataAttribute).then(() => { }
2427
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2428
+ );
2429
+ })
2430
+ .catch(e);
2431
+ }
2432
+ //await this._highlightElements(frame, cssAnchor);
2433
+ const element = await frame.locator(dataAttribute).first();
2434
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2435
+ // await this._unhighlightElements(frame, dataAttribute);
2436
+ if (element) {
2437
+ await this.scrollIfNeeded(element, state.info);
2438
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2439
+ await _screenshot(state, this, element);
2440
+ }
2441
+ else {
2442
+ await _screenshot(state, this);
2443
+ }
2444
+ return state.info;
2445
+ }
2446
+ }
2447
+ }
2448
+ }
2449
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2211
2450
  }
2212
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2213
- serviceUrl = "https://stage.api.blinq.io";
2451
+ catch (e) {
2452
+ await _commandError(state, e, this);
2453
+ }
2454
+ finally {
2455
+ _commandFinally(state, this);
2214
2456
  }
2215
- return serviceUrl;
2216
2457
  }
2217
2458
  async visualVerification(text, options = {}, world = null) {
2218
2459
  const startTime = Date.now();
@@ -2228,7 +2469,7 @@ class StableBrowser {
2228
2469
  throw new Error("TOKEN is not set");
2229
2470
  }
2230
2471
  try {
2231
- let serviceUrl = this._getServerUrl();
2472
+ let serviceUrl = _getServerUrl();
2232
2473
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2233
2474
  info.screenshotPath = screenshotPath;
2234
2475
  const screenshot = await this.takeScreenshot();
@@ -2317,18 +2558,19 @@ class StableBrowser {
2317
2558
  let screenshotPath = null;
2318
2559
  const info = {};
2319
2560
  info.log = "";
2561
+ info.locatorLog = new LocatorLog(selectors);
2320
2562
  info.operation = "getTableData";
2321
2563
  info.selectors = selectors;
2322
2564
  try {
2323
2565
  let table = await this._locate(selectors, info, _params);
2324
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2566
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info, table));
2325
2567
  const tableData = await getTableData(this.page, table);
2326
2568
  return tableData;
2327
2569
  }
2328
2570
  catch (e) {
2329
2571
  this.logger.error("getTableData failed " + info.log);
2330
2572
  this.logger.error(e);
2331
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2573
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info, table));
2332
2574
  info.screenshotPath = screenshotPath;
2333
2575
  Object.assign(e, { info: info });
2334
2576
  error = e;
@@ -2392,7 +2634,7 @@ class StableBrowser {
2392
2634
  info.operation = "analyzeTable";
2393
2635
  info.selectors = selectors;
2394
2636
  info.query = query;
2395
- query = this._fixUsingParams(query, _params);
2637
+ query = _fixUsingParams(query, _params);
2396
2638
  info.query_fixed = query;
2397
2639
  info.operator = operator;
2398
2640
  info.value = value;
@@ -2609,7 +2851,7 @@ class StableBrowser {
2609
2851
  throwError: false,
2610
2852
  };
2611
2853
  try {
2612
- await _preCommand(state, this);
2854
+ await _preCommand(state, this, world);
2613
2855
  await this.page.close();
2614
2856
  }
2615
2857
  catch (e) {
@@ -2620,6 +2862,11 @@ class StableBrowser {
2620
2862
  _commandFinally(state, this);
2621
2863
  }
2622
2864
  }
2865
+ saveTestDataAsGlobal(options, world) {
2866
+ const dataFile = this._getDataFile(world);
2867
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2868
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2869
+ }
2623
2870
  async setViewportSize(width, hight, options = {}, world = null) {
2624
2871
  const startTime = Date.now();
2625
2872
  let error = null;
@@ -2717,19 +2964,34 @@ class StableBrowser {
2717
2964
  }
2718
2965
  }
2719
2966
  async beforeStep(world, step) {
2720
- this.stepName = step.pickleStep.text;
2721
- this.logger.info("step: " + this.stepName);
2722
2967
  if (this.stepIndex === undefined) {
2723
2968
  this.stepIndex = 0;
2724
2969
  }
2725
2970
  else {
2726
2971
  this.stepIndex++;
2727
2972
  }
2973
+ if (step && step.pickleStep && step.pickleStep.text) {
2974
+ this.stepName = step.pickleStep.text;
2975
+ this.logger.info("step: " + this.stepName);
2976
+ }
2977
+ else if (step && step.text) {
2978
+ this.stepName = step.text;
2979
+ }
2980
+ else {
2981
+ this.stepName = "step " + this.stepIndex;
2982
+ }
2728
2983
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2729
2984
  if (this.context.browserObject.context) {
2730
2985
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2731
2986
  }
2732
2987
  }
2988
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2989
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2990
+ // check if @global_test_data tag is present
2991
+ if (this.tags.includes("@global_test_data")) {
2992
+ this.saveTestDataAsGlobal({}, world);
2993
+ }
2994
+ }
2733
2995
  }
2734
2996
  async afterStep(world, step) {
2735
2997
  this.stepName = null;
@@ -2747,156 +3009,5 @@ function createTimedPromise(promise, label) {
2747
3009
  .then((result) => ({ status: "fulfilled", label, result }))
2748
3010
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2749
3011
  }
2750
- const KEYBOARD_EVENTS = [
2751
- "ALT",
2752
- "AltGraph",
2753
- "CapsLock",
2754
- "Control",
2755
- "Fn",
2756
- "FnLock",
2757
- "Hyper",
2758
- "Meta",
2759
- "NumLock",
2760
- "ScrollLock",
2761
- "Shift",
2762
- "Super",
2763
- "Symbol",
2764
- "SymbolLock",
2765
- "Enter",
2766
- "Tab",
2767
- "ArrowDown",
2768
- "ArrowLeft",
2769
- "ArrowRight",
2770
- "ArrowUp",
2771
- "End",
2772
- "Home",
2773
- "PageDown",
2774
- "PageUp",
2775
- "Backspace",
2776
- "Clear",
2777
- "Copy",
2778
- "CrSel",
2779
- "Cut",
2780
- "Delete",
2781
- "EraseEof",
2782
- "ExSel",
2783
- "Insert",
2784
- "Paste",
2785
- "Redo",
2786
- "Undo",
2787
- "Accept",
2788
- "Again",
2789
- "Attn",
2790
- "Cancel",
2791
- "ContextMenu",
2792
- "Escape",
2793
- "Execute",
2794
- "Find",
2795
- "Finish",
2796
- "Help",
2797
- "Pause",
2798
- "Play",
2799
- "Props",
2800
- "Select",
2801
- "ZoomIn",
2802
- "ZoomOut",
2803
- "BrightnessDown",
2804
- "BrightnessUp",
2805
- "Eject",
2806
- "LogOff",
2807
- "Power",
2808
- "PowerOff",
2809
- "PrintScreen",
2810
- "Hibernate",
2811
- "Standby",
2812
- "WakeUp",
2813
- "AllCandidates",
2814
- "Alphanumeric",
2815
- "CodeInput",
2816
- "Compose",
2817
- "Convert",
2818
- "Dead",
2819
- "FinalMode",
2820
- "GroupFirst",
2821
- "GroupLast",
2822
- "GroupNext",
2823
- "GroupPrevious",
2824
- "ModeChange",
2825
- "NextCandidate",
2826
- "NonConvert",
2827
- "PreviousCandidate",
2828
- "Process",
2829
- "SingleCandidate",
2830
- "HangulMode",
2831
- "HanjaMode",
2832
- "JunjaMode",
2833
- "Eisu",
2834
- "Hankaku",
2835
- "Hiragana",
2836
- "HiraganaKatakana",
2837
- "KanaMode",
2838
- "KanjiMode",
2839
- "Katakana",
2840
- "Romaji",
2841
- "Zenkaku",
2842
- "ZenkakuHanaku",
2843
- "F1",
2844
- "F2",
2845
- "F3",
2846
- "F4",
2847
- "F5",
2848
- "F6",
2849
- "F7",
2850
- "F8",
2851
- "F9",
2852
- "F10",
2853
- "F11",
2854
- "F12",
2855
- "Soft1",
2856
- "Soft2",
2857
- "Soft3",
2858
- "Soft4",
2859
- "ChannelDown",
2860
- "ChannelUp",
2861
- "Close",
2862
- "MailForward",
2863
- "MailReply",
2864
- "MailSend",
2865
- "MediaFastForward",
2866
- "MediaPause",
2867
- "MediaPlay",
2868
- "MediaPlayPause",
2869
- "MediaRecord",
2870
- "MediaRewind",
2871
- "MediaStop",
2872
- "MediaTrackNext",
2873
- "MediaTrackPrevious",
2874
- "AudioBalanceLeft",
2875
- "AudioBalanceRight",
2876
- "AudioBassBoostDown",
2877
- "AudioBassBoostToggle",
2878
- "AudioBassBoostUp",
2879
- "AudioFaderFront",
2880
- "AudioFaderRear",
2881
- "AudioSurroundModeNext",
2882
- "AudioTrebleDown",
2883
- "AudioTrebleUp",
2884
- "AudioVolumeDown",
2885
- "AudioVolumeMute",
2886
- "AudioVolumeUp",
2887
- "MicrophoneToggle",
2888
- "MicrophoneVolumeDown",
2889
- "MicrophoneVolumeMute",
2890
- "MicrophoneVolumeUp",
2891
- "TV",
2892
- "TV3DMode",
2893
- "TVAntennaCable",
2894
- "TVAudioDescription",
2895
- ];
2896
- function unEscapeString(str) {
2897
- const placeholder = "__NEWLINE__";
2898
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2899
- return str;
2900
- }
2901
3012
  export { StableBrowser };
2902
3013
  //# sourceMappingURL=stable_browser.js.map