automation_model 1.0.427-dev → 1.0.427

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