automation_model 1.0.517-dev → 1.0.517-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 +37 -3
  33. package/lib/network.js.map +1 -1
  34. package/lib/stable_browser.d.ts +55 -19
  35. package/lib/stable_browser.js +633 -528
  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 = [];
@@ -229,7 +218,7 @@ class StableBrowser {
229
218
  this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
230
219
  }
231
220
  catch (error) {
232
- console.error("Error in request listener", error);
221
+ // console.error("Error in request listener", error);
233
222
  context.networkLogger.push({
234
223
  error: "not able to listen",
235
224
  message: error.message,
@@ -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);
@@ -501,24 +400,24 @@ class StableBrowser {
501
400
  info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
502
401
  return;
503
402
  }
504
- locator = this._getLocator({ css: locatorString }, scope, _params);
403
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
505
404
  }
506
405
  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);
406
+ let text = _fixUsingParams(locatorSearch.text, _params);
407
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
509
408
  if (result.elementCount === 0) {
510
409
  info.failCause.textNotFound = true;
511
410
  info.failCause.lastError = "failed to locate element by text: " + text;
512
411
  return;
513
412
  }
514
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
413
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
515
414
  if (locatorSearch.childCss) {
516
415
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
517
416
  }
518
- locator = this._getLocator(locatorSearch, scope, _params);
417
+ locator = await this._getLocator(locatorSearch, scope, _params);
519
418
  }
520
419
  else {
521
- locator = this._getLocator(locatorSearch, scope, _params);
420
+ locator = await this._getLocator(locatorSearch, scope, _params);
522
421
  }
523
422
  // let cssHref = false;
524
423
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -531,18 +430,27 @@ class StableBrowser {
531
430
  //info.log += "total elements found " + count + "\n";
532
431
  //let visibleCount = 0;
533
432
  let visibleLocator = null;
534
- if (locatorSearch.index && locatorSearch.index < count) {
433
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
535
434
  foundLocators.push(locator.nth(locatorSearch.index));
435
+ if (info.locatorLog) {
436
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
437
+ }
536
438
  return;
537
439
  }
440
+ if (info.locatorLog && count === 0) {
441
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
442
+ }
538
443
  for (let j = 0; j < count; j++) {
539
444
  let visible = await locator.nth(j).isVisible();
540
445
  const enabled = await locator.nth(j).isEnabled();
541
446
  if (!visibleOnly) {
542
447
  visible = true;
543
448
  }
544
- if (visible && enabled) {
449
+ if (visible && (allowDisabled || enabled)) {
545
450
  foundLocators.push(locator.nth(j));
451
+ if (info.locatorLog) {
452
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
453
+ }
546
454
  }
547
455
  else {
548
456
  info.failCause.visible = visible;
@@ -550,8 +458,16 @@ class StableBrowser {
550
458
  if (!info.printMessages) {
551
459
  info.printMessages = {};
552
460
  }
461
+ if (info.locatorLog && !visible) {
462
+ info.failCause.lastError = "element " + originalLocatorSearch + " is not visible";
463
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
464
+ }
465
+ if (info.locatorLog && !enabled) {
466
+ info.failCause.lastError = "element " + originalLocatorSearch + " is disabled, ";
467
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
468
+ }
553
469
  if (!info.printMessages[j.toString()]) {
554
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
470
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
555
471
  info.printMessages[j.toString()] = true;
556
472
  }
557
473
  }
@@ -567,7 +483,7 @@ class StableBrowser {
567
483
  if (!info) {
568
484
  info = {};
569
485
  }
570
- info.log += "scan for popup handlers" + "\n";
486
+ //info.log += "scan for popup handlers" + "\n";
571
487
  const handlerGroup = [];
572
488
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
573
489
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -594,16 +510,28 @@ class StableBrowser {
594
510
  }
595
511
  if (result.foundElements.length > 0) {
596
512
  let dialogCloseLocator = result.foundElements[0].locator;
597
- await dialogCloseLocator.click();
598
- // wait for the dialog to close
599
- await dialogCloseLocator.waitFor({ state: "hidden" });
513
+ try {
514
+ await scope?.evaluate(() => {
515
+ window.__isClosingPopups = true;
516
+ });
517
+ await dialogCloseLocator.click();
518
+ // wait for the dialog to close
519
+ await dialogCloseLocator.waitFor({ state: "hidden" });
520
+ }
521
+ catch (e) {
522
+ }
523
+ finally {
524
+ await scope?.evaluate(() => {
525
+ window.__isClosingPopups = false;
526
+ });
527
+ }
600
528
  return { rerun: true };
601
529
  }
602
530
  }
603
531
  }
604
532
  return { rerun: false };
605
533
  }
606
- async _locate(selectors, info, _params, timeout) {
534
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
607
535
  if (!timeout) {
608
536
  timeout = 30000;
609
537
  }
@@ -613,7 +541,7 @@ class StableBrowser {
613
541
  let selector = selectors.locators[j];
614
542
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
615
543
  }
616
- let element = await this._locate_internal(selectors, info, _params, timeout);
544
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
617
545
  if (!element.rerun) {
618
546
  return element;
619
547
  }
@@ -626,6 +554,7 @@ class StableBrowser {
626
554
  info.failCause = {};
627
555
  info.log = "";
628
556
  }
557
+ let startTime = Date.now();
629
558
  let scope = this.page;
630
559
  if (selectors.frame) {
631
560
  return selectors.frame;
@@ -656,9 +585,11 @@ class StableBrowser {
656
585
  }
657
586
  return framescope;
658
587
  };
588
+ let fLocator = null;
659
589
  while (true) {
660
590
  let frameFound = false;
661
591
  if (selectors.nestFrmLoc) {
592
+ fLocator = selectors.nestFrmLoc;
662
593
  scope = await findFrame(selectors.nestFrmLoc, scope);
663
594
  frameFound = true;
664
595
  break;
@@ -667,6 +598,7 @@ class StableBrowser {
667
598
  for (let i = 0; i < selectors.frameLocators.length; i++) {
668
599
  let frameLocator = selectors.frameLocators[i];
669
600
  if (frameLocator.css) {
601
+ fLocator = frameLocator.css;
670
602
  scope = scope.frameLocator(frameLocator.css);
671
603
  frameFound = true;
672
604
  break;
@@ -674,11 +606,15 @@ class StableBrowser {
674
606
  }
675
607
  }
676
608
  if (!frameFound && selectors.iframe_src) {
609
+ fLocator = selectors.iframe_src;
677
610
  scope = this.page.frame({ url: selectors.iframe_src });
678
611
  }
679
612
  if (!scope) {
680
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
681
- if (performance.now() - startTime > timeout) {
613
+ if (info && info.locatorLog) {
614
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
615
+ }
616
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
617
+ if (Date.now() - startTime > timeout) {
682
618
  info.failCause.iframeNotFound = true;
683
619
  info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
684
620
  throw new Error("unable to locate iframe " + selectors.iframe_src);
@@ -686,6 +622,9 @@ class StableBrowser {
686
622
  await new Promise((resolve) => setTimeout(resolve, 1000));
687
623
  }
688
624
  else {
625
+ if (info && info.locatorLog) {
626
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
627
+ }
689
628
  break;
690
629
  }
691
630
  }
@@ -702,16 +641,18 @@ class StableBrowser {
702
641
  return bodyContent;
703
642
  });
704
643
  }
705
- async _locate_internal(selectors, info, _params, timeout = 30000) {
644
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
706
645
  if (!info) {
707
646
  info = {};
708
647
  info.failCause = {};
709
648
  info.log = "";
649
+ info.locatorLog = new LocatorLog(selectors);
710
650
  }
711
651
  let highPriorityTimeout = 5000;
712
652
  let visibleOnlyTimeout = 6000;
713
- let startTime = performance.now();
653
+ let startTime = Date.now();
714
654
  let locatorsCount = 0;
655
+ let lazy_scroll = false;
715
656
  //let arrayMode = Array.isArray(selectors);
716
657
  let scope = await this._findFrameScope(selectors, timeout, info);
717
658
  let selectorsLocators = null;
@@ -749,17 +690,17 @@ class StableBrowser {
749
690
  }
750
691
  // info.log += "scanning locators in priority 1" + "\n";
751
692
  let onlyPriority3 = selectorsLocators[0].priority === 3;
752
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
693
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled);
753
694
  if (result.foundElements.length === 0) {
754
695
  // info.log += "scanning locators in priority 2" + "\n";
755
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
696
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled);
756
697
  }
757
698
  if (result.foundElements.length === 0 && onlyPriority3) {
758
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
699
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
759
700
  }
760
701
  else {
761
702
  if (result.foundElements.length === 0 && !highPriorityOnly) {
762
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
703
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
763
704
  }
764
705
  }
765
706
  let foundElements = result.foundElements;
@@ -800,26 +741,38 @@ class StableBrowser {
800
741
  return maxCountElement.locator;
801
742
  }
802
743
  }
803
- if (performance.now() - startTime > timeout) {
744
+ if (Date.now() - startTime > timeout) {
804
745
  break;
805
746
  }
806
- if (performance.now() - startTime > highPriorityTimeout) {
807
- info.log += "high priority timeout, will try all elements" + "\n";
747
+ if (Date.now() - startTime > highPriorityTimeout) {
748
+ //info.log += "high priority timeout, will try all elements" + "\n";
808
749
  highPriorityOnly = false;
750
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
751
+ lazy_scroll = true;
752
+ await scrollPageToLoadLazyElements(this.page);
753
+ }
809
754
  }
810
- if (performance.now() - startTime > visibleOnlyTimeout) {
811
- info.log += "visible only timeout, will try all elements" + "\n";
755
+ if (Date.now() - startTime > visibleOnlyTimeout) {
756
+ //info.log += "visible only timeout, will try all elements" + "\n";
812
757
  visibleOnly = false;
813
758
  }
814
759
  await new Promise((resolve) => setTimeout(resolve, 1000));
815
760
  }
816
761
  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";
762
+ // if (info.locatorLog) {
763
+ // const lines = info.locatorLog.toString().split("\n");
764
+ // for (let line of lines) {
765
+ // this.logger.debug(line);
766
+ // }
767
+ // }
768
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
818
769
  info.failCause.locatorNotFound = true;
819
- info.failCause.lastError = "failed to locate unique element";
770
+ if (!info?.failCause?.lastError) {
771
+ info.failCause.lastError = "failed to locate unique element";
772
+ }
820
773
  throw new Error("failed to locate first element no elements found, " + info.log);
821
774
  }
822
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
775
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false) {
823
776
  let foundElements = [];
824
777
  const result = {
825
778
  foundElements: foundElements,
@@ -827,14 +780,15 @@ class StableBrowser {
827
780
  for (let i = 0; i < locatorsGroup.length; i++) {
828
781
  let foundLocators = [];
829
782
  try {
830
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
783
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled);
831
784
  }
832
785
  catch (e) {
833
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
834
- this.logger.debug(e);
786
+ // this call can fail it the browser is navigating
787
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
788
+ // this.logger.debug(e);
835
789
  foundLocators = [];
836
790
  try {
837
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
791
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled);
838
792
  }
839
793
  catch (e) {
840
794
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -850,11 +804,27 @@ class StableBrowser {
850
804
  }
851
805
  if (foundLocators.length > 1) {
852
806
  info.failCause.foundMultiple = true;
807
+ if (info.locatorLog) {
808
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
809
+ }
853
810
  }
854
811
  }
855
812
  return result;
856
813
  }
857
814
  async simpleClick(elementDescription, _params, options = {}, world = null) {
815
+ const state = {
816
+ locate: false,
817
+ scroll: false,
818
+ highlight: false,
819
+ _params,
820
+ options,
821
+ world,
822
+ type: Types.CLICK,
823
+ text: "Click element",
824
+ operation: "simpleClick",
825
+ log: "***** click on " + elementDescription + " *****\n",
826
+ };
827
+ _preCommand(state, this, world);
858
828
  const startTime = Date.now();
859
829
  let timeout = 30000;
860
830
  if (options && options.timeout) {
@@ -879,13 +849,31 @@ class StableBrowser {
879
849
  catch (e) {
880
850
  if (performance.now() - startTime > timeout) {
881
851
  // throw e;
882
- await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
852
+ try {
853
+ await _commandError(state, "timeout looking for " + elementDescription, this);
854
+ }
855
+ finally {
856
+ _commandFinally(state, this);
857
+ }
883
858
  }
884
859
  }
885
860
  await new Promise((resolve) => setTimeout(resolve, 3000));
886
861
  }
887
862
  }
888
863
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
864
+ const state = {
865
+ locate: false,
866
+ scroll: false,
867
+ highlight: false,
868
+ _params,
869
+ options,
870
+ world,
871
+ type: Types.FILL,
872
+ text: "Fill element",
873
+ operation: "simpleClickType",
874
+ log: "***** click type on " + elementDescription + " *****\n",
875
+ };
876
+ _preCommand(state, this, world);
889
877
  const startTime = Date.now();
890
878
  let timeout = 30000;
891
879
  if (options && options.timeout) {
@@ -910,7 +898,12 @@ class StableBrowser {
910
898
  catch (e) {
911
899
  if (performance.now() - startTime > timeout) {
912
900
  // throw e;
913
- await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
901
+ try {
902
+ await _commandError(state, "timeout looking for " + elementDescription, this);
903
+ }
904
+ finally {
905
+ _commandFinally(state, this);
906
+ }
914
907
  }
915
908
  }
916
909
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -928,19 +921,19 @@ class StableBrowser {
928
921
  log: "***** click on " + selectors.element_name + " *****\n",
929
922
  };
930
923
  try {
931
- await _preCommand(state, this);
932
- if (state.options && state.options.context) {
933
- state.selectors.locators[0].text = state.options.context;
934
- }
924
+ await _preCommand(state, this, world);
925
+ // if (state.options && state.options.context) {
926
+ // state.selectors.locators[0].text = state.options.context;
927
+ // }
935
928
  try {
936
929
  await state.element.click();
937
- await new Promise((resolve) => setTimeout(resolve, 1000));
930
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
938
931
  }
939
932
  catch (e) {
940
933
  // await this.closeUnexpectedPopups();
941
934
  state.element = await this._locate(selectors, state.info, _params);
942
935
  await state.element.dispatchEvent("click");
943
- await new Promise((resolve) => setTimeout(resolve, 1000));
936
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
944
937
  }
945
938
  await this.waitForPageLoad();
946
939
  return state.info;
@@ -964,14 +957,20 @@ class StableBrowser {
964
957
  log: "***** check " + selectors.element_name + " *****\n",
965
958
  };
966
959
  try {
967
- await _preCommand(state, this);
960
+ await _preCommand(state, this, world);
968
961
  state.info.checked = checked;
969
962
  // let element = await this._locate(selectors, info, _params);
970
963
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
971
964
  try {
972
- // await this._highlightElements(element);
965
+ if (world && world.screenshot && !world.screenshotPath) {
966
+ // console.log(`Highlighting while running from recorder`);
967
+ await this._highlightElements(element);
968
+ await new Promise((resolve) => setTimeout(resolve, 100));
969
+ await this._unHighlightElements(element);
970
+ }
973
971
  await state.element.setChecked(checked);
974
- await new Promise((resolve) => setTimeout(resolve, 1000));
972
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
973
+ // await this._unHighlightElements(element);
975
974
  }
976
975
  catch (e) {
977
976
  if (e.message && e.message.includes("did not change its state")) {
@@ -1007,9 +1006,10 @@ class StableBrowser {
1007
1006
  log: "***** hover " + selectors.element_name + " *****\n",
1008
1007
  };
1009
1008
  try {
1010
- await _preCommand(state, this);
1009
+ await _preCommand(state, this, world);
1011
1010
  try {
1012
1011
  await state.element.hover();
1012
+ await _screenshot(state, this);
1013
1013
  await new Promise((resolve) => setTimeout(resolve, 1000));
1014
1014
  }
1015
1015
  catch (e) {
@@ -1017,9 +1017,10 @@ class StableBrowser {
1017
1017
  state.info.log += "hover failed, will try again" + "\n";
1018
1018
  state.element = await this._locate(selectors, state.info, _params);
1019
1019
  await state.element.hover({ timeout: 10000 });
1020
+ await _screenshot(state, this);
1020
1021
  await new Promise((resolve) => setTimeout(resolve, 1000));
1021
1022
  }
1022
- await _screenshot(state, this);
1023
+ // await _screenshot(state, this);
1023
1024
  await this.waitForPageLoad();
1024
1025
  return state.info;
1025
1026
  }
@@ -1046,7 +1047,7 @@ class StableBrowser {
1046
1047
  log: "***** select option " + selectors.element_name + " *****\n",
1047
1048
  };
1048
1049
  try {
1049
- await _preCommand(state, this);
1050
+ await _preCommand(state, this, world);
1050
1051
  try {
1051
1052
  await state.element.selectOption(values);
1052
1053
  }
@@ -1080,7 +1081,7 @@ class StableBrowser {
1080
1081
  log: "",
1081
1082
  };
1082
1083
  try {
1083
- await _preCommand(state, this);
1084
+ await _preCommand(state, this, world);
1084
1085
  const valueSegment = state.value.split("&&");
1085
1086
  for (let i = 0; i < valueSegment.length; i++) {
1086
1087
  if (i > 0) {
@@ -1123,7 +1124,7 @@ class StableBrowser {
1123
1124
  log: "***** set input value " + selectors.element_name + " *****\n",
1124
1125
  };
1125
1126
  try {
1126
- await _preCommand(state, this);
1127
+ await _preCommand(state, this, world);
1127
1128
  let value = await this._replaceWithLocalData(state.value, this);
1128
1129
  try {
1129
1130
  await state.element.evaluateHandle((el, value) => {
@@ -1160,7 +1161,7 @@ class StableBrowser {
1160
1161
  throwError: false,
1161
1162
  };
1162
1163
  try {
1163
- await _preCommand(state, this);
1164
+ await _preCommand(state, this, world);
1164
1165
  try {
1165
1166
  await state.element.click();
1166
1167
  await new Promise((resolve) => setTimeout(resolve, 500));
@@ -1234,7 +1235,7 @@ class StableBrowser {
1234
1235
  _value = newValue;
1235
1236
  }
1236
1237
  try {
1237
- await _preCommand(state, this);
1238
+ await _preCommand(state, this, world);
1238
1239
  state.info.value = _value;
1239
1240
  if (options === null || options === undefined || !options.press) {
1240
1241
  try {
@@ -1291,7 +1292,12 @@ class StableBrowser {
1291
1292
  await this.waitForPageLoad();
1292
1293
  }
1293
1294
  else if (enter === false) {
1294
- await state.element.dispatchEvent("change");
1295
+ try {
1296
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1297
+ }
1298
+ catch (e) {
1299
+ // ignore
1300
+ }
1295
1301
  //await this.page.keyboard.press("Tab");
1296
1302
  }
1297
1303
  else {
@@ -1322,7 +1328,7 @@ class StableBrowser {
1322
1328
  log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
1323
1329
  };
1324
1330
  try {
1325
- await _preCommand(state, this);
1331
+ await _preCommand(state, this, world);
1326
1332
  await state.element.fill(value);
1327
1333
  await state.element.dispatchEvent("change");
1328
1334
  if (enter) {
@@ -1348,6 +1354,7 @@ class StableBrowser {
1348
1354
  let screenshotPath = null;
1349
1355
  if (!info.log) {
1350
1356
  info.log = "";
1357
+ info.locatorLog = new LocatorLog(selectors);
1351
1358
  }
1352
1359
  info.operation = "getText";
1353
1360
  info.selectors = selectors;
@@ -1367,9 +1374,19 @@ class StableBrowser {
1367
1374
  catch (e) {
1368
1375
  //ignore
1369
1376
  }
1370
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1377
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info, element));
1371
1378
  try {
1372
- await this._highlightElements(element);
1379
+ if (world && world.screenshot && !world.screenshotPath) {
1380
+ // console.log(`Highlighting for get text while running from recorder`);
1381
+ this._highlightElements(element)
1382
+ .then(async () => {
1383
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1384
+ this._unhighlightElements(element).then(() => { }
1385
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
1386
+ );
1387
+ })
1388
+ .catch(e);
1389
+ }
1373
1390
  const elementText = await element.innerText();
1374
1391
  return {
1375
1392
  text: elementText,
@@ -1381,7 +1398,7 @@ class StableBrowser {
1381
1398
  }
1382
1399
  catch (e) {
1383
1400
  //await this.closeUnexpectedPopups();
1384
- this.logger.info("no innerText will use textContent");
1401
+ this.logger.info("no innerText, will use textContent");
1385
1402
  const elementText = await element.textContent();
1386
1403
  return { text: elementText, screenshotId, screenshotPath, value: value };
1387
1404
  }
@@ -1416,13 +1433,13 @@ class StableBrowser {
1416
1433
  }
1417
1434
  let foundObj = null;
1418
1435
  try {
1419
- await _preCommand(state, this);
1436
+ await _preCommand(state, this, world);
1420
1437
  state.info.pattern = pattern;
1421
1438
  foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
1422
1439
  if (foundObj && foundObj.element) {
1423
1440
  await this.scrollIfNeeded(foundObj.element, state.info);
1424
1441
  }
1425
- await _screenshot(state, this);
1442
+ await _screenshot(state, this, foundObj.element);
1426
1443
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1427
1444
  pattern = pattern.replace("{text}", escapedText);
1428
1445
  let regex = new RegExp(pattern, "im");
@@ -1467,12 +1484,12 @@ class StableBrowser {
1467
1484
  }
1468
1485
  let foundObj = null;
1469
1486
  try {
1470
- await _preCommand(state, this);
1487
+ await _preCommand(state, this, world);
1471
1488
  foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1472
1489
  if (foundObj && foundObj.element) {
1473
1490
  await this.scrollIfNeeded(foundObj.element, state.info);
1474
1491
  }
1475
- await _screenshot(state, this);
1492
+ await _screenshot(state, this, foundObj.element);
1476
1493
  const dateAlternatives = findDateAlternatives(text);
1477
1494
  const numberAlternatives = findNumberAlternatives(text);
1478
1495
  if (dateAlternatives.date) {
@@ -1661,7 +1678,7 @@ class StableBrowser {
1661
1678
  }
1662
1679
  return data;
1663
1680
  }
1664
- async _screenShot(options = {}, world = null, info = null) {
1681
+ async _screenShot(options = {}, world = null, info = null, focusedElement = null) {
1665
1682
  // collect url/path/title
1666
1683
  if (info) {
1667
1684
  if (!info.title) {
@@ -1686,13 +1703,11 @@ class StableBrowser {
1686
1703
  if (!fs.existsSync(world.screenshotPath)) {
1687
1704
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1688
1705
  }
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");
1706
+ // to make sure the path doesn't start with -
1707
+ const uuidStr = "id_" + randomUUID();
1708
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1694
1709
  try {
1695
- await this.takeScreenshot(screenshotPath);
1710
+ await this.takeScreenshot(screenshotPath, focusedElement);
1696
1711
  // let buffer = await this.page.screenshot({ timeout: 4000 });
1697
1712
  // // save the buffer to the screenshot path asynchrously
1698
1713
  // fs.writeFile(screenshotPath, buffer, (err) => {
@@ -1700,20 +1715,20 @@ class StableBrowser {
1700
1715
  // this.logger.info("unable to save screenshot " + screenshotPath);
1701
1716
  // }
1702
1717
  // });
1718
+ result.screenshotId = uuidStr;
1719
+ result.screenshotPath = screenshotPath;
1720
+ if (info && info.box) {
1721
+ // await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1722
+ }
1703
1723
  }
1704
1724
  catch (e) {
1705
1725
  this.logger.info("unable to take screenshot, ignored");
1706
1726
  }
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
1727
  }
1713
1728
  else if (options && options.screenshot) {
1714
1729
  result.screenshotPath = options.screenshotPath;
1715
1730
  try {
1716
- await this.takeScreenshot(options.screenshotPath);
1731
+ await this.takeScreenshot(options.screenshotPath, focusedElement);
1717
1732
  // let buffer = await this.page.screenshot({ timeout: 4000 });
1718
1733
  // // save the buffer to the screenshot path asynchrously
1719
1734
  // fs.writeFile(options.screenshotPath, buffer, (err) => {
@@ -1726,12 +1741,12 @@ class StableBrowser {
1726
1741
  this.logger.info("unable to take screenshot, ignored");
1727
1742
  }
1728
1743
  if (info && info.box) {
1729
- await drawRectangle(options.screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1744
+ // await drawRectangle(options.screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1730
1745
  }
1731
1746
  }
1732
1747
  return result;
1733
1748
  }
1734
- async takeScreenshot(screenshotPath) {
1749
+ async takeScreenshot(screenshotPath, focusedElement = null) {
1735
1750
  const playContext = this.context.playContext;
1736
1751
  // Using CDP to capture the screenshot
1737
1752
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
@@ -1743,6 +1758,15 @@ class StableBrowser {
1743
1758
  document.documentElement.clientWidth,
1744
1759
  ])));
1745
1760
  let screenshotBuffer = null;
1761
+ // if (focusedElement) {
1762
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1763
+ // await this._unhighlightElements(focusedElement);
1764
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1765
+ // console.log(`Unhighlighted previous element`);
1766
+ // }
1767
+ if (focusedElement) {
1768
+ await this._highlightElements(focusedElement);
1769
+ }
1746
1770
  if (this.context.browserName === "chromium") {
1747
1771
  const client = await playContext.newCDPSession(this.page);
1748
1772
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1764,6 +1788,10 @@ class StableBrowser {
1764
1788
  else {
1765
1789
  screenshotBuffer = await this.page.screenshot();
1766
1790
  }
1791
+ if (focusedElement) {
1792
+ // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1793
+ await this._unhighlightElements(focusedElement);
1794
+ }
1767
1795
  let image = await Jimp.read(screenshotBuffer);
1768
1796
  // Get the image dimensions
1769
1797
  const { width, height } = image.bitmap;
@@ -1790,7 +1818,7 @@ class StableBrowser {
1790
1818
  };
1791
1819
  await new Promise((resolve) => setTimeout(resolve, 2000));
1792
1820
  try {
1793
- await _preCommand(state, this);
1821
+ await _preCommand(state, this, world);
1794
1822
  await expect(state.element).toHaveCount(1, { timeout: 10000 });
1795
1823
  return state.info;
1796
1824
  }
@@ -1816,7 +1844,7 @@ class StableBrowser {
1816
1844
  };
1817
1845
  await new Promise((resolve) => setTimeout(resolve, 2000));
1818
1846
  try {
1819
- await _preCommand(state, this);
1847
+ await _preCommand(state, this, world);
1820
1848
  switch (attribute) {
1821
1849
  case "inner_text":
1822
1850
  state.value = await state.element.innerText();
@@ -1834,6 +1862,70 @@ class StableBrowser {
1834
1862
  state.info.value = state.value;
1835
1863
  this.setTestData({ [variable]: state.value }, world);
1836
1864
  this.logger.info("set test data: " + variable + "=" + state.value);
1865
+ await new Promise((resolve) => setTimeout(resolve, 500));
1866
+ return state.info;
1867
+ }
1868
+ catch (e) {
1869
+ await _commandError(state, e, this);
1870
+ }
1871
+ finally {
1872
+ _commandFinally(state, this);
1873
+ }
1874
+ }
1875
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1876
+ const state = {
1877
+ selectors,
1878
+ _params,
1879
+ attribute,
1880
+ value,
1881
+ options,
1882
+ world,
1883
+ type: Types.VERIFY_ATTRIBUTE,
1884
+ highlight: true,
1885
+ screenshot: true,
1886
+ text: `Verify element attribute`,
1887
+ operation: "verifyAttribute",
1888
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1889
+ allowDisabled: true,
1890
+ };
1891
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1892
+ let val;
1893
+ try {
1894
+ await _preCommand(state, this, world);
1895
+ switch (attribute) {
1896
+ case "innerText":
1897
+ val = String(await state.element.innerText());
1898
+ break;
1899
+ case "value":
1900
+ val = String(await state.element.inputValue());
1901
+ break;
1902
+ case "checked":
1903
+ val = String(await state.element.isChecked());
1904
+ break;
1905
+ case "disabled":
1906
+ val = String(await state.element.isDisabled());
1907
+ break;
1908
+ case "readOnly":
1909
+ const isEditable = await state.element.isEditable();
1910
+ val = String(!isEditable);
1911
+ break;
1912
+ default:
1913
+ val = String(await state.element.getAttribute(attribute));
1914
+ break;
1915
+ }
1916
+ state.info.expectedValue = val;
1917
+ let regex;
1918
+ if (value.startsWith("/") && value.endsWith("/")) {
1919
+ const patternBody = value.slice(1, -1);
1920
+ regex = new RegExp(patternBody, "g");
1921
+ }
1922
+ else {
1923
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1924
+ regex = new RegExp(escapedPattern, "g");
1925
+ }
1926
+ if (!val.match(regex)) {
1927
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1928
+ }
1837
1929
  return state.info;
1838
1930
  }
1839
1931
  catch (e) {
@@ -1861,7 +1953,7 @@ class StableBrowser {
1861
1953
  if (options && options.timeout) {
1862
1954
  timeout = options.timeout;
1863
1955
  }
1864
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1956
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1865
1957
  const request = {
1866
1958
  method: "POST",
1867
1959
  url: serviceUrl,
@@ -1932,27 +2024,33 @@ class StableBrowser {
1932
2024
  async _highlightElements(scope, css) {
1933
2025
  try {
1934
2026
  if (!scope) {
2027
+ // console.log(`Scope is not defined`);
1935
2028
  return;
1936
2029
  }
1937
2030
  if (!css) {
1938
2031
  scope
1939
2032
  .evaluate((node) => {
1940
2033
  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);
2034
+ let originalOutline = node.style.outline;
2035
+ // console.log(`Original outline was: ${originalOutline}`);
2036
+ node.__previousOutline = originalOutline;
2037
+ node.style.outline = "2px solid red";
2038
+ // console.log(`New outline is: ${node.style.outline}`);
2039
+ // if (window) {
2040
+ // window.addEventListener("beforeunload", function (e) {
2041
+ // node.style.outline = originalBorder;
2042
+ // });
2043
+ // }
2044
+ // setTimeout(function () {
2045
+ // node.style.outline = originalBorder;
2046
+ //}, 2000);
1951
2047
  }
1952
2048
  })
1953
- .then(() => { })
2049
+ .then(() => {
2050
+ })
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,27 @@ 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
- .then(() => { })
2085
+ .then(() => {
2086
+ })
1987
2087
  .catch((e) => {
1988
2088
  // ignore
2089
+ // console.error(`Could not highlight css: ${e}`);
1989
2090
  });
1990
2091
  }
1991
2092
  }
@@ -1993,6 +2094,60 @@ class StableBrowser {
1993
2094
  console.debug(error);
1994
2095
  }
1995
2096
  }
2097
+ async _unhighlightElements(scope, css) {
2098
+ try {
2099
+ if (!scope) {
2100
+ return;
2101
+ }
2102
+ if (!css) {
2103
+ scope
2104
+ .evaluate((node) => {
2105
+ if (node && node.style) {
2106
+ if (!node.__previousOutline) {
2107
+ node.style.outline = "";
2108
+ }
2109
+ else {
2110
+ node.style.outline = node.__previousOutline;
2111
+ }
2112
+ }
2113
+ })
2114
+ .then(() => {
2115
+ })
2116
+ .catch((e) => {
2117
+ // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2118
+ });
2119
+ }
2120
+ else {
2121
+ scope
2122
+ .evaluate(([css]) => {
2123
+ if (!css) {
2124
+ return;
2125
+ }
2126
+ let elements = Array.from(document.querySelectorAll(css));
2127
+ for (i = 0; i < elements.length; i++) {
2128
+ let element = elements[i];
2129
+ if (!element.style) {
2130
+ return;
2131
+ }
2132
+ if (!element.__previousOutline) {
2133
+ element.style.outline = "";
2134
+ }
2135
+ else {
2136
+ element.style.outline = element.__previousOutline;
2137
+ }
2138
+ }
2139
+ })
2140
+ .then(() => {
2141
+ })
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;
@@ -2034,7 +2189,7 @@ class StableBrowser {
2034
2189
  }
2035
2190
  finally {
2036
2191
  const endTime = Date.now();
2037
- this._reportToWorld(world, {
2192
+ _reportToWorld(world, {
2038
2193
  type: Types.VERIFY_PAGE_PATH,
2039
2194
  text: "Verify page path",
2040
2195
  screenshotId,
@@ -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();
@@ -2269,7 +2510,7 @@ class StableBrowser {
2269
2510
  }
2270
2511
  finally {
2271
2512
  const endTime = Date.now();
2272
- this._reportToWorld(world, {
2513
+ _reportToWorld(world, {
2273
2514
  type: Types.VERIFY_VISUAL,
2274
2515
  text: "Visual verification",
2275
2516
  screenshotId,
@@ -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;
@@ -2337,7 +2579,7 @@ class StableBrowser {
2337
2579
  }
2338
2580
  finally {
2339
2581
  const endTime = Date.now();
2340
- this._reportToWorld(world, {
2582
+ _reportToWorld(world, {
2341
2583
  element_name: selectors.element_name,
2342
2584
  type: Types.GET_TABLE_DATA,
2343
2585
  text: "Get table data",
@@ -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;
@@ -2503,7 +2745,7 @@ class StableBrowser {
2503
2745
  }
2504
2746
  finally {
2505
2747
  const endTime = Date.now();
2506
- this._reportToWorld(world, {
2748
+ _reportToWorld(world, {
2507
2749
  element_name: selectors.element_name,
2508
2750
  type: Types.ANALYZE_TABLE,
2509
2751
  text: "Analyze table",
@@ -2576,7 +2818,7 @@ class StableBrowser {
2576
2818
  await new Promise((resolve) => setTimeout(resolve, 2000));
2577
2819
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2578
2820
  const endTime = Date.now();
2579
- this._reportToWorld(world, {
2821
+ _reportToWorld(world, {
2580
2822
  type: Types.GET_PAGE_STATUS,
2581
2823
  text: "Wait for page load",
2582
2824
  screenshotId,
@@ -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;
@@ -2643,7 +2890,7 @@ class StableBrowser {
2643
2890
  await new Promise((resolve) => setTimeout(resolve, 2000));
2644
2891
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2645
2892
  const endTime = Date.now();
2646
- this._reportToWorld(world, {
2893
+ _reportToWorld(world, {
2647
2894
  type: Types.SET_VIEWPORT,
2648
2895
  text: "set viewport size to " + width + "x" + hight,
2649
2896
  screenshotId,
@@ -2680,7 +2927,7 @@ class StableBrowser {
2680
2927
  await new Promise((resolve) => setTimeout(resolve, 2000));
2681
2928
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2682
2929
  const endTime = Date.now();
2683
- this._reportToWorld(world, {
2930
+ _reportToWorld(world, {
2684
2931
  type: Types.GET_PAGE_STATUS,
2685
2932
  text: "page relaod",
2686
2933
  screenshotId,
@@ -2716,26 +2963,35 @@ class StableBrowser {
2716
2963
  console.log("#-#");
2717
2964
  }
2718
2965
  }
2719
- _reportToWorld(world, properties) {
2720
- if (!world || !world.attach) {
2721
- return;
2722
- }
2723
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2724
- }
2725
2966
  async beforeStep(world, step) {
2726
- this.stepName = step.pickleStep.text;
2727
- this.logger.info("step: " + this.stepName);
2728
2967
  if (this.stepIndex === undefined) {
2729
2968
  this.stepIndex = 0;
2730
2969
  }
2731
2970
  else {
2732
2971
  this.stepIndex++;
2733
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
+ }
2734
2983
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2735
2984
  if (this.context.browserObject.context) {
2736
2985
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2737
2986
  }
2738
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
+ }
2739
2995
  }
2740
2996
  async afterStep(world, step) {
2741
2997
  this.stepName = null;
@@ -2753,156 +3009,5 @@ function createTimedPromise(promise, label) {
2753
3009
  .then((result) => ({ status: "fulfilled", label, result }))
2754
3010
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2755
3011
  }
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
3012
  export { StableBrowser };
2908
3013
  //# sourceMappingURL=stable_browser.js.map