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