automation_model 1.0.516-dev → 1.0.516-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 +97 -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 +1 -0
  11. package/lib/command_common.js +43 -8
  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 +52 -17
  35. package/lib/stable_browser.js +455 -477
  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
@@ -10,15 +10,17 @@ import { getDateTimeValue } from "./date_time.js";
10
10
  import drawRectangle from "./drawRect.js";
11
11
  //import { closeUnexpectedPopups } from "./popups.js";
12
12
  import { getTableCells, getTableData } from "./table_analyze.js";
13
- import { maskValue, replaceWithLocalTestData } from "./utils.js";
13
+ import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
14
14
  import csv from "csv-parser";
15
15
  import { Readable } from "node:stream";
16
16
  import readline from "readline";
17
17
  import { getContext } from "./init_browser.js";
18
18
  import { locate_element } from "./locate_element.js";
19
- import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot } from "./command_common.js";
19
+ import { randomUUID } from "crypto";
20
+ import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
20
21
  import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
21
- const Types = {
22
+ import { LocatorLog } from "./locator_log.js";
23
+ export const Types = {
22
24
  CLICK: "click_element",
23
25
  NAVIGATE: "navigate",
24
26
  FILL: "fill_element",
@@ -29,6 +31,8 @@ const Types = {
29
31
  GET_PAGE_STATUS: "get_page_status",
30
32
  CLICK_ROW_ACTION: "click_row_action",
31
33
  VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
34
+ VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
35
+ VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
32
36
  ANALYZE_TABLE: "analyze_table",
33
37
  SELECT: "select_combobox",
34
38
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -45,6 +49,8 @@ const Types = {
45
49
  LOAD_DATA: "load_data",
46
50
  SET_INPUT: "set_input",
47
51
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
52
+ VERIFY_ATTRIBUTE: "verify_element_attribute",
53
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
48
54
  };
49
55
  export const apps = {};
50
56
  class StableBrowser {
@@ -58,6 +64,7 @@ class StableBrowser {
58
64
  networkLogger = null;
59
65
  configuration = null;
60
66
  appName = "main";
67
+ tags = null;
61
68
  constructor(browser, page, logger = null, context = null, world = null) {
62
69
  this.browser = browser;
63
70
  this.page = page;
@@ -88,17 +95,17 @@ class StableBrowser {
88
95
  catch (e) {
89
96
  this.logger.error("unable to read ai_config.json");
90
97
  }
91
- context.pageLoading = { status: false };
92
- context.pages = [this.page];
93
98
  const logFolder = path.join(this.project_path, "logs", "web");
94
99
  this.world = world;
100
+ context.pages = [this.page];
101
+ context.pageLoading = { status: false };
95
102
  this.registerEventListeners(this.context);
96
103
  registerNetworkEvents(this.world, this, this.context, this.page);
97
104
  registerDownloadEvent(this.page, this.world, this.context);
98
105
  }
99
106
  registerEventListeners(context) {
100
107
  this.registerConsoleLogListener(this.page, context);
101
- this.registerRequestListener(this.page, context, this.webLogFile);
108
+ // this.registerRequestListener(this.page, context, this.webLogFile);
102
109
  if (!context.pageLoading) {
103
110
  context.pageLoading = { status: false };
104
111
  }
@@ -143,7 +150,7 @@ class StableBrowser {
143
150
  if (this.appName === appName) {
144
151
  return;
145
152
  }
146
- let newContextCreated = false;
153
+ let navigate = false;
147
154
  if (!apps[appName]) {
148
155
  let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
149
156
  newContextCreated = true;
@@ -154,32 +161,15 @@ class StableBrowser {
154
161
  };
155
162
  }
156
163
  const tempContext = {};
157
- this._copyContext(this, tempContext);
158
- this._copyContext(apps[appName], this);
164
+ _copyContext(this, tempContext);
165
+ _copyContext(apps[appName], this);
159
166
  apps[this.appName] = tempContext;
160
167
  this.appName = appName;
161
- if (newContextCreated) {
162
- this.registerEventListeners(this.context);
168
+ if (navigate) {
163
169
  await this.goto(this.context.environment.baseUrl);
164
170
  await this.waitForPageLoad();
165
171
  }
166
172
  }
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
173
  registerConsoleLogListener(page, context) {
184
174
  if (!this.context.webLogger) {
185
175
  this.context.webLogger = [];
@@ -229,7 +219,7 @@ class StableBrowser {
229
219
  this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
230
220
  }
231
221
  catch (error) {
232
- console.error("Error in request listener", error);
222
+ // console.error("Error in request listener", error);
233
223
  context.networkLogger.push({
234
224
  error: "not able to listen",
235
225
  message: error.message,
@@ -243,55 +233,48 @@ class StableBrowser {
243
233
  // async closeUnexpectedPopups() {
244
234
  // await closeUnexpectedPopups(this.page);
245
235
  // }
246
- async goto(url) {
236
+ async goto(url, world = null) {
247
237
  if (!url.startsWith("http")) {
248
238
  url = "https://" + url;
249
239
  }
250
- await this.page.goto(url, {
251
- timeout: 60000,
252
- });
253
- }
254
- _fixUsingParams(text, _params) {
255
- if (!_params || typeof text !== "string") {
256
- return text;
240
+ const state = {
241
+ value: url,
242
+ world: world,
243
+ type: Types.NAVIGATE,
244
+ text: `Navigate Page to: ${url}`,
245
+ operation: "goto",
246
+ log: "***** navigate page to " + url + " *****\n",
247
+ info: {},
248
+ locate: false,
249
+ scroll: false,
250
+ screenshot: false,
251
+ highlight: false,
252
+ };
253
+ try {
254
+ await _preCommand(state, this);
255
+ await this.page.goto(url, {
256
+ timeout: 60000,
257
+ });
258
+ await _screenshot(state, this);
257
259
  }
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]);
260
+ catch (error) {
261
+ console.error("Error on goto", error);
262
+ _commandError(state, error, this);
265
263
  }
266
- return text;
267
- }
268
- _fixLocatorUsingParams(locator, _params) {
269
- // check if not null
270
- if (!locator) {
271
- return locator;
264
+ finally {
265
+ _commandFinally(state, this);
272
266
  }
273
- // clone the locator
274
- locator = JSON.parse(JSON.stringify(locator));
275
- this.scanAndManipulate(locator, _params);
276
- return locator;
277
- }
278
- _isObject(value) {
279
- return value && typeof value === "object" && value.constructor === Object;
280
267
  }
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);
268
+ async _getLocator(locator, scope, _params) {
269
+ locator = _fixLocatorUsingParams(locator, _params);
270
+ // locator = await this._replaceWithLocalData(locator);
271
+ for (let key in locator) {
272
+ if (typeof locator[key] !== "string")
273
+ continue;
274
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
275
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
290
276
  }
291
277
  }
292
- }
293
- _getLocator(locator, scope, _params) {
294
- locator = this._fixLocatorUsingParams(locator, _params);
295
278
  let locatorReturn;
296
279
  if (locator.role) {
297
280
  if (locator.role[1].nameReg) {
@@ -299,7 +282,7 @@ class StableBrowser {
299
282
  delete locator.role[1].nameReg;
300
283
  }
301
284
  // if (locator.role[1].name) {
302
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
285
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
303
286
  // }
304
287
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
305
288
  }
@@ -342,140 +325,54 @@ class StableBrowser {
342
325
  if (css && css.locator) {
343
326
  css = css.locator;
344
327
  }
345
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
328
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
346
329
  if (result.elementCount === 0) {
347
330
  return;
348
331
  }
349
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
332
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
350
333
  // css climb to parent element
351
334
  const climbArray = [];
352
335
  for (let i = 0; i < climb; i++) {
353
336
  climbArray.push("..");
354
337
  }
355
338
  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;
339
+ let resultCss = textElementCss + " >> " + climbXpath;
340
+ if (css) {
341
+ resultCss = resultCss + " >> " + css;
342
+ }
343
+ return resultCss;
344
+ }
345
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
346
+ const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
347
+ const locator = scope.locator(query);
348
+ const count = await locator.count();
349
+ if (!tag1) {
350
+ tag1 = "*";
351
+ }
352
+ const randomToken = Math.random().toString(36).substring(7);
353
+ let tagCount = 0;
354
+ for (let i = 0; i < count; i++) {
355
+ const element = locator.nth(i);
356
+ // check if the tag matches
357
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
358
+ if (!tag.startsWith("*")) {
359
+ if (el.tagName.toLowerCase() !== tag) {
360
+ return false;
366
361
  }
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
362
  }
412
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
413
- elements = elements.concat(shadowElements);
414
- }
415
- let randomToken = null;
416
- const foundElements = [];
417
- if (regex) {
418
- if (!regexpSearch) {
419
- regexpSearch = new RegExp(text, "im");
420
- }
421
- for (let i = 0; i < elements.length; i++) {
422
- const element = elements[i];
423
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
424
- (element.value && regexpSearch.test(element.value))) {
425
- foundElements.push(element);
426
- }
363
+ if (!el.setAttribute) {
364
+ el = el.parentElement;
427
365
  }
366
+ el.setAttribute("data-blinq-id-" + randomToken, "");
367
+ return true;
368
+ }, [tag1, randomToken]))) {
369
+ continue;
428
370
  }
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]);
371
+ tagCount++;
372
+ }
373
+ return { elementCount: tagCount, randomToken };
477
374
  }
478
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
375
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false) {
479
376
  if (!info) {
480
377
  info = {};
481
378
  }
@@ -484,10 +381,13 @@ class StableBrowser {
484
381
  }
485
382
  if (!info.log) {
486
383
  info.log = "";
384
+ info.locatorLog = new LocatorLog(selectorHierarchy);
487
385
  }
488
386
  let locatorSearch = selectorHierarchy[index];
387
+ let originalLocatorSearch = "";
489
388
  try {
490
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
389
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
390
+ locatorSearch = JSON.parse(originalLocatorSearch);
491
391
  }
492
392
  catch (e) {
493
393
  console.error(e);
@@ -501,24 +401,24 @@ class StableBrowser {
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);
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);
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));
@@ -929,18 +923,18 @@ class StableBrowser {
929
923
  };
930
924
  try {
931
925
  await _preCommand(state, this);
932
- if (state.options && state.options.context) {
933
- state.selectors.locators[0].text = state.options.context;
934
- }
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
- await new Promise((resolve) => setTimeout(resolve, 1000));
931
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
938
932
  }
939
933
  catch (e) {
940
934
  // await this.closeUnexpectedPopups();
941
935
  state.element = await this._locate(selectors, state.info, _params);
942
936
  await state.element.dispatchEvent("click");
943
- await new Promise((resolve) => setTimeout(resolve, 1000));
937
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
944
938
  }
945
939
  await this.waitForPageLoad();
946
940
  return state.info;
@@ -1019,7 +1013,6 @@ class StableBrowser {
1019
1013
  await state.element.hover({ timeout: 10000 });
1020
1014
  await new Promise((resolve) => setTimeout(resolve, 1000));
1021
1015
  }
1022
- await _screenshot(state, this);
1023
1016
  await this.waitForPageLoad();
1024
1017
  return state.info;
1025
1018
  }
@@ -1291,7 +1284,12 @@ class StableBrowser {
1291
1284
  await this.waitForPageLoad();
1292
1285
  }
1293
1286
  else if (enter === false) {
1294
- await state.element.dispatchEvent("change");
1287
+ try {
1288
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1289
+ }
1290
+ catch (e) {
1291
+ // ignore
1292
+ }
1295
1293
  //await this.page.keyboard.press("Tab");
1296
1294
  }
1297
1295
  else {
@@ -1348,6 +1346,7 @@ class StableBrowser {
1348
1346
  let screenshotPath = null;
1349
1347
  if (!info.log) {
1350
1348
  info.log = "";
1349
+ info.locatorLog = new LocatorLog(selectors);
1351
1350
  }
1352
1351
  info.operation = "getText";
1353
1352
  info.selectors = selectors;
@@ -1686,11 +1685,9 @@ class StableBrowser {
1686
1685
  if (!fs.existsSync(world.screenshotPath)) {
1687
1686
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1688
1687
  }
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");
1688
+ // to make sure the path doesn't start with -
1689
+ const uuidStr = "id_" + randomUUID();
1690
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1694
1691
  try {
1695
1692
  await this.takeScreenshot(screenshotPath);
1696
1693
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1704,7 +1701,7 @@ class StableBrowser {
1704
1701
  catch (e) {
1705
1702
  this.logger.info("unable to take screenshot, ignored");
1706
1703
  }
1707
- result.screenshotId = nextIndex;
1704
+ result.screenshotId = uuidStr;
1708
1705
  result.screenshotPath = screenshotPath;
1709
1706
  if (info && info.box) {
1710
1707
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1843,6 +1840,69 @@ class StableBrowser {
1843
1840
  _commandFinally(state, this);
1844
1841
  }
1845
1842
  }
1843
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1844
+ const state = {
1845
+ selectors,
1846
+ _params,
1847
+ attribute,
1848
+ value,
1849
+ options,
1850
+ world,
1851
+ type: Types.VERIFY_ATTRIBUTE,
1852
+ highlight: true,
1853
+ screenshot: true,
1854
+ text: `Verify element attribute`,
1855
+ operation: "verifyAttribute",
1856
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1857
+ allowDisabled: true,
1858
+ };
1859
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1860
+ let val;
1861
+ try {
1862
+ await _preCommand(state, this);
1863
+ switch (attribute) {
1864
+ case "innerText":
1865
+ val = String(await state.element.innerText());
1866
+ break;
1867
+ case "value":
1868
+ val = String(await state.element.inputValue());
1869
+ break;
1870
+ case "checked":
1871
+ val = String(await state.element.isChecked());
1872
+ break;
1873
+ case "disabled":
1874
+ val = String(await state.element.isDisabled());
1875
+ break;
1876
+ case "readOnly":
1877
+ const isEditable = await state.element.isEditable();
1878
+ val = String(!isEditable);
1879
+ break;
1880
+ default:
1881
+ val = String(await state.element.getAttribute(attribute));
1882
+ break;
1883
+ }
1884
+ state.info.expectedValue = val;
1885
+ let regex;
1886
+ if (value.startsWith("/") && value.endsWith("/")) {
1887
+ const patternBody = value.slice(1, -1);
1888
+ regex = new RegExp(patternBody, "g");
1889
+ }
1890
+ else {
1891
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1892
+ regex = new RegExp(escapedPattern, "g");
1893
+ }
1894
+ if (!val.match(regex)) {
1895
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1896
+ }
1897
+ return state.info;
1898
+ }
1899
+ catch (e) {
1900
+ await _commandError(state, e, this);
1901
+ }
1902
+ finally {
1903
+ _commandFinally(state, this);
1904
+ }
1905
+ }
1846
1906
  async extractEmailData(emailAddress, options, world) {
1847
1907
  if (!emailAddress) {
1848
1908
  throw new Error("email address is null");
@@ -1861,7 +1921,7 @@ class StableBrowser {
1861
1921
  if (options && options.timeout) {
1862
1922
  timeout = options.timeout;
1863
1923
  }
1864
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1924
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1865
1925
  const request = {
1866
1926
  method: "POST",
1867
1927
  url: serviceUrl,
@@ -1938,15 +1998,15 @@ class StableBrowser {
1938
1998
  scope
1939
1999
  .evaluate((node) => {
1940
2000
  if (node && node.style) {
1941
- let originalBorder = node.style.border;
1942
- node.style.border = "2px solid red";
2001
+ let originalBorder = node.style.outline;
2002
+ node.style.outline = "2px solid red";
1943
2003
  if (window) {
1944
2004
  window.addEventListener("beforeunload", function (e) {
1945
- node.style.border = originalBorder;
2005
+ node.style.outline = originalBorder;
1946
2006
  });
1947
2007
  }
1948
2008
  setTimeout(function () {
1949
- node.style.border = originalBorder;
2009
+ node.style.outline = originalBorder;
1950
2010
  }, 2000);
1951
2011
  }
1952
2012
  })
@@ -1968,17 +2028,17 @@ class StableBrowser {
1968
2028
  if (!element.style) {
1969
2029
  return;
1970
2030
  }
1971
- var originalBorder = element.style.border;
2031
+ var originalBorder = element.style.outline;
1972
2032
  // Set the new border to be red and 2px solid
1973
- element.style.border = "2px solid red";
2033
+ element.style.outline = "2px solid red";
1974
2034
  if (window) {
1975
2035
  window.addEventListener("beforeunload", function (e) {
1976
- element.style.border = originalBorder;
2036
+ element.style.outline = originalBorder;
1977
2037
  });
1978
2038
  }
1979
2039
  // Set a timeout to revert to the original border after 2 seconds
1980
2040
  setTimeout(function () {
1981
- element.style.border = originalBorder;
2041
+ element.style.outline = originalBorder;
1982
2042
  }, 2000);
1983
2043
  }
1984
2044
  return;
@@ -2034,7 +2094,7 @@ class StableBrowser {
2034
2094
  }
2035
2095
  finally {
2036
2096
  const endTime = Date.now();
2037
- this._reportToWorld(world, {
2097
+ _reportToWorld(world, {
2038
2098
  type: Types.VERIFY_PAGE_PATH,
2039
2099
  text: "Verify page path",
2040
2100
  screenshotId,
@@ -2054,6 +2114,35 @@ class StableBrowser {
2054
2114
  });
2055
2115
  }
2056
2116
  }
2117
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2118
+ const frames = this.page.frames();
2119
+ let results = [];
2120
+ let ignoreCase = false;
2121
+ for (let i = 0; i < frames.length; i++) {
2122
+ if (dateAlternatives.date) {
2123
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2124
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2125
+ result.frame = frames[i];
2126
+ results.push(result);
2127
+ }
2128
+ }
2129
+ else if (numberAlternatives.number) {
2130
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2131
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2132
+ result.frame = frames[i];
2133
+ results.push(result);
2134
+ }
2135
+ }
2136
+ else {
2137
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2138
+ result.frame = frames[i];
2139
+ results.push(result);
2140
+ }
2141
+ }
2142
+ state.info.results = results;
2143
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2144
+ return resultWithElementsFound;
2145
+ }
2057
2146
  async verifyTextExistInPage(text, options = {}, world = null) {
2058
2147
  text = unEscapeString(text);
2059
2148
  const state = {
@@ -2063,7 +2152,7 @@ class StableBrowser {
2063
2152
  locate: false,
2064
2153
  scroll: false,
2065
2154
  highlight: false,
2066
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2155
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2067
2156
  text: `Verify text exists in page`,
2068
2157
  operation: "verifyTextExistInPage",
2069
2158
  log: "***** verify text " + text + " exists in page *****\n",
@@ -2081,31 +2170,7 @@ class StableBrowser {
2081
2170
  await _preCommand(state, this);
2082
2171
  state.info.text = text;
2083
2172
  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);
2173
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2109
2174
  if (resultWithElementsFound.length === 0) {
2110
2175
  if (Date.now() - state.startTime > timeout) {
2111
2176
  throw new Error(`Text ${text} not found in page`);
@@ -2115,9 +2180,9 @@ class StableBrowser {
2115
2180
  }
2116
2181
  if (resultWithElementsFound[0].randomToken) {
2117
2182
  const frame = resultWithElementsFound[0].frame;
2118
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2183
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2119
2184
  await this._highlightElements(frame, dataAttribute);
2120
- const element = await frame.$(dataAttribute);
2185
+ const element = await frame.locator(dataAttribute).first();
2121
2186
  if (element) {
2122
2187
  await this.scrollIfNeeded(element, state.info);
2123
2188
  await element.dispatchEvent("bvt_verify_page_contains_text");
@@ -2162,31 +2227,7 @@ class StableBrowser {
2162
2227
  await _preCommand(state, this);
2163
2228
  state.info.text = text;
2164
2229
  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);
2230
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2190
2231
  if (resultWithElementsFound.length === 0) {
2191
2232
  await _screenshot(state, this);
2192
2233
  return state.info;
@@ -2204,15 +2245,88 @@ class StableBrowser {
2204
2245
  _commandFinally(state, this);
2205
2246
  }
2206
2247
  }
2207
- _getServerUrl() {
2208
- let serviceUrl = "https://api.blinq.io";
2209
- if (process.env.NODE_ENV_BLINQ === "dev") {
2210
- serviceUrl = "https://dev.api.blinq.io";
2248
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2249
+ textAnchor = unEscapeString(textAnchor);
2250
+ textToVerify = unEscapeString(textToVerify);
2251
+ const state = {
2252
+ text_search: textToVerify,
2253
+ options,
2254
+ world,
2255
+ locate: false,
2256
+ scroll: false,
2257
+ highlight: false,
2258
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2259
+ text: `Verify text with relation to another text`,
2260
+ operation: "verify_text_with_relation",
2261
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2262
+ };
2263
+ const timeout = this._getLoadTimeout(options);
2264
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2265
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2266
+ if (newValue !== textAnchor) {
2267
+ this.logger.info(textAnchor + "=" + newValue);
2268
+ textAnchor = newValue;
2269
+ }
2270
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2271
+ if (newValue !== textToVerify) {
2272
+ this.logger.info(textToVerify + "=" + newValue);
2273
+ textToVerify = newValue;
2274
+ }
2275
+ let dateAlternatives = findDateAlternatives(textToVerify);
2276
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2277
+ let foundAncore = false;
2278
+ try {
2279
+ await _preCommand(state, this);
2280
+ state.info.text = textToVerify;
2281
+ while (true) {
2282
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2283
+ if (resultWithElementsFound.length === 0) {
2284
+ if (Date.now() - state.startTime > timeout) {
2285
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2286
+ }
2287
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2288
+ continue;
2289
+ }
2290
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2291
+ foundAncore = true;
2292
+ const result = resultWithElementsFound[i];
2293
+ const token = result.randomToken;
2294
+ const frame = result.frame;
2295
+ let css = `[data-blinq-id-${token}]`;
2296
+ const climbArray1 = [];
2297
+ for (let i = 0; i < climb; i++) {
2298
+ climbArray1.push("..");
2299
+ }
2300
+ let climbXpath = "xpath=" + climbArray1.join("/");
2301
+ css = css + " >> " + climbXpath;
2302
+ const count = await frame.locator(css).count();
2303
+ for (let j = 0; j < count; j++) {
2304
+ const continer = await frame.locator(css).nth(j);
2305
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2306
+ if (result.elementCount > 0) {
2307
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2308
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2309
+ await this._highlightElements(frame, dataAttribute);
2310
+ //await this._highlightElements(frame, cssAnchor);
2311
+ const element = await frame.locator(dataAttribute).first();
2312
+ if (element) {
2313
+ await this.scrollIfNeeded(element, state.info);
2314
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2315
+ }
2316
+ await _screenshot(state, this);
2317
+ return state.info;
2318
+ }
2319
+ }
2320
+ }
2321
+ }
2322
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2323
+ }
2324
+ catch (e) {
2325
+ await _commandError(state, e, this);
2211
2326
  }
2212
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2213
- serviceUrl = "https://stage.api.blinq.io";
2327
+ finally {
2328
+ _commandFinally(state, this);
2214
2329
  }
2215
- return serviceUrl;
2216
2330
  }
2217
2331
  async visualVerification(text, options = {}, world = null) {
2218
2332
  const startTime = Date.now();
@@ -2228,7 +2342,7 @@ class StableBrowser {
2228
2342
  throw new Error("TOKEN is not set");
2229
2343
  }
2230
2344
  try {
2231
- let serviceUrl = this._getServerUrl();
2345
+ let serviceUrl = _getServerUrl();
2232
2346
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2233
2347
  info.screenshotPath = screenshotPath;
2234
2348
  const screenshot = await this.takeScreenshot();
@@ -2269,7 +2383,7 @@ class StableBrowser {
2269
2383
  }
2270
2384
  finally {
2271
2385
  const endTime = Date.now();
2272
- this._reportToWorld(world, {
2386
+ _reportToWorld(world, {
2273
2387
  type: Types.VERIFY_VISUAL,
2274
2388
  text: "Visual verification",
2275
2389
  screenshotId,
@@ -2317,6 +2431,7 @@ class StableBrowser {
2317
2431
  let screenshotPath = null;
2318
2432
  const info = {};
2319
2433
  info.log = "";
2434
+ info.locatorLog = new LocatorLog(selectors);
2320
2435
  info.operation = "getTableData";
2321
2436
  info.selectors = selectors;
2322
2437
  try {
@@ -2337,7 +2452,7 @@ class StableBrowser {
2337
2452
  }
2338
2453
  finally {
2339
2454
  const endTime = Date.now();
2340
- this._reportToWorld(world, {
2455
+ _reportToWorld(world, {
2341
2456
  element_name: selectors.element_name,
2342
2457
  type: Types.GET_TABLE_DATA,
2343
2458
  text: "Get table data",
@@ -2392,7 +2507,7 @@ class StableBrowser {
2392
2507
  info.operation = "analyzeTable";
2393
2508
  info.selectors = selectors;
2394
2509
  info.query = query;
2395
- query = this._fixUsingParams(query, _params);
2510
+ query = _fixUsingParams(query, _params);
2396
2511
  info.query_fixed = query;
2397
2512
  info.operator = operator;
2398
2513
  info.value = value;
@@ -2503,7 +2618,7 @@ class StableBrowser {
2503
2618
  }
2504
2619
  finally {
2505
2620
  const endTime = Date.now();
2506
- this._reportToWorld(world, {
2621
+ _reportToWorld(world, {
2507
2622
  element_name: selectors.element_name,
2508
2623
  type: Types.ANALYZE_TABLE,
2509
2624
  text: "Analyze table",
@@ -2576,7 +2691,7 @@ class StableBrowser {
2576
2691
  await new Promise((resolve) => setTimeout(resolve, 2000));
2577
2692
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2578
2693
  const endTime = Date.now();
2579
- this._reportToWorld(world, {
2694
+ _reportToWorld(world, {
2580
2695
  type: Types.GET_PAGE_STATUS,
2581
2696
  text: "Wait for page load",
2582
2697
  screenshotId,
@@ -2620,6 +2735,11 @@ class StableBrowser {
2620
2735
  _commandFinally(state, this);
2621
2736
  }
2622
2737
  }
2738
+ saveTestDataAsGlobal(options, world) {
2739
+ const dataFile = this._getDataFile(world);
2740
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2741
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2742
+ }
2623
2743
  async setViewportSize(width, hight, options = {}, world = null) {
2624
2744
  const startTime = Date.now();
2625
2745
  let error = null;
@@ -2643,7 +2763,7 @@ class StableBrowser {
2643
2763
  await new Promise((resolve) => setTimeout(resolve, 2000));
2644
2764
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2645
2765
  const endTime = Date.now();
2646
- this._reportToWorld(world, {
2766
+ _reportToWorld(world, {
2647
2767
  type: Types.SET_VIEWPORT,
2648
2768
  text: "set viewport size to " + width + "x" + hight,
2649
2769
  screenshotId,
@@ -2680,7 +2800,7 @@ class StableBrowser {
2680
2800
  await new Promise((resolve) => setTimeout(resolve, 2000));
2681
2801
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2682
2802
  const endTime = Date.now();
2683
- this._reportToWorld(world, {
2803
+ _reportToWorld(world, {
2684
2804
  type: Types.GET_PAGE_STATUS,
2685
2805
  text: "page relaod",
2686
2806
  screenshotId,
@@ -2716,26 +2836,35 @@ class StableBrowser {
2716
2836
  console.log("#-#");
2717
2837
  }
2718
2838
  }
2719
- _reportToWorld(world, properties) {
2720
- if (!world || !world.attach) {
2721
- return;
2722
- }
2723
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2724
- }
2725
2839
  async beforeStep(world, step) {
2726
- this.stepName = step.pickleStep.text;
2727
- this.logger.info("step: " + this.stepName);
2728
2840
  if (this.stepIndex === undefined) {
2729
2841
  this.stepIndex = 0;
2730
2842
  }
2731
2843
  else {
2732
2844
  this.stepIndex++;
2733
2845
  }
2846
+ if (step && step.pickleStep && step.pickleStep.text) {
2847
+ this.stepName = step.pickleStep.text;
2848
+ this.logger.info("step: " + this.stepName);
2849
+ }
2850
+ else if (step && step.text) {
2851
+ this.stepName = step.text;
2852
+ }
2853
+ else {
2854
+ this.stepName = "step " + this.stepIndex;
2855
+ }
2734
2856
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2735
2857
  if (this.context.browserObject.context) {
2736
2858
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2737
2859
  }
2738
2860
  }
2861
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2862
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2863
+ // check if @global_test_data tag is present
2864
+ if (this.tags.includes("@global_test_data")) {
2865
+ this.saveTestDataAsGlobal({}, world);
2866
+ }
2867
+ }
2739
2868
  }
2740
2869
  async afterStep(world, step) {
2741
2870
  this.stepName = null;
@@ -2753,156 +2882,5 @@ function createTimedPromise(promise, label) {
2753
2882
  .then((result) => ({ status: "fulfilled", label, result }))
2754
2883
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2755
2884
  }
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
2885
  export { StableBrowser };
2908
2886
  //# sourceMappingURL=stable_browser.js.map