automation_model 1.0.432-dev → 1.0.432

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 (60) hide show
  1. package/README.md +130 -0
  2. package/lib/api.d.ts +43 -1
  3. package/lib/api.js +241 -41
  4. package/lib/api.js.map +1 -1
  5. package/lib/auto_page.d.ts +5 -2
  6. package/lib/auto_page.js +177 -46
  7. package/lib/auto_page.js.map +1 -1
  8. package/lib/browser_manager.d.ts +7 -3
  9. package/lib/browser_manager.js +157 -49
  10. package/lib/browser_manager.js.map +1 -1
  11. package/lib/bruno.d.ts +1 -0
  12. package/lib/bruno.js +301 -0
  13. package/lib/bruno.js.map +1 -0
  14. package/lib/command_common.d.ts +6 -0
  15. package/lib/command_common.js +198 -0
  16. package/lib/command_common.js.map +1 -0
  17. package/lib/environment.d.ts +3 -0
  18. package/lib/environment.js +5 -2
  19. package/lib/environment.js.map +1 -1
  20. package/lib/error-messages.d.ts +6 -0
  21. package/lib/error-messages.js +206 -0
  22. package/lib/error-messages.js.map +1 -0
  23. package/lib/generation_scripts.d.ts +4 -0
  24. package/lib/generation_scripts.js +2 -0
  25. package/lib/generation_scripts.js.map +1 -0
  26. package/lib/index.d.ts +2 -0
  27. package/lib/index.js +2 -0
  28. package/lib/index.js.map +1 -1
  29. package/lib/init_browser.d.ts +5 -2
  30. package/lib/init_browser.js +124 -7
  31. package/lib/init_browser.js.map +1 -1
  32. package/lib/locate_element.d.ts +7 -0
  33. package/lib/locate_element.js +215 -0
  34. package/lib/locate_element.js.map +1 -0
  35. package/lib/locator.d.ts +36 -0
  36. package/lib/locator.js +165 -0
  37. package/lib/locator.js.map +1 -1
  38. package/lib/locator_log.d.ts +26 -0
  39. package/lib/locator_log.js +69 -0
  40. package/lib/locator_log.js.map +1 -0
  41. package/lib/network.d.ts +3 -0
  42. package/lib/network.js +183 -0
  43. package/lib/network.js.map +1 -0
  44. package/lib/scripts/axe.mini.js +12 -0
  45. package/lib/stable_browser.d.ts +103 -36
  46. package/lib/stable_browser.js +1781 -1242
  47. package/lib/stable_browser.js.map +1 -1
  48. package/lib/table.d.ts +13 -0
  49. package/lib/table.js +187 -0
  50. package/lib/table.js.map +1 -0
  51. package/lib/table_helper.d.ts +19 -0
  52. package/lib/table_helper.js +116 -0
  53. package/lib/table_helper.js.map +1 -0
  54. package/lib/test_context.d.ts +6 -0
  55. package/lib/test_context.js +14 -10
  56. package/lib/test_context.js.map +1 -1
  57. package/lib/utils.d.ts +22 -2
  58. package/lib/utils.js +664 -11
  59. package/lib/utils.js.map +1 -1
  60. package/package.json +13 -8
@@ -2,21 +2,30 @@
2
2
  import { expect } from "@playwright/test";
3
3
  import dayjs from "dayjs";
4
4
  import fs from "fs";
5
+ import { Jimp } from "jimp";
5
6
  import path from "path";
6
7
  import reg_parser from "regex-parser";
7
- import sharp from "sharp";
8
8
  import { findDateAlternatives, findNumberAlternatives } from "./analyze_helper.js";
9
9
  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 objectPath from "object-path";
14
- import { decrypt } from "./utils.js";
13
+ import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
15
14
  import csv from "csv-parser";
16
15
  import { Readable } from "node:stream";
17
16
  import readline from "readline";
18
- const Types = {
17
+ import { getContext, refreshBrowser } from "./init_browser.js";
18
+ import { getTestData } from "./auto_page.js";
19
+ import { locate_element } from "./locate_element.js";
20
+ import { randomUUID } from "crypto";
21
+ import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
22
+ import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
23
+ import { LocatorLog } from "./locator_log.js";
24
+ import axios from "axios";
25
+ import { _findCellArea, findElementsInArea } from "./table_helper.js";
26
+ export const Types = {
19
27
  CLICK: "click_element",
28
+ WAIT_ELEMENT: "wait_element",
20
29
  NAVIGATE: "navigate",
21
30
  FILL: "fill_element",
22
31
  EXECUTE: "execute_page_method",
@@ -26,6 +35,8 @@ const Types = {
26
35
  GET_PAGE_STATUS: "get_page_status",
27
36
  CLICK_ROW_ACTION: "click_row_action",
28
37
  VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
38
+ VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
39
+ VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
29
40
  ANALYZE_TABLE: "analyze_table",
30
41
  SELECT: "select_combobox",
31
42
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -36,21 +47,41 @@ const Types = {
36
47
  UNCHECK: "uncheck_element",
37
48
  EXTRACT: "extract_attribute",
38
49
  CLOSE_PAGE: "close_page",
50
+ TABLE_OPERATION: "table_operation",
39
51
  SET_DATE_TIME: "set_date_time",
40
52
  SET_VIEWPORT: "set_viewport",
41
53
  VERIFY_VISUAL: "verify_visual",
42
54
  LOAD_DATA: "load_data",
43
55
  SET_INPUT: "set_input",
56
+ WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
57
+ VERIFY_ATTRIBUTE: "verify_element_attribute",
58
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
59
+ BRUNO: "bruno",
60
+ };
61
+ export const apps = {};
62
+ const formatElementName = (elementName) => {
63
+ return elementName ? JSON.stringify(elementName) : "element";
44
64
  };
45
65
  class StableBrowser {
46
- constructor(browser, page, logger = null, context = null) {
66
+ browser;
67
+ page;
68
+ logger;
69
+ context;
70
+ world;
71
+ project_path = null;
72
+ webLogFile = null;
73
+ networkLogger = null;
74
+ configuration = null;
75
+ appName = "main";
76
+ tags = null;
77
+ isRecording = false;
78
+ initSnapshotTaken = false;
79
+ constructor(browser, page, logger = null, context = null, world = null) {
47
80
  this.browser = browser;
48
81
  this.page = page;
49
82
  this.logger = logger;
50
83
  this.context = context;
51
- this.project_path = null;
52
- this.webLogFile = null;
53
- this.configuration = null;
84
+ this.world = world;
54
85
  if (!this.logger) {
55
86
  this.logger = console;
56
87
  }
@@ -75,17 +106,32 @@ class StableBrowser {
75
106
  catch (e) {
76
107
  this.logger.error("unable to read ai_config.json");
77
108
  }
78
- const logFolder = path.join(this.project_path, "logs", "web");
79
- this.webLogFile = this.getWebLogFile(logFolder);
80
- this.registerConsoleLogListener(page, context, this.webLogFile);
81
- this.registerRequestListener();
82
- context.pages = [this.page];
83
109
  context.pageLoading = { status: false };
110
+ context.pages = [this.page];
111
+ const logFolder = path.join(this.project_path, "logs", "web");
112
+ this.world = world;
113
+ this.registerEventListeners(this.context);
114
+ registerNetworkEvents(this.world, this, this.context, this.page);
115
+ registerDownloadEvent(this.page, this.world, this.context);
116
+ }
117
+ registerEventListeners(context) {
118
+ this.registerConsoleLogListener(this.page, context);
119
+ // this.registerRequestListener(this.page, context, this.webLogFile);
120
+ if (!context.pageLoading) {
121
+ context.pageLoading = { status: false };
122
+ }
84
123
  context.playContext.on("page", async function (page) {
124
+ if (this.configuration && this.configuration.closePopups === true) {
125
+ console.log("close unexpected popups");
126
+ await page.close();
127
+ return;
128
+ }
85
129
  context.pageLoading.status = true;
86
130
  this.page = page;
87
131
  context.page = page;
88
132
  context.pages.push(page);
133
+ registerNetworkEvents(this.world, this, context, this.page);
134
+ registerDownloadEvent(this.page, this.world, context);
89
135
  page.on("close", async () => {
90
136
  if (this.context && this.context.pages && this.context.pages.length > 1) {
91
137
  this.context.pages.pop();
@@ -110,117 +156,164 @@ class StableBrowser {
110
156
  context.pageLoading.status = false;
111
157
  }.bind(this));
112
158
  }
113
- getWebLogFile(logFolder) {
114
- if (!fs.existsSync(logFolder)) {
115
- fs.mkdirSync(logFolder, { recursive: true });
159
+ async switchApp(appName) {
160
+ // check if the current app (this.appName) is the same as the new app
161
+ if (this.appName === appName) {
162
+ return;
163
+ }
164
+ let newContextCreated = false;
165
+ if (!apps[appName]) {
166
+ let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
167
+ newContextCreated = true;
168
+ apps[appName] = {
169
+ context: newContext,
170
+ browser: newContext.browser,
171
+ page: newContext.page,
172
+ };
173
+ }
174
+ const tempContext = {};
175
+ _copyContext(this, tempContext);
176
+ _copyContext(apps[appName], this);
177
+ apps[this.appName] = tempContext;
178
+ this.appName = appName;
179
+ if (newContextCreated) {
180
+ this.registerEventListeners(this.context);
181
+ await this.goto(this.context.environment.baseUrl);
182
+ await this.waitForPageLoad();
183
+ }
184
+ }
185
+ async switchTab(tabTitleOrIndex) {
186
+ // first check if the tabNameOrIndex is a number
187
+ let index = parseInt(tabTitleOrIndex);
188
+ if (!isNaN(index)) {
189
+ if (index >= 0 && index < this.context.pages.length) {
190
+ this.page = this.context.pages[index];
191
+ this.context.page = this.page;
192
+ await this.page.bringToFront();
193
+ return;
194
+ }
116
195
  }
117
- let nextIndex = 1;
118
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
119
- nextIndex++;
196
+ // if the tabNameOrIndex is a string, find the tab by name
197
+ for (let i = 0; i < this.context.pages.length; i++) {
198
+ let page = this.context.pages[i];
199
+ let title = await page.title();
200
+ if (title.includes(tabTitleOrIndex)) {
201
+ this.page = page;
202
+ this.context.page = this.page;
203
+ await this.page.bringToFront();
204
+ return;
205
+ }
120
206
  }
121
- const fileName = nextIndex + ".json";
122
- return path.join(logFolder, fileName);
207
+ throw new Error("Tab not found: " + tabTitleOrIndex);
123
208
  }
124
- registerConsoleLogListener(page, context, logFile) {
209
+ registerConsoleLogListener(page, context) {
125
210
  if (!this.context.webLogger) {
126
211
  this.context.webLogger = [];
127
212
  }
128
213
  page.on("console", async (msg) => {
129
- this.context.webLogger.push({
214
+ const obj = {
130
215
  type: msg.type(),
131
216
  text: msg.text(),
132
217
  location: msg.location(),
133
218
  time: new Date().toISOString(),
134
- });
135
- await fs.promises.writeFile(logFile, JSON.stringify(this.context.webLogger, null, 2));
219
+ };
220
+ this.context.webLogger.push(obj);
221
+ if (msg.type() === "error") {
222
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
223
+ }
136
224
  });
137
225
  }
138
- registerRequestListener() {
139
- this.page.on("request", async (data) => {
226
+ registerRequestListener(page, context, logFile) {
227
+ if (!this.context.networkLogger) {
228
+ this.context.networkLogger = [];
229
+ }
230
+ page.on("request", async (data) => {
231
+ const startTime = new Date().getTime();
140
232
  try {
141
- const pageUrl = new URL(this.page.url());
233
+ const pageUrl = new URL(page.url());
142
234
  const requestUrl = new URL(data.url());
143
235
  if (pageUrl.hostname === requestUrl.hostname) {
144
236
  const method = data.method();
145
- if (method === "POST" || method === "GET" || method === "PUT" || method === "DELETE" || method === "PATCH") {
237
+ if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
146
238
  const token = await data.headerValue("Authorization");
147
239
  if (token) {
148
- this.context.authtoken = token;
240
+ context.authtoken = token;
149
241
  }
150
242
  }
151
243
  }
244
+ const response = await data.response();
245
+ const endTime = new Date().getTime();
246
+ const obj = {
247
+ url: data.url(),
248
+ method: data.method(),
249
+ postData: data.postData(),
250
+ error: data.failure() ? data.failure().errorText : null,
251
+ duration: endTime - startTime,
252
+ startTime,
253
+ };
254
+ context.networkLogger.push(obj);
255
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
152
256
  }
153
257
  catch (error) {
154
- console.error("Error in request listener", error);
258
+ // console.error("Error in request listener", error);
259
+ context.networkLogger.push({
260
+ error: "not able to listen",
261
+ message: error.message,
262
+ stack: error.stack,
263
+ time: new Date().toISOString(),
264
+ });
265
+ // await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
155
266
  }
156
267
  });
157
268
  }
158
269
  // async closeUnexpectedPopups() {
159
270
  // await closeUnexpectedPopups(this.page);
160
271
  // }
161
- async goto(url) {
272
+ async goto(url, world = null) {
273
+ if (!url) {
274
+ throw new Error("url is null, verify that the environment file is correct");
275
+ }
162
276
  if (!url.startsWith("http")) {
163
277
  url = "https://" + url;
164
278
  }
165
- await this.page.goto(url, {
166
- timeout: 60000,
167
- });
168
- }
169
- _validateSelectors(selectors) {
170
- if (!selectors) {
171
- throw new Error("selectors is null");
172
- }
173
- if (!selectors.locators) {
174
- throw new Error("selectors.locators is null");
175
- }
176
- if (!Array.isArray(selectors.locators)) {
177
- throw new Error("selectors.locators expected to be array");
178
- }
179
- if (selectors.locators.length === 0) {
180
- throw new Error("selectors.locators expected to be non empty array");
181
- }
182
- }
183
- _fixUsingParams(text, _params) {
184
- if (!_params || typeof text !== "string") {
185
- return text;
279
+ const state = {
280
+ value: url,
281
+ world: world,
282
+ type: Types.NAVIGATE,
283
+ text: `Navigate Page to: ${url}`,
284
+ operation: "goto",
285
+ log: "***** navigate page to " + url + " *****\n",
286
+ info: {},
287
+ locate: false,
288
+ scroll: false,
289
+ screenshot: false,
290
+ highlight: false,
291
+ };
292
+ try {
293
+ await _preCommand(state, this);
294
+ await this.page.goto(url, {
295
+ timeout: 60000,
296
+ });
297
+ await _screenshot(state, this);
186
298
  }
187
- for (let key in _params) {
188
- let regValue = key;
189
- if (key.startsWith("_")) {
190
- // remove the _ prefix
191
- regValue = key.substring(1);
192
- }
193
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
299
+ catch (error) {
300
+ console.error("Error on goto", error);
301
+ _commandError(state, error, this);
194
302
  }
195
- return text;
196
- }
197
- _fixLocatorUsingParams(locator, _params) {
198
- // check if not null
199
- if (!locator) {
200
- return locator;
303
+ finally {
304
+ await _commandFinally(state, this);
201
305
  }
202
- // clone the locator
203
- locator = JSON.parse(JSON.stringify(locator));
204
- this.scanAndManipulate(locator, _params);
205
- return locator;
206
306
  }
207
- _isObject(value) {
208
- return value && typeof value === "object" && value.constructor === Object;
209
- }
210
- scanAndManipulate(currentObj, _params) {
211
- for (const key in currentObj) {
212
- if (typeof currentObj[key] === "string") {
213
- // Perform string manipulation
214
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
215
- }
216
- else if (this._isObject(currentObj[key])) {
217
- // Recursively scan nested objects
218
- this.scanAndManipulate(currentObj[key], _params);
307
+ async _getLocator(locator, scope, _params) {
308
+ locator = _fixLocatorUsingParams(locator, _params);
309
+ // locator = await this._replaceWithLocalData(locator);
310
+ for (let key in locator) {
311
+ if (typeof locator[key] !== "string")
312
+ continue;
313
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
314
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
219
315
  }
220
316
  }
221
- }
222
- _getLocator(locator, scope, _params) {
223
- locator = this._fixLocatorUsingParams(locator, _params);
224
317
  let locatorReturn;
225
318
  if (locator.role) {
226
319
  if (locator.role[1].nameReg) {
@@ -228,7 +321,7 @@ class StableBrowser {
228
321
  delete locator.role[1].nameReg;
229
322
  }
230
323
  // if (locator.role[1].name) {
231
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
324
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
232
325
  // }
233
326
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
234
327
  }
@@ -247,7 +340,7 @@ class StableBrowser {
247
340
  locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
248
341
  }
249
342
  }
250
- if (locator === null || locator === void 0 ? void 0 : locator.engine) {
343
+ if (locator?.engine) {
251
344
  if (locator.engine === "css") {
252
345
  locatorReturn = scope.locator(locator.selector);
253
346
  }
@@ -268,192 +361,181 @@ class StableBrowser {
268
361
  return locatorReturn;
269
362
  }
270
363
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
271
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
364
+ if (css && css.locator) {
365
+ css = css.locator;
366
+ }
367
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
272
368
  if (result.elementCount === 0) {
273
369
  return;
274
370
  }
275
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
371
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
276
372
  // css climb to parent element
277
373
  const climbArray = [];
278
374
  for (let i = 0; i < climb; i++) {
279
375
  climbArray.push("..");
280
376
  }
281
377
  let climbXpath = "xpath=" + climbArray.join("/");
282
- return textElementCss + " >> " + climbXpath + " >> " + css;
283
- }
284
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
285
- //const stringifyText = JSON.stringify(text);
286
- return await scope.evaluate(([text, tag, regex, partial]) => {
287
- function isParent(parent, child) {
288
- let currentNode = child.parentNode;
289
- while (currentNode !== null) {
290
- if (currentNode === parent) {
291
- return true;
292
- }
293
- currentNode = currentNode.parentNode;
294
- }
295
- return false;
296
- }
297
- document.isParent = isParent;
298
- function collectAllShadowDomElements(element, result = []) {
299
- // Check and add the element if it has a shadow root
300
- if (element.shadowRoot) {
301
- result.push(element);
302
- // Also search within the shadow root
303
- document.collectAllShadowDomElements(element.shadowRoot, result);
304
- }
305
- // Iterate over child nodes
306
- element.childNodes.forEach((child) => {
307
- // Recursively call the function for each child node
308
- document.collectAllShadowDomElements(child, result);
309
- });
310
- return result;
311
- }
312
- document.collectAllShadowDomElements = collectAllShadowDomElements;
313
- if (!tag) {
314
- tag = "*";
315
- }
316
- let elements = Array.from(document.querySelectorAll(tag));
317
- let shadowHosts = [];
318
- document.collectAllShadowDomElements(document, shadowHosts);
319
- for (let i = 0; i < shadowHosts.length; i++) {
320
- let shadowElement = shadowHosts[i].shadowRoot;
321
- if (!shadowElement) {
322
- console.log("shadowElement is null, for host " + shadowHosts[i]);
323
- continue;
324
- }
325
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
326
- elements = elements.concat(shadowElements);
327
- }
328
- let randomToken = null;
329
- const foundElements = [];
330
- if (regex) {
331
- let regexpSearch = new RegExp(text, "im");
332
- for (let i = 0; i < elements.length; i++) {
333
- const element = elements[i];
334
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
335
- (element.value && regexpSearch.test(element.value))) {
336
- foundElements.push(element);
337
- }
338
- }
339
- }
340
- else {
341
- text = text.trim();
342
- for (let i = 0; i < elements.length; i++) {
343
- const element = elements[i];
344
- if (partial) {
345
- if ((element.innerText && element.innerText.trim().includes(text)) ||
346
- (element.value && element.value.includes(text))) {
347
- foundElements.push(element);
348
- }
349
- }
350
- else {
351
- if ((element.innerText && element.innerText.trim() === text) ||
352
- (element.value && element.value === text)) {
353
- foundElements.push(element);
354
- }
355
- }
356
- }
357
- }
358
- let noChildElements = [];
359
- for (let i = 0; i < foundElements.length; i++) {
360
- let element = foundElements[i];
361
- let hasChild = false;
362
- for (let j = 0; j < foundElements.length; j++) {
363
- if (i === j) {
364
- continue;
365
- }
366
- if (isParent(element, foundElements[j])) {
367
- hasChild = true;
368
- break;
369
- }
370
- }
371
- if (!hasChild) {
372
- noChildElements.push(element);
373
- }
374
- }
375
- let elementCount = 0;
376
- if (noChildElements.length > 0) {
377
- for (let i = 0; i < noChildElements.length; i++) {
378
- if (randomToken === null) {
379
- randomToken = Math.random().toString(36).substring(7);
380
- }
381
- let element = noChildElements[i];
382
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
383
- elementCount++;
384
- }
378
+ let resultCss = textElementCss + " >> " + climbXpath;
379
+ if (css) {
380
+ resultCss = resultCss + " >> " + css;
381
+ }
382
+ return resultCss;
383
+ }
384
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
385
+ const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
386
+ const locator = scope.locator(query);
387
+ const count = await locator.count();
388
+ if (!tag1) {
389
+ tag1 = "*";
390
+ }
391
+ const randomToken = Math.random().toString(36).substring(7);
392
+ let tagCount = 0;
393
+ for (let i = 0; i < count; i++) {
394
+ const element = locator.nth(i);
395
+ // check if the tag matches
396
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
397
+ if (!tag.startsWith("*")) {
398
+ if (el.tagName.toLowerCase() !== tag) {
399
+ return false;
400
+ }
401
+ }
402
+ if (!el.setAttribute) {
403
+ el = el.parentElement;
404
+ }
405
+ // remove any attributes start with data-blinq-id
406
+ // for (let i = 0; i < el.attributes.length; i++) {
407
+ // if (el.attributes[i].name.startsWith("data-blinq-id")) {
408
+ // el.removeAttribute(el.attributes[i].name);
409
+ // }
410
+ // }
411
+ el.setAttribute("data-blinq-id-" + randomToken, "");
412
+ return true;
413
+ }, [tag1, randomToken]))) {
414
+ continue;
385
415
  }
386
- return { elementCount: elementCount, randomToken: randomToken };
387
- }, [text1, tag1, regex1, partial1]);
416
+ tagCount++;
417
+ }
418
+ return { elementCount: tagCount, randomToken };
388
419
  }
389
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
420
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
421
+ if (!info) {
422
+ info = {};
423
+ }
424
+ if (!info.failCause) {
425
+ info.failCause = {};
426
+ }
427
+ if (!info.log) {
428
+ info.log = "";
429
+ info.locatorLog = new LocatorLog(selectorHierarchy);
430
+ }
390
431
  let locatorSearch = selectorHierarchy[index];
432
+ let originalLocatorSearch = "";
433
+ try {
434
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
435
+ locatorSearch = JSON.parse(originalLocatorSearch);
436
+ }
437
+ catch (e) {
438
+ console.error(e);
439
+ }
391
440
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
392
441
  let locator = null;
393
442
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
394
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
443
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
444
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
395
445
  if (!locatorString) {
446
+ info.failCause.textNotFound = true;
447
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
396
448
  return;
397
449
  }
398
- locator = this._getLocator({ css: locatorString }, scope, _params);
450
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
399
451
  }
400
452
  else if (locatorSearch.text) {
401
- let result = await this._locateElementByText(scope, this._fixUsingParams(locatorSearch.text, _params), locatorSearch.tag, false, locatorSearch.partial === true, _params);
453
+ let text = _fixUsingParams(locatorSearch.text, _params);
454
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
402
455
  if (result.elementCount === 0) {
456
+ info.failCause.textNotFound = true;
457
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
403
458
  return;
404
459
  }
405
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
460
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
406
461
  if (locatorSearch.childCss) {
407
462
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
408
463
  }
409
- locator = this._getLocator(locatorSearch, scope, _params);
464
+ locator = await this._getLocator(locatorSearch, scope, _params);
410
465
  }
411
466
  else {
412
- locator = this._getLocator(locatorSearch, scope, _params);
467
+ locator = await this._getLocator(locatorSearch, scope, _params);
413
468
  }
414
469
  // let cssHref = false;
415
470
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
416
471
  // cssHref = true;
417
472
  // }
418
473
  let count = await locator.count();
474
+ if (count > 0 && !info.failCause.count) {
475
+ info.failCause.count = count;
476
+ }
419
477
  //info.log += "total elements found " + count + "\n";
420
478
  //let visibleCount = 0;
421
479
  let visibleLocator = null;
422
- if (locatorSearch.index && locatorSearch.index < count) {
480
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
423
481
  foundLocators.push(locator.nth(locatorSearch.index));
482
+ if (info.locatorLog) {
483
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
484
+ }
424
485
  return;
425
486
  }
487
+ if (info.locatorLog && count === 0) {
488
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
489
+ }
426
490
  for (let j = 0; j < count; j++) {
427
491
  let visible = await locator.nth(j).isVisible();
428
492
  const enabled = await locator.nth(j).isEnabled();
429
493
  if (!visibleOnly) {
430
494
  visible = true;
431
495
  }
432
- if (visible && enabled) {
496
+ if (visible && (allowDisabled || enabled)) {
433
497
  foundLocators.push(locator.nth(j));
498
+ if (info.locatorLog) {
499
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
500
+ }
434
501
  }
435
502
  else {
503
+ info.failCause.visible = visible;
504
+ info.failCause.enabled = enabled;
436
505
  if (!info.printMessages) {
437
506
  info.printMessages = {};
438
507
  }
508
+ if (info.locatorLog && !visible) {
509
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
510
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
511
+ }
512
+ if (info.locatorLog && !enabled) {
513
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
514
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
515
+ }
439
516
  if (!info.printMessages[j.toString()]) {
440
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
517
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
441
518
  info.printMessages[j.toString()] = true;
442
519
  }
443
520
  }
444
521
  }
445
522
  }
446
523
  async closeUnexpectedPopups(info, _params) {
524
+ if (!info) {
525
+ info = {};
526
+ info.failCause = {};
527
+ info.log = "";
528
+ }
447
529
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
448
530
  if (!info) {
449
531
  info = {};
450
532
  }
451
- info.log += "scan for popup handlers" + "\n";
533
+ //info.log += "scan for popup handlers" + "\n";
452
534
  const handlerGroup = [];
453
535
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
454
536
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
455
537
  }
456
- const scopes = [this.page, ...this.page.frames()];
538
+ const scopes = this.page.frames().filter((frame) => frame.url() !== "about:blank");
457
539
  let result = null;
458
540
  let scope = null;
459
541
  for (let i = 0; i < scopes.length; i++) {
@@ -475,42 +557,116 @@ class StableBrowser {
475
557
  }
476
558
  if (result.foundElements.length > 0) {
477
559
  let dialogCloseLocator = result.foundElements[0].locator;
478
- await dialogCloseLocator.click();
560
+ try {
561
+ await scope?.evaluate(() => {
562
+ window.__isClosingPopups = true;
563
+ });
564
+ await dialogCloseLocator.click();
565
+ // wait for the dialog to close
566
+ await dialogCloseLocator.waitFor({ state: "hidden" });
567
+ }
568
+ catch (e) {
569
+ }
570
+ finally {
571
+ await scope?.evaluate(() => {
572
+ window.__isClosingPopups = false;
573
+ });
574
+ }
479
575
  return { rerun: true };
480
576
  }
481
577
  }
482
578
  }
483
579
  return { rerun: false };
484
580
  }
485
- async _locate(selectors, info, _params, timeout = 30000) {
581
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
582
+ if (!timeout) {
583
+ timeout = 30000;
584
+ }
486
585
  for (let i = 0; i < 3; i++) {
487
586
  info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
488
587
  for (let j = 0; j < selectors.locators.length; j++) {
489
588
  let selector = selectors.locators[j];
490
589
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
491
590
  }
492
- let element = await this._locate_internal(selectors, info, _params, timeout);
591
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
493
592
  if (!element.rerun) {
494
- return element;
593
+ const randomToken = Math.random().toString(36).substring(7);
594
+ element.evaluate((el, randomToken) => {
595
+ el.setAttribute("data-blinq-id-" + randomToken, "");
596
+ }, randomToken);
597
+ // if (element._frame) {
598
+ // return element;
599
+ // }
600
+ const scope = element._frame ?? element.page();
601
+ let newElementSelector = "[data-blinq-id-" + randomToken + "]";
602
+ let prefixSelector = "";
603
+ const frameControlSelector = " >> internal:control=enter-frame";
604
+ const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
605
+ if (frameSelectorIndex !== -1) {
606
+ // remove everything after the >> internal:control=enter-frame
607
+ const frameSelector = element._selector.substring(0, frameSelectorIndex);
608
+ prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
609
+ }
610
+ // if (element?._frame?._selector) {
611
+ // prefixSelector = element._frame._selector + " >> " + prefixSelector;
612
+ // }
613
+ const newSelector = prefixSelector + newElementSelector;
614
+ return scope.locator(newSelector);
495
615
  }
496
616
  }
497
617
  throw new Error("unable to locate element " + JSON.stringify(selectors));
498
618
  }
499
- async _locate_internal(selectors, info, _params, timeout = 30000) {
500
- let highPriorityTimeout = 5000;
501
- let visibleOnlyTimeout = 6000;
502
- let startTime = performance.now();
503
- let locatorsCount = 0;
504
- //let arrayMode = Array.isArray(selectors);
619
+ async _findFrameScope(selectors, timeout = 30000, info) {
620
+ if (!info) {
621
+ info = {};
622
+ info.failCause = {};
623
+ info.log = "";
624
+ }
625
+ let startTime = Date.now();
505
626
  let scope = this.page;
627
+ if (selectors.frame) {
628
+ return selectors.frame;
629
+ }
506
630
  if (selectors.iframe_src || selectors.frameLocators) {
507
- info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
631
+ const findFrame = async (frame, framescope) => {
632
+ for (let i = 0; i < frame.selectors.length; i++) {
633
+ let frameLocator = frame.selectors[i];
634
+ if (frameLocator.css) {
635
+ let testframescope = framescope.frameLocator(frameLocator.css);
636
+ if (frameLocator.index) {
637
+ testframescope = framescope.nth(frameLocator.index);
638
+ }
639
+ try {
640
+ await testframescope.owner().evaluateHandle(() => true, null, {
641
+ timeout: 5000,
642
+ });
643
+ framescope = testframescope;
644
+ break;
645
+ }
646
+ catch (error) {
647
+ console.error("frame not found " + frameLocator.css);
648
+ }
649
+ }
650
+ }
651
+ if (frame.children) {
652
+ return await findFrame(frame.children, framescope);
653
+ }
654
+ return framescope;
655
+ };
656
+ let fLocator = null;
508
657
  while (true) {
509
658
  let frameFound = false;
659
+ if (selectors.nestFrmLoc) {
660
+ fLocator = selectors.nestFrmLoc;
661
+ scope = await findFrame(selectors.nestFrmLoc, scope);
662
+ frameFound = true;
663
+ break;
664
+ }
510
665
  if (selectors.frameLocators) {
511
666
  for (let i = 0; i < selectors.frameLocators.length; i++) {
512
667
  let frameLocator = selectors.frameLocators[i];
513
668
  if (frameLocator.css) {
669
+ fLocator = frameLocator.css;
514
670
  scope = scope.frameLocator(frameLocator.css);
515
671
  frameFound = true;
516
672
  break;
@@ -518,20 +674,55 @@ class StableBrowser {
518
674
  }
519
675
  }
520
676
  if (!frameFound && selectors.iframe_src) {
677
+ fLocator = selectors.iframe_src;
521
678
  scope = this.page.frame({ url: selectors.iframe_src });
522
679
  }
523
680
  if (!scope) {
524
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
525
- if (performance.now() - startTime > timeout) {
681
+ if (info && info.locatorLog) {
682
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
683
+ }
684
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
685
+ if (Date.now() - startTime > timeout) {
686
+ info.failCause.iframeNotFound = true;
687
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
526
688
  throw new Error("unable to locate iframe " + selectors.iframe_src);
527
689
  }
528
690
  await new Promise((resolve) => setTimeout(resolve, 1000));
529
691
  }
530
692
  else {
693
+ if (info && info.locatorLog) {
694
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
695
+ }
531
696
  break;
532
697
  }
533
698
  }
534
699
  }
700
+ if (!scope) {
701
+ scope = this.page;
702
+ }
703
+ return scope;
704
+ }
705
+ async _getDocumentBody(selectors, timeout = 30000, info) {
706
+ let scope = await this._findFrameScope(selectors, timeout, info);
707
+ return scope.evaluate(() => {
708
+ var bodyContent = document.body.innerHTML;
709
+ return bodyContent;
710
+ });
711
+ }
712
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
713
+ if (!info) {
714
+ info = {};
715
+ info.failCause = {};
716
+ info.log = "";
717
+ info.locatorLog = new LocatorLog(selectors);
718
+ }
719
+ let highPriorityTimeout = 5000;
720
+ let visibleOnlyTimeout = 6000;
721
+ let startTime = Date.now();
722
+ let locatorsCount = 0;
723
+ let lazy_scroll = false;
724
+ //let arrayMode = Array.isArray(selectors);
725
+ let scope = await this._findFrameScope(selectors, timeout, info);
535
726
  let selectorsLocators = null;
536
727
  selectorsLocators = selectors.locators;
537
728
  // group selectors by priority
@@ -567,18 +758,13 @@ class StableBrowser {
567
758
  }
568
759
  // info.log += "scanning locators in priority 1" + "\n";
569
760
  let onlyPriority3 = selectorsLocators[0].priority === 3;
570
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
761
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
571
762
  if (result.foundElements.length === 0) {
572
763
  // info.log += "scanning locators in priority 2" + "\n";
573
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
764
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
574
765
  }
575
- if (result.foundElements.length === 0 && onlyPriority3) {
576
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
577
- }
578
- else {
579
- if (result.foundElements.length === 0 && !highPriorityOnly) {
580
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
581
- }
766
+ if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
767
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
582
768
  }
583
769
  let foundElements = result.foundElements;
584
770
  if (foundElements.length === 1 && foundElements[0].unique) {
@@ -618,24 +804,43 @@ class StableBrowser {
618
804
  return maxCountElement.locator;
619
805
  }
620
806
  }
621
- if (performance.now() - startTime > timeout) {
807
+ if (Date.now() - startTime > timeout) {
622
808
  break;
623
809
  }
624
- if (performance.now() - startTime > highPriorityTimeout) {
625
- info.log += "high priority timeout, will try all elements" + "\n";
810
+ if (Date.now() - startTime > highPriorityTimeout) {
811
+ //info.log += "high priority timeout, will try all elements" + "\n";
626
812
  highPriorityOnly = false;
813
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
814
+ lazy_scroll = true;
815
+ await scrollPageToLoadLazyElements(this.page);
816
+ }
627
817
  }
628
- if (performance.now() - startTime > visibleOnlyTimeout) {
629
- info.log += "visible only timeout, will try all elements" + "\n";
818
+ if (Date.now() - startTime > visibleOnlyTimeout) {
819
+ //info.log += "visible only timeout, will try all elements" + "\n";
630
820
  visibleOnly = false;
631
821
  }
632
822
  await new Promise((resolve) => setTimeout(resolve, 1000));
823
+ // sheck of more of half of the timeout has passed
824
+ if (Date.now() - startTime > timeout / 2) {
825
+ highPriorityOnly = false;
826
+ visibleOnly = false;
827
+ }
633
828
  }
634
829
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
635
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
830
+ // if (info.locatorLog) {
831
+ // const lines = info.locatorLog.toString().split("\n");
832
+ // for (let line of lines) {
833
+ // this.logger.debug(line);
834
+ // }
835
+ // }
836
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
837
+ info.failCause.locatorNotFound = true;
838
+ if (!info?.failCause?.lastError) {
839
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
840
+ }
636
841
  throw new Error("failed to locate first element no elements found, " + info.log);
637
842
  }
638
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
843
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
639
844
  let foundElements = [];
640
845
  const result = {
641
846
  foundElements: foundElements,
@@ -643,14 +848,15 @@ class StableBrowser {
643
848
  for (let i = 0; i < locatorsGroup.length; i++) {
644
849
  let foundLocators = [];
645
850
  try {
646
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
851
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
647
852
  }
648
853
  catch (e) {
649
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
650
- this.logger.debug(e);
854
+ // this call can fail it the browser is navigating
855
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
856
+ // this.logger.debug(e);
651
857
  foundLocators = [];
652
858
  try {
653
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
859
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
654
860
  }
655
861
  catch (e) {
656
862
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -664,90 +870,228 @@ class StableBrowser {
664
870
  });
665
871
  result.locatorIndex = i;
666
872
  }
873
+ if (foundLocators.length > 1) {
874
+ // remove elements that consume the same space with 10 pixels tolerance
875
+ const boxes = [];
876
+ for (let j = 0; j < foundLocators.length; j++) {
877
+ boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
878
+ }
879
+ for (let j = 0; j < boxes.length; j++) {
880
+ for (let k = 0; k < boxes.length; k++) {
881
+ if (j === k) {
882
+ continue;
883
+ }
884
+ // check if x, y, width, height are the same with 10 pixels tolerance
885
+ if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
886
+ Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
887
+ Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
888
+ Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
889
+ // as the element is not unique, will remove it
890
+ boxes.splice(k, 1);
891
+ k--;
892
+ }
893
+ }
894
+ }
895
+ if (boxes.length === 1) {
896
+ result.foundElements.push({
897
+ locator: boxes[0].locator.first(),
898
+ box: boxes[0].box,
899
+ unique: true,
900
+ });
901
+ result.locatorIndex = i;
902
+ }
903
+ else {
904
+ info.failCause.foundMultiple = true;
905
+ if (info.locatorLog) {
906
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
907
+ }
908
+ }
909
+ }
667
910
  }
668
911
  return result;
669
912
  }
670
- async click(selectors, _params, options = {}, world = null) {
671
- this._validateSelectors(selectors);
913
+ async simpleClick(elementDescription, _params, options = {}, world = null) {
914
+ const state = {
915
+ locate: false,
916
+ scroll: false,
917
+ highlight: false,
918
+ _params,
919
+ options,
920
+ world,
921
+ type: Types.CLICK,
922
+ text: "Click element",
923
+ operation: "simpleClick",
924
+ log: "***** click on " + elementDescription + " *****\n",
925
+ };
926
+ _preCommand(state, this);
672
927
  const startTime = Date.now();
673
- if (options && options.context) {
674
- selectors.locators[0].text = options.context;
928
+ let timeout = 30000;
929
+ if (options && options.timeout) {
930
+ timeout = options.timeout;
675
931
  }
676
- const info = {};
677
- info.log = "***** click on " + selectors.element_name + " *****\n";
678
- info.operation = "click";
679
- info.selectors = selectors;
680
- let error = null;
681
- let screenshotId = null;
682
- let screenshotPath = null;
683
- try {
684
- let element = await this._locate(selectors, info, _params);
685
- await this.scrollIfNeeded(element, info);
686
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
932
+ while (true) {
687
933
  try {
688
- await this._highlightElements(element);
689
- await element.click();
690
- await new Promise((resolve) => setTimeout(resolve, 1000));
934
+ const result = await locate_element(this.context, elementDescription, "click");
935
+ if (result?.elementNumber >= 0) {
936
+ const selectors = {
937
+ frame: result?.frame,
938
+ locators: [
939
+ {
940
+ css: result?.css,
941
+ },
942
+ ],
943
+ };
944
+ await this.click(selectors, _params, options, world);
945
+ return;
946
+ }
691
947
  }
692
948
  catch (e) {
693
- // await this.closeUnexpectedPopups();
694
- info.log += "click failed, will try again" + "\n";
695
- element = await this._locate(selectors, info, _params);
696
- await element.dispatchEvent("click");
697
- await new Promise((resolve) => setTimeout(resolve, 1000));
949
+ if (performance.now() - startTime > timeout) {
950
+ // throw e;
951
+ try {
952
+ await _commandError(state, "timeout looking for " + elementDescription, this);
953
+ }
954
+ finally {
955
+ await _commandFinally(state, this);
956
+ }
957
+ }
958
+ }
959
+ await new Promise((resolve) => setTimeout(resolve, 3000));
960
+ }
961
+ }
962
+ async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
963
+ const state = {
964
+ locate: false,
965
+ scroll: false,
966
+ highlight: false,
967
+ _params,
968
+ options,
969
+ world,
970
+ type: Types.FILL,
971
+ text: "Fill element",
972
+ operation: "simpleClickType",
973
+ log: "***** click type on " + elementDescription + " *****\n",
974
+ };
975
+ _preCommand(state, this);
976
+ const startTime = Date.now();
977
+ let timeout = 30000;
978
+ if (options && options.timeout) {
979
+ timeout = options.timeout;
980
+ }
981
+ while (true) {
982
+ try {
983
+ const result = await locate_element(this.context, elementDescription, "fill", value);
984
+ if (result?.elementNumber >= 0) {
985
+ const selectors = {
986
+ frame: result?.frame,
987
+ locators: [
988
+ {
989
+ css: result?.css,
990
+ },
991
+ ],
992
+ };
993
+ await this.clickType(selectors, value, false, _params, options, world);
994
+ return;
995
+ }
996
+ }
997
+ catch (e) {
998
+ if (performance.now() - startTime > timeout) {
999
+ // throw e;
1000
+ try {
1001
+ await _commandError(state, "timeout looking for " + elementDescription, this);
1002
+ }
1003
+ finally {
1004
+ await _commandFinally(state, this);
1005
+ }
1006
+ }
698
1007
  }
1008
+ await new Promise((resolve) => setTimeout(resolve, 3000));
1009
+ }
1010
+ }
1011
+ async click(selectors, _params, options = {}, world = null) {
1012
+ const state = {
1013
+ selectors,
1014
+ _params,
1015
+ options,
1016
+ world,
1017
+ text: "Click element",
1018
+ _text: "Click on " + selectors.element_name,
1019
+ type: Types.CLICK,
1020
+ operation: "click",
1021
+ log: "***** click on " + selectors.element_name + " *****\n",
1022
+ };
1023
+ try {
1024
+ await _preCommand(state, this);
1025
+ await performAction("click", state.element, options, this, state, _params);
699
1026
  await this.waitForPageLoad();
700
- return info;
1027
+ return state.info;
701
1028
  }
702
1029
  catch (e) {
703
- this.logger.error("click failed " + info.log);
704
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
705
- info.screenshotPath = screenshotPath;
706
- Object.assign(e, { info: info });
707
- error = e;
708
- throw e;
1030
+ await _commandError(state, e, this);
709
1031
  }
710
1032
  finally {
711
- const endTime = Date.now();
712
- this._reportToWorld(world, {
713
- element_name: selectors.element_name,
714
- type: Types.CLICK,
715
- text: `Click element`,
716
- screenshotId,
717
- result: error
718
- ? {
719
- status: "FAILED",
720
- startTime,
721
- endTime,
722
- message: error === null || error === void 0 ? void 0 : error.message,
723
- }
724
- : {
725
- status: "PASSED",
726
- startTime,
727
- endTime,
728
- },
729
- info: info,
730
- });
1033
+ await _commandFinally(state, this);
1034
+ }
1035
+ }
1036
+ async waitForElement(selectors, _params, options = {}, world = null) {
1037
+ const timeout = this._getFindElementTimeout(options);
1038
+ const state = {
1039
+ selectors,
1040
+ _params,
1041
+ options,
1042
+ world,
1043
+ text: "Wait for element",
1044
+ _text: "Wait for " + selectors.element_name,
1045
+ type: Types.WAIT_ELEMENT,
1046
+ operation: "waitForElement",
1047
+ log: "***** wait for " + selectors.element_name + " *****\n",
1048
+ };
1049
+ let found = false;
1050
+ try {
1051
+ await _preCommand(state, this);
1052
+ // if (state.options && state.options.context) {
1053
+ // state.selectors.locators[0].text = state.options.context;
1054
+ // }
1055
+ await state.element.waitFor({ timeout: timeout });
1056
+ found = true;
1057
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1058
+ }
1059
+ catch (e) {
1060
+ console.error("Error on waitForElement", e);
1061
+ // await _commandError(state, e, this);
1062
+ }
1063
+ finally {
1064
+ await _commandFinally(state, this);
731
1065
  }
1066
+ return found;
732
1067
  }
733
1068
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
734
- this._validateSelectors(selectors);
735
- const startTime = Date.now();
736
- const info = {};
737
- info.log = "";
738
- info.operation = "setCheck";
739
- info.checked = checked;
740
- info.selectors = selectors;
741
- let error = null;
742
- let screenshotId = null;
743
- let screenshotPath = null;
1069
+ const state = {
1070
+ selectors,
1071
+ _params,
1072
+ options,
1073
+ world,
1074
+ type: checked ? Types.CHECK : Types.UNCHECK,
1075
+ text: checked ? `Check element` : `Uncheck element`,
1076
+ _text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
1077
+ operation: "setCheck",
1078
+ log: "***** check " + selectors.element_name + " *****\n",
1079
+ };
744
1080
  try {
745
- let element = await this._locate(selectors, info, _params);
746
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1081
+ await _preCommand(state, this);
1082
+ state.info.checked = checked;
1083
+ // let element = await this._locate(selectors, info, _params);
1084
+ // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
747
1085
  try {
748
- await this._highlightElements(element);
749
- await element.setChecked(checked);
1086
+ // if (world && world.screenshot && !world.screenshotPath) {
1087
+ // console.log(`Highlighting while running from recorder`);
1088
+ await this._highlightElements(state.element);
1089
+ await state.element.setChecked(checked);
750
1090
  await new Promise((resolve) => setTimeout(resolve, 1000));
1091
+ // await this._unHighlightElements(element);
1092
+ // }
1093
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1094
+ // await this._unHighlightElements(element);
751
1095
  }
752
1096
  catch (e) {
753
1097
  if (e.message && e.message.includes("did not change its state")) {
@@ -755,179 +1099,102 @@ class StableBrowser {
755
1099
  }
756
1100
  else {
757
1101
  //await this.closeUnexpectedPopups();
758
- info.log += "setCheck failed, will try again" + "\n";
759
- element = await this._locate(selectors, info, _params);
760
- await element.setChecked(checked, { timeout: 5000, force: true });
1102
+ state.info.log += "setCheck failed, will try again" + "\n";
1103
+ state.element = await this._locate(selectors, state.info, _params);
1104
+ await state.element.setChecked(checked, { timeout: 5000, force: true });
761
1105
  await new Promise((resolve) => setTimeout(resolve, 1000));
762
1106
  }
763
1107
  }
764
1108
  await this.waitForPageLoad();
765
- return info;
1109
+ return state.info;
766
1110
  }
767
1111
  catch (e) {
768
- this.logger.error("setCheck failed " + info.log);
769
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
770
- info.screenshotPath = screenshotPath;
771
- Object.assign(e, { info: info });
772
- error = e;
773
- throw e;
1112
+ await _commandError(state, e, this);
774
1113
  }
775
1114
  finally {
776
- const endTime = Date.now();
777
- this._reportToWorld(world, {
778
- element_name: selectors.element_name,
779
- type: checked ? Types.CHECK : Types.UNCHECK,
780
- text: checked ? `Check element` : `Uncheck element`,
781
- screenshotId,
782
- result: error
783
- ? {
784
- status: "FAILED",
785
- startTime,
786
- endTime,
787
- message: error === null || error === void 0 ? void 0 : error.message,
788
- }
789
- : {
790
- status: "PASSED",
791
- startTime,
792
- endTime,
793
- },
794
- info: info,
795
- });
1115
+ await _commandFinally(state, this);
796
1116
  }
797
1117
  }
798
1118
  async hover(selectors, _params, options = {}, world = null) {
799
- this._validateSelectors(selectors);
800
- const startTime = Date.now();
801
- const info = {};
802
- info.log = "";
803
- info.operation = "hover";
804
- info.selectors = selectors;
805
- let error = null;
806
- let screenshotId = null;
807
- let screenshotPath = null;
1119
+ const state = {
1120
+ selectors,
1121
+ _params,
1122
+ options,
1123
+ world,
1124
+ type: Types.HOVER,
1125
+ text: `Hover element`,
1126
+ _text: `Hover on ${selectors.element_name}`,
1127
+ operation: "hover",
1128
+ log: "***** hover " + selectors.element_name + " *****\n",
1129
+ };
808
1130
  try {
809
- let element = await this._locate(selectors, info, _params);
810
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
811
- try {
812
- await this._highlightElements(element);
813
- await element.hover();
814
- await new Promise((resolve) => setTimeout(resolve, 1000));
815
- }
816
- catch (e) {
817
- //await this.closeUnexpectedPopups();
818
- info.log += "hover failed, will try again" + "\n";
819
- element = await this._locate(selectors, info, _params);
820
- await element.hover({ timeout: 10000 });
821
- await new Promise((resolve) => setTimeout(resolve, 1000));
822
- }
1131
+ await _preCommand(state, this);
1132
+ await performAction("hover", state.element, options, this, state, _params);
1133
+ await _screenshot(state, this);
823
1134
  await this.waitForPageLoad();
824
- return info;
1135
+ return state.info;
825
1136
  }
826
1137
  catch (e) {
827
- this.logger.error("hover failed " + info.log);
828
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
829
- info.screenshotPath = screenshotPath;
830
- Object.assign(e, { info: info });
831
- error = e;
832
- throw e;
1138
+ await _commandError(state, e, this);
833
1139
  }
834
1140
  finally {
835
- const endTime = Date.now();
836
- this._reportToWorld(world, {
837
- element_name: selectors.element_name,
838
- type: Types.HOVER,
839
- text: `Hover element`,
840
- screenshotId,
841
- result: error
842
- ? {
843
- status: "FAILED",
844
- startTime,
845
- endTime,
846
- message: error === null || error === void 0 ? void 0 : error.message,
847
- }
848
- : {
849
- status: "PASSED",
850
- startTime,
851
- endTime,
852
- },
853
- info: info,
854
- });
1141
+ await _commandFinally(state, this);
855
1142
  }
856
1143
  }
857
1144
  async selectOption(selectors, values, _params = null, options = {}, world = null) {
858
- this._validateSelectors(selectors);
859
1145
  if (!values) {
860
1146
  throw new Error("values is null");
861
1147
  }
862
- const startTime = Date.now();
863
- let error = null;
864
- let screenshotId = null;
865
- let screenshotPath = null;
866
- const info = {};
867
- info.log = "";
868
- info.operation = "selectOptions";
869
- info.selectors = selectors;
1148
+ const state = {
1149
+ selectors,
1150
+ _params,
1151
+ options,
1152
+ world,
1153
+ value: values.toString(),
1154
+ type: Types.SELECT,
1155
+ text: `Select option: ${values}`,
1156
+ _text: `Select option: ${values} on ${selectors.element_name}`,
1157
+ operation: "selectOption",
1158
+ log: "***** select option " + selectors.element_name + " *****\n",
1159
+ };
870
1160
  try {
871
- let element = await this._locate(selectors, info, _params);
872
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1161
+ await _preCommand(state, this);
873
1162
  try {
874
- await this._highlightElements(element);
875
- await element.selectOption(values);
1163
+ await state.element.selectOption(values);
876
1164
  }
877
1165
  catch (e) {
878
1166
  //await this.closeUnexpectedPopups();
879
- info.log += "selectOption failed, will try force" + "\n";
880
- await element.selectOption(values, { timeout: 10000, force: true });
1167
+ state.info.log += "selectOption failed, will try force" + "\n";
1168
+ await state.element.selectOption(values, { timeout: 10000, force: true });
881
1169
  }
882
1170
  await this.waitForPageLoad();
883
- return info;
1171
+ return state.info;
884
1172
  }
885
1173
  catch (e) {
886
- this.logger.error("selectOption failed " + info.log);
887
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
888
- info.screenshotPath = screenshotPath;
889
- Object.assign(e, { info: info });
890
- this.logger.info("click failed, will try next selector");
891
- error = e;
892
- throw e;
1174
+ await _commandError(state, e, this);
893
1175
  }
894
1176
  finally {
895
- const endTime = Date.now();
896
- this._reportToWorld(world, {
897
- element_name: selectors.element_name,
898
- type: Types.SELECT,
899
- text: `Select option: ${values}`,
900
- value: values.toString(),
901
- screenshotId,
902
- result: error
903
- ? {
904
- status: "FAILED",
905
- startTime,
906
- endTime,
907
- message: error === null || error === void 0 ? void 0 : error.message,
908
- }
909
- : {
910
- status: "PASSED",
911
- startTime,
912
- endTime,
913
- },
914
- info: info,
915
- });
1177
+ await _commandFinally(state, this);
916
1178
  }
917
1179
  }
918
1180
  async type(_value, _params = null, options = {}, world = null) {
919
- const startTime = Date.now();
920
- let error = null;
921
- let screenshotId = null;
922
- let screenshotPath = null;
923
- const info = {};
924
- info.log = "";
925
- info.operation = "type";
926
- _value = this._fixUsingParams(_value, _params);
927
- info.value = _value;
1181
+ const state = {
1182
+ value: _value,
1183
+ _params,
1184
+ options,
1185
+ world,
1186
+ locate: false,
1187
+ scroll: false,
1188
+ highlight: false,
1189
+ type: Types.TYPE_PRESS,
1190
+ text: `Type value: ${_value}`,
1191
+ _text: `Type value: ${_value}`,
1192
+ operation: "type",
1193
+ log: "",
1194
+ };
928
1195
  try {
929
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
930
- const valueSegment = _value.split("&&");
1196
+ await _preCommand(state, this);
1197
+ const valueSegment = state.value.split("&&");
931
1198
  for (let i = 0; i < valueSegment.length; i++) {
932
1199
  if (i > 0) {
933
1200
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -947,134 +1214,77 @@ class StableBrowser {
947
1214
  await this.page.keyboard.type(value);
948
1215
  }
949
1216
  }
950
- return info;
1217
+ return state.info;
951
1218
  }
952
1219
  catch (e) {
953
- //await this.closeUnexpectedPopups();
954
- this.logger.error("type failed " + info.log);
955
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
956
- info.screenshotPath = screenshotPath;
957
- Object.assign(e, { info: info });
958
- error = e;
959
- throw e;
1220
+ await _commandError(state, e, this);
960
1221
  }
961
1222
  finally {
962
- const endTime = Date.now();
963
- this._reportToWorld(world, {
964
- type: Types.TYPE_PRESS,
965
- screenshotId,
966
- value: _value,
967
- text: `type value: ${_value}`,
968
- result: error
969
- ? {
970
- status: "FAILED",
971
- startTime,
972
- endTime,
973
- message: error === null || error === void 0 ? void 0 : error.message,
974
- }
975
- : {
976
- status: "PASSED",
977
- startTime,
978
- endTime,
979
- },
980
- info: info,
981
- });
1223
+ await _commandFinally(state, this);
982
1224
  }
983
1225
  }
984
1226
  async setInputValue(selectors, value, _params = null, options = {}, world = null) {
985
- // set input value for non fillable inputs like date, time, range, color, etc.
986
- this._validateSelectors(selectors);
987
- const startTime = Date.now();
988
- const info = {};
989
- info.log = "***** set input value " + selectors.element_name + " *****\n";
990
- info.operation = "setInputValue";
991
- info.selectors = selectors;
992
- value = this._fixUsingParams(value, _params);
993
- info.value = value;
994
- let error = null;
995
- let screenshotId = null;
996
- let screenshotPath = null;
1227
+ const state = {
1228
+ selectors,
1229
+ _params,
1230
+ value,
1231
+ options,
1232
+ world,
1233
+ type: Types.SET_INPUT,
1234
+ text: `Set input value`,
1235
+ operation: "setInputValue",
1236
+ log: "***** set input value " + selectors.element_name + " *****\n",
1237
+ };
997
1238
  try {
998
- value = await this._replaceWithLocalData(value, this);
999
- let element = await this._locate(selectors, info, _params);
1000
- await this.scrollIfNeeded(element, info);
1001
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1002
- await this._highlightElements(element);
1239
+ await _preCommand(state, this);
1240
+ let value = await this._replaceWithLocalData(state.value, this);
1003
1241
  try {
1004
- await element.evaluateHandle((el, value) => {
1242
+ await state.element.evaluateHandle((el, value) => {
1005
1243
  el.value = value;
1006
1244
  }, value);
1007
1245
  }
1008
1246
  catch (error) {
1009
1247
  this.logger.error("setInputValue failed, will try again");
1010
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1011
- info.screenshotPath = screenshotPath;
1012
- Object.assign(error, { info: info });
1013
- await element.evaluateHandle((el, value) => {
1248
+ await _screenshot(state, this);
1249
+ Object.assign(error, { info: state.info });
1250
+ await state.element.evaluateHandle((el, value) => {
1014
1251
  el.value = value;
1015
1252
  });
1016
1253
  }
1017
1254
  }
1018
1255
  catch (e) {
1019
- this.logger.error("setInputValue failed " + info.log);
1020
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1021
- info.screenshotPath = screenshotPath;
1022
- Object.assign(e, { info: info });
1023
- error = e;
1024
- throw e;
1256
+ await _commandError(state, e, this);
1025
1257
  }
1026
1258
  finally {
1027
- const endTime = Date.now();
1028
- this._reportToWorld(world, {
1029
- element_name: selectors.element_name,
1030
- type: Types.SET_INPUT,
1031
- text: `Set input value`,
1032
- value: value,
1033
- screenshotId,
1034
- result: error
1035
- ? {
1036
- status: "FAILED",
1037
- startTime,
1038
- endTime,
1039
- message: error === null || error === void 0 ? void 0 : error.message,
1040
- }
1041
- : {
1042
- status: "PASSED",
1043
- startTime,
1044
- endTime,
1045
- },
1046
- info: info,
1047
- });
1259
+ await _commandFinally(state, this);
1048
1260
  }
1049
1261
  }
1050
1262
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1051
- this._validateSelectors(selectors);
1052
- const startTime = Date.now();
1053
- let error = null;
1054
- let screenshotId = null;
1055
- let screenshotPath = null;
1056
- const info = {};
1057
- info.log = "";
1058
- info.operation = Types.SET_DATE_TIME;
1059
- info.selectors = selectors;
1060
- info.value = value;
1263
+ const state = {
1264
+ selectors,
1265
+ _params,
1266
+ value: await this._replaceWithLocalData(value, this),
1267
+ options,
1268
+ world,
1269
+ type: Types.SET_DATE_TIME,
1270
+ text: `Set date time value: ${value}`,
1271
+ _text: `Set date time value: ${value} on ${selectors.element_name}`,
1272
+ operation: "setDateTime",
1273
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1274
+ throwError: false,
1275
+ };
1061
1276
  try {
1062
- value = await this._replaceWithLocalData(value, this);
1063
- let element = await this._locate(selectors, info, _params);
1064
- //insert red border around the element
1065
- await this.scrollIfNeeded(element, info);
1066
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1067
- await this._highlightElements(element);
1277
+ await _preCommand(state, this);
1068
1278
  try {
1069
- await element.click();
1279
+ await performAction("click", state.element, options, this, state, _params);
1070
1280
  await new Promise((resolve) => setTimeout(resolve, 500));
1071
1281
  if (format) {
1072
- value = dayjs(value).format(format);
1073
- await element.fill(value);
1282
+ state.value = dayjs(state.value).format(format);
1283
+ await state.element.fill(state.value);
1074
1284
  }
1075
1285
  else {
1076
- const dateTimeValue = await getDateTimeValue({ value, element });
1077
- await element.evaluateHandle((el, dateTimeValue) => {
1286
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1287
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1078
1288
  el.value = ""; // clear input
1079
1289
  el.value = dateTimeValue;
1080
1290
  }, dateTimeValue);
@@ -1087,20 +1297,19 @@ class StableBrowser {
1087
1297
  }
1088
1298
  catch (err) {
1089
1299
  //await this.closeUnexpectedPopups();
1090
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1300
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1091
1301
  this.logger.info("Trying again");
1092
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1093
- info.screenshotPath = screenshotPath;
1094
- Object.assign(err, { info: info });
1302
+ await _screenshot(state, this);
1303
+ Object.assign(err, { info: state.info });
1095
1304
  await element.click();
1096
1305
  await new Promise((resolve) => setTimeout(resolve, 500));
1097
1306
  if (format) {
1098
- value = dayjs(value).format(format);
1099
- await element.fill(value);
1307
+ state.value = dayjs(state.value).format(format);
1308
+ await state.element.fill(state.value);
1100
1309
  }
1101
1310
  else {
1102
- const dateTimeValue = await getDateTimeValue({ value, element });
1103
- await element.evaluateHandle((el, dateTimeValue) => {
1311
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1312
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1104
1313
  el.value = ""; // clear input
1105
1314
  el.value = dateTimeValue;
1106
1315
  }, dateTimeValue);
@@ -1113,84 +1322,63 @@ class StableBrowser {
1113
1322
  }
1114
1323
  }
1115
1324
  catch (e) {
1116
- error = e;
1117
- throw e;
1325
+ await _commandError(state, e, this);
1118
1326
  }
1119
1327
  finally {
1120
- const endTime = Date.now();
1121
- this._reportToWorld(world, {
1122
- element_name: selectors.element_name,
1123
- type: Types.SET_DATE_TIME,
1124
- screenshotId,
1125
- value: value,
1126
- text: `setDateTime input with value: ${value}`,
1127
- result: error
1128
- ? {
1129
- status: "FAILED",
1130
- startTime,
1131
- endTime,
1132
- message: error === null || error === void 0 ? void 0 : error.message,
1133
- }
1134
- : {
1135
- status: "PASSED",
1136
- startTime,
1137
- endTime,
1138
- },
1139
- info: info,
1140
- });
1328
+ await _commandFinally(state, this);
1141
1329
  }
1142
1330
  }
1143
1331
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1144
- this._validateSelectors(selectors);
1145
- const startTime = Date.now();
1146
- let error = null;
1147
- let screenshotId = null;
1148
- let screenshotPath = null;
1149
- const info = {};
1150
- info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
1151
- info.operation = "clickType";
1152
- info.selectors = selectors;
1332
+ _value = unEscapeString(_value);
1153
1333
  const newValue = await this._replaceWithLocalData(_value, world);
1334
+ const state = {
1335
+ selectors,
1336
+ _params,
1337
+ value: newValue,
1338
+ originalValue: _value,
1339
+ options,
1340
+ world,
1341
+ type: Types.FILL,
1342
+ text: `Click type input with value: ${_value}`,
1343
+ _text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
1344
+ operation: "clickType",
1345
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1346
+ };
1347
+ if (!options) {
1348
+ options = {};
1349
+ }
1154
1350
  if (newValue !== _value) {
1155
1351
  //this.logger.info(_value + "=" + newValue);
1156
1352
  _value = newValue;
1157
1353
  }
1158
- info.value = _value;
1159
1354
  try {
1160
- let element = await this._locate(selectors, info, _params);
1161
- //insert red border around the element
1162
- await this.scrollIfNeeded(element, info);
1163
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1164
- await this._highlightElements(element);
1165
- if (options === null || options === undefined || !options.press) {
1355
+ await _preCommand(state, this);
1356
+ state.info.value = _value;
1357
+ if (!options.press) {
1166
1358
  try {
1167
- let currentValue = await element.inputValue();
1359
+ let currentValue = await state.element.inputValue();
1168
1360
  if (currentValue) {
1169
- await element.fill("");
1361
+ await state.element.fill("");
1170
1362
  }
1171
1363
  }
1172
1364
  catch (e) {
1173
1365
  this.logger.info("unable to clear input value");
1174
1366
  }
1175
1367
  }
1176
- if (options === null || options === undefined || options.press) {
1177
- try {
1178
- await element.click({ timeout: 5000 });
1179
- }
1180
- catch (e) {
1181
- await element.dispatchEvent("click");
1182
- }
1368
+ if (options.press) {
1369
+ options.timeout = 5000;
1370
+ await performAction("click", state.element, options, this, state, _params);
1183
1371
  }
1184
1372
  else {
1185
1373
  try {
1186
- await element.focus();
1374
+ await state.element.focus();
1187
1375
  }
1188
1376
  catch (e) {
1189
- await element.dispatchEvent("focus");
1377
+ await state.element.dispatchEvent("focus");
1190
1378
  }
1191
1379
  }
1192
1380
  await new Promise((resolve) => setTimeout(resolve, 500));
1193
- const valueSegment = _value.split("&&");
1381
+ const valueSegment = state.value.split("&&");
1194
1382
  for (let i = 0; i < valueSegment.length; i++) {
1195
1383
  if (i > 0) {
1196
1384
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1210,13 +1398,19 @@ class StableBrowser {
1210
1398
  await new Promise((resolve) => setTimeout(resolve, 500));
1211
1399
  }
1212
1400
  }
1401
+ await _screenshot(state, this);
1213
1402
  if (enter === true) {
1214
1403
  await new Promise((resolve) => setTimeout(resolve, 2000));
1215
1404
  await this.page.keyboard.press("Enter");
1216
1405
  await this.waitForPageLoad();
1217
1406
  }
1218
1407
  else if (enter === false) {
1219
- await element.dispatchEvent("change");
1408
+ try {
1409
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1410
+ }
1411
+ catch (e) {
1412
+ // ignore
1413
+ }
1220
1414
  //await this.page.keyboard.press("Tab");
1221
1415
  }
1222
1416
  else {
@@ -1225,111 +1419,60 @@ class StableBrowser {
1225
1419
  await this.waitForPageLoad();
1226
1420
  }
1227
1421
  }
1228
- return info;
1422
+ return state.info;
1229
1423
  }
1230
1424
  catch (e) {
1231
- //await this.closeUnexpectedPopups();
1232
- this.logger.error("fill failed " + JSON.stringify(info));
1233
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1234
- info.screenshotPath = screenshotPath;
1235
- Object.assign(e, { info: info });
1236
- error = e;
1237
- throw e;
1425
+ await _commandError(state, e, this);
1238
1426
  }
1239
1427
  finally {
1240
- const endTime = Date.now();
1241
- this._reportToWorld(world, {
1242
- element_name: selectors.element_name,
1243
- type: Types.FILL,
1244
- screenshotId,
1245
- value: _value,
1246
- text: `clickType input with value: ${_value}`,
1247
- result: error
1248
- ? {
1249
- status: "FAILED",
1250
- startTime,
1251
- endTime,
1252
- message: error === null || error === void 0 ? void 0 : error.message,
1253
- }
1254
- : {
1255
- status: "PASSED",
1256
- startTime,
1257
- endTime,
1258
- },
1259
- info: info,
1260
- });
1428
+ await _commandFinally(state, this);
1261
1429
  }
1262
1430
  }
1263
1431
  async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
1264
- this._validateSelectors(selectors);
1265
- const startTime = Date.now();
1266
- let error = null;
1267
- let screenshotId = null;
1268
- let screenshotPath = null;
1269
- const info = {};
1270
- info.log = "***** fill on " + selectors.element_name + " with value " + value + "*****\n";
1271
- info.operation = "fill";
1272
- info.selectors = selectors;
1273
- info.value = value;
1432
+ const state = {
1433
+ selectors,
1434
+ _params,
1435
+ value: unEscapeString(value),
1436
+ options,
1437
+ world,
1438
+ type: Types.FILL,
1439
+ text: `Fill input with value: ${value}`,
1440
+ operation: "fill",
1441
+ log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
1442
+ };
1274
1443
  try {
1275
- let element = await this._locate(selectors, info, _params);
1276
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1277
- await this._highlightElements(element);
1278
- await element.fill(value);
1279
- await element.dispatchEvent("change");
1444
+ await _preCommand(state, this);
1445
+ await state.element.fill(value);
1446
+ await state.element.dispatchEvent("change");
1280
1447
  if (enter) {
1281
1448
  await new Promise((resolve) => setTimeout(resolve, 2000));
1282
1449
  await this.page.keyboard.press("Enter");
1283
1450
  }
1284
1451
  await this.waitForPageLoad();
1285
- return info;
1452
+ return state.info;
1286
1453
  }
1287
1454
  catch (e) {
1288
- //await this.closeUnexpectedPopups();
1289
- this.logger.error("fill failed " + info.log);
1290
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1291
- info.screenshotPath = screenshotPath;
1292
- Object.assign(e, { info: info });
1293
- error = e;
1294
- throw e;
1455
+ await _commandError(state, e, this);
1295
1456
  }
1296
1457
  finally {
1297
- const endTime = Date.now();
1298
- this._reportToWorld(world, {
1299
- element_name: selectors.element_name,
1300
- type: Types.FILL,
1301
- screenshotId,
1302
- value,
1303
- text: `Fill input with value: ${value}`,
1304
- result: error
1305
- ? {
1306
- status: "FAILED",
1307
- startTime,
1308
- endTime,
1309
- message: error === null || error === void 0 ? void 0 : error.message,
1310
- }
1311
- : {
1312
- status: "PASSED",
1313
- startTime,
1314
- endTime,
1315
- },
1316
- info: info,
1317
- });
1458
+ await _commandFinally(state, this);
1318
1459
  }
1319
1460
  }
1320
1461
  async getText(selectors, _params = null, options = {}, info = {}, world = null) {
1321
1462
  return await this._getText(selectors, 0, _params, options, info, world);
1322
1463
  }
1323
1464
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1324
- this._validateSelectors(selectors);
1465
+ const timeout = this._getFindElementTimeout(options);
1466
+ _validateSelectors(selectors);
1325
1467
  let screenshotId = null;
1326
1468
  let screenshotPath = null;
1327
1469
  if (!info.log) {
1328
1470
  info.log = "";
1471
+ info.locatorLog = new LocatorLog(selectors);
1329
1472
  }
1330
1473
  info.operation = "getText";
1331
1474
  info.selectors = selectors;
1332
- let element = await this._locate(selectors, info, _params);
1475
+ let element = await this._locate(selectors, info, _params, timeout);
1333
1476
  if (climb > 0) {
1334
1477
  const climbArray = [];
1335
1478
  for (let i = 0; i < climb; i++) {
@@ -1348,6 +1491,18 @@ class StableBrowser {
1348
1491
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1349
1492
  try {
1350
1493
  await this._highlightElements(element);
1494
+ // if (world && world.screenshot && !world.screenshotPath) {
1495
+ // // console.log(`Highlighting for get text while running from recorder`);
1496
+ // this._highlightElements(element)
1497
+ // .then(async () => {
1498
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1499
+ // this._unhighlightElements(element).then(
1500
+ // () => {}
1501
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1502
+ // );
1503
+ // })
1504
+ // .catch(e);
1505
+ // }
1351
1506
  const elementText = await element.innerText();
1352
1507
  return {
1353
1508
  text: elementText,
@@ -1359,192 +1514,149 @@ class StableBrowser {
1359
1514
  }
1360
1515
  catch (e) {
1361
1516
  //await this.closeUnexpectedPopups();
1362
- this.logger.info("no innerText will use textContent");
1517
+ this.logger.info("no innerText, will use textContent");
1363
1518
  const elementText = await element.textContent();
1364
1519
  return { text: elementText, screenshotId, screenshotPath, value: value };
1365
1520
  }
1366
1521
  }
1367
1522
  async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
1368
- var _a;
1369
- this._validateSelectors(selectors);
1370
1523
  if (!pattern) {
1371
1524
  throw new Error("pattern is null");
1372
1525
  }
1373
1526
  if (!text) {
1374
1527
  throw new Error("text is null");
1375
1528
  }
1529
+ const state = {
1530
+ selectors,
1531
+ _params,
1532
+ pattern,
1533
+ value: pattern,
1534
+ options,
1535
+ world,
1536
+ locate: false,
1537
+ scroll: false,
1538
+ screenshot: false,
1539
+ highlight: false,
1540
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1541
+ text: `Verify element contains pattern: ${pattern}`,
1542
+ _text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
1543
+ operation: "containsPattern",
1544
+ log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1545
+ };
1376
1546
  const newValue = await this._replaceWithLocalData(text, world);
1377
1547
  if (newValue !== text) {
1378
1548
  this.logger.info(text + "=" + newValue);
1379
1549
  text = newValue;
1380
1550
  }
1381
- const startTime = Date.now();
1382
- let error = null;
1383
- let screenshotId = null;
1384
- let screenshotPath = null;
1385
- const info = {};
1386
- info.log =
1387
- "***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
1388
- info.operation = "containsPattern";
1389
- info.selectors = selectors;
1390
- info.value = text;
1391
- info.pattern = pattern;
1392
1551
  let foundObj = null;
1393
1552
  try {
1394
- foundObj = await this._getText(selectors, 0, _params, options, info, world);
1553
+ await _preCommand(state, this);
1554
+ state.info.pattern = pattern;
1555
+ foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
1395
1556
  if (foundObj && foundObj.element) {
1396
- await this.scrollIfNeeded(foundObj.element, info);
1557
+ await this.scrollIfNeeded(foundObj.element, state.info);
1397
1558
  }
1398
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1559
+ await _screenshot(state, this);
1399
1560
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1400
1561
  pattern = pattern.replace("{text}", escapedText);
1401
1562
  let regex = new RegExp(pattern, "im");
1402
- if (!regex.test(foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) && !((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(text))) {
1403
- info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1563
+ if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
1564
+ state.info.foundText = foundObj?.text;
1404
1565
  throw new Error("element doesn't contain text " + text);
1405
1566
  }
1406
- return info;
1567
+ return state.info;
1407
1568
  }
1408
1569
  catch (e) {
1409
- //await this.closeUnexpectedPopups();
1410
- this.logger.error("verify element contains text failed " + info.log);
1411
- this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
1412
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1413
- info.screenshotPath = screenshotPath;
1414
- Object.assign(e, { info: info });
1415
- error = e;
1416
- throw e;
1570
+ this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
1571
+ await _commandError(state, e, this);
1417
1572
  }
1418
1573
  finally {
1419
- const endTime = Date.now();
1420
- this._reportToWorld(world, {
1421
- element_name: selectors.element_name,
1422
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1423
- value: pattern,
1424
- text: `Verify element contains pattern: ${pattern}`,
1425
- screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
1426
- result: error
1427
- ? {
1428
- status: "FAILED",
1429
- startTime,
1430
- endTime,
1431
- message: error === null || error === void 0 ? void 0 : error.message,
1432
- }
1433
- : {
1434
- status: "PASSED",
1435
- startTime,
1436
- endTime,
1437
- },
1438
- info: info,
1439
- });
1574
+ await _commandFinally(state, this);
1440
1575
  }
1441
1576
  }
1442
1577
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1443
- var _a, _b, _c;
1444
- this._validateSelectors(selectors);
1578
+ const timeout = this._getFindElementTimeout(options);
1579
+ const startTime = Date.now();
1580
+ const state = {
1581
+ selectors,
1582
+ _params,
1583
+ value: text,
1584
+ options,
1585
+ world,
1586
+ locate: false,
1587
+ scroll: false,
1588
+ screenshot: false,
1589
+ highlight: false,
1590
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1591
+ text: `Verify element contains text: ${text}`,
1592
+ operation: "containsText",
1593
+ log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
1594
+ };
1445
1595
  if (!text) {
1446
1596
  throw new Error("text is null");
1447
1597
  }
1448
- const startTime = Date.now();
1449
- let error = null;
1450
- let screenshotId = null;
1451
- let screenshotPath = null;
1452
- const info = {};
1453
- info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
1454
- info.operation = "containsText";
1455
- info.selectors = selectors;
1598
+ text = unEscapeString(text);
1456
1599
  const newValue = await this._replaceWithLocalData(text, world);
1457
1600
  if (newValue !== text) {
1458
1601
  this.logger.info(text + "=" + newValue);
1459
1602
  text = newValue;
1460
1603
  }
1461
- info.value = text;
1462
1604
  let foundObj = null;
1463
1605
  try {
1464
- foundObj = await this._getText(selectors, climb, _params, options, info, world);
1465
- if (foundObj && foundObj.element) {
1466
- await this.scrollIfNeeded(foundObj.element, info);
1467
- }
1468
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1469
- const dateAlternatives = findDateAlternatives(text);
1470
- const numberAlternatives = findNumberAlternatives(text);
1471
- if (dateAlternatives.date) {
1472
- for (let i = 0; i < dateAlternatives.dates.length; i++) {
1473
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(dateAlternatives.dates[i])) ||
1474
- ((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(dateAlternatives.dates[i]))) {
1475
- return info;
1606
+ while (Date.now() - startTime < timeout) {
1607
+ try {
1608
+ await _preCommand(state, this);
1609
+ foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
1610
+ if (foundObj && foundObj.element) {
1611
+ await this.scrollIfNeeded(foundObj.element, state.info);
1612
+ }
1613
+ await _screenshot(state, this);
1614
+ const dateAlternatives = findDateAlternatives(text);
1615
+ const numberAlternatives = findNumberAlternatives(text);
1616
+ if (dateAlternatives.date) {
1617
+ for (let i = 0; i < dateAlternatives.dates.length; i++) {
1618
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1619
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1620
+ return state.info;
1621
+ }
1622
+ }
1476
1623
  }
1477
- }
1478
- throw new Error("element doesn't contain text " + text);
1479
- }
1480
- else if (numberAlternatives.number) {
1481
- for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1482
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(numberAlternatives.numbers[i])) ||
1483
- ((_b = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _b === void 0 ? void 0 : _b.includes(numberAlternatives.numbers[i]))) {
1484
- return info;
1624
+ else if (numberAlternatives.number) {
1625
+ for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1626
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1627
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1628
+ return state.info;
1629
+ }
1630
+ }
1631
+ }
1632
+ else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
1633
+ return state.info;
1485
1634
  }
1486
1635
  }
1487
- throw new Error("element doesn't contain text " + text);
1488
- }
1489
- else if (!(foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(text)) && !((_c = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _c === void 0 ? void 0 : _c.includes(text))) {
1490
- info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1491
- info.value = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value;
1492
- throw new Error("element doesn't contain text " + text);
1636
+ catch (e) {
1637
+ // Log error but continue retrying until timeout is reached
1638
+ this.logger.warn("Retrying containsText due to: " + e.message);
1639
+ }
1640
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
1493
1641
  }
1494
- return info;
1642
+ state.info.foundText = foundObj?.text;
1643
+ state.info.value = foundObj?.value;
1644
+ throw new Error("element doesn't contain text " + text);
1495
1645
  }
1496
1646
  catch (e) {
1497
- //await this.closeUnexpectedPopups();
1498
- this.logger.error("verify element contains text failed " + info.log);
1499
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1500
- info.screenshotPath = screenshotPath;
1501
- Object.assign(e, { info: info });
1502
- error = e;
1647
+ await _commandError(state, e, this);
1503
1648
  throw e;
1504
1649
  }
1505
1650
  finally {
1506
- const endTime = Date.now();
1507
- this._reportToWorld(world, {
1508
- element_name: selectors.element_name,
1509
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1510
- text: `Verify element contains text: ${text}`,
1511
- value: text,
1512
- screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
1513
- result: error
1514
- ? {
1515
- status: "FAILED",
1516
- startTime,
1517
- endTime,
1518
- message: error === null || error === void 0 ? void 0 : error.message,
1519
- }
1520
- : {
1521
- status: "PASSED",
1522
- startTime,
1523
- endTime,
1524
- },
1525
- info: info,
1526
- });
1651
+ await _commandFinally(state, this);
1527
1652
  }
1528
1653
  }
1529
- _getDataFile(world = null) {
1530
- let dataFile = null;
1531
- if (world && world.reportFolder) {
1532
- dataFile = path.join(world.reportFolder, "data.json");
1533
- }
1534
- else if (this.reportFolder) {
1535
- dataFile = path.join(this.reportFolder, "data.json");
1536
- }
1537
- else if (this.context && this.context.reportFolder) {
1538
- dataFile = path.join(this.context.reportFolder, "data.json");
1539
- }
1540
- else {
1541
- dataFile = "data.json";
1542
- }
1543
- return dataFile;
1544
- }
1545
1654
  async waitForUserInput(message, world = null) {
1546
1655
  if (!message) {
1547
- message = "Press any key to continue";
1656
+ message = "# Wait for user input. Press any key to continue";
1657
+ }
1658
+ else {
1659
+ message = "# Wait for user input. " + message;
1548
1660
  }
1549
1661
  message += "\n";
1550
1662
  const value = await new Promise((resolve) => {
@@ -1567,13 +1679,22 @@ class StableBrowser {
1567
1679
  return;
1568
1680
  }
1569
1681
  // if data file exists, load it
1570
- const dataFile = this._getDataFile(world);
1682
+ const dataFile = _getDataFile(world, this.context, this);
1571
1683
  let data = this.getTestData(world);
1572
1684
  // merge the testData with the existing data
1573
1685
  Object.assign(data, testData);
1574
1686
  // save the data to the file
1575
1687
  fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
1576
1688
  }
1689
+ overwriteTestData(testData, world = null) {
1690
+ if (!testData) {
1691
+ return;
1692
+ }
1693
+ // if data file exists, load it
1694
+ const dataFile = _getDataFile(world, this.context, this);
1695
+ // save the data to the file
1696
+ fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
1697
+ }
1577
1698
  _getDataFilePath(fileName) {
1578
1699
  let dataFile = path.join(this.project_path, "data", fileName);
1579
1700
  if (fs.existsSync(dataFile)) {
@@ -1670,7 +1791,7 @@ class StableBrowser {
1670
1791
  }
1671
1792
  }
1672
1793
  getTestData(world = null) {
1673
- const dataFile = this._getDataFile(world);
1794
+ const dataFile = _getDataFile(world, this.context, this);
1674
1795
  let data = {};
1675
1796
  if (fs.existsSync(dataFile)) {
1676
1797
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1702,11 +1823,9 @@ class StableBrowser {
1702
1823
  if (!fs.existsSync(world.screenshotPath)) {
1703
1824
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1704
1825
  }
1705
- let nextIndex = 1;
1706
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1707
- nextIndex++;
1708
- }
1709
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1826
+ // to make sure the path doesn't start with -
1827
+ const uuidStr = "id_" + randomUUID();
1828
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1710
1829
  try {
1711
1830
  await this.takeScreenshot(screenshotPath);
1712
1831
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1716,15 +1835,15 @@ class StableBrowser {
1716
1835
  // this.logger.info("unable to save screenshot " + screenshotPath);
1717
1836
  // }
1718
1837
  // });
1838
+ result.screenshotId = uuidStr;
1839
+ result.screenshotPath = screenshotPath;
1840
+ if (info && info.box) {
1841
+ await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1842
+ }
1719
1843
  }
1720
1844
  catch (e) {
1721
1845
  this.logger.info("unable to take screenshot, ignored");
1722
1846
  }
1723
- result.screenshotId = nextIndex;
1724
- result.screenshotPath = screenshotPath;
1725
- if (info && info.box) {
1726
- await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1727
- }
1728
1847
  }
1729
1848
  else if (options && options.screenshot) {
1730
1849
  result.screenshotPath = options.screenshotPath;
@@ -1749,7 +1868,6 @@ class StableBrowser {
1749
1868
  }
1750
1869
  async takeScreenshot(screenshotPath) {
1751
1870
  const playContext = this.context.playContext;
1752
- const client = await playContext.newCDPSession(this.page);
1753
1871
  // Using CDP to capture the screenshot
1754
1872
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
1755
1873
  document.body.scrollWidth,
@@ -1759,164 +1877,198 @@ class StableBrowser {
1759
1877
  document.body.clientWidth,
1760
1878
  document.documentElement.clientWidth,
1761
1879
  ])));
1762
- const viewportHeight = Math.max(...(await this.page.evaluate(() => [
1763
- document.body.scrollHeight,
1764
- document.documentElement.scrollHeight,
1765
- document.body.offsetHeight,
1766
- document.documentElement.offsetHeight,
1767
- document.body.clientHeight,
1768
- document.documentElement.clientHeight,
1769
- ])));
1770
- const { data } = await client.send("Page.captureScreenshot", {
1771
- format: "png",
1772
- // clip: {
1773
- // x: 0,
1774
- // y: 0,
1775
- // width: viewportWidth,
1776
- // height: viewportHeight,
1777
- // scale: 1,
1778
- // },
1779
- });
1780
- if (!screenshotPath) {
1781
- return data;
1782
- }
1783
- let screenshotBuffer = Buffer.from(data, "base64");
1784
- const sharpBuffer = sharp(screenshotBuffer);
1785
- const metadata = await sharpBuffer.metadata();
1786
- //check if you are on retina display and reduce the quality of the image
1787
- if (metadata.width > viewportWidth || metadata.height > viewportHeight) {
1788
- screenshotBuffer = await sharpBuffer
1789
- .resize(viewportWidth, viewportHeight, {
1790
- fit: sharp.fit.inside,
1791
- withoutEnlargement: true,
1792
- })
1793
- .toBuffer();
1794
- }
1795
- fs.writeFileSync(screenshotPath, screenshotBuffer);
1796
- await client.detach();
1880
+ let screenshotBuffer = null;
1881
+ // if (focusedElement) {
1882
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1883
+ // await this._unhighlightElements(focusedElement);
1884
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1885
+ // console.log(`Unhighlighted previous element`);
1886
+ // }
1887
+ // if (focusedElement) {
1888
+ // await this._highlightElements(focusedElement);
1889
+ // }
1890
+ if (this.context.browserName === "chromium") {
1891
+ const client = await playContext.newCDPSession(this.page);
1892
+ const { data } = await client.send("Page.captureScreenshot", {
1893
+ format: "png",
1894
+ // clip: {
1895
+ // x: 0,
1896
+ // y: 0,
1897
+ // width: viewportWidth,
1898
+ // height: viewportHeight,
1899
+ // scale: 1,
1900
+ // },
1901
+ });
1902
+ await client.detach();
1903
+ if (!screenshotPath) {
1904
+ return data;
1905
+ }
1906
+ screenshotBuffer = Buffer.from(data, "base64");
1907
+ }
1908
+ else {
1909
+ screenshotBuffer = await this.page.screenshot();
1910
+ }
1911
+ // if (focusedElement) {
1912
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1913
+ // await this._unhighlightElements(focusedElement);
1914
+ // }
1915
+ let image = await Jimp.read(screenshotBuffer);
1916
+ // Get the image dimensions
1917
+ const { width, height } = image.bitmap;
1918
+ const resizeRatio = viewportWidth / width;
1919
+ // Resize the image to fit within the viewport dimensions without enlarging
1920
+ if (width > viewportWidth) {
1921
+ image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
1922
+ await image.write(screenshotPath);
1923
+ }
1924
+ else {
1925
+ fs.writeFileSync(screenshotPath, screenshotBuffer);
1926
+ }
1927
+ return screenshotBuffer;
1797
1928
  }
1798
1929
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1799
- this._validateSelectors(selectors);
1800
- const startTime = Date.now();
1801
- let error = null;
1802
- let screenshotId = null;
1803
- let screenshotPath = null;
1930
+ const state = {
1931
+ selectors,
1932
+ _params,
1933
+ options,
1934
+ world,
1935
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1936
+ text: `Verify element exists in page`,
1937
+ operation: "verifyElementExistInPage",
1938
+ log: "***** verify element " + selectors.element_name + " exists in page *****\n",
1939
+ };
1804
1940
  await new Promise((resolve) => setTimeout(resolve, 2000));
1805
- const info = {};
1806
- info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
1807
- info.operation = "verify";
1808
- info.selectors = selectors;
1809
1941
  try {
1810
- const element = await this._locate(selectors, info, _params);
1811
- if (element) {
1812
- await this.scrollIfNeeded(element, info);
1813
- }
1814
- await this._highlightElements(element);
1815
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1816
- await expect(element).toHaveCount(1, { timeout: 10000 });
1817
- return info;
1942
+ await _preCommand(state, this);
1943
+ await expect(state.element).toHaveCount(1, { timeout: 10000 });
1944
+ return state.info;
1818
1945
  }
1819
1946
  catch (e) {
1820
- //await this.closeUnexpectedPopups();
1821
- this.logger.error("verify failed " + info.log);
1822
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1823
- info.screenshotPath = screenshotPath;
1824
- Object.assign(e, { info: info });
1825
- error = e;
1826
- throw e;
1947
+ await _commandError(state, e, this);
1827
1948
  }
1828
1949
  finally {
1829
- const endTime = Date.now();
1830
- this._reportToWorld(world, {
1831
- element_name: selectors.element_name,
1832
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1833
- text: "Verify element exists in page",
1834
- screenshotId,
1835
- result: error
1836
- ? {
1837
- status: "FAILED",
1838
- startTime,
1839
- endTime,
1840
- message: error === null || error === void 0 ? void 0 : error.message,
1841
- }
1842
- : {
1843
- status: "PASSED",
1844
- startTime,
1845
- endTime,
1846
- },
1847
- info: info,
1848
- });
1950
+ await _commandFinally(state, this);
1849
1951
  }
1850
1952
  }
1851
1953
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1852
- this._validateSelectors(selectors);
1853
- const startTime = Date.now();
1854
- let error = null;
1855
- let screenshotId = null;
1856
- let screenshotPath = null;
1954
+ const state = {
1955
+ selectors,
1956
+ _params,
1957
+ attribute,
1958
+ variable,
1959
+ options,
1960
+ world,
1961
+ type: Types.EXTRACT,
1962
+ text: `Extract attribute from element`,
1963
+ _text: `Extract attribute ${attribute} from ${selectors.element_name}`,
1964
+ operation: "extractAttribute",
1965
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1966
+ allowDisabled: true,
1967
+ };
1857
1968
  await new Promise((resolve) => setTimeout(resolve, 2000));
1858
- const info = {};
1859
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1860
- info.operation = "extract";
1861
- info.selectors = selectors;
1862
1969
  try {
1863
- const element = await this._locate(selectors, info, _params);
1864
- await this._highlightElements(element);
1865
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1970
+ await _preCommand(state, this);
1866
1971
  switch (attribute) {
1867
1972
  case "inner_text":
1868
- info.value = await element.innerText();
1973
+ state.value = await state.element.innerText();
1869
1974
  break;
1870
1975
  case "href":
1871
- info.value = await element.getAttribute("href");
1976
+ state.value = await state.element.getAttribute("href");
1872
1977
  break;
1873
1978
  case "value":
1874
- info.value = await element.inputValue();
1979
+ state.value = await state.element.inputValue();
1980
+ break;
1981
+ case "text":
1982
+ state.value = await state.element.textContent();
1875
1983
  break;
1876
1984
  default:
1877
- info.value = await element.getAttribute(attribute);
1985
+ state.value = await state.element.getAttribute(attribute);
1878
1986
  break;
1879
1987
  }
1880
- this[variable] = info.value;
1881
- if (world) {
1882
- world[variable] = info.value;
1883
- }
1884
- this.setTestData({ [variable]: info.value }, world);
1885
- this.logger.info("set test data: " + variable + "=" + info.value);
1886
- return info;
1988
+ state.info.value = state.value;
1989
+ this.setTestData({ [variable]: state.value }, world);
1990
+ this.logger.info("set test data: " + variable + "=" + state.value);
1991
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1992
+ return state.info;
1887
1993
  }
1888
1994
  catch (e) {
1889
- //await this.closeUnexpectedPopups();
1890
- this.logger.error("extract failed " + info.log);
1891
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1892
- info.screenshotPath = screenshotPath;
1893
- Object.assign(e, { info: info });
1894
- error = e;
1895
- throw e;
1995
+ await _commandError(state, e, this);
1896
1996
  }
1897
1997
  finally {
1898
- const endTime = Date.now();
1899
- this._reportToWorld(world, {
1900
- element_name: selectors.element_name,
1901
- type: Types.EXTRACT_ATTRIBUTE,
1902
- variable: variable,
1903
- value: info.value,
1904
- text: "Extract attribute from element",
1905
- screenshotId,
1906
- result: error
1907
- ? {
1908
- status: "FAILED",
1909
- startTime,
1910
- endTime,
1911
- message: error === null || error === void 0 ? void 0 : error.message,
1912
- }
1913
- : {
1914
- status: "PASSED",
1915
- startTime,
1916
- endTime,
1917
- },
1918
- info: info,
1919
- });
1998
+ await _commandFinally(state, this);
1999
+ }
2000
+ }
2001
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
2002
+ const state = {
2003
+ selectors,
2004
+ _params,
2005
+ attribute,
2006
+ value,
2007
+ options,
2008
+ world,
2009
+ type: Types.VERIFY_ATTRIBUTE,
2010
+ highlight: true,
2011
+ screenshot: true,
2012
+ text: `Verify element attribute`,
2013
+ _text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
2014
+ operation: "verifyAttribute",
2015
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
2016
+ allowDisabled: true,
2017
+ };
2018
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2019
+ let val;
2020
+ let expectedValue;
2021
+ try {
2022
+ await _preCommand(state, this);
2023
+ expectedValue = await replaceWithLocalTestData(state.value, world);
2024
+ state.info.expectedValue = expectedValue;
2025
+ switch (attribute) {
2026
+ case "innerText":
2027
+ val = String(await state.element.innerText());
2028
+ break;
2029
+ case "text":
2030
+ val = String(await state.element.textContent());
2031
+ break;
2032
+ case "value":
2033
+ val = String(await state.element.inputValue());
2034
+ break;
2035
+ case "checked":
2036
+ val = String(await state.element.isChecked());
2037
+ break;
2038
+ case "disabled":
2039
+ val = String(await state.element.isDisabled());
2040
+ break;
2041
+ case "readOnly":
2042
+ const isEditable = await state.element.isEditable();
2043
+ val = String(!isEditable);
2044
+ break;
2045
+ default:
2046
+ val = String(await state.element.getAttribute(attribute));
2047
+ break;
2048
+ }
2049
+ state.info.value = val;
2050
+ let regex;
2051
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
2052
+ const patternBody = expectedValue.slice(1, -1);
2053
+ regex = new RegExp(patternBody, "g");
2054
+ }
2055
+ else {
2056
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2057
+ regex = new RegExp(escapedPattern, "g");
2058
+ }
2059
+ if (!val.match(regex)) {
2060
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
2061
+ state.info.failCause.assertionFailed = true;
2062
+ state.info.failCause.lastError = errorMessage;
2063
+ throw new Error(errorMessage);
2064
+ }
2065
+ return state.info;
2066
+ }
2067
+ catch (e) {
2068
+ await _commandError(state, e, this);
2069
+ }
2070
+ finally {
2071
+ await _commandFinally(state, this);
1920
2072
  }
1921
2073
  }
1922
2074
  async extractEmailData(emailAddress, options, world) {
@@ -1937,7 +2089,7 @@ class StableBrowser {
1937
2089
  if (options && options.timeout) {
1938
2090
  timeout = options.timeout;
1939
2091
  }
1940
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
2092
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1941
2093
  const request = {
1942
2094
  method: "POST",
1943
2095
  url: serviceUrl,
@@ -1993,7 +2145,8 @@ class StableBrowser {
1993
2145
  catch (e) {
1994
2146
  errorCount++;
1995
2147
  if (errorCount > 3) {
1996
- throw e;
2148
+ // throw e;
2149
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1997
2150
  }
1998
2151
  // ignore
1999
2152
  }
@@ -2007,27 +2160,32 @@ class StableBrowser {
2007
2160
  async _highlightElements(scope, css) {
2008
2161
  try {
2009
2162
  if (!scope) {
2163
+ // console.log(`Scope is not defined`);
2010
2164
  return;
2011
2165
  }
2012
2166
  if (!css) {
2013
2167
  scope
2014
2168
  .evaluate((node) => {
2015
2169
  if (node && node.style) {
2016
- let originalBorder = node.style.border;
2017
- node.style.border = "2px solid red";
2170
+ let originalOutline = node.style.outline;
2171
+ // console.log(`Original outline was: ${originalOutline}`);
2172
+ // node.__previousOutline = originalOutline;
2173
+ node.style.outline = "2px solid red";
2174
+ // console.log(`New outline is: ${node.style.outline}`);
2018
2175
  if (window) {
2019
2176
  window.addEventListener("beforeunload", function (e) {
2020
- node.style.border = originalBorder;
2177
+ node.style.outline = originalOutline;
2021
2178
  });
2022
2179
  }
2023
2180
  setTimeout(function () {
2024
- node.style.border = originalBorder;
2181
+ node.style.outline = originalOutline;
2025
2182
  }, 2000);
2026
2183
  }
2027
2184
  })
2028
2185
  .then(() => { })
2029
2186
  .catch((e) => {
2030
2187
  // ignore
2188
+ // console.error(`Could not highlight node : ${e}`);
2031
2189
  });
2032
2190
  }
2033
2191
  else {
@@ -2043,17 +2201,18 @@ class StableBrowser {
2043
2201
  if (!element.style) {
2044
2202
  return;
2045
2203
  }
2046
- var originalBorder = element.style.border;
2204
+ let originalOutline = element.style.outline;
2205
+ element.__previousOutline = originalOutline;
2047
2206
  // Set the new border to be red and 2px solid
2048
- element.style.border = "2px solid red";
2207
+ element.style.outline = "2px solid red";
2049
2208
  if (window) {
2050
2209
  window.addEventListener("beforeunload", function (e) {
2051
- element.style.border = originalBorder;
2210
+ element.style.outline = originalOutline;
2052
2211
  });
2053
2212
  }
2054
2213
  // Set a timeout to revert to the original border after 2 seconds
2055
2214
  setTimeout(function () {
2056
- element.style.border = originalBorder;
2215
+ element.style.outline = originalOutline;
2057
2216
  }, 2000);
2058
2217
  }
2059
2218
  return;
@@ -2061,6 +2220,7 @@ class StableBrowser {
2061
2220
  .then(() => { })
2062
2221
  .catch((e) => {
2063
2222
  // ignore
2223
+ // console.error(`Could not highlight css: ${e}`);
2064
2224
  });
2065
2225
  }
2066
2226
  }
@@ -2068,6 +2228,54 @@ class StableBrowser {
2068
2228
  console.debug(error);
2069
2229
  }
2070
2230
  }
2231
+ // async _unhighlightElements(scope, css) {
2232
+ // try {
2233
+ // if (!scope) {
2234
+ // return;
2235
+ // }
2236
+ // if (!css) {
2237
+ // scope
2238
+ // .evaluate((node) => {
2239
+ // if (node && node.style) {
2240
+ // if (!node.__previousOutline) {
2241
+ // node.style.outline = "";
2242
+ // } else {
2243
+ // node.style.outline = node.__previousOutline;
2244
+ // }
2245
+ // }
2246
+ // })
2247
+ // .then(() => {})
2248
+ // .catch((e) => {
2249
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2250
+ // });
2251
+ // } else {
2252
+ // scope
2253
+ // .evaluate(([css]) => {
2254
+ // if (!css) {
2255
+ // return;
2256
+ // }
2257
+ // let elements = Array.from(document.querySelectorAll(css));
2258
+ // for (i = 0; i < elements.length; i++) {
2259
+ // let element = elements[i];
2260
+ // if (!element.style) {
2261
+ // return;
2262
+ // }
2263
+ // if (!element.__previousOutline) {
2264
+ // element.style.outline = "";
2265
+ // } else {
2266
+ // element.style.outline = element.__previousOutline;
2267
+ // }
2268
+ // }
2269
+ // })
2270
+ // .then(() => {})
2271
+ // .catch((e) => {
2272
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2273
+ // });
2274
+ // }
2275
+ // } catch (error) {
2276
+ // // console.debug(error);
2277
+ // }
2278
+ // }
2071
2279
  async verifyPagePath(pathPart, options = {}, world = null) {
2072
2280
  const startTime = Date.now();
2073
2281
  let error = null;
@@ -2104,20 +2312,22 @@ class StableBrowser {
2104
2312
  info.screenshotPath = screenshotPath;
2105
2313
  Object.assign(e, { info: info });
2106
2314
  error = e;
2107
- throw e;
2315
+ // throw e;
2316
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2108
2317
  }
2109
2318
  finally {
2110
2319
  const endTime = Date.now();
2111
- this._reportToWorld(world, {
2320
+ _reportToWorld(world, {
2112
2321
  type: Types.VERIFY_PAGE_PATH,
2113
2322
  text: "Verify page path",
2323
+ _text: "Verify the page path contains " + pathPart,
2114
2324
  screenshotId,
2115
2325
  result: error
2116
2326
  ? {
2117
2327
  status: "FAILED",
2118
2328
  startTime,
2119
2329
  endTime,
2120
- message: error === null || error === void 0 ? void 0 : error.message,
2330
+ message: error?.message,
2121
2331
  }
2122
2332
  : {
2123
2333
  status: "PASSED",
@@ -2128,94 +2338,58 @@ class StableBrowser {
2128
2338
  });
2129
2339
  }
2130
2340
  }
2131
- async verifyTextExistInPage(text, options = {}, world = null) {
2341
+ async verifyPageTitle(title, options = {}, world = null) {
2132
2342
  const startTime = Date.now();
2133
- const timeout = this._getLoadTimeout(options);
2134
2343
  let error = null;
2135
2344
  let screenshotId = null;
2136
2345
  let screenshotPath = null;
2137
2346
  await new Promise((resolve) => setTimeout(resolve, 2000));
2138
2347
  const info = {};
2139
- info.log = "***** verify text " + text + " exists in page *****\n";
2140
- info.operation = "verifyTextExistInPage";
2141
- const newValue = await this._replaceWithLocalData(text, world);
2142
- if (newValue !== text) {
2143
- this.logger.info(text + "=" + newValue);
2144
- text = newValue;
2145
- }
2146
- info.text = text;
2147
- let dateAlternatives = findDateAlternatives(text);
2148
- let numberAlternatives = findNumberAlternatives(text);
2348
+ info.log = "***** verify page title " + title + " *****\n";
2349
+ info.operation = "verifyPageTitle";
2350
+ const newValue = await this._replaceWithLocalData(title, world);
2351
+ if (newValue !== title) {
2352
+ this.logger.info(title + "=" + newValue);
2353
+ title = newValue;
2354
+ }
2355
+ info.title = title;
2149
2356
  try {
2150
- while (true) {
2151
- const frames = this.page.frames();
2152
- let results = [];
2153
- for (let i = 0; i < frames.length; i++) {
2154
- if (dateAlternatives.date) {
2155
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2156
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
2157
- result.frame = frames[i];
2158
- results.push(result);
2159
- }
2160
- }
2161
- else if (numberAlternatives.number) {
2162
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2163
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
2164
- result.frame = frames[i];
2165
- results.push(result);
2166
- }
2167
- }
2168
- else {
2169
- const result = await this._locateElementByText(frames[i], text, "*", true, {});
2170
- result.frame = frames[i];
2171
- results.push(result);
2172
- }
2173
- }
2174
- info.results = results;
2175
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2176
- if (resultWithElementsFound.length === 0) {
2177
- if (Date.now() - startTime > timeout) {
2178
- throw new Error(`Text ${text} not found in page`);
2357
+ for (let i = 0; i < 30; i++) {
2358
+ const foundTitle = await this.page.title();
2359
+ if (!foundTitle.includes(title)) {
2360
+ if (i === 29) {
2361
+ throw new Error(`url ${foundTitle} doesn't contain ${title}`);
2179
2362
  }
2180
2363
  await new Promise((resolve) => setTimeout(resolve, 1000));
2181
2364
  continue;
2182
2365
  }
2183
- if (resultWithElementsFound[0].randomToken) {
2184
- const frame = resultWithElementsFound[0].frame;
2185
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2186
- await this._highlightElements(frame, dataAttribute);
2187
- const element = await frame.$(dataAttribute);
2188
- if (element) {
2189
- await this.scrollIfNeeded(element, info);
2190
- await element.dispatchEvent("bvt_verify_page_contains_text");
2191
- }
2192
- }
2193
2366
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2194
2367
  return info;
2195
2368
  }
2196
- // await expect(element).toHaveCount(1, { timeout: 10000 });
2197
2369
  }
2198
2370
  catch (e) {
2199
2371
  //await this.closeUnexpectedPopups();
2200
- this.logger.error("verify text exist in page failed " + info.log);
2372
+ this.logger.error("verify page title failed " + info.log);
2201
2373
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2202
2374
  info.screenshotPath = screenshotPath;
2203
2375
  Object.assign(e, { info: info });
2204
2376
  error = e;
2205
- throw e;
2377
+ // throw e;
2378
+ await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
2206
2379
  }
2207
2380
  finally {
2208
2381
  const endTime = Date.now();
2209
- this._reportToWorld(world, {
2210
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2211
- text: "Verify text exists in page",
2382
+ _reportToWorld(world, {
2383
+ type: Types.VERIFY_PAGE_PATH,
2384
+ text: "Verify page title",
2385
+ _text: "Verify the page title contains " + title,
2212
2386
  screenshotId,
2213
2387
  result: error
2214
2388
  ? {
2215
2389
  status: "FAILED",
2216
2390
  startTime,
2217
2391
  endTime,
2218
- message: error === null || error === void 0 ? void 0 : error.message,
2392
+ message: error?.message,
2219
2393
  }
2220
2394
  : {
2221
2395
  status: "PASSED",
@@ -2226,15 +2400,317 @@ class StableBrowser {
2226
2400
  });
2227
2401
  }
2228
2402
  }
2229
- _getServerUrl() {
2230
- let serviceUrl = "https://api.blinq.io";
2231
- if (process.env.NODE_ENV_BLINQ === "dev") {
2232
- serviceUrl = "https://dev.api.blinq.io";
2403
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
2404
+ const frames = this.page.frames();
2405
+ let results = [];
2406
+ // let ignoreCase = false;
2407
+ for (let i = 0; i < frames.length; i++) {
2408
+ if (dateAlternatives.date) {
2409
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2410
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2411
+ result.frame = frames[i];
2412
+ results.push(result);
2413
+ }
2414
+ }
2415
+ else if (numberAlternatives.number) {
2416
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2417
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2418
+ result.frame = frames[i];
2419
+ results.push(result);
2420
+ }
2421
+ }
2422
+ else {
2423
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
2424
+ result.frame = frames[i];
2425
+ results.push(result);
2426
+ }
2427
+ }
2428
+ state.info.results = results;
2429
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2430
+ return resultWithElementsFound;
2431
+ }
2432
+ async verifyTextExistInPage(text, options = {}, world = null) {
2433
+ text = unEscapeString(text);
2434
+ const state = {
2435
+ text_search: text,
2436
+ options,
2437
+ world,
2438
+ locate: false,
2439
+ scroll: false,
2440
+ highlight: false,
2441
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2442
+ text: `Verify the text '${text}' exists in page`,
2443
+ _text: `Verify the text '${text}' exists in page`,
2444
+ operation: "verifyTextExistInPage",
2445
+ log: "***** verify text " + text + " exists in page *****\n",
2446
+ };
2447
+ if (testForRegex(text)) {
2448
+ text = text.replace(/\\"/g, '"');
2449
+ }
2450
+ const timeout = this._getFindElementTimeout(options);
2451
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2452
+ const newValue = await this._replaceWithLocalData(text, world);
2453
+ if (newValue !== text) {
2454
+ this.logger.info(text + "=" + newValue);
2455
+ text = newValue;
2456
+ }
2457
+ let dateAlternatives = findDateAlternatives(text);
2458
+ let numberAlternatives = findNumberAlternatives(text);
2459
+ try {
2460
+ await _preCommand(state, this);
2461
+ state.info.text = text;
2462
+ while (true) {
2463
+ let resultWithElementsFound = {
2464
+ length: 0,
2465
+ };
2466
+ try {
2467
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2468
+ }
2469
+ catch (error) {
2470
+ // ignore
2471
+ }
2472
+ if (resultWithElementsFound.length === 0) {
2473
+ if (Date.now() - state.startTime > timeout) {
2474
+ throw new Error(`Text ${text} not found in page`);
2475
+ }
2476
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2477
+ continue;
2478
+ }
2479
+ try {
2480
+ if (resultWithElementsFound[0].randomToken) {
2481
+ const frame = resultWithElementsFound[0].frame;
2482
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2483
+ await this._highlightElements(frame, dataAttribute);
2484
+ // if (world && world.screenshot && !world.screenshotPath) {
2485
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2486
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2487
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2488
+ // this._unhighlightElements(frame, dataAttribute)
2489
+ // .then(async () => {
2490
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2491
+ // })
2492
+ // .catch(
2493
+ // (e) => {}
2494
+ // console.error(e)
2495
+ // );
2496
+ // });
2497
+ // }
2498
+ const element = await frame.locator(dataAttribute).first();
2499
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2500
+ // await this._unhighlightElements(frame, dataAttribute);
2501
+ if (element) {
2502
+ await this.scrollIfNeeded(element, state.info);
2503
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2504
+ // await _screenshot(state, this, element);
2505
+ }
2506
+ }
2507
+ await _screenshot(state, this);
2508
+ return state.info;
2509
+ }
2510
+ catch (error) {
2511
+ console.error(error);
2512
+ }
2513
+ }
2514
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2515
+ }
2516
+ catch (e) {
2517
+ await _commandError(state, e, this);
2518
+ }
2519
+ finally {
2520
+ await _commandFinally(state, this);
2521
+ }
2522
+ }
2523
+ async waitForTextToDisappear(text, options = {}, world = null) {
2524
+ text = unEscapeString(text);
2525
+ const state = {
2526
+ text_search: text,
2527
+ options,
2528
+ world,
2529
+ locate: false,
2530
+ scroll: false,
2531
+ highlight: false,
2532
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2533
+ text: `Verify the text '${text}' does not exist in page`,
2534
+ _text: `Verify the text '${text}' does not exist in page`,
2535
+ operation: "verifyTextNotExistInPage",
2536
+ log: "***** verify text " + text + " does not exist in page *****\n",
2537
+ };
2538
+ if (testForRegex(text)) {
2539
+ text = text.replace(/\\"/g, '"');
2540
+ }
2541
+ const timeout = this._getFindElementTimeout(options);
2542
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2543
+ const newValue = await this._replaceWithLocalData(text, world);
2544
+ if (newValue !== text) {
2545
+ this.logger.info(text + "=" + newValue);
2546
+ text = newValue;
2547
+ }
2548
+ let dateAlternatives = findDateAlternatives(text);
2549
+ let numberAlternatives = findNumberAlternatives(text);
2550
+ try {
2551
+ await _preCommand(state, this);
2552
+ state.info.text = text;
2553
+ let resultWithElementsFound = {
2554
+ length: null, // initial cannot be 0
2555
+ };
2556
+ while (true) {
2557
+ try {
2558
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2559
+ }
2560
+ catch (error) {
2561
+ // ignore
2562
+ }
2563
+ if (resultWithElementsFound.length === 0) {
2564
+ await _screenshot(state, this);
2565
+ return state.info;
2566
+ }
2567
+ if (Date.now() - state.startTime > timeout) {
2568
+ throw new Error(`Text ${text} found in page`);
2569
+ }
2570
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2571
+ }
2572
+ }
2573
+ catch (e) {
2574
+ await _commandError(state, e, this);
2575
+ }
2576
+ finally {
2577
+ await _commandFinally(state, this);
2578
+ }
2579
+ }
2580
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2581
+ textAnchor = unEscapeString(textAnchor);
2582
+ textToVerify = unEscapeString(textToVerify);
2583
+ const state = {
2584
+ text_search: textToVerify,
2585
+ options,
2586
+ world,
2587
+ locate: false,
2588
+ scroll: false,
2589
+ highlight: false,
2590
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2591
+ text: `Verify text with relation to another text`,
2592
+ _text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
2593
+ operation: "verify_text_with_relation",
2594
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2595
+ };
2596
+ const timeout = this._getFindElementTimeout(options);
2597
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2598
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2599
+ if (newValue !== textAnchor) {
2600
+ this.logger.info(textAnchor + "=" + newValue);
2601
+ textAnchor = newValue;
2602
+ }
2603
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2604
+ if (newValue !== textToVerify) {
2605
+ this.logger.info(textToVerify + "=" + newValue);
2606
+ textToVerify = newValue;
2607
+ }
2608
+ let dateAlternatives = findDateAlternatives(textToVerify);
2609
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2610
+ let foundAncore = false;
2611
+ try {
2612
+ await _preCommand(state, this);
2613
+ state.info.text = textToVerify;
2614
+ let resultWithElementsFound = {
2615
+ length: 0,
2616
+ };
2617
+ while (true) {
2618
+ try {
2619
+ resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
2620
+ }
2621
+ catch (error) {
2622
+ // ignore
2623
+ }
2624
+ if (resultWithElementsFound.length === 0) {
2625
+ if (Date.now() - state.startTime > timeout) {
2626
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2627
+ }
2628
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2629
+ continue;
2630
+ }
2631
+ try {
2632
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2633
+ foundAncore = true;
2634
+ const result = resultWithElementsFound[i];
2635
+ const token = result.randomToken;
2636
+ const frame = result.frame;
2637
+ let css = `[data-blinq-id-${token}]`;
2638
+ const climbArray1 = [];
2639
+ for (let i = 0; i < climb; i++) {
2640
+ climbArray1.push("..");
2641
+ }
2642
+ let climbXpath = "xpath=" + climbArray1.join("/");
2643
+ css = css + " >> " + climbXpath;
2644
+ const count = await frame.locator(css).count();
2645
+ for (let j = 0; j < count; j++) {
2646
+ const continer = await frame.locator(css).nth(j);
2647
+ const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
2648
+ if (result.elementCount > 0) {
2649
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2650
+ await this._highlightElements(frame, dataAttribute);
2651
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2652
+ // if (world && world.screenshot && !world.screenshotPath) {
2653
+ // console.log(`Highlighting for vtrt while running from recorder`);
2654
+ // this._highlightElements(frame, dataAttribute)
2655
+ // .then(async () => {
2656
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2657
+ // this._unhighlightElements(frame, dataAttribute).then(
2658
+ // () => {}
2659
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2660
+ // );
2661
+ // })
2662
+ // .catch(e);
2663
+ // }
2664
+ //await this._highlightElements(frame, cssAnchor);
2665
+ const element = await frame.locator(dataAttribute).first();
2666
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2667
+ // await this._unhighlightElements(frame, dataAttribute);
2668
+ if (element) {
2669
+ await this.scrollIfNeeded(element, state.info);
2670
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2671
+ }
2672
+ await _screenshot(state, this);
2673
+ return state.info;
2674
+ }
2675
+ }
2676
+ }
2677
+ }
2678
+ catch (error) {
2679
+ console.error(error);
2680
+ }
2681
+ }
2682
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2683
+ }
2684
+ catch (e) {
2685
+ await _commandError(state, e, this);
2686
+ }
2687
+ finally {
2688
+ await _commandFinally(state, this);
2233
2689
  }
2234
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2235
- serviceUrl = "https://stage.api.blinq.io";
2690
+ }
2691
+ async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
2692
+ const frames = this.page.frames();
2693
+ let results = [];
2694
+ let ignoreCase = false;
2695
+ for (let i = 0; i < frames.length; i++) {
2696
+ const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
2697
+ result.frame = frames[i];
2698
+ const climbArray = [];
2699
+ for (let i = 0; i < climb; i++) {
2700
+ climbArray.push("..");
2701
+ }
2702
+ let climbXpath = "xpath=" + climbArray.join("/");
2703
+ const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
2704
+ const count = await frames[i].locator(newLocator).count();
2705
+ if (count > 0) {
2706
+ result.elementCount = count;
2707
+ result.locator = newLocator;
2708
+ results.push(result);
2709
+ }
2236
2710
  }
2237
- return serviceUrl;
2711
+ // state.info.results = results;
2712
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2713
+ return resultWithElementsFound;
2238
2714
  }
2239
2715
  async visualVerification(text, options = {}, world = null) {
2240
2716
  const startTime = Date.now();
@@ -2250,14 +2726,17 @@ class StableBrowser {
2250
2726
  throw new Error("TOKEN is not set");
2251
2727
  }
2252
2728
  try {
2253
- let serviceUrl = this._getServerUrl();
2729
+ let serviceUrl = _getServerUrl();
2254
2730
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2255
2731
  info.screenshotPath = screenshotPath;
2256
2732
  const screenshot = await this.takeScreenshot();
2257
- const request = {
2258
- method: "POST",
2733
+ let request = {
2734
+ method: "post",
2735
+ maxBodyLength: Infinity,
2259
2736
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2260
2737
  headers: {
2738
+ "x-bvt-project-id": path.basename(this.project_path),
2739
+ "x-source": "aaa",
2261
2740
  "Content-Type": "application/json",
2262
2741
  Authorization: `Bearer ${process.env.TOKEN}`,
2263
2742
  },
@@ -2266,7 +2745,7 @@ class StableBrowser {
2266
2745
  screenshot: screenshot,
2267
2746
  }),
2268
2747
  };
2269
- let result = await this.context.api.request(request);
2748
+ const result = await axios.request(request);
2270
2749
  if (result.data.status !== true) {
2271
2750
  throw new Error("Visual validation failed");
2272
2751
  }
@@ -2286,20 +2765,22 @@ class StableBrowser {
2286
2765
  info.screenshotPath = screenshotPath;
2287
2766
  Object.assign(e, { info: info });
2288
2767
  error = e;
2289
- throw e;
2768
+ // throw e;
2769
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2290
2770
  }
2291
2771
  finally {
2292
2772
  const endTime = Date.now();
2293
- this._reportToWorld(world, {
2773
+ _reportToWorld(world, {
2294
2774
  type: Types.VERIFY_VISUAL,
2295
2775
  text: "Visual verification",
2776
+ _text: "Visual verification of " + text,
2296
2777
  screenshotId,
2297
2778
  result: error
2298
2779
  ? {
2299
2780
  status: "FAILED",
2300
2781
  startTime,
2301
2782
  endTime,
2302
- message: error === null || error === void 0 ? void 0 : error.message,
2783
+ message: error?.message,
2303
2784
  }
2304
2785
  : {
2305
2786
  status: "PASSED",
@@ -2331,13 +2812,14 @@ class StableBrowser {
2331
2812
  this.logger.info("Table data verified");
2332
2813
  }
2333
2814
  async getTableData(selectors, _params = null, options = {}, world = null) {
2334
- this._validateSelectors(selectors);
2815
+ _validateSelectors(selectors);
2335
2816
  const startTime = Date.now();
2336
2817
  let error = null;
2337
2818
  let screenshotId = null;
2338
2819
  let screenshotPath = null;
2339
2820
  const info = {};
2340
2821
  info.log = "";
2822
+ info.locatorLog = new LocatorLog(selectors);
2341
2823
  info.operation = "getTableData";
2342
2824
  info.selectors = selectors;
2343
2825
  try {
@@ -2353,11 +2835,12 @@ class StableBrowser {
2353
2835
  info.screenshotPath = screenshotPath;
2354
2836
  Object.assign(e, { info: info });
2355
2837
  error = e;
2356
- throw e;
2838
+ // throw e;
2839
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2357
2840
  }
2358
2841
  finally {
2359
2842
  const endTime = Date.now();
2360
- this._reportToWorld(world, {
2843
+ _reportToWorld(world, {
2361
2844
  element_name: selectors.element_name,
2362
2845
  type: Types.GET_TABLE_DATA,
2363
2846
  text: "Get table data",
@@ -2367,7 +2850,7 @@ class StableBrowser {
2367
2850
  status: "FAILED",
2368
2851
  startTime,
2369
2852
  endTime,
2370
- message: error === null || error === void 0 ? void 0 : error.message,
2853
+ message: error?.message,
2371
2854
  }
2372
2855
  : {
2373
2856
  status: "PASSED",
@@ -2379,7 +2862,7 @@ class StableBrowser {
2379
2862
  }
2380
2863
  }
2381
2864
  async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
2382
- this._validateSelectors(selectors);
2865
+ _validateSelectors(selectors);
2383
2866
  if (!query) {
2384
2867
  throw new Error("query is null");
2385
2868
  }
@@ -2412,7 +2895,7 @@ class StableBrowser {
2412
2895
  info.operation = "analyzeTable";
2413
2896
  info.selectors = selectors;
2414
2897
  info.query = query;
2415
- query = this._fixUsingParams(query, _params);
2898
+ query = _fixUsingParams(query, _params);
2416
2899
  info.query_fixed = query;
2417
2900
  info.operator = operator;
2418
2901
  info.value = value;
@@ -2518,11 +3001,12 @@ class StableBrowser {
2518
3001
  info.screenshotPath = screenshotPath;
2519
3002
  Object.assign(e, { info: info });
2520
3003
  error = e;
2521
- throw e;
3004
+ // throw e;
3005
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2522
3006
  }
2523
3007
  finally {
2524
3008
  const endTime = Date.now();
2525
- this._reportToWorld(world, {
3009
+ _reportToWorld(world, {
2526
3010
  element_name: selectors.element_name,
2527
3011
  type: Types.ANALYZE_TABLE,
2528
3012
  text: "Analyze table",
@@ -2532,7 +3016,7 @@ class StableBrowser {
2532
3016
  status: "FAILED",
2533
3017
  startTime,
2534
3018
  endTime,
2535
- message: error === null || error === void 0 ? void 0 : error.message,
3019
+ message: error?.message,
2536
3020
  }
2537
3021
  : {
2538
3022
  status: "PASSED",
@@ -2544,27 +3028,7 @@ class StableBrowser {
2544
3028
  }
2545
3029
  }
2546
3030
  async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
2547
- if (!value) {
2548
- return value;
2549
- }
2550
- // find all the accurance of {{(.*?)}} and replace with the value
2551
- let regex = /{{(.*?)}}/g;
2552
- let matches = value.match(regex);
2553
- if (matches) {
2554
- const testData = this.getTestData(world);
2555
- for (let i = 0; i < matches.length; i++) {
2556
- let match = matches[i];
2557
- let key = match.substring(2, match.length - 2);
2558
- let newValue = objectPath.get(testData, key, null);
2559
- if (newValue !== null) {
2560
- value = value.replace(match, newValue);
2561
- }
2562
- }
2563
- }
2564
- if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
2565
- return await decrypt(value, null, totpWait);
2566
- }
2567
- return value;
3031
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2568
3032
  }
2569
3033
  _getLoadTimeout(options) {
2570
3034
  let timeout = 15000;
@@ -2576,6 +3040,32 @@ class StableBrowser {
2576
3040
  }
2577
3041
  return timeout;
2578
3042
  }
3043
+ _getFindElementTimeout(options) {
3044
+ if (options && options.timeout) {
3045
+ return options.timeout;
3046
+ }
3047
+ if (this.configuration.find_element_timeout) {
3048
+ return this.configuration.find_element_timeout;
3049
+ }
3050
+ return 30000;
3051
+ }
3052
+ async saveStoreState(path = null, world = null) {
3053
+ const storageState = await this.page.context().storageState();
3054
+ //const testDataFile = _getDataFile(world, this.context, this);
3055
+ if (path) {
3056
+ // save { storageState: storageState } into the path
3057
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
3058
+ }
3059
+ else {
3060
+ await this.setTestData({ storageState: storageState }, world);
3061
+ }
3062
+ }
3063
+ async restoreSaveState(path = null, world = null) {
3064
+ await refreshBrowser(this, path, world);
3065
+ this.registerEventListeners(this.context);
3066
+ registerNetworkEvents(this.world, this, this.context, this.page);
3067
+ registerDownloadEvent(this.page, this.world, this.context);
3068
+ }
2579
3069
  async waitForPageLoad(options = {}, world = null) {
2580
3070
  let timeout = this._getLoadTimeout(options);
2581
3071
  const promiseArray = [];
@@ -2615,7 +3105,7 @@ class StableBrowser {
2615
3105
  await new Promise((resolve) => setTimeout(resolve, 2000));
2616
3106
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2617
3107
  const endTime = Date.now();
2618
- this._reportToWorld(world, {
3108
+ _reportToWorld(world, {
2619
3109
  type: Types.GET_PAGE_STATUS,
2620
3110
  text: "Wait for page load",
2621
3111
  screenshotId,
@@ -2624,7 +3114,7 @@ class StableBrowser {
2624
3114
  status: "FAILED",
2625
3115
  startTime,
2626
3116
  endTime,
2627
- message: error === null || error === void 0 ? void 0 : error.message,
3117
+ message: error?.message,
2628
3118
  }
2629
3119
  : {
2630
3120
  status: "PASSED",
@@ -2635,41 +3125,123 @@ class StableBrowser {
2635
3125
  }
2636
3126
  }
2637
3127
  async closePage(options = {}, world = null) {
2638
- const startTime = Date.now();
2639
- let error = null;
2640
- let screenshotId = null;
2641
- let screenshotPath = null;
2642
- const info = {};
3128
+ const state = {
3129
+ options,
3130
+ world,
3131
+ locate: false,
3132
+ scroll: false,
3133
+ highlight: false,
3134
+ type: Types.CLOSE_PAGE,
3135
+ text: `Close page`,
3136
+ _text: `Close the page`,
3137
+ operation: "closePage",
3138
+ log: "***** close page *****\n",
3139
+ throwError: false,
3140
+ };
2643
3141
  try {
3142
+ await _preCommand(state, this);
2644
3143
  await this.page.close();
2645
3144
  }
2646
3145
  catch (e) {
2647
3146
  console.log(".");
3147
+ await _commandError(state, e, this);
2648
3148
  }
2649
3149
  finally {
2650
- await new Promise((resolve) => setTimeout(resolve, 2000));
2651
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2652
- const endTime = Date.now();
2653
- this._reportToWorld(world, {
2654
- type: Types.CLOSE_PAGE,
2655
- text: "close page",
2656
- screenshotId,
2657
- result: error
2658
- ? {
2659
- status: "FAILED",
2660
- startTime,
2661
- endTime,
2662
- message: error === null || error === void 0 ? void 0 : error.message,
3150
+ await _commandFinally(state, this);
3151
+ }
3152
+ }
3153
+ async tableCellOperation(headerText, rowText, options, _params, world = null) {
3154
+ let operation = null;
3155
+ if (!options || !options.operation) {
3156
+ throw new Error("operation is not defined");
3157
+ }
3158
+ operation = options.operation;
3159
+ // validate operation is one of the supported operations
3160
+ if (operation != "click" && operation != "hover+click") {
3161
+ throw new Error("operation is not supported");
3162
+ }
3163
+ const state = {
3164
+ options,
3165
+ world,
3166
+ locate: false,
3167
+ scroll: false,
3168
+ highlight: false,
3169
+ type: Types.TABLE_OPERATION,
3170
+ text: `Table operation`,
3171
+ _text: `Table ${operation} operation`,
3172
+ operation: operation,
3173
+ log: "***** Table operation *****\n",
3174
+ };
3175
+ const timeout = this._getFindElementTimeout(options);
3176
+ try {
3177
+ await _preCommand(state, this);
3178
+ const start = Date.now();
3179
+ let cellArea = null;
3180
+ while (true) {
3181
+ try {
3182
+ cellArea = await _findCellArea(headerText, rowText, this, state);
3183
+ if (cellArea) {
3184
+ break;
2663
3185
  }
2664
- : {
2665
- status: "PASSED",
2666
- startTime,
2667
- endTime,
2668
- },
2669
- info: info,
2670
- });
3186
+ }
3187
+ catch (e) {
3188
+ // ignore
3189
+ }
3190
+ if (Date.now() - start > timeout) {
3191
+ throw new Error(`Cell not found in table`);
3192
+ }
3193
+ await new Promise((resolve) => setTimeout(resolve, 1000));
3194
+ }
3195
+ switch (operation) {
3196
+ case "click":
3197
+ if (!options.css) {
3198
+ // will click in the center of the cell
3199
+ let xOffset = 0;
3200
+ let yOffset = 0;
3201
+ if (options.xOffset) {
3202
+ xOffset = options.xOffset;
3203
+ }
3204
+ if (options.yOffset) {
3205
+ yOffset = options.yOffset;
3206
+ }
3207
+ await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
3208
+ }
3209
+ else {
3210
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3211
+ if (results.length === 0) {
3212
+ throw new Error(`Element not found in cell area`);
3213
+ }
3214
+ state.element = results[0];
3215
+ await performAction("click", state.element, options, this, state, _params);
3216
+ }
3217
+ break;
3218
+ case "hover+click":
3219
+ if (!options.css) {
3220
+ throw new Error("css is not defined");
3221
+ }
3222
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3223
+ if (results.length === 0) {
3224
+ throw new Error(`Element not found in cell area`);
3225
+ }
3226
+ state.element = results[0];
3227
+ await performAction("hover+click", state.element, options, this, state, _params);
3228
+ break;
3229
+ default:
3230
+ throw new Error("operation is not supported");
3231
+ }
3232
+ }
3233
+ catch (e) {
3234
+ await _commandError(state, e, this);
3235
+ }
3236
+ finally {
3237
+ await _commandFinally(state, this);
2671
3238
  }
2672
3239
  }
3240
+ saveTestDataAsGlobal(options, world) {
3241
+ const dataFile = _getDataFile(world, this.context, this);
3242
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
3243
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
3244
+ }
2673
3245
  async setViewportSize(width, hight, options = {}, world = null) {
2674
3246
  const startTime = Date.now();
2675
3247
  let error = null;
@@ -2687,21 +3259,23 @@ class StableBrowser {
2687
3259
  }
2688
3260
  catch (e) {
2689
3261
  console.log(".");
3262
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2690
3263
  }
2691
3264
  finally {
2692
3265
  await new Promise((resolve) => setTimeout(resolve, 2000));
2693
3266
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2694
3267
  const endTime = Date.now();
2695
- this._reportToWorld(world, {
3268
+ _reportToWorld(world, {
2696
3269
  type: Types.SET_VIEWPORT,
2697
3270
  text: "set viewport size to " + width + "x" + hight,
3271
+ _text: "Set the viewport size to " + width + "x" + hight,
2698
3272
  screenshotId,
2699
3273
  result: error
2700
3274
  ? {
2701
3275
  status: "FAILED",
2702
3276
  startTime,
2703
3277
  endTime,
2704
- message: error === null || error === void 0 ? void 0 : error.message,
3278
+ message: error?.message,
2705
3279
  }
2706
3280
  : {
2707
3281
  status: "PASSED",
@@ -2723,12 +3297,13 @@ class StableBrowser {
2723
3297
  }
2724
3298
  catch (e) {
2725
3299
  console.log(".");
3300
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2726
3301
  }
2727
3302
  finally {
2728
3303
  await new Promise((resolve) => setTimeout(resolve, 2000));
2729
3304
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2730
3305
  const endTime = Date.now();
2731
- this._reportToWorld(world, {
3306
+ _reportToWorld(world, {
2732
3307
  type: Types.GET_PAGE_STATUS,
2733
3308
  text: "page relaod",
2734
3309
  screenshotId,
@@ -2737,7 +3312,7 @@ class StableBrowser {
2737
3312
  status: "FAILED",
2738
3313
  startTime,
2739
3314
  endTime,
2740
- message: error === null || error === void 0 ? void 0 : error.message,
3315
+ message: error?.message,
2741
3316
  }
2742
3317
  : {
2743
3318
  status: "PASSED",
@@ -2764,11 +3339,121 @@ class StableBrowser {
2764
3339
  console.log("#-#");
2765
3340
  }
2766
3341
  }
2767
- _reportToWorld(world, properties) {
2768
- if (!world || !world.attach) {
2769
- return;
3342
+ async beforeScenario(world, scenario) {
3343
+ this.beforeScenarioCalled = true;
3344
+ if (scenario && scenario.pickle && scenario.pickle.name) {
3345
+ this.scenarioName = scenario.pickle.name;
3346
+ }
3347
+ if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
3348
+ this.featureName = scenario.gherkinDocument.feature.name;
3349
+ }
3350
+ if (this.context) {
3351
+ this.context.examplesRow = extractStepExampleParameters(scenario);
3352
+ }
3353
+ if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
3354
+ this.tags = scenario.pickle.tags.map((tag) => tag.name);
3355
+ // check if @global_test_data tag is present
3356
+ if (this.tags.includes("@global_test_data")) {
3357
+ this.saveTestDataAsGlobal({}, world);
3358
+ }
3359
+ }
3360
+ // update test data based on feature/scenario
3361
+ let envName = null;
3362
+ if (this.context && this.context.environment) {
3363
+ envName = this.context.environment.name;
3364
+ }
3365
+ if (!process.env.TEMP_RUN) {
3366
+ await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
3367
+ }
3368
+ }
3369
+ async afterScenario(world, scenario) { }
3370
+ async beforeStep(world, step) {
3371
+ if (!this.beforeScenarioCalled) {
3372
+ this.beforeScenario(world, step);
3373
+ }
3374
+ if (this.stepIndex === undefined) {
3375
+ this.stepIndex = 0;
3376
+ }
3377
+ else {
3378
+ this.stepIndex++;
3379
+ }
3380
+ if (step && step.pickleStep && step.pickleStep.text) {
3381
+ this.stepName = step.pickleStep.text;
3382
+ this.logger.info("step: " + this.stepName);
3383
+ }
3384
+ else if (step && step.text) {
3385
+ this.stepName = step.text;
3386
+ }
3387
+ else {
3388
+ this.stepName = "step " + this.stepIndex;
3389
+ }
3390
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
3391
+ if (this.context.browserObject.context) {
3392
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
3393
+ }
3394
+ }
3395
+ if (this.initSnapshotTaken === false) {
3396
+ this.initSnapshotTaken = true;
3397
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3398
+ const snapshot = await this.getAriaSnapshot();
3399
+ if (snapshot) {
3400
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
3401
+ }
3402
+ }
3403
+ }
3404
+ }
3405
+ async getAriaSnapshot() {
3406
+ try {
3407
+ // find the page url
3408
+ const url = await this.page.url();
3409
+ // extract the path from the url
3410
+ const path = new URL(url).pathname;
3411
+ // get the page title
3412
+ const title = await this.page.title();
3413
+ // go over other frams
3414
+ const frames = this.page.frames();
3415
+ const snapshots = [];
3416
+ const content = [`- path: ${path}`, `- title: ${title}`];
3417
+ const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
3418
+ for (let i = 0; i < frames.length; i++) {
3419
+ content.push(`- frame: ${i}`);
3420
+ const frame = frames[i];
3421
+ const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
3422
+ content.push(snapshot);
3423
+ }
3424
+ return content.join("\n");
3425
+ }
3426
+ catch (e) {
3427
+ console.error(e);
3428
+ }
3429
+ return null;
3430
+ }
3431
+ async afterStep(world, step) {
3432
+ this.stepName = null;
3433
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
3434
+ if (this.context.browserObject.context) {
3435
+ await this.context.browserObject.context.tracing.stopChunk({
3436
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
3437
+ });
3438
+ if (world && world.attach) {
3439
+ await world.attach(JSON.stringify({
3440
+ type: "trace",
3441
+ traceFilePath: `trace-${this.stepIndex}.zip`,
3442
+ }), "application/json+trace");
3443
+ }
3444
+ // console.log("trace file created", `trace-${this.stepIndex}.zip`);
3445
+ }
3446
+ }
3447
+ if (this.context) {
3448
+ this.context.examplesRow = null;
3449
+ }
3450
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3451
+ const snapshot = await this.getAriaSnapshot();
3452
+ if (snapshot) {
3453
+ const obj = {};
3454
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
3455
+ }
2770
3456
  }
2771
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2772
3457
  }
2773
3458
  }
2774
3459
  function createTimedPromise(promise, label) {
@@ -2776,151 +3461,5 @@ function createTimedPromise(promise, label) {
2776
3461
  .then((result) => ({ status: "fulfilled", label, result }))
2777
3462
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2778
3463
  }
2779
- const KEYBOARD_EVENTS = [
2780
- "ALT",
2781
- "AltGraph",
2782
- "CapsLock",
2783
- "Control",
2784
- "Fn",
2785
- "FnLock",
2786
- "Hyper",
2787
- "Meta",
2788
- "NumLock",
2789
- "ScrollLock",
2790
- "Shift",
2791
- "Super",
2792
- "Symbol",
2793
- "SymbolLock",
2794
- "Enter",
2795
- "Tab",
2796
- "ArrowDown",
2797
- "ArrowLeft",
2798
- "ArrowRight",
2799
- "ArrowUp",
2800
- "End",
2801
- "Home",
2802
- "PageDown",
2803
- "PageUp",
2804
- "Backspace",
2805
- "Clear",
2806
- "Copy",
2807
- "CrSel",
2808
- "Cut",
2809
- "Delete",
2810
- "EraseEof",
2811
- "ExSel",
2812
- "Insert",
2813
- "Paste",
2814
- "Redo",
2815
- "Undo",
2816
- "Accept",
2817
- "Again",
2818
- "Attn",
2819
- "Cancel",
2820
- "ContextMenu",
2821
- "Escape",
2822
- "Execute",
2823
- "Find",
2824
- "Finish",
2825
- "Help",
2826
- "Pause",
2827
- "Play",
2828
- "Props",
2829
- "Select",
2830
- "ZoomIn",
2831
- "ZoomOut",
2832
- "BrightnessDown",
2833
- "BrightnessUp",
2834
- "Eject",
2835
- "LogOff",
2836
- "Power",
2837
- "PowerOff",
2838
- "PrintScreen",
2839
- "Hibernate",
2840
- "Standby",
2841
- "WakeUp",
2842
- "AllCandidates",
2843
- "Alphanumeric",
2844
- "CodeInput",
2845
- "Compose",
2846
- "Convert",
2847
- "Dead",
2848
- "FinalMode",
2849
- "GroupFirst",
2850
- "GroupLast",
2851
- "GroupNext",
2852
- "GroupPrevious",
2853
- "ModeChange",
2854
- "NextCandidate",
2855
- "NonConvert",
2856
- "PreviousCandidate",
2857
- "Process",
2858
- "SingleCandidate",
2859
- "HangulMode",
2860
- "HanjaMode",
2861
- "JunjaMode",
2862
- "Eisu",
2863
- "Hankaku",
2864
- "Hiragana",
2865
- "HiraganaKatakana",
2866
- "KanaMode",
2867
- "KanjiMode",
2868
- "Katakana",
2869
- "Romaji",
2870
- "Zenkaku",
2871
- "ZenkakuHanaku",
2872
- "F1",
2873
- "F2",
2874
- "F3",
2875
- "F4",
2876
- "F5",
2877
- "F6",
2878
- "F7",
2879
- "F8",
2880
- "F9",
2881
- "F10",
2882
- "F11",
2883
- "F12",
2884
- "Soft1",
2885
- "Soft2",
2886
- "Soft3",
2887
- "Soft4",
2888
- "ChannelDown",
2889
- "ChannelUp",
2890
- "Close",
2891
- "MailForward",
2892
- "MailReply",
2893
- "MailSend",
2894
- "MediaFastForward",
2895
- "MediaPause",
2896
- "MediaPlay",
2897
- "MediaPlayPause",
2898
- "MediaRecord",
2899
- "MediaRewind",
2900
- "MediaStop",
2901
- "MediaTrackNext",
2902
- "MediaTrackPrevious",
2903
- "AudioBalanceLeft",
2904
- "AudioBalanceRight",
2905
- "AudioBassBoostDown",
2906
- "AudioBassBoostToggle",
2907
- "AudioBassBoostUp",
2908
- "AudioFaderFront",
2909
- "AudioFaderRear",
2910
- "AudioSurroundModeNext",
2911
- "AudioTrebleDown",
2912
- "AudioTrebleUp",
2913
- "AudioVolumeDown",
2914
- "AudioVolumeMute",
2915
- "AudioVolumeUp",
2916
- "MicrophoneToggle",
2917
- "MicrophoneVolumeDown",
2918
- "MicrophoneVolumeMute",
2919
- "MicrophoneVolumeUp",
2920
- "TV",
2921
- "TV3DMode",
2922
- "TVAntennaCable",
2923
- "TVAudioDescription",
2924
- ];
2925
3464
  export { StableBrowser };
2926
3465
  //# sourceMappingURL=stable_browser.js.map