automation_model 1.0.419-dev → 1.0.419

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