automation_model 1.0.426-dev → 1.0.426

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