automation_model 1.0.508-dev → 1.0.508-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 (47) hide show
  1. package/lib/api.d.ts +2 -1
  2. package/lib/api.js +98 -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 +6 -3
  8. package/lib/browser_manager.js +90 -32
  9. package/lib/browser_manager.js.map +1 -1
  10. package/lib/command_common.d.ts +1 -0
  11. package/lib/command_common.js +37 -5
  12. package/lib/command_common.js.map +1 -1
  13. package/lib/error-messages.js +8 -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 +20 -6
  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 +16 -3
  33. package/lib/network.js.map +1 -1
  34. package/lib/stable_browser.d.ts +54 -17
  35. package/lib/stable_browser.js +481 -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/test_context.d.ts +1 -0
  41. package/lib/test_context.js +1 -0
  42. package/lib/test_context.js.map +1 -1
  43. package/lib/utils.d.ts +13 -1
  44. package/lib/utils.js +298 -3
  45. package/lib/utils.js.map +1 -1
  46. package/package.json +6 -5
  47. /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,9 +150,9 @@ 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
- let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this);
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;
150
157
  apps[appName] = {
151
158
  context: newContext,
@@ -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
267
  }
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);
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,143 +325,69 @@ 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
- }
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
362
  }
421
- for (let i = 0; i < elements.length; i++) {
422
- const element = elements[i];
423
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
424
- (element.value && regexpSearch.test(element.value))) {
425
- foundElements.push(element);
426
- }
427
- }
428
- }
429
- else {
430
- text = text.trim();
431
- for (let i = 0; i < elements.length; i++) {
432
- const element = elements[i];
433
- if (partial) {
434
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
435
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
436
- foundElements.push(element);
437
- }
438
- }
439
- else {
440
- if ((element.innerText && element.innerText.trim() === text) ||
441
- (element.value && element.value === text)) {
442
- foundElements.push(element);
443
- }
444
- }
363
+ if (!el.setAttribute) {
364
+ el = el.parentElement;
445
365
  }
366
+ el.setAttribute("data-blinq-id-" + randomToken, "");
367
+ return true;
368
+ }, [tag1, randomToken]))) {
369
+ continue;
446
370
  }
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) {
376
+ if (!info) {
377
+ info = {};
378
+ }
379
+ if (!info.failCause) {
380
+ info.failCause = {};
381
+ }
382
+ if (!info.log) {
383
+ info.log = "";
384
+ info.locatorLog = new LocatorLog(selectorHierarchy);
385
+ }
479
386
  let locatorSearch = selectorHierarchy[index];
387
+ let originalLocatorSearch = "";
480
388
  try {
481
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
389
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
390
+ locatorSearch = JSON.parse(originalLocatorSearch);
482
391
  }
483
392
  catch (e) {
484
393
  console.error(e);
@@ -492,24 +401,24 @@ class StableBrowser {
492
401
  info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
493
402
  return;
494
403
  }
495
- locator = this._getLocator({ css: locatorString }, scope, _params);
404
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
496
405
  }
497
406
  else if (locatorSearch.text) {
498
- let text = this._fixUsingParams(locatorSearch.text, _params);
499
- 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);
500
409
  if (result.elementCount === 0) {
501
410
  info.failCause.textNotFound = true;
502
411
  info.failCause.lastError = "failed to locate element by text: " + text;
503
412
  return;
504
413
  }
505
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
414
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
506
415
  if (locatorSearch.childCss) {
507
416
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
508
417
  }
509
- locator = this._getLocator(locatorSearch, scope, _params);
418
+ locator = await this._getLocator(locatorSearch, scope, _params);
510
419
  }
511
420
  else {
512
- locator = this._getLocator(locatorSearch, scope, _params);
421
+ locator = await this._getLocator(locatorSearch, scope, _params);
513
422
  }
514
423
  // let cssHref = false;
515
424
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -522,18 +431,27 @@ class StableBrowser {
522
431
  //info.log += "total elements found " + count + "\n";
523
432
  //let visibleCount = 0;
524
433
  let visibleLocator = null;
525
- if (locatorSearch.index && locatorSearch.index < count) {
434
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
526
435
  foundLocators.push(locator.nth(locatorSearch.index));
436
+ if (info.locatorLog) {
437
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
438
+ }
527
439
  return;
528
440
  }
441
+ if (info.locatorLog && count === 0) {
442
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
443
+ }
529
444
  for (let j = 0; j < count; j++) {
530
445
  let visible = await locator.nth(j).isVisible();
531
446
  const enabled = await locator.nth(j).isEnabled();
532
447
  if (!visibleOnly) {
533
448
  visible = true;
534
449
  }
535
- if (visible && enabled) {
450
+ if (visible && (allowDisabled || enabled)) {
536
451
  foundLocators.push(locator.nth(j));
452
+ if (info.locatorLog) {
453
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
454
+ }
537
455
  }
538
456
  else {
539
457
  info.failCause.visible = visible;
@@ -541,8 +459,14 @@ class StableBrowser {
541
459
  if (!info.printMessages) {
542
460
  info.printMessages = {};
543
461
  }
462
+ if (info.locatorLog && !visible) {
463
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
464
+ }
465
+ if (info.locatorLog && !enabled) {
466
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
467
+ }
544
468
  if (!info.printMessages[j.toString()]) {
545
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
469
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
546
470
  info.printMessages[j.toString()] = true;
547
471
  }
548
472
  }
@@ -558,7 +482,7 @@ class StableBrowser {
558
482
  if (!info) {
559
483
  info = {};
560
484
  }
561
- info.log += "scan for popup handlers" + "\n";
485
+ //info.log += "scan for popup handlers" + "\n";
562
486
  const handlerGroup = [];
563
487
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
564
488
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -585,16 +509,28 @@ class StableBrowser {
585
509
  }
586
510
  if (result.foundElements.length > 0) {
587
511
  let dialogCloseLocator = result.foundElements[0].locator;
588
- await dialogCloseLocator.click();
589
- // wait for the dialog to close
590
- await dialogCloseLocator.waitFor({ state: "hidden" });
512
+ try {
513
+ await scope?.evaluate(() => {
514
+ window.__isClosingPopups = true;
515
+ });
516
+ await dialogCloseLocator.click();
517
+ // wait for the dialog to close
518
+ await dialogCloseLocator.waitFor({ state: "hidden" });
519
+ }
520
+ catch (e) {
521
+ }
522
+ finally {
523
+ await scope?.evaluate(() => {
524
+ window.__isClosingPopups = false;
525
+ });
526
+ }
591
527
  return { rerun: true };
592
528
  }
593
529
  }
594
530
  }
595
531
  return { rerun: false };
596
532
  }
597
- async _locate(selectors, info, _params, timeout) {
533
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
598
534
  if (!timeout) {
599
535
  timeout = 30000;
600
536
  }
@@ -604,7 +540,7 @@ class StableBrowser {
604
540
  let selector = selectors.locators[j];
605
541
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
606
542
  }
607
- let element = await this._locate_internal(selectors, info, _params, timeout);
543
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
608
544
  if (!element.rerun) {
609
545
  return element;
610
546
  }
@@ -617,6 +553,7 @@ class StableBrowser {
617
553
  info.failCause = {};
618
554
  info.log = "";
619
555
  }
556
+ let startTime = Date.now();
620
557
  let scope = this.page;
621
558
  if (selectors.frame) {
622
559
  return selectors.frame;
@@ -647,9 +584,11 @@ class StableBrowser {
647
584
  }
648
585
  return framescope;
649
586
  };
587
+ let fLocator = null;
650
588
  while (true) {
651
589
  let frameFound = false;
652
590
  if (selectors.nestFrmLoc) {
591
+ fLocator = selectors.nestFrmLoc;
653
592
  scope = await findFrame(selectors.nestFrmLoc, scope);
654
593
  frameFound = true;
655
594
  break;
@@ -658,6 +597,7 @@ class StableBrowser {
658
597
  for (let i = 0; i < selectors.frameLocators.length; i++) {
659
598
  let frameLocator = selectors.frameLocators[i];
660
599
  if (frameLocator.css) {
600
+ fLocator = frameLocator.css;
661
601
  scope = scope.frameLocator(frameLocator.css);
662
602
  frameFound = true;
663
603
  break;
@@ -665,11 +605,15 @@ class StableBrowser {
665
605
  }
666
606
  }
667
607
  if (!frameFound && selectors.iframe_src) {
608
+ fLocator = selectors.iframe_src;
668
609
  scope = this.page.frame({ url: selectors.iframe_src });
669
610
  }
670
611
  if (!scope) {
671
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
672
- if (performance.now() - startTime > timeout) {
612
+ if (info && info.locatorLog) {
613
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
614
+ }
615
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
616
+ if (Date.now() - startTime > timeout) {
673
617
  info.failCause.iframeNotFound = true;
674
618
  info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
675
619
  throw new Error("unable to locate iframe " + selectors.iframe_src);
@@ -677,6 +621,9 @@ class StableBrowser {
677
621
  await new Promise((resolve) => setTimeout(resolve, 1000));
678
622
  }
679
623
  else {
624
+ if (info && info.locatorLog) {
625
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
626
+ }
680
627
  break;
681
628
  }
682
629
  }
@@ -693,16 +640,18 @@ class StableBrowser {
693
640
  return bodyContent;
694
641
  });
695
642
  }
696
- async _locate_internal(selectors, info, _params, timeout = 30000) {
643
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
697
644
  if (!info) {
698
645
  info = {};
699
646
  info.failCause = {};
700
647
  info.log = "";
648
+ info.locatorLog = new LocatorLog(selectors);
701
649
  }
702
650
  let highPriorityTimeout = 5000;
703
651
  let visibleOnlyTimeout = 6000;
704
- let startTime = performance.now();
652
+ let startTime = Date.now();
705
653
  let locatorsCount = 0;
654
+ let lazy_scroll = false;
706
655
  //let arrayMode = Array.isArray(selectors);
707
656
  let scope = await this._findFrameScope(selectors, timeout, info);
708
657
  let selectorsLocators = null;
@@ -740,17 +689,17 @@ class StableBrowser {
740
689
  }
741
690
  // info.log += "scanning locators in priority 1" + "\n";
742
691
  let onlyPriority3 = selectorsLocators[0].priority === 3;
743
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
692
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled);
744
693
  if (result.foundElements.length === 0) {
745
694
  // info.log += "scanning locators in priority 2" + "\n";
746
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
695
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled);
747
696
  }
748
697
  if (result.foundElements.length === 0 && onlyPriority3) {
749
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
698
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
750
699
  }
751
700
  else {
752
701
  if (result.foundElements.length === 0 && !highPriorityOnly) {
753
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
702
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
754
703
  }
755
704
  }
756
705
  let foundElements = result.foundElements;
@@ -791,26 +740,36 @@ class StableBrowser {
791
740
  return maxCountElement.locator;
792
741
  }
793
742
  }
794
- if (performance.now() - startTime > timeout) {
743
+ if (Date.now() - startTime > timeout) {
795
744
  break;
796
745
  }
797
- if (performance.now() - startTime > highPriorityTimeout) {
798
- info.log += "high priority timeout, will try all elements" + "\n";
746
+ if (Date.now() - startTime > highPriorityTimeout) {
747
+ //info.log += "high priority timeout, will try all elements" + "\n";
799
748
  highPriorityOnly = false;
749
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
750
+ lazy_scroll = true;
751
+ await scrollPageToLoadLazyElements(this.page);
752
+ }
800
753
  }
801
- if (performance.now() - startTime > visibleOnlyTimeout) {
802
- info.log += "visible only timeout, will try all elements" + "\n";
754
+ if (Date.now() - startTime > visibleOnlyTimeout) {
755
+ //info.log += "visible only timeout, will try all elements" + "\n";
803
756
  visibleOnly = false;
804
757
  }
805
758
  await new Promise((resolve) => setTimeout(resolve, 1000));
806
759
  }
807
760
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
808
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
761
+ // if (info.locatorLog) {
762
+ // const lines = info.locatorLog.toString().split("\n");
763
+ // for (let line of lines) {
764
+ // this.logger.debug(line);
765
+ // }
766
+ // }
767
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
809
768
  info.failCause.locatorNotFound = true;
810
769
  info.failCause.lastError = "failed to locate unique element";
811
770
  throw new Error("failed to locate first element no elements found, " + info.log);
812
771
  }
813
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
772
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false) {
814
773
  let foundElements = [];
815
774
  const result = {
816
775
  foundElements: foundElements,
@@ -818,14 +777,15 @@ class StableBrowser {
818
777
  for (let i = 0; i < locatorsGroup.length; i++) {
819
778
  let foundLocators = [];
820
779
  try {
821
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
780
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled);
822
781
  }
823
782
  catch (e) {
824
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
825
- this.logger.debug(e);
783
+ // this call can fail it the browser is navigating
784
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
785
+ // this.logger.debug(e);
826
786
  foundLocators = [];
827
787
  try {
828
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
788
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled);
829
789
  }
830
790
  catch (e) {
831
791
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -841,11 +801,27 @@ class StableBrowser {
841
801
  }
842
802
  if (foundLocators.length > 1) {
843
803
  info.failCause.foundMultiple = true;
804
+ if (info.locatorLog) {
805
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
806
+ }
844
807
  }
845
808
  }
846
809
  return result;
847
810
  }
848
811
  async simpleClick(elementDescription, _params, options = {}, world = null) {
812
+ const state = {
813
+ locate: false,
814
+ scroll: false,
815
+ highlight: false,
816
+ _params,
817
+ options,
818
+ world,
819
+ type: Types.CLICK,
820
+ text: "Click element",
821
+ operation: "simpleClick",
822
+ log: "***** click on " + elementDescription + " *****\n",
823
+ };
824
+ _preCommand(state, this);
849
825
  const startTime = Date.now();
850
826
  let timeout = 30000;
851
827
  if (options && options.timeout) {
@@ -870,13 +846,31 @@ class StableBrowser {
870
846
  catch (e) {
871
847
  if (performance.now() - startTime > timeout) {
872
848
  // throw e;
873
- await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
849
+ try {
850
+ await _commandError(state, "timeout looking for " + elementDescription, this);
851
+ }
852
+ finally {
853
+ _commandFinally(state, this);
854
+ }
874
855
  }
875
856
  }
876
857
  await new Promise((resolve) => setTimeout(resolve, 3000));
877
858
  }
878
859
  }
879
860
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
861
+ const state = {
862
+ locate: false,
863
+ scroll: false,
864
+ highlight: false,
865
+ _params,
866
+ options,
867
+ world,
868
+ type: Types.FILL,
869
+ text: "Fill element",
870
+ operation: "simpleClickType",
871
+ log: "***** click type on " + elementDescription + " *****\n",
872
+ };
873
+ _preCommand(state, this);
880
874
  const startTime = Date.now();
881
875
  let timeout = 30000;
882
876
  if (options && options.timeout) {
@@ -901,7 +895,12 @@ class StableBrowser {
901
895
  catch (e) {
902
896
  if (performance.now() - startTime > timeout) {
903
897
  // throw e;
904
- await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
898
+ try {
899
+ await _commandError(state, "timeout looking for " + elementDescription, this);
900
+ }
901
+ finally {
902
+ _commandFinally(state, this);
903
+ }
905
904
  }
906
905
  }
907
906
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -920,18 +919,18 @@ class StableBrowser {
920
919
  };
921
920
  try {
922
921
  await _preCommand(state, this);
923
- if (state.options && state.options.context) {
924
- state.selectors.locators[0].text = state.options.context;
925
- }
922
+ // if (state.options && state.options.context) {
923
+ // state.selectors.locators[0].text = state.options.context;
924
+ // }
926
925
  try {
927
926
  await state.element.click();
928
- await new Promise((resolve) => setTimeout(resolve, 1000));
927
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
929
928
  }
930
929
  catch (e) {
931
930
  // await this.closeUnexpectedPopups();
932
931
  state.element = await this._locate(selectors, state.info, _params);
933
932
  await state.element.dispatchEvent("click");
934
- await new Promise((resolve) => setTimeout(resolve, 1000));
933
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
935
934
  }
936
935
  await this.waitForPageLoad();
937
936
  return state.info;
@@ -1010,7 +1009,6 @@ class StableBrowser {
1010
1009
  await state.element.hover({ timeout: 10000 });
1011
1010
  await new Promise((resolve) => setTimeout(resolve, 1000));
1012
1011
  }
1013
- await _screenshot(state, this);
1014
1012
  await this.waitForPageLoad();
1015
1013
  return state.info;
1016
1014
  }
@@ -1282,7 +1280,12 @@ class StableBrowser {
1282
1280
  await this.waitForPageLoad();
1283
1281
  }
1284
1282
  else if (enter === false) {
1285
- await state.element.dispatchEvent("change");
1283
+ try {
1284
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1285
+ }
1286
+ catch (e) {
1287
+ // ignore
1288
+ }
1286
1289
  //await this.page.keyboard.press("Tab");
1287
1290
  }
1288
1291
  else {
@@ -1339,6 +1342,7 @@ class StableBrowser {
1339
1342
  let screenshotPath = null;
1340
1343
  if (!info.log) {
1341
1344
  info.log = "";
1345
+ info.locatorLog = new LocatorLog(selectors);
1342
1346
  }
1343
1347
  info.operation = "getText";
1344
1348
  info.selectors = selectors;
@@ -1677,11 +1681,9 @@ class StableBrowser {
1677
1681
  if (!fs.existsSync(world.screenshotPath)) {
1678
1682
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1679
1683
  }
1680
- let nextIndex = 1;
1681
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1682
- nextIndex++;
1683
- }
1684
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1684
+ // to make sure the path doesn't start with -
1685
+ const uuidStr = "id_" + randomUUID();
1686
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1685
1687
  try {
1686
1688
  await this.takeScreenshot(screenshotPath);
1687
1689
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1695,7 +1697,7 @@ class StableBrowser {
1695
1697
  catch (e) {
1696
1698
  this.logger.info("unable to take screenshot, ignored");
1697
1699
  }
1698
- result.screenshotId = nextIndex;
1700
+ result.screenshotId = uuidStr;
1699
1701
  result.screenshotPath = screenshotPath;
1700
1702
  if (info && info.box) {
1701
1703
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1823,10 +1825,6 @@ class StableBrowser {
1823
1825
  break;
1824
1826
  }
1825
1827
  state.info.value = state.value;
1826
- this[variable] = state.value;
1827
- if (world) {
1828
- world[variable] = state.value;
1829
- }
1830
1828
  this.setTestData({ [variable]: state.value }, world);
1831
1829
  this.logger.info("set test data: " + variable + "=" + state.value);
1832
1830
  return state.info;
@@ -1838,6 +1836,69 @@ class StableBrowser {
1838
1836
  _commandFinally(state, this);
1839
1837
  }
1840
1838
  }
1839
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1840
+ const state = {
1841
+ selectors,
1842
+ _params,
1843
+ attribute,
1844
+ value,
1845
+ options,
1846
+ world,
1847
+ type: Types.VERIFY_ATTRIBUTE,
1848
+ highlight: true,
1849
+ screenshot: true,
1850
+ text: `Verify element attribute`,
1851
+ operation: "verifyAttribute",
1852
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1853
+ allowDisabled: true,
1854
+ };
1855
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1856
+ let val;
1857
+ try {
1858
+ await _preCommand(state, this);
1859
+ switch (attribute) {
1860
+ case "innerText":
1861
+ val = String(await state.element.innerText());
1862
+ break;
1863
+ case "value":
1864
+ val = String(await state.element.inputValue());
1865
+ break;
1866
+ case "checked":
1867
+ val = String(await state.element.isChecked());
1868
+ break;
1869
+ case "disabled":
1870
+ val = String(await state.element.isDisabled());
1871
+ break;
1872
+ case "readOnly":
1873
+ const isEditable = await state.element.isEditable();
1874
+ val = String(!isEditable);
1875
+ break;
1876
+ default:
1877
+ val = String(await state.element.getAttribute(attribute));
1878
+ break;
1879
+ }
1880
+ state.info.expectedValue = val;
1881
+ let regex;
1882
+ if (value.startsWith("/") && value.endsWith("/")) {
1883
+ const patternBody = value.slice(1, -1);
1884
+ regex = new RegExp(patternBody, "g");
1885
+ }
1886
+ else {
1887
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1888
+ regex = new RegExp(escapedPattern, "g");
1889
+ }
1890
+ if (!val.match(regex)) {
1891
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1892
+ }
1893
+ return state.info;
1894
+ }
1895
+ catch (e) {
1896
+ await _commandError(state, e, this);
1897
+ }
1898
+ finally {
1899
+ _commandFinally(state, this);
1900
+ }
1901
+ }
1841
1902
  async extractEmailData(emailAddress, options, world) {
1842
1903
  if (!emailAddress) {
1843
1904
  throw new Error("email address is null");
@@ -1856,7 +1917,7 @@ class StableBrowser {
1856
1917
  if (options && options.timeout) {
1857
1918
  timeout = options.timeout;
1858
1919
  }
1859
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1920
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1860
1921
  const request = {
1861
1922
  method: "POST",
1862
1923
  url: serviceUrl,
@@ -1933,15 +1994,15 @@ class StableBrowser {
1933
1994
  scope
1934
1995
  .evaluate((node) => {
1935
1996
  if (node && node.style) {
1936
- let originalBorder = node.style.border;
1937
- node.style.border = "2px solid red";
1997
+ let originalBorder = node.style.outline;
1998
+ node.style.outline = "2px solid red";
1938
1999
  if (window) {
1939
2000
  window.addEventListener("beforeunload", function (e) {
1940
- node.style.border = originalBorder;
2001
+ node.style.outline = originalBorder;
1941
2002
  });
1942
2003
  }
1943
2004
  setTimeout(function () {
1944
- node.style.border = originalBorder;
2005
+ node.style.outline = originalBorder;
1945
2006
  }, 2000);
1946
2007
  }
1947
2008
  })
@@ -1963,17 +2024,17 @@ class StableBrowser {
1963
2024
  if (!element.style) {
1964
2025
  return;
1965
2026
  }
1966
- var originalBorder = element.style.border;
2027
+ var originalBorder = element.style.outline;
1967
2028
  // Set the new border to be red and 2px solid
1968
- element.style.border = "2px solid red";
2029
+ element.style.outline = "2px solid red";
1969
2030
  if (window) {
1970
2031
  window.addEventListener("beforeunload", function (e) {
1971
- element.style.border = originalBorder;
2032
+ element.style.outline = originalBorder;
1972
2033
  });
1973
2034
  }
1974
2035
  // Set a timeout to revert to the original border after 2 seconds
1975
2036
  setTimeout(function () {
1976
- element.style.border = originalBorder;
2037
+ element.style.outline = originalBorder;
1977
2038
  }, 2000);
1978
2039
  }
1979
2040
  return;
@@ -2029,7 +2090,7 @@ class StableBrowser {
2029
2090
  }
2030
2091
  finally {
2031
2092
  const endTime = Date.now();
2032
- this._reportToWorld(world, {
2093
+ _reportToWorld(world, {
2033
2094
  type: Types.VERIFY_PAGE_PATH,
2034
2095
  text: "Verify page path",
2035
2096
  screenshotId,
@@ -2049,6 +2110,35 @@ class StableBrowser {
2049
2110
  });
2050
2111
  }
2051
2112
  }
2113
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2114
+ const frames = this.page.frames();
2115
+ let results = [];
2116
+ let ignoreCase = false;
2117
+ for (let i = 0; i < frames.length; i++) {
2118
+ if (dateAlternatives.date) {
2119
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2120
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2121
+ result.frame = frames[i];
2122
+ results.push(result);
2123
+ }
2124
+ }
2125
+ else if (numberAlternatives.number) {
2126
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2127
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2128
+ result.frame = frames[i];
2129
+ results.push(result);
2130
+ }
2131
+ }
2132
+ else {
2133
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2134
+ result.frame = frames[i];
2135
+ results.push(result);
2136
+ }
2137
+ }
2138
+ state.info.results = results;
2139
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2140
+ return resultWithElementsFound;
2141
+ }
2052
2142
  async verifyTextExistInPage(text, options = {}, world = null) {
2053
2143
  text = unEscapeString(text);
2054
2144
  const state = {
@@ -2058,7 +2148,7 @@ class StableBrowser {
2058
2148
  locate: false,
2059
2149
  scroll: false,
2060
2150
  highlight: false,
2061
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2151
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2062
2152
  text: `Verify text exists in page`,
2063
2153
  operation: "verifyTextExistInPage",
2064
2154
  log: "***** verify text " + text + " exists in page *****\n",
@@ -2076,31 +2166,7 @@ class StableBrowser {
2076
2166
  await _preCommand(state, this);
2077
2167
  state.info.text = text;
2078
2168
  while (true) {
2079
- const frames = this.page.frames();
2080
- let results = [];
2081
- for (let i = 0; i < frames.length; i++) {
2082
- if (dateAlternatives.date) {
2083
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2084
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2085
- result.frame = frames[i];
2086
- results.push(result);
2087
- }
2088
- }
2089
- else if (numberAlternatives.number) {
2090
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2091
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2092
- result.frame = frames[i];
2093
- results.push(result);
2094
- }
2095
- }
2096
- else {
2097
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2098
- result.frame = frames[i];
2099
- results.push(result);
2100
- }
2101
- }
2102
- state.info.results = results;
2103
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2169
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2104
2170
  if (resultWithElementsFound.length === 0) {
2105
2171
  if (Date.now() - state.startTime > timeout) {
2106
2172
  throw new Error(`Text ${text} not found in page`);
@@ -2110,9 +2176,9 @@ class StableBrowser {
2110
2176
  }
2111
2177
  if (resultWithElementsFound[0].randomToken) {
2112
2178
  const frame = resultWithElementsFound[0].frame;
2113
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2179
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2114
2180
  await this._highlightElements(frame, dataAttribute);
2115
- const element = await frame.$(dataAttribute);
2181
+ const element = await frame.locator(dataAttribute).first();
2116
2182
  if (element) {
2117
2183
  await this.scrollIfNeeded(element, state.info);
2118
2184
  await element.dispatchEvent("bvt_verify_page_contains_text");
@@ -2157,31 +2223,7 @@ class StableBrowser {
2157
2223
  await _preCommand(state, this);
2158
2224
  state.info.text = text;
2159
2225
  while (true) {
2160
- const frames = this.page.frames();
2161
- let results = [];
2162
- for (let i = 0; i < frames.length; i++) {
2163
- if (dateAlternatives.date) {
2164
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2165
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2166
- result.frame = frames[i];
2167
- results.push(result);
2168
- }
2169
- }
2170
- else if (numberAlternatives.number) {
2171
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2172
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2173
- result.frame = frames[i];
2174
- results.push(result);
2175
- }
2176
- }
2177
- else {
2178
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2179
- result.frame = frames[i];
2180
- results.push(result);
2181
- }
2182
- }
2183
- state.info.results = results;
2184
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2226
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2185
2227
  if (resultWithElementsFound.length === 0) {
2186
2228
  await _screenshot(state, this);
2187
2229
  return state.info;
@@ -2199,15 +2241,88 @@ class StableBrowser {
2199
2241
  _commandFinally(state, this);
2200
2242
  }
2201
2243
  }
2202
- _getServerUrl() {
2203
- let serviceUrl = "https://api.blinq.io";
2204
- if (process.env.NODE_ENV_BLINQ === "dev") {
2205
- serviceUrl = "https://dev.api.blinq.io";
2244
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2245
+ textAnchor = unEscapeString(textAnchor);
2246
+ textToVerify = unEscapeString(textToVerify);
2247
+ const state = {
2248
+ text_search: textToVerify,
2249
+ options,
2250
+ world,
2251
+ locate: false,
2252
+ scroll: false,
2253
+ highlight: false,
2254
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2255
+ text: `Verify text with relation to another text`,
2256
+ operation: "verify_text_with_relation",
2257
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2258
+ };
2259
+ const timeout = this._getLoadTimeout(options);
2260
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2261
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2262
+ if (newValue !== textAnchor) {
2263
+ this.logger.info(textAnchor + "=" + newValue);
2264
+ textAnchor = newValue;
2265
+ }
2266
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2267
+ if (newValue !== textToVerify) {
2268
+ this.logger.info(textToVerify + "=" + newValue);
2269
+ textToVerify = newValue;
2270
+ }
2271
+ let dateAlternatives = findDateAlternatives(textToVerify);
2272
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2273
+ let foundAncore = false;
2274
+ try {
2275
+ await _preCommand(state, this);
2276
+ state.info.text = textToVerify;
2277
+ while (true) {
2278
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2279
+ if (resultWithElementsFound.length === 0) {
2280
+ if (Date.now() - state.startTime > timeout) {
2281
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2282
+ }
2283
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2284
+ continue;
2285
+ }
2286
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2287
+ foundAncore = true;
2288
+ const result = resultWithElementsFound[i];
2289
+ const token = result.randomToken;
2290
+ const frame = result.frame;
2291
+ let css = `[data-blinq-id-${token}]`;
2292
+ const climbArray1 = [];
2293
+ for (let i = 0; i < climb; i++) {
2294
+ climbArray1.push("..");
2295
+ }
2296
+ let climbXpath = "xpath=" + climbArray1.join("/");
2297
+ css = css + " >> " + climbXpath;
2298
+ const count = await frame.locator(css).count();
2299
+ for (let j = 0; j < count; j++) {
2300
+ const continer = await frame.locator(css).nth(j);
2301
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2302
+ if (result.elementCount > 0) {
2303
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2304
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2305
+ await this._highlightElements(frame, dataAttribute);
2306
+ //await this._highlightElements(frame, cssAnchor);
2307
+ const element = await frame.locator(dataAttribute).first();
2308
+ if (element) {
2309
+ await this.scrollIfNeeded(element, state.info);
2310
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2311
+ }
2312
+ await _screenshot(state, this);
2313
+ return state.info;
2314
+ }
2315
+ }
2316
+ }
2317
+ }
2318
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2319
+ }
2320
+ catch (e) {
2321
+ await _commandError(state, e, this);
2206
2322
  }
2207
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2208
- serviceUrl = "https://stage.api.blinq.io";
2323
+ finally {
2324
+ _commandFinally(state, this);
2209
2325
  }
2210
- return serviceUrl;
2211
2326
  }
2212
2327
  async visualVerification(text, options = {}, world = null) {
2213
2328
  const startTime = Date.now();
@@ -2223,7 +2338,7 @@ class StableBrowser {
2223
2338
  throw new Error("TOKEN is not set");
2224
2339
  }
2225
2340
  try {
2226
- let serviceUrl = this._getServerUrl();
2341
+ let serviceUrl = _getServerUrl();
2227
2342
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2228
2343
  info.screenshotPath = screenshotPath;
2229
2344
  const screenshot = await this.takeScreenshot();
@@ -2264,7 +2379,7 @@ class StableBrowser {
2264
2379
  }
2265
2380
  finally {
2266
2381
  const endTime = Date.now();
2267
- this._reportToWorld(world, {
2382
+ _reportToWorld(world, {
2268
2383
  type: Types.VERIFY_VISUAL,
2269
2384
  text: "Visual verification",
2270
2385
  screenshotId,
@@ -2312,6 +2427,7 @@ class StableBrowser {
2312
2427
  let screenshotPath = null;
2313
2428
  const info = {};
2314
2429
  info.log = "";
2430
+ info.locatorLog = new LocatorLog(selectors);
2315
2431
  info.operation = "getTableData";
2316
2432
  info.selectors = selectors;
2317
2433
  try {
@@ -2332,7 +2448,7 @@ class StableBrowser {
2332
2448
  }
2333
2449
  finally {
2334
2450
  const endTime = Date.now();
2335
- this._reportToWorld(world, {
2451
+ _reportToWorld(world, {
2336
2452
  element_name: selectors.element_name,
2337
2453
  type: Types.GET_TABLE_DATA,
2338
2454
  text: "Get table data",
@@ -2387,7 +2503,7 @@ class StableBrowser {
2387
2503
  info.operation = "analyzeTable";
2388
2504
  info.selectors = selectors;
2389
2505
  info.query = query;
2390
- query = this._fixUsingParams(query, _params);
2506
+ query = _fixUsingParams(query, _params);
2391
2507
  info.query_fixed = query;
2392
2508
  info.operator = operator;
2393
2509
  info.value = value;
@@ -2498,7 +2614,7 @@ class StableBrowser {
2498
2614
  }
2499
2615
  finally {
2500
2616
  const endTime = Date.now();
2501
- this._reportToWorld(world, {
2617
+ _reportToWorld(world, {
2502
2618
  element_name: selectors.element_name,
2503
2619
  type: Types.ANALYZE_TABLE,
2504
2620
  text: "Analyze table",
@@ -2571,7 +2687,7 @@ class StableBrowser {
2571
2687
  await new Promise((resolve) => setTimeout(resolve, 2000));
2572
2688
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2573
2689
  const endTime = Date.now();
2574
- this._reportToWorld(world, {
2690
+ _reportToWorld(world, {
2575
2691
  type: Types.GET_PAGE_STATUS,
2576
2692
  text: "Wait for page load",
2577
2693
  screenshotId,
@@ -2615,6 +2731,11 @@ class StableBrowser {
2615
2731
  _commandFinally(state, this);
2616
2732
  }
2617
2733
  }
2734
+ saveTestDataAsGlobal(options, world) {
2735
+ const dataFile = this._getDataFile(world);
2736
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2737
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2738
+ }
2618
2739
  async setViewportSize(width, hight, options = {}, world = null) {
2619
2740
  const startTime = Date.now();
2620
2741
  let error = null;
@@ -2638,7 +2759,7 @@ class StableBrowser {
2638
2759
  await new Promise((resolve) => setTimeout(resolve, 2000));
2639
2760
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2640
2761
  const endTime = Date.now();
2641
- this._reportToWorld(world, {
2762
+ _reportToWorld(world, {
2642
2763
  type: Types.SET_VIEWPORT,
2643
2764
  text: "set viewport size to " + width + "x" + hight,
2644
2765
  screenshotId,
@@ -2675,7 +2796,7 @@ class StableBrowser {
2675
2796
  await new Promise((resolve) => setTimeout(resolve, 2000));
2676
2797
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2677
2798
  const endTime = Date.now();
2678
- this._reportToWorld(world, {
2799
+ _reportToWorld(world, {
2679
2800
  type: Types.GET_PAGE_STATUS,
2680
2801
  text: "page relaod",
2681
2802
  screenshotId,
@@ -2711,11 +2832,45 @@ class StableBrowser {
2711
2832
  console.log("#-#");
2712
2833
  }
2713
2834
  }
2714
- _reportToWorld(world, properties) {
2715
- if (!world || !world.attach) {
2716
- return;
2835
+ async beforeStep(world, step) {
2836
+ if (this.stepIndex === undefined) {
2837
+ this.stepIndex = 0;
2838
+ }
2839
+ else {
2840
+ this.stepIndex++;
2841
+ }
2842
+ if (step && step.pickleStep && step.pickleStep.text) {
2843
+ this.stepName = step.pickleStep.text;
2844
+ this.logger.info("step: " + this.stepName);
2845
+ }
2846
+ else if (step && step.text) {
2847
+ this.stepName = step.text;
2848
+ }
2849
+ else {
2850
+ this.stepName = "step " + this.stepIndex;
2851
+ }
2852
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2853
+ if (this.context.browserObject.context) {
2854
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2855
+ }
2856
+ }
2857
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2858
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2859
+ // check if @global_test_data tag is present
2860
+ if (this.tags.includes("@global_test_data")) {
2861
+ this.saveTestDataAsGlobal({}, world);
2862
+ }
2863
+ }
2864
+ }
2865
+ async afterStep(world, step) {
2866
+ this.stepName = null;
2867
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2868
+ if (this.context.browserObject.context) {
2869
+ await this.context.browserObject.context.tracing.stopChunk({
2870
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2871
+ });
2872
+ }
2717
2873
  }
2718
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2719
2874
  }
2720
2875
  }
2721
2876
  function createTimedPromise(promise, label) {
@@ -2723,156 +2878,5 @@ function createTimedPromise(promise, label) {
2723
2878
  .then((result) => ({ status: "fulfilled", label, result }))
2724
2879
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2725
2880
  }
2726
- const KEYBOARD_EVENTS = [
2727
- "ALT",
2728
- "AltGraph",
2729
- "CapsLock",
2730
- "Control",
2731
- "Fn",
2732
- "FnLock",
2733
- "Hyper",
2734
- "Meta",
2735
- "NumLock",
2736
- "ScrollLock",
2737
- "Shift",
2738
- "Super",
2739
- "Symbol",
2740
- "SymbolLock",
2741
- "Enter",
2742
- "Tab",
2743
- "ArrowDown",
2744
- "ArrowLeft",
2745
- "ArrowRight",
2746
- "ArrowUp",
2747
- "End",
2748
- "Home",
2749
- "PageDown",
2750
- "PageUp",
2751
- "Backspace",
2752
- "Clear",
2753
- "Copy",
2754
- "CrSel",
2755
- "Cut",
2756
- "Delete",
2757
- "EraseEof",
2758
- "ExSel",
2759
- "Insert",
2760
- "Paste",
2761
- "Redo",
2762
- "Undo",
2763
- "Accept",
2764
- "Again",
2765
- "Attn",
2766
- "Cancel",
2767
- "ContextMenu",
2768
- "Escape",
2769
- "Execute",
2770
- "Find",
2771
- "Finish",
2772
- "Help",
2773
- "Pause",
2774
- "Play",
2775
- "Props",
2776
- "Select",
2777
- "ZoomIn",
2778
- "ZoomOut",
2779
- "BrightnessDown",
2780
- "BrightnessUp",
2781
- "Eject",
2782
- "LogOff",
2783
- "Power",
2784
- "PowerOff",
2785
- "PrintScreen",
2786
- "Hibernate",
2787
- "Standby",
2788
- "WakeUp",
2789
- "AllCandidates",
2790
- "Alphanumeric",
2791
- "CodeInput",
2792
- "Compose",
2793
- "Convert",
2794
- "Dead",
2795
- "FinalMode",
2796
- "GroupFirst",
2797
- "GroupLast",
2798
- "GroupNext",
2799
- "GroupPrevious",
2800
- "ModeChange",
2801
- "NextCandidate",
2802
- "NonConvert",
2803
- "PreviousCandidate",
2804
- "Process",
2805
- "SingleCandidate",
2806
- "HangulMode",
2807
- "HanjaMode",
2808
- "JunjaMode",
2809
- "Eisu",
2810
- "Hankaku",
2811
- "Hiragana",
2812
- "HiraganaKatakana",
2813
- "KanaMode",
2814
- "KanjiMode",
2815
- "Katakana",
2816
- "Romaji",
2817
- "Zenkaku",
2818
- "ZenkakuHanaku",
2819
- "F1",
2820
- "F2",
2821
- "F3",
2822
- "F4",
2823
- "F5",
2824
- "F6",
2825
- "F7",
2826
- "F8",
2827
- "F9",
2828
- "F10",
2829
- "F11",
2830
- "F12",
2831
- "Soft1",
2832
- "Soft2",
2833
- "Soft3",
2834
- "Soft4",
2835
- "ChannelDown",
2836
- "ChannelUp",
2837
- "Close",
2838
- "MailForward",
2839
- "MailReply",
2840
- "MailSend",
2841
- "MediaFastForward",
2842
- "MediaPause",
2843
- "MediaPlay",
2844
- "MediaPlayPause",
2845
- "MediaRecord",
2846
- "MediaRewind",
2847
- "MediaStop",
2848
- "MediaTrackNext",
2849
- "MediaTrackPrevious",
2850
- "AudioBalanceLeft",
2851
- "AudioBalanceRight",
2852
- "AudioBassBoostDown",
2853
- "AudioBassBoostToggle",
2854
- "AudioBassBoostUp",
2855
- "AudioFaderFront",
2856
- "AudioFaderRear",
2857
- "AudioSurroundModeNext",
2858
- "AudioTrebleDown",
2859
- "AudioTrebleUp",
2860
- "AudioVolumeDown",
2861
- "AudioVolumeMute",
2862
- "AudioVolumeUp",
2863
- "MicrophoneToggle",
2864
- "MicrophoneVolumeDown",
2865
- "MicrophoneVolumeMute",
2866
- "MicrophoneVolumeUp",
2867
- "TV",
2868
- "TV3DMode",
2869
- "TVAntennaCable",
2870
- "TVAudioDescription",
2871
- ];
2872
- function unEscapeString(str) {
2873
- const placeholder = "__NEWLINE__";
2874
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2875
- return str;
2876
- }
2877
2881
  export { StableBrowser };
2878
2882
  //# sourceMappingURL=stable_browser.js.map