automation_model 1.0.507-dev → 1.0.507-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 +55 -17
  35. package/lib/stable_browser.js +520 -451
  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",
@@ -44,6 +48,9 @@ const Types = {
44
48
  VERIFY_VISUAL: "verify_visual",
45
49
  LOAD_DATA: "load_data",
46
50
  SET_INPUT: "set_input",
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",
47
54
  };
48
55
  export const apps = {};
49
56
  class StableBrowser {
@@ -57,6 +64,7 @@ class StableBrowser {
57
64
  networkLogger = null;
58
65
  configuration = null;
59
66
  appName = "main";
67
+ tags = null;
60
68
  constructor(browser, page, logger = null, context = null, world = null) {
61
69
  this.browser = browser;
62
70
  this.page = page;
@@ -87,17 +95,17 @@ class StableBrowser {
87
95
  catch (e) {
88
96
  this.logger.error("unable to read ai_config.json");
89
97
  }
90
- context.pageLoading = { status: false };
91
- context.pages = [this.page];
92
98
  const logFolder = path.join(this.project_path, "logs", "web");
93
99
  this.world = world;
100
+ context.pages = [this.page];
101
+ context.pageLoading = { status: false };
94
102
  this.registerEventListeners(this.context);
95
103
  registerNetworkEvents(this.world, this, this.context, this.page);
96
104
  registerDownloadEvent(this.page, this.world, this.context);
97
105
  }
98
106
  registerEventListeners(context) {
99
107
  this.registerConsoleLogListener(this.page, context);
100
- this.registerRequestListener(this.page, context, this.webLogFile);
108
+ // this.registerRequestListener(this.page, context, this.webLogFile);
101
109
  if (!context.pageLoading) {
102
110
  context.pageLoading = { status: false };
103
111
  }
@@ -142,9 +150,9 @@ class StableBrowser {
142
150
  if (this.appName === appName) {
143
151
  return;
144
152
  }
145
- let newContextCreated = false;
153
+ let navigate = false;
146
154
  if (!apps[appName]) {
147
- 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);
148
156
  newContextCreated = true;
149
157
  apps[appName] = {
150
158
  context: newContext,
@@ -153,32 +161,15 @@ class StableBrowser {
153
161
  };
154
162
  }
155
163
  const tempContext = {};
156
- this._copyContext(this, tempContext);
157
- this._copyContext(apps[appName], this);
164
+ _copyContext(this, tempContext);
165
+ _copyContext(apps[appName], this);
158
166
  apps[this.appName] = tempContext;
159
167
  this.appName = appName;
160
- if (newContextCreated) {
161
- this.registerEventListeners(this.context);
168
+ if (navigate) {
162
169
  await this.goto(this.context.environment.baseUrl);
163
170
  await this.waitForPageLoad();
164
171
  }
165
172
  }
166
- _copyContext(from, to) {
167
- to.browser = from.browser;
168
- to.page = from.page;
169
- to.context = from.context;
170
- }
171
- getWebLogFile(logFolder) {
172
- if (!fs.existsSync(logFolder)) {
173
- fs.mkdirSync(logFolder, { recursive: true });
174
- }
175
- let nextIndex = 1;
176
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
177
- nextIndex++;
178
- }
179
- const fileName = nextIndex + ".json";
180
- return path.join(logFolder, fileName);
181
- }
182
173
  registerConsoleLogListener(page, context) {
183
174
  if (!this.context.webLogger) {
184
175
  this.context.webLogger = [];
@@ -228,7 +219,7 @@ class StableBrowser {
228
219
  this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
229
220
  }
230
221
  catch (error) {
231
- console.error("Error in request listener", error);
222
+ // console.error("Error in request listener", error);
232
223
  context.networkLogger.push({
233
224
  error: "not able to listen",
234
225
  message: error.message,
@@ -242,55 +233,48 @@ class StableBrowser {
242
233
  // async closeUnexpectedPopups() {
243
234
  // await closeUnexpectedPopups(this.page);
244
235
  // }
245
- async goto(url) {
236
+ async goto(url, world = null) {
246
237
  if (!url.startsWith("http")) {
247
238
  url = "https://" + url;
248
239
  }
249
- await this.page.goto(url, {
250
- timeout: 60000,
251
- });
252
- }
253
- _fixUsingParams(text, _params) {
254
- if (!_params || typeof text !== "string") {
255
- 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);
256
259
  }
257
- for (let key in _params) {
258
- let regValue = key;
259
- if (key.startsWith("_")) {
260
- // remove the _ prefix
261
- regValue = key.substring(1);
262
- }
263
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
260
+ catch (error) {
261
+ console.error("Error on goto", error);
262
+ _commandError(state, error, this);
264
263
  }
265
- return text;
266
- }
267
- _fixLocatorUsingParams(locator, _params) {
268
- // check if not null
269
- if (!locator) {
270
- return locator;
264
+ finally {
265
+ _commandFinally(state, this);
271
266
  }
272
- // clone the locator
273
- locator = JSON.parse(JSON.stringify(locator));
274
- this.scanAndManipulate(locator, _params);
275
- return locator;
276
267
  }
277
- _isObject(value) {
278
- return value && typeof value === "object" && value.constructor === Object;
279
- }
280
- scanAndManipulate(currentObj, _params) {
281
- for (const key in currentObj) {
282
- if (typeof currentObj[key] === "string") {
283
- // Perform string manipulation
284
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
285
- }
286
- else if (this._isObject(currentObj[key])) {
287
- // Recursively scan nested objects
288
- 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);
289
276
  }
290
277
  }
291
- }
292
- _getLocator(locator, scope, _params) {
293
- locator = this._fixLocatorUsingParams(locator, _params);
294
278
  let locatorReturn;
295
279
  if (locator.role) {
296
280
  if (locator.role[1].nameReg) {
@@ -298,7 +282,7 @@ class StableBrowser {
298
282
  delete locator.role[1].nameReg;
299
283
  }
300
284
  // if (locator.role[1].name) {
301
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
285
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
302
286
  // }
303
287
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
304
288
  }
@@ -341,143 +325,69 @@ class StableBrowser {
341
325
  if (css && css.locator) {
342
326
  css = css.locator;
343
327
  }
344
- 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);
345
329
  if (result.elementCount === 0) {
346
330
  return;
347
331
  }
348
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
332
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
349
333
  // css climb to parent element
350
334
  const climbArray = [];
351
335
  for (let i = 0; i < climb; i++) {
352
336
  climbArray.push("..");
353
337
  }
354
338
  let climbXpath = "xpath=" + climbArray.join("/");
355
- return textElementCss + " >> " + climbXpath + " >> " + css;
356
- }
357
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
358
- //const stringifyText = JSON.stringify(text);
359
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
360
- function isParent(parent, child) {
361
- let currentNode = child.parentNode;
362
- while (currentNode !== null) {
363
- if (currentNode === parent) {
364
- return true;
365
- }
366
- currentNode = currentNode.parentNode;
367
- }
368
- return false;
369
- }
370
- document.isParent = isParent;
371
- function getRegex(str) {
372
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
373
- if (!match) {
374
- return null;
375
- }
376
- let [_, pattern, flags] = match;
377
- return new RegExp(pattern, flags);
378
- }
379
- document.getRegex = getRegex;
380
- function collectAllShadowDomElements(element, result = []) {
381
- // Check and add the element if it has a shadow root
382
- if (element.shadowRoot) {
383
- result.push(element);
384
- // Also search within the shadow root
385
- document.collectAllShadowDomElements(element.shadowRoot, result);
386
- }
387
- // Iterate over child nodes
388
- element.childNodes.forEach((child) => {
389
- // Recursively call the function for each child node
390
- document.collectAllShadowDomElements(child, result);
391
- });
392
- return result;
393
- }
394
- document.collectAllShadowDomElements = collectAllShadowDomElements;
395
- if (!tag) {
396
- tag = "*:not(script, style, head)";
397
- }
398
- let regexpSearch = document.getRegex(text);
399
- if (regexpSearch) {
400
- regex = true;
401
- }
402
- let elements = Array.from(document.querySelectorAll(tag));
403
- let shadowHosts = [];
404
- document.collectAllShadowDomElements(document, shadowHosts);
405
- for (let i = 0; i < shadowHosts.length; i++) {
406
- let shadowElement = shadowHosts[i].shadowRoot;
407
- if (!shadowElement) {
408
- console.log("shadowElement is null, for host " + shadowHosts[i]);
409
- continue;
410
- }
411
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
412
- elements = elements.concat(shadowElements);
413
- }
414
- let randomToken = null;
415
- const foundElements = [];
416
- if (regex) {
417
- if (!regexpSearch) {
418
- regexpSearch = new RegExp(text, "im");
419
- }
420
- for (let i = 0; i < elements.length; i++) {
421
- const element = elements[i];
422
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
423
- (element.value && regexpSearch.test(element.value))) {
424
- foundElements.push(element);
425
- }
426
- }
427
- }
428
- else {
429
- text = text.trim();
430
- for (let i = 0; i < elements.length; i++) {
431
- const element = elements[i];
432
- if (partial) {
433
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
434
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
435
- foundElements.push(element);
436
- }
437
- }
438
- else {
439
- if ((element.innerText && element.innerText.trim() === text) ||
440
- (element.value && element.value === text)) {
441
- foundElements.push(element);
442
- }
443
- }
444
- }
445
- }
446
- let noChildElements = [];
447
- for (let i = 0; i < foundElements.length; i++) {
448
- let element = foundElements[i];
449
- let hasChild = false;
450
- for (let j = 0; j < foundElements.length; j++) {
451
- if (i === j) {
452
- continue;
453
- }
454
- if (isParent(element, foundElements[j])) {
455
- hasChild = true;
456
- break;
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;
457
361
  }
458
362
  }
459
- if (!hasChild) {
460
- noChildElements.push(element);
461
- }
462
- }
463
- let elementCount = 0;
464
- if (noChildElements.length > 0) {
465
- for (let i = 0; i < noChildElements.length; i++) {
466
- if (randomToken === null) {
467
- randomToken = Math.random().toString(36).substring(7);
468
- }
469
- let element = noChildElements[i];
470
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
471
- elementCount++;
363
+ if (!el.setAttribute) {
364
+ el = el.parentElement;
472
365
  }
366
+ el.setAttribute("data-blinq-id-" + randomToken, "");
367
+ return true;
368
+ }, [tag1, randomToken]))) {
369
+ continue;
473
370
  }
474
- return { elementCount: elementCount, randomToken: randomToken };
475
- }, [text1, tag1, regex1, partial1]);
371
+ tagCount++;
372
+ }
373
+ return { elementCount: tagCount, randomToken };
476
374
  }
477
- 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
+ }
478
386
  let locatorSearch = selectorHierarchy[index];
387
+ let originalLocatorSearch = "";
479
388
  try {
480
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
389
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
390
+ locatorSearch = JSON.parse(originalLocatorSearch);
481
391
  }
482
392
  catch (e) {
483
393
  console.error(e);
@@ -491,24 +401,24 @@ class StableBrowser {
491
401
  info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
492
402
  return;
493
403
  }
494
- locator = this._getLocator({ css: locatorString }, scope, _params);
404
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
495
405
  }
496
406
  else if (locatorSearch.text) {
497
- let text = this._fixUsingParams(locatorSearch.text, _params);
498
- 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);
499
409
  if (result.elementCount === 0) {
500
410
  info.failCause.textNotFound = true;
501
411
  info.failCause.lastError = "failed to locate element by text: " + text;
502
412
  return;
503
413
  }
504
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
414
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
505
415
  if (locatorSearch.childCss) {
506
416
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
507
417
  }
508
- locator = this._getLocator(locatorSearch, scope, _params);
418
+ locator = await this._getLocator(locatorSearch, scope, _params);
509
419
  }
510
420
  else {
511
- locator = this._getLocator(locatorSearch, scope, _params);
421
+ locator = await this._getLocator(locatorSearch, scope, _params);
512
422
  }
513
423
  // let cssHref = false;
514
424
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -521,18 +431,27 @@ class StableBrowser {
521
431
  //info.log += "total elements found " + count + "\n";
522
432
  //let visibleCount = 0;
523
433
  let visibleLocator = null;
524
- if (locatorSearch.index && locatorSearch.index < count) {
434
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
525
435
  foundLocators.push(locator.nth(locatorSearch.index));
436
+ if (info.locatorLog) {
437
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
438
+ }
526
439
  return;
527
440
  }
441
+ if (info.locatorLog && count === 0) {
442
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
443
+ }
528
444
  for (let j = 0; j < count; j++) {
529
445
  let visible = await locator.nth(j).isVisible();
530
446
  const enabled = await locator.nth(j).isEnabled();
531
447
  if (!visibleOnly) {
532
448
  visible = true;
533
449
  }
534
- if (visible && enabled) {
450
+ if (visible && (allowDisabled || enabled)) {
535
451
  foundLocators.push(locator.nth(j));
452
+ if (info.locatorLog) {
453
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
454
+ }
536
455
  }
537
456
  else {
538
457
  info.failCause.visible = visible;
@@ -540,8 +459,14 @@ class StableBrowser {
540
459
  if (!info.printMessages) {
541
460
  info.printMessages = {};
542
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
+ }
543
468
  if (!info.printMessages[j.toString()]) {
544
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
469
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
545
470
  info.printMessages[j.toString()] = true;
546
471
  }
547
472
  }
@@ -557,7 +482,7 @@ class StableBrowser {
557
482
  if (!info) {
558
483
  info = {};
559
484
  }
560
- info.log += "scan for popup handlers" + "\n";
485
+ //info.log += "scan for popup handlers" + "\n";
561
486
  const handlerGroup = [];
562
487
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
563
488
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -584,16 +509,28 @@ class StableBrowser {
584
509
  }
585
510
  if (result.foundElements.length > 0) {
586
511
  let dialogCloseLocator = result.foundElements[0].locator;
587
- await dialogCloseLocator.click();
588
- // wait for the dialog to close
589
- 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
+ }
590
527
  return { rerun: true };
591
528
  }
592
529
  }
593
530
  }
594
531
  return { rerun: false };
595
532
  }
596
- async _locate(selectors, info, _params, timeout) {
533
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
597
534
  if (!timeout) {
598
535
  timeout = 30000;
599
536
  }
@@ -603,7 +540,7 @@ class StableBrowser {
603
540
  let selector = selectors.locators[j];
604
541
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
605
542
  }
606
- let element = await this._locate_internal(selectors, info, _params, timeout);
543
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
607
544
  if (!element.rerun) {
608
545
  return element;
609
546
  }
@@ -616,6 +553,7 @@ class StableBrowser {
616
553
  info.failCause = {};
617
554
  info.log = "";
618
555
  }
556
+ let startTime = Date.now();
619
557
  let scope = this.page;
620
558
  if (selectors.frame) {
621
559
  return selectors.frame;
@@ -646,9 +584,11 @@ class StableBrowser {
646
584
  }
647
585
  return framescope;
648
586
  };
587
+ let fLocator = null;
649
588
  while (true) {
650
589
  let frameFound = false;
651
590
  if (selectors.nestFrmLoc) {
591
+ fLocator = selectors.nestFrmLoc;
652
592
  scope = await findFrame(selectors.nestFrmLoc, scope);
653
593
  frameFound = true;
654
594
  break;
@@ -657,6 +597,7 @@ class StableBrowser {
657
597
  for (let i = 0; i < selectors.frameLocators.length; i++) {
658
598
  let frameLocator = selectors.frameLocators[i];
659
599
  if (frameLocator.css) {
600
+ fLocator = frameLocator.css;
660
601
  scope = scope.frameLocator(frameLocator.css);
661
602
  frameFound = true;
662
603
  break;
@@ -664,11 +605,15 @@ class StableBrowser {
664
605
  }
665
606
  }
666
607
  if (!frameFound && selectors.iframe_src) {
608
+ fLocator = selectors.iframe_src;
667
609
  scope = this.page.frame({ url: selectors.iframe_src });
668
610
  }
669
611
  if (!scope) {
670
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
671
- 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) {
672
617
  info.failCause.iframeNotFound = true;
673
618
  info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
674
619
  throw new Error("unable to locate iframe " + selectors.iframe_src);
@@ -676,6 +621,9 @@ class StableBrowser {
676
621
  await new Promise((resolve) => setTimeout(resolve, 1000));
677
622
  }
678
623
  else {
624
+ if (info && info.locatorLog) {
625
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
626
+ }
679
627
  break;
680
628
  }
681
629
  }
@@ -692,16 +640,18 @@ class StableBrowser {
692
640
  return bodyContent;
693
641
  });
694
642
  }
695
- async _locate_internal(selectors, info, _params, timeout = 30000) {
643
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
696
644
  if (!info) {
697
645
  info = {};
698
646
  info.failCause = {};
699
647
  info.log = "";
648
+ info.locatorLog = new LocatorLog(selectors);
700
649
  }
701
650
  let highPriorityTimeout = 5000;
702
651
  let visibleOnlyTimeout = 6000;
703
- let startTime = performance.now();
652
+ let startTime = Date.now();
704
653
  let locatorsCount = 0;
654
+ let lazy_scroll = false;
705
655
  //let arrayMode = Array.isArray(selectors);
706
656
  let scope = await this._findFrameScope(selectors, timeout, info);
707
657
  let selectorsLocators = null;
@@ -739,17 +689,17 @@ class StableBrowser {
739
689
  }
740
690
  // info.log += "scanning locators in priority 1" + "\n";
741
691
  let onlyPriority3 = selectorsLocators[0].priority === 3;
742
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
692
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled);
743
693
  if (result.foundElements.length === 0) {
744
694
  // info.log += "scanning locators in priority 2" + "\n";
745
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
695
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled);
746
696
  }
747
697
  if (result.foundElements.length === 0 && onlyPriority3) {
748
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
698
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
749
699
  }
750
700
  else {
751
701
  if (result.foundElements.length === 0 && !highPriorityOnly) {
752
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
702
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
753
703
  }
754
704
  }
755
705
  let foundElements = result.foundElements;
@@ -790,26 +740,36 @@ class StableBrowser {
790
740
  return maxCountElement.locator;
791
741
  }
792
742
  }
793
- if (performance.now() - startTime > timeout) {
743
+ if (Date.now() - startTime > timeout) {
794
744
  break;
795
745
  }
796
- if (performance.now() - startTime > highPriorityTimeout) {
797
- 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";
798
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
+ }
799
753
  }
800
- if (performance.now() - startTime > visibleOnlyTimeout) {
801
- 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";
802
756
  visibleOnly = false;
803
757
  }
804
758
  await new Promise((resolve) => setTimeout(resolve, 1000));
805
759
  }
806
760
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
807
- 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";
808
768
  info.failCause.locatorNotFound = true;
809
769
  info.failCause.lastError = "failed to locate unique element";
810
770
  throw new Error("failed to locate first element no elements found, " + info.log);
811
771
  }
812
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
772
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false) {
813
773
  let foundElements = [];
814
774
  const result = {
815
775
  foundElements: foundElements,
@@ -817,14 +777,15 @@ class StableBrowser {
817
777
  for (let i = 0; i < locatorsGroup.length; i++) {
818
778
  let foundLocators = [];
819
779
  try {
820
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
780
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled);
821
781
  }
822
782
  catch (e) {
823
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
824
- 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);
825
786
  foundLocators = [];
826
787
  try {
827
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
788
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled);
828
789
  }
829
790
  catch (e) {
830
791
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -840,11 +801,27 @@ class StableBrowser {
840
801
  }
841
802
  if (foundLocators.length > 1) {
842
803
  info.failCause.foundMultiple = true;
804
+ if (info.locatorLog) {
805
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
806
+ }
843
807
  }
844
808
  }
845
809
  return result;
846
810
  }
847
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);
848
825
  const startTime = Date.now();
849
826
  let timeout = 30000;
850
827
  if (options && options.timeout) {
@@ -869,13 +846,31 @@ class StableBrowser {
869
846
  catch (e) {
870
847
  if (performance.now() - startTime > timeout) {
871
848
  // throw e;
872
- 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
+ }
873
855
  }
874
856
  }
875
857
  await new Promise((resolve) => setTimeout(resolve, 3000));
876
858
  }
877
859
  }
878
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);
879
874
  const startTime = Date.now();
880
875
  let timeout = 30000;
881
876
  if (options && options.timeout) {
@@ -900,7 +895,12 @@ class StableBrowser {
900
895
  catch (e) {
901
896
  if (performance.now() - startTime > timeout) {
902
897
  // throw e;
903
- 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
+ }
904
904
  }
905
905
  }
906
906
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -919,18 +919,18 @@ class StableBrowser {
919
919
  };
920
920
  try {
921
921
  await _preCommand(state, this);
922
- if (state.options && state.options.context) {
923
- state.selectors.locators[0].text = state.options.context;
924
- }
922
+ // if (state.options && state.options.context) {
923
+ // state.selectors.locators[0].text = state.options.context;
924
+ // }
925
925
  try {
926
926
  await state.element.click();
927
- await new Promise((resolve) => setTimeout(resolve, 1000));
927
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
928
928
  }
929
929
  catch (e) {
930
930
  // await this.closeUnexpectedPopups();
931
931
  state.element = await this._locate(selectors, state.info, _params);
932
932
  await state.element.dispatchEvent("click");
933
- await new Promise((resolve) => setTimeout(resolve, 1000));
933
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
934
934
  }
935
935
  await this.waitForPageLoad();
936
936
  return state.info;
@@ -1009,7 +1009,6 @@ class StableBrowser {
1009
1009
  await state.element.hover({ timeout: 10000 });
1010
1010
  await new Promise((resolve) => setTimeout(resolve, 1000));
1011
1011
  }
1012
- await _screenshot(state, this);
1013
1012
  await this.waitForPageLoad();
1014
1013
  return state.info;
1015
1014
  }
@@ -1338,6 +1337,7 @@ class StableBrowser {
1338
1337
  let screenshotPath = null;
1339
1338
  if (!info.log) {
1340
1339
  info.log = "";
1340
+ info.locatorLog = new LocatorLog(selectors);
1341
1341
  }
1342
1342
  info.operation = "getText";
1343
1343
  info.selectors = selectors;
@@ -1676,11 +1676,9 @@ class StableBrowser {
1676
1676
  if (!fs.existsSync(world.screenshotPath)) {
1677
1677
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1678
1678
  }
1679
- let nextIndex = 1;
1680
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1681
- nextIndex++;
1682
- }
1683
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1679
+ // to make sure the path doesn't start with -
1680
+ const uuidStr = "id_" + randomUUID();
1681
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1684
1682
  try {
1685
1683
  await this.takeScreenshot(screenshotPath);
1686
1684
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1694,7 +1692,7 @@ class StableBrowser {
1694
1692
  catch (e) {
1695
1693
  this.logger.info("unable to take screenshot, ignored");
1696
1694
  }
1697
- result.screenshotId = nextIndex;
1695
+ result.screenshotId = uuidStr;
1698
1696
  result.screenshotPath = screenshotPath;
1699
1697
  if (info && info.box) {
1700
1698
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1822,10 +1820,6 @@ class StableBrowser {
1822
1820
  break;
1823
1821
  }
1824
1822
  state.info.value = state.value;
1825
- this[variable] = state.value;
1826
- if (world) {
1827
- world[variable] = state.value;
1828
- }
1829
1823
  this.setTestData({ [variable]: state.value }, world);
1830
1824
  this.logger.info("set test data: " + variable + "=" + state.value);
1831
1825
  return state.info;
@@ -1837,6 +1831,69 @@ class StableBrowser {
1837
1831
  _commandFinally(state, this);
1838
1832
  }
1839
1833
  }
1834
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1835
+ const state = {
1836
+ selectors,
1837
+ _params,
1838
+ attribute,
1839
+ value,
1840
+ options,
1841
+ world,
1842
+ type: Types.VERIFY_ATTRIBUTE,
1843
+ highlight: true,
1844
+ screenshot: true,
1845
+ text: `Verify element attribute`,
1846
+ operation: "verifyAttribute",
1847
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1848
+ allowDisabled: true
1849
+ };
1850
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1851
+ let val;
1852
+ try {
1853
+ await _preCommand(state, this);
1854
+ switch (attribute) {
1855
+ case "innerText":
1856
+ val = String(await state.element.innerText());
1857
+ break;
1858
+ case "value":
1859
+ val = String(await state.element.inputValue());
1860
+ break;
1861
+ case "checked":
1862
+ val = String(await state.element.isChecked());
1863
+ break;
1864
+ case "disabled":
1865
+ val = String(await state.element.isDisabled());
1866
+ break;
1867
+ case "readOnly":
1868
+ const isEditable = await state.element.isEditable();
1869
+ val = String(!isEditable);
1870
+ break;
1871
+ default:
1872
+ val = String(await state.element.getAttribute(attribute));
1873
+ break;
1874
+ }
1875
+ state.info.expectedValue = val;
1876
+ let regex;
1877
+ if (value.startsWith("/") && value.endsWith("/")) {
1878
+ const patternBody = value.slice(1, -1);
1879
+ regex = new RegExp(patternBody, "g");
1880
+ }
1881
+ else {
1882
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1883
+ regex = new RegExp(escapedPattern, "g");
1884
+ }
1885
+ if (!val.match(regex)) {
1886
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1887
+ }
1888
+ return state.info;
1889
+ }
1890
+ catch (e) {
1891
+ await _commandError(state, e, this);
1892
+ }
1893
+ finally {
1894
+ _commandFinally(state, this);
1895
+ }
1896
+ }
1840
1897
  async extractEmailData(emailAddress, options, world) {
1841
1898
  if (!emailAddress) {
1842
1899
  throw new Error("email address is null");
@@ -1855,7 +1912,7 @@ class StableBrowser {
1855
1912
  if (options && options.timeout) {
1856
1913
  timeout = options.timeout;
1857
1914
  }
1858
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1915
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1859
1916
  const request = {
1860
1917
  method: "POST",
1861
1918
  url: serviceUrl,
@@ -1932,15 +1989,15 @@ class StableBrowser {
1932
1989
  scope
1933
1990
  .evaluate((node) => {
1934
1991
  if (node && node.style) {
1935
- let originalBorder = node.style.border;
1936
- node.style.border = "2px solid red";
1992
+ let originalBorder = node.style.outline;
1993
+ node.style.outline = "2px solid red";
1937
1994
  if (window) {
1938
1995
  window.addEventListener("beforeunload", function (e) {
1939
- node.style.border = originalBorder;
1996
+ node.style.outline = originalBorder;
1940
1997
  });
1941
1998
  }
1942
1999
  setTimeout(function () {
1943
- node.style.border = originalBorder;
2000
+ node.style.outline = originalBorder;
1944
2001
  }, 2000);
1945
2002
  }
1946
2003
  })
@@ -1962,17 +2019,17 @@ class StableBrowser {
1962
2019
  if (!element.style) {
1963
2020
  return;
1964
2021
  }
1965
- var originalBorder = element.style.border;
2022
+ var originalBorder = element.style.outline;
1966
2023
  // Set the new border to be red and 2px solid
1967
- element.style.border = "2px solid red";
2024
+ element.style.outline = "2px solid red";
1968
2025
  if (window) {
1969
2026
  window.addEventListener("beforeunload", function (e) {
1970
- element.style.border = originalBorder;
2027
+ element.style.outline = originalBorder;
1971
2028
  });
1972
2029
  }
1973
2030
  // Set a timeout to revert to the original border after 2 seconds
1974
2031
  setTimeout(function () {
1975
- element.style.border = originalBorder;
2032
+ element.style.outline = originalBorder;
1976
2033
  }, 2000);
1977
2034
  }
1978
2035
  return;
@@ -2028,7 +2085,7 @@ class StableBrowser {
2028
2085
  }
2029
2086
  finally {
2030
2087
  const endTime = Date.now();
2031
- this._reportToWorld(world, {
2088
+ _reportToWorld(world, {
2032
2089
  type: Types.VERIFY_PAGE_PATH,
2033
2090
  text: "Verify page path",
2034
2091
  screenshotId,
@@ -2048,6 +2105,35 @@ class StableBrowser {
2048
2105
  });
2049
2106
  }
2050
2107
  }
2108
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2109
+ const frames = this.page.frames();
2110
+ let results = [];
2111
+ let ignoreCase = false;
2112
+ for (let i = 0; i < frames.length; i++) {
2113
+ if (dateAlternatives.date) {
2114
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2115
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2116
+ result.frame = frames[i];
2117
+ results.push(result);
2118
+ }
2119
+ }
2120
+ else if (numberAlternatives.number) {
2121
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2122
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2123
+ result.frame = frames[i];
2124
+ results.push(result);
2125
+ }
2126
+ }
2127
+ else {
2128
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2129
+ result.frame = frames[i];
2130
+ results.push(result);
2131
+ }
2132
+ }
2133
+ state.info.results = results;
2134
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2135
+ return resultWithElementsFound;
2136
+ }
2051
2137
  async verifyTextExistInPage(text, options = {}, world = null) {
2052
2138
  text = unEscapeString(text);
2053
2139
  const state = {
@@ -2057,7 +2143,7 @@ class StableBrowser {
2057
2143
  locate: false,
2058
2144
  scroll: false,
2059
2145
  highlight: false,
2060
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2146
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2061
2147
  text: `Verify text exists in page`,
2062
2148
  operation: "verifyTextExistInPage",
2063
2149
  log: "***** verify text " + text + " exists in page *****\n",
@@ -2075,31 +2161,7 @@ class StableBrowser {
2075
2161
  await _preCommand(state, this);
2076
2162
  state.info.text = text;
2077
2163
  while (true) {
2078
- const frames = this.page.frames();
2079
- let results = [];
2080
- for (let i = 0; i < frames.length; i++) {
2081
- if (dateAlternatives.date) {
2082
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2083
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2084
- result.frame = frames[i];
2085
- results.push(result);
2086
- }
2087
- }
2088
- else if (numberAlternatives.number) {
2089
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2090
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2091
- result.frame = frames[i];
2092
- results.push(result);
2093
- }
2094
- }
2095
- else {
2096
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2097
- result.frame = frames[i];
2098
- results.push(result);
2099
- }
2100
- }
2101
- state.info.results = results;
2102
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2164
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2103
2165
  if (resultWithElementsFound.length === 0) {
2104
2166
  if (Date.now() - state.startTime > timeout) {
2105
2167
  throw new Error(`Text ${text} not found in page`);
@@ -2109,9 +2171,9 @@ class StableBrowser {
2109
2171
  }
2110
2172
  if (resultWithElementsFound[0].randomToken) {
2111
2173
  const frame = resultWithElementsFound[0].frame;
2112
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2174
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2113
2175
  await this._highlightElements(frame, dataAttribute);
2114
- const element = await frame.$(dataAttribute);
2176
+ const element = await frame.locator(dataAttribute).first();
2115
2177
  if (element) {
2116
2178
  await this.scrollIfNeeded(element, state.info);
2117
2179
  await element.dispatchEvent("bvt_verify_page_contains_text");
@@ -2129,15 +2191,133 @@ class StableBrowser {
2129
2191
  _commandFinally(state, this);
2130
2192
  }
2131
2193
  }
2132
- _getServerUrl() {
2133
- let serviceUrl = "https://api.blinq.io";
2134
- if (process.env.NODE_ENV_BLINQ === "dev") {
2135
- serviceUrl = "https://dev.api.blinq.io";
2194
+ async waitForTextToDisappear(text, options = {}, world = null) {
2195
+ text = unEscapeString(text);
2196
+ const state = {
2197
+ text_search: text,
2198
+ options,
2199
+ world,
2200
+ locate: false,
2201
+ scroll: false,
2202
+ highlight: false,
2203
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2204
+ text: `Verify text does not exist in page`,
2205
+ operation: "verifyTextNotExistInPage",
2206
+ log: "***** verify text " + text + " does not exist in page *****\n",
2207
+ };
2208
+ const timeout = this._getLoadTimeout(options);
2209
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2210
+ const newValue = await this._replaceWithLocalData(text, world);
2211
+ if (newValue !== text) {
2212
+ this.logger.info(text + "=" + newValue);
2213
+ text = newValue;
2214
+ }
2215
+ let dateAlternatives = findDateAlternatives(text);
2216
+ let numberAlternatives = findNumberAlternatives(text);
2217
+ try {
2218
+ await _preCommand(state, this);
2219
+ state.info.text = text;
2220
+ while (true) {
2221
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2222
+ if (resultWithElementsFound.length === 0) {
2223
+ await _screenshot(state, this);
2224
+ return state.info;
2225
+ }
2226
+ if (Date.now() - state.startTime > timeout) {
2227
+ throw new Error(`Text ${text} found in page`);
2228
+ }
2229
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2230
+ }
2231
+ }
2232
+ catch (e) {
2233
+ await _commandError(state, e, this);
2234
+ }
2235
+ finally {
2236
+ _commandFinally(state, this);
2237
+ }
2238
+ }
2239
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2240
+ textAnchor = unEscapeString(textAnchor);
2241
+ textToVerify = unEscapeString(textToVerify);
2242
+ const state = {
2243
+ text_search: textToVerify,
2244
+ options,
2245
+ world,
2246
+ locate: false,
2247
+ scroll: false,
2248
+ highlight: false,
2249
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2250
+ text: `Verify text with relation to another text`,
2251
+ operation: "verify_text_with_relation",
2252
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2253
+ };
2254
+ const timeout = this._getLoadTimeout(options);
2255
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2256
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2257
+ if (newValue !== textAnchor) {
2258
+ this.logger.info(textAnchor + "=" + newValue);
2259
+ textAnchor = newValue;
2260
+ }
2261
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2262
+ if (newValue !== textToVerify) {
2263
+ this.logger.info(textToVerify + "=" + newValue);
2264
+ textToVerify = newValue;
2265
+ }
2266
+ let dateAlternatives = findDateAlternatives(textToVerify);
2267
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2268
+ let foundAncore = false;
2269
+ try {
2270
+ await _preCommand(state, this);
2271
+ state.info.text = textToVerify;
2272
+ while (true) {
2273
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2274
+ if (resultWithElementsFound.length === 0) {
2275
+ if (Date.now() - state.startTime > timeout) {
2276
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2277
+ }
2278
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2279
+ continue;
2280
+ }
2281
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2282
+ foundAncore = true;
2283
+ const result = resultWithElementsFound[i];
2284
+ const token = result.randomToken;
2285
+ const frame = result.frame;
2286
+ let css = `[data-blinq-id-${token}]`;
2287
+ const climbArray1 = [];
2288
+ for (let i = 0; i < climb; i++) {
2289
+ climbArray1.push("..");
2290
+ }
2291
+ let climbXpath = "xpath=" + climbArray1.join("/");
2292
+ css = css + " >> " + climbXpath;
2293
+ const count = await frame.locator(css).count();
2294
+ for (let j = 0; j < count; j++) {
2295
+ const continer = await frame.locator(css).nth(j);
2296
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2297
+ if (result.elementCount > 0) {
2298
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2299
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2300
+ await this._highlightElements(frame, dataAttribute);
2301
+ //await this._highlightElements(frame, cssAnchor);
2302
+ const element = await frame.locator(dataAttribute).first();
2303
+ if (element) {
2304
+ await this.scrollIfNeeded(element, state.info);
2305
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2306
+ }
2307
+ await _screenshot(state, this);
2308
+ return state.info;
2309
+ }
2310
+ }
2311
+ }
2312
+ }
2313
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2136
2314
  }
2137
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2138
- serviceUrl = "https://stage.api.blinq.io";
2315
+ catch (e) {
2316
+ await _commandError(state, e, this);
2317
+ }
2318
+ finally {
2319
+ _commandFinally(state, this);
2139
2320
  }
2140
- return serviceUrl;
2141
2321
  }
2142
2322
  async visualVerification(text, options = {}, world = null) {
2143
2323
  const startTime = Date.now();
@@ -2153,7 +2333,7 @@ class StableBrowser {
2153
2333
  throw new Error("TOKEN is not set");
2154
2334
  }
2155
2335
  try {
2156
- let serviceUrl = this._getServerUrl();
2336
+ let serviceUrl = _getServerUrl();
2157
2337
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2158
2338
  info.screenshotPath = screenshotPath;
2159
2339
  const screenshot = await this.takeScreenshot();
@@ -2194,7 +2374,7 @@ class StableBrowser {
2194
2374
  }
2195
2375
  finally {
2196
2376
  const endTime = Date.now();
2197
- this._reportToWorld(world, {
2377
+ _reportToWorld(world, {
2198
2378
  type: Types.VERIFY_VISUAL,
2199
2379
  text: "Visual verification",
2200
2380
  screenshotId,
@@ -2242,6 +2422,7 @@ class StableBrowser {
2242
2422
  let screenshotPath = null;
2243
2423
  const info = {};
2244
2424
  info.log = "";
2425
+ info.locatorLog = new LocatorLog(selectors);
2245
2426
  info.operation = "getTableData";
2246
2427
  info.selectors = selectors;
2247
2428
  try {
@@ -2262,7 +2443,7 @@ class StableBrowser {
2262
2443
  }
2263
2444
  finally {
2264
2445
  const endTime = Date.now();
2265
- this._reportToWorld(world, {
2446
+ _reportToWorld(world, {
2266
2447
  element_name: selectors.element_name,
2267
2448
  type: Types.GET_TABLE_DATA,
2268
2449
  text: "Get table data",
@@ -2317,7 +2498,7 @@ class StableBrowser {
2317
2498
  info.operation = "analyzeTable";
2318
2499
  info.selectors = selectors;
2319
2500
  info.query = query;
2320
- query = this._fixUsingParams(query, _params);
2501
+ query = _fixUsingParams(query, _params);
2321
2502
  info.query_fixed = query;
2322
2503
  info.operator = operator;
2323
2504
  info.value = value;
@@ -2428,7 +2609,7 @@ class StableBrowser {
2428
2609
  }
2429
2610
  finally {
2430
2611
  const endTime = Date.now();
2431
- this._reportToWorld(world, {
2612
+ _reportToWorld(world, {
2432
2613
  element_name: selectors.element_name,
2433
2614
  type: Types.ANALYZE_TABLE,
2434
2615
  text: "Analyze table",
@@ -2501,7 +2682,7 @@ class StableBrowser {
2501
2682
  await new Promise((resolve) => setTimeout(resolve, 2000));
2502
2683
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2503
2684
  const endTime = Date.now();
2504
- this._reportToWorld(world, {
2685
+ _reportToWorld(world, {
2505
2686
  type: Types.GET_PAGE_STATUS,
2506
2687
  text: "Wait for page load",
2507
2688
  screenshotId,
@@ -2545,6 +2726,11 @@ class StableBrowser {
2545
2726
  _commandFinally(state, this);
2546
2727
  }
2547
2728
  }
2729
+ saveTestDataAsGlobal(options, world) {
2730
+ const dataFile = this._getDataFile(world);
2731
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2732
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2733
+ }
2548
2734
  async setViewportSize(width, hight, options = {}, world = null) {
2549
2735
  const startTime = Date.now();
2550
2736
  let error = null;
@@ -2568,7 +2754,7 @@ class StableBrowser {
2568
2754
  await new Promise((resolve) => setTimeout(resolve, 2000));
2569
2755
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2570
2756
  const endTime = Date.now();
2571
- this._reportToWorld(world, {
2757
+ _reportToWorld(world, {
2572
2758
  type: Types.SET_VIEWPORT,
2573
2759
  text: "set viewport size to " + width + "x" + hight,
2574
2760
  screenshotId,
@@ -2605,7 +2791,7 @@ class StableBrowser {
2605
2791
  await new Promise((resolve) => setTimeout(resolve, 2000));
2606
2792
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2607
2793
  const endTime = Date.now();
2608
- this._reportToWorld(world, {
2794
+ _reportToWorld(world, {
2609
2795
  type: Types.GET_PAGE_STATUS,
2610
2796
  text: "page relaod",
2611
2797
  screenshotId,
@@ -2641,11 +2827,45 @@ class StableBrowser {
2641
2827
  console.log("#-#");
2642
2828
  }
2643
2829
  }
2644
- _reportToWorld(world, properties) {
2645
- if (!world || !world.attach) {
2646
- return;
2830
+ async beforeStep(world, step) {
2831
+ if (this.stepIndex === undefined) {
2832
+ this.stepIndex = 0;
2833
+ }
2834
+ else {
2835
+ this.stepIndex++;
2836
+ }
2837
+ if (step && step.pickleStep && step.pickleStep.text) {
2838
+ this.stepName = step.pickleStep.text;
2839
+ this.logger.info("step: " + this.stepName);
2840
+ }
2841
+ else if (step && step.text) {
2842
+ this.stepName = step.text;
2843
+ }
2844
+ else {
2845
+ this.stepName = "step " + this.stepIndex;
2846
+ }
2847
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2848
+ if (this.context.browserObject.context) {
2849
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2850
+ }
2851
+ }
2852
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2853
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2854
+ // check if @global_test_data tag is present
2855
+ if (this.tags.includes("@global_test_data")) {
2856
+ this.saveTestDataAsGlobal({}, world);
2857
+ }
2858
+ }
2859
+ }
2860
+ async afterStep(world, step) {
2861
+ this.stepName = null;
2862
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2863
+ if (this.context.browserObject.context) {
2864
+ await this.context.browserObject.context.tracing.stopChunk({
2865
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2866
+ });
2867
+ }
2647
2868
  }
2648
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2649
2869
  }
2650
2870
  }
2651
2871
  function createTimedPromise(promise, label) {
@@ -2653,156 +2873,5 @@ function createTimedPromise(promise, label) {
2653
2873
  .then((result) => ({ status: "fulfilled", label, result }))
2654
2874
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2655
2875
  }
2656
- const KEYBOARD_EVENTS = [
2657
- "ALT",
2658
- "AltGraph",
2659
- "CapsLock",
2660
- "Control",
2661
- "Fn",
2662
- "FnLock",
2663
- "Hyper",
2664
- "Meta",
2665
- "NumLock",
2666
- "ScrollLock",
2667
- "Shift",
2668
- "Super",
2669
- "Symbol",
2670
- "SymbolLock",
2671
- "Enter",
2672
- "Tab",
2673
- "ArrowDown",
2674
- "ArrowLeft",
2675
- "ArrowRight",
2676
- "ArrowUp",
2677
- "End",
2678
- "Home",
2679
- "PageDown",
2680
- "PageUp",
2681
- "Backspace",
2682
- "Clear",
2683
- "Copy",
2684
- "CrSel",
2685
- "Cut",
2686
- "Delete",
2687
- "EraseEof",
2688
- "ExSel",
2689
- "Insert",
2690
- "Paste",
2691
- "Redo",
2692
- "Undo",
2693
- "Accept",
2694
- "Again",
2695
- "Attn",
2696
- "Cancel",
2697
- "ContextMenu",
2698
- "Escape",
2699
- "Execute",
2700
- "Find",
2701
- "Finish",
2702
- "Help",
2703
- "Pause",
2704
- "Play",
2705
- "Props",
2706
- "Select",
2707
- "ZoomIn",
2708
- "ZoomOut",
2709
- "BrightnessDown",
2710
- "BrightnessUp",
2711
- "Eject",
2712
- "LogOff",
2713
- "Power",
2714
- "PowerOff",
2715
- "PrintScreen",
2716
- "Hibernate",
2717
- "Standby",
2718
- "WakeUp",
2719
- "AllCandidates",
2720
- "Alphanumeric",
2721
- "CodeInput",
2722
- "Compose",
2723
- "Convert",
2724
- "Dead",
2725
- "FinalMode",
2726
- "GroupFirst",
2727
- "GroupLast",
2728
- "GroupNext",
2729
- "GroupPrevious",
2730
- "ModeChange",
2731
- "NextCandidate",
2732
- "NonConvert",
2733
- "PreviousCandidate",
2734
- "Process",
2735
- "SingleCandidate",
2736
- "HangulMode",
2737
- "HanjaMode",
2738
- "JunjaMode",
2739
- "Eisu",
2740
- "Hankaku",
2741
- "Hiragana",
2742
- "HiraganaKatakana",
2743
- "KanaMode",
2744
- "KanjiMode",
2745
- "Katakana",
2746
- "Romaji",
2747
- "Zenkaku",
2748
- "ZenkakuHanaku",
2749
- "F1",
2750
- "F2",
2751
- "F3",
2752
- "F4",
2753
- "F5",
2754
- "F6",
2755
- "F7",
2756
- "F8",
2757
- "F9",
2758
- "F10",
2759
- "F11",
2760
- "F12",
2761
- "Soft1",
2762
- "Soft2",
2763
- "Soft3",
2764
- "Soft4",
2765
- "ChannelDown",
2766
- "ChannelUp",
2767
- "Close",
2768
- "MailForward",
2769
- "MailReply",
2770
- "MailSend",
2771
- "MediaFastForward",
2772
- "MediaPause",
2773
- "MediaPlay",
2774
- "MediaPlayPause",
2775
- "MediaRecord",
2776
- "MediaRewind",
2777
- "MediaStop",
2778
- "MediaTrackNext",
2779
- "MediaTrackPrevious",
2780
- "AudioBalanceLeft",
2781
- "AudioBalanceRight",
2782
- "AudioBassBoostDown",
2783
- "AudioBassBoostToggle",
2784
- "AudioBassBoostUp",
2785
- "AudioFaderFront",
2786
- "AudioFaderRear",
2787
- "AudioSurroundModeNext",
2788
- "AudioTrebleDown",
2789
- "AudioTrebleUp",
2790
- "AudioVolumeDown",
2791
- "AudioVolumeMute",
2792
- "AudioVolumeUp",
2793
- "MicrophoneToggle",
2794
- "MicrophoneVolumeDown",
2795
- "MicrophoneVolumeMute",
2796
- "MicrophoneVolumeUp",
2797
- "TV",
2798
- "TV3DMode",
2799
- "TVAntennaCable",
2800
- "TVAudioDescription",
2801
- ];
2802
- function unEscapeString(str) {
2803
- const placeholder = "__NEWLINE__";
2804
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2805
- return str;
2806
- }
2807
2876
  export { StableBrowser };
2808
2877
  //# sourceMappingURL=stable_browser.js.map