automation_model 1.0.422-dev → 1.0.422

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 +142 -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 +153 -48
  10. package/lib/browser_manager.js.map +1 -1
  11. package/lib/command_common.d.ts +6 -0
  12. package/lib/command_common.js +182 -0
  13. package/lib/command_common.js.map +1 -0
  14. package/lib/environment.d.ts +3 -0
  15. package/lib/environment.js +5 -2
  16. package/lib/environment.js.map +1 -1
  17. package/lib/error-messages.d.ts +6 -0
  18. package/lib/error-messages.js +206 -0
  19. package/lib/error-messages.js.map +1 -0
  20. package/lib/generation_scripts.d.ts +4 -0
  21. package/lib/generation_scripts.js +2 -0
  22. package/lib/generation_scripts.js.map +1 -0
  23. package/lib/index.d.ts +1 -0
  24. package/lib/index.js +1 -0
  25. package/lib/index.js.map +1 -1
  26. package/lib/init_browser.d.ts +5 -2
  27. package/lib/init_browser.js +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 +1753 -1263
  44. package/lib/stable_browser.js.map +1 -1
  45. package/lib/table.d.ts +13 -0
  46. package/lib/table.js +187 -0
  47. package/lib/table.js.map +1 -0
  48. package/lib/table_helper.d.ts +19 -0
  49. package/lib/table_helper.js +101 -0
  50. package/lib/table_helper.js.map +1 -0
  51. package/lib/test_context.d.ts +6 -0
  52. package/lib/test_context.js +14 -10
  53. package/lib/test_context.js.map +1 -1
  54. package/lib/utils.d.ts +22 -2
  55. package/lib/utils.js +649 -11
  56. package/lib/utils.js.map +1 -1
  57. package/package.json +14 -9
@@ -2,20 +2,29 @@
2
2
  import { expect } from "@playwright/test";
3
3
  import dayjs from "dayjs";
4
4
  import fs from "fs";
5
+ import { Jimp } from "jimp";
5
6
  import path from "path";
6
7
  import reg_parser from "regex-parser";
7
- import sharp from "sharp";
8
8
  import { findDateAlternatives, findNumberAlternatives } from "./analyze_helper.js";
9
9
  import { getDateTimeValue } from "./date_time.js";
10
10
  import drawRectangle from "./drawRect.js";
11
11
  //import { closeUnexpectedPopups } from "./popups.js";
12
12
  import { getTableCells, getTableData } from "./table_analyze.js";
13
- import objectPath from "object-path";
14
- import { decrypt } from "./utils.js";
13
+ import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
15
14
  import csv from "csv-parser";
16
15
  import { Readable } from "node:stream";
17
- const Types = {
16
+ import readline from "readline";
17
+ import { getContext, refreshBrowser } from "./init_browser.js";
18
+ import { locate_element } from "./locate_element.js";
19
+ import { randomUUID } from "crypto";
20
+ import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
21
+ import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
22
+ import { LocatorLog } from "./locator_log.js";
23
+ import axios from "axios";
24
+ import { _findCellArea, findElementsInArea } from "./table_helper.js";
25
+ export const Types = {
18
26
  CLICK: "click_element",
27
+ WAIT_ELEMENT: "wait_element",
19
28
  NAVIGATE: "navigate",
20
29
  FILL: "fill_element",
21
30
  EXECUTE: "execute_page_method",
@@ -25,6 +34,8 @@ const Types = {
25
34
  GET_PAGE_STATUS: "get_page_status",
26
35
  CLICK_ROW_ACTION: "click_row_action",
27
36
  VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
37
+ VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
38
+ VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
28
39
  ANALYZE_TABLE: "analyze_table",
29
40
  SELECT: "select_combobox",
30
41
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -35,21 +46,40 @@ const Types = {
35
46
  UNCHECK: "uncheck_element",
36
47
  EXTRACT: "extract_attribute",
37
48
  CLOSE_PAGE: "close_page",
49
+ TABLE_OPERATION: "table_operation",
38
50
  SET_DATE_TIME: "set_date_time",
39
51
  SET_VIEWPORT: "set_viewport",
40
52
  VERIFY_VISUAL: "verify_visual",
41
53
  LOAD_DATA: "load_data",
42
54
  SET_INPUT: "set_input",
55
+ WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
56
+ VERIFY_ATTRIBUTE: "verify_element_attribute",
57
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
58
+ };
59
+ export const apps = {};
60
+ const formatElementName = (elementName) => {
61
+ return elementName ? JSON.stringify(elementName) : "element";
43
62
  };
44
63
  class StableBrowser {
45
- constructor(browser, page, logger = null, context = null) {
64
+ browser;
65
+ page;
66
+ logger;
67
+ context;
68
+ world;
69
+ project_path = null;
70
+ webLogFile = null;
71
+ networkLogger = null;
72
+ configuration = null;
73
+ appName = "main";
74
+ tags = null;
75
+ isRecording = false;
76
+ initSnapshotTaken = false;
77
+ constructor(browser, page, logger = null, context = null, world = null) {
46
78
  this.browser = browser;
47
79
  this.page = page;
48
80
  this.logger = logger;
49
81
  this.context = context;
50
- this.project_path = null;
51
- this.webLogFile = null;
52
- this.configuration = null;
82
+ this.world = world;
53
83
  if (!this.logger) {
54
84
  this.logger = console;
55
85
  }
@@ -74,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({ timeout: 5000 });
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.click({ timeout: 10000, force: true });
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, { timeout: 5000 });
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({ timeout: 10000 });
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, { timeout: 5000 });
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, { timeout: 10000 });
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,192 @@ 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();
1854
1954
  break;
1855
1955
  default:
1856
- info.value = await element.getAttribute(attribute);
1956
+ state.value = await state.element.getAttribute(attribute);
1857
1957
  break;
1858
1958
  }
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;
1959
+ state.info.value = state.value;
1960
+ this.setTestData({ [variable]: state.value }, world);
1961
+ this.logger.info("set test data: " + variable + "=" + state.value);
1962
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1963
+ return state.info;
1866
1964
  }
1867
1965
  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;
1966
+ await _commandError(state, e, this);
1875
1967
  }
1876
1968
  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
- });
1969
+ _commandFinally(state, this);
1970
+ }
1971
+ }
1972
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1973
+ const state = {
1974
+ selectors,
1975
+ _params,
1976
+ attribute,
1977
+ value,
1978
+ options,
1979
+ world,
1980
+ type: Types.VERIFY_ATTRIBUTE,
1981
+ highlight: true,
1982
+ screenshot: true,
1983
+ text: `Verify element attribute`,
1984
+ _text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
1985
+ operation: "verifyAttribute",
1986
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1987
+ allowDisabled: true,
1988
+ };
1989
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1990
+ let val;
1991
+ let expectedValue;
1992
+ try {
1993
+ await _preCommand(state, this);
1994
+ expectedValue = state.value;
1995
+ state.info.expectedValue = expectedValue;
1996
+ switch (attribute) {
1997
+ case "innerText":
1998
+ val = String(await state.element.innerText());
1999
+ break;
2000
+ case "value":
2001
+ val = String(await state.element.inputValue());
2002
+ break;
2003
+ case "checked":
2004
+ val = String(await state.element.isChecked());
2005
+ break;
2006
+ case "disabled":
2007
+ val = String(await state.element.isDisabled());
2008
+ break;
2009
+ case "readOnly":
2010
+ const isEditable = await state.element.isEditable();
2011
+ val = String(!isEditable);
2012
+ break;
2013
+ default:
2014
+ val = String(await state.element.getAttribute(attribute));
2015
+ break;
2016
+ }
2017
+ state.info.value = val;
2018
+ let regex;
2019
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
2020
+ const patternBody = expectedValue.slice(1, -1);
2021
+ regex = new RegExp(patternBody, "g");
2022
+ }
2023
+ else {
2024
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2025
+ regex = new RegExp(escapedPattern, "g");
2026
+ }
2027
+ if (!val.match(regex)) {
2028
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
2029
+ state.info.failCause.assertionFailed = true;
2030
+ state.info.failCause.lastError = errorMessage;
2031
+ throw new Error(errorMessage);
2032
+ }
2033
+ return state.info;
2034
+ }
2035
+ catch (e) {
2036
+ await _commandError(state, e, this);
2037
+ }
2038
+ finally {
2039
+ _commandFinally(state, this);
1899
2040
  }
1900
2041
  }
1901
2042
  async extractEmailData(emailAddress, options, world) {
@@ -1916,7 +2057,7 @@ class StableBrowser {
1916
2057
  if (options && options.timeout) {
1917
2058
  timeout = options.timeout;
1918
2059
  }
1919
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
2060
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1920
2061
  const request = {
1921
2062
  method: "POST",
1922
2063
  url: serviceUrl,
@@ -1972,7 +2113,8 @@ class StableBrowser {
1972
2113
  catch (e) {
1973
2114
  errorCount++;
1974
2115
  if (errorCount > 3) {
1975
- throw e;
2116
+ // throw e;
2117
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1976
2118
  }
1977
2119
  // ignore
1978
2120
  }
@@ -1986,27 +2128,32 @@ class StableBrowser {
1986
2128
  async _highlightElements(scope, css) {
1987
2129
  try {
1988
2130
  if (!scope) {
2131
+ // console.log(`Scope is not defined`);
1989
2132
  return;
1990
2133
  }
1991
2134
  if (!css) {
1992
2135
  scope
1993
2136
  .evaluate((node) => {
1994
2137
  if (node && node.style) {
1995
- let originalBorder = node.style.border;
1996
- node.style.border = "2px solid red";
2138
+ let originalOutline = node.style.outline;
2139
+ // console.log(`Original outline was: ${originalOutline}`);
2140
+ // node.__previousOutline = originalOutline;
2141
+ node.style.outline = "2px solid red";
2142
+ // console.log(`New outline is: ${node.style.outline}`);
1997
2143
  if (window) {
1998
2144
  window.addEventListener("beforeunload", function (e) {
1999
- node.style.border = originalBorder;
2145
+ node.style.outline = originalOutline;
2000
2146
  });
2001
2147
  }
2002
2148
  setTimeout(function () {
2003
- node.style.border = originalBorder;
2149
+ node.style.outline = originalOutline;
2004
2150
  }, 2000);
2005
2151
  }
2006
2152
  })
2007
2153
  .then(() => { })
2008
2154
  .catch((e) => {
2009
2155
  // ignore
2156
+ // console.error(`Could not highlight node : ${e}`);
2010
2157
  });
2011
2158
  }
2012
2159
  else {
@@ -2022,17 +2169,18 @@ class StableBrowser {
2022
2169
  if (!element.style) {
2023
2170
  return;
2024
2171
  }
2025
- var originalBorder = element.style.border;
2172
+ let originalOutline = element.style.outline;
2173
+ element.__previousOutline = originalOutline;
2026
2174
  // Set the new border to be red and 2px solid
2027
- element.style.border = "2px solid red";
2175
+ element.style.outline = "2px solid red";
2028
2176
  if (window) {
2029
2177
  window.addEventListener("beforeunload", function (e) {
2030
- element.style.border = originalBorder;
2178
+ element.style.outline = originalOutline;
2031
2179
  });
2032
2180
  }
2033
2181
  // Set a timeout to revert to the original border after 2 seconds
2034
2182
  setTimeout(function () {
2035
- element.style.border = originalBorder;
2183
+ element.style.outline = originalOutline;
2036
2184
  }, 2000);
2037
2185
  }
2038
2186
  return;
@@ -2040,6 +2188,7 @@ class StableBrowser {
2040
2188
  .then(() => { })
2041
2189
  .catch((e) => {
2042
2190
  // ignore
2191
+ // console.error(`Could not highlight css: ${e}`);
2043
2192
  });
2044
2193
  }
2045
2194
  }
@@ -2047,6 +2196,54 @@ class StableBrowser {
2047
2196
  console.debug(error);
2048
2197
  }
2049
2198
  }
2199
+ // async _unhighlightElements(scope, css) {
2200
+ // try {
2201
+ // if (!scope) {
2202
+ // return;
2203
+ // }
2204
+ // if (!css) {
2205
+ // scope
2206
+ // .evaluate((node) => {
2207
+ // if (node && node.style) {
2208
+ // if (!node.__previousOutline) {
2209
+ // node.style.outline = "";
2210
+ // } else {
2211
+ // node.style.outline = node.__previousOutline;
2212
+ // }
2213
+ // }
2214
+ // })
2215
+ // .then(() => {})
2216
+ // .catch((e) => {
2217
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2218
+ // });
2219
+ // } else {
2220
+ // scope
2221
+ // .evaluate(([css]) => {
2222
+ // if (!css) {
2223
+ // return;
2224
+ // }
2225
+ // let elements = Array.from(document.querySelectorAll(css));
2226
+ // for (i = 0; i < elements.length; i++) {
2227
+ // let element = elements[i];
2228
+ // if (!element.style) {
2229
+ // return;
2230
+ // }
2231
+ // if (!element.__previousOutline) {
2232
+ // element.style.outline = "";
2233
+ // } else {
2234
+ // element.style.outline = element.__previousOutline;
2235
+ // }
2236
+ // }
2237
+ // })
2238
+ // .then(() => {})
2239
+ // .catch((e) => {
2240
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2241
+ // });
2242
+ // }
2243
+ // } catch (error) {
2244
+ // // console.debug(error);
2245
+ // }
2246
+ // }
2050
2247
  async verifyPagePath(pathPart, options = {}, world = null) {
2051
2248
  const startTime = Date.now();
2052
2249
  let error = null;
@@ -2083,20 +2280,22 @@ class StableBrowser {
2083
2280
  info.screenshotPath = screenshotPath;
2084
2281
  Object.assign(e, { info: info });
2085
2282
  error = e;
2086
- throw e;
2283
+ // throw e;
2284
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2087
2285
  }
2088
2286
  finally {
2089
2287
  const endTime = Date.now();
2090
- this._reportToWorld(world, {
2288
+ _reportToWorld(world, {
2091
2289
  type: Types.VERIFY_PAGE_PATH,
2092
2290
  text: "Verify page path",
2291
+ _text: "Verify the page path contains " + pathPart,
2093
2292
  screenshotId,
2094
2293
  result: error
2095
2294
  ? {
2096
2295
  status: "FAILED",
2097
2296
  startTime,
2098
2297
  endTime,
2099
- message: error === null || error === void 0 ? void 0 : error.message,
2298
+ message: error?.message,
2100
2299
  }
2101
2300
  : {
2102
2301
  status: "PASSED",
@@ -2107,94 +2306,58 @@ class StableBrowser {
2107
2306
  });
2108
2307
  }
2109
2308
  }
2110
- async verifyTextExistInPage(text, options = {}, world = null) {
2309
+ async verifyPageTitle(title, options = {}, world = null) {
2111
2310
  const startTime = Date.now();
2112
- const timeout = this._getLoadTimeout(options);
2113
2311
  let error = null;
2114
2312
  let screenshotId = null;
2115
2313
  let screenshotPath = null;
2116
2314
  await new Promise((resolve) => setTimeout(resolve, 2000));
2117
2315
  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);
2316
+ info.log = "***** verify page title " + title + " *****\n";
2317
+ info.operation = "verifyPageTitle";
2318
+ const newValue = await this._replaceWithLocalData(title, world);
2319
+ if (newValue !== title) {
2320
+ this.logger.info(title + "=" + newValue);
2321
+ title = newValue;
2322
+ }
2323
+ info.title = title;
2128
2324
  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`);
2325
+ for (let i = 0; i < 30; i++) {
2326
+ const foundTitle = await this.page.title();
2327
+ if (!foundTitle.includes(title)) {
2328
+ if (i === 29) {
2329
+ throw new Error(`url ${foundTitle} doesn't contain ${title}`);
2158
2330
  }
2159
2331
  await new Promise((resolve) => setTimeout(resolve, 1000));
2160
2332
  continue;
2161
2333
  }
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
2334
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2173
2335
  return info;
2174
2336
  }
2175
- // await expect(element).toHaveCount(1, { timeout: 10000 });
2176
2337
  }
2177
2338
  catch (e) {
2178
2339
  //await this.closeUnexpectedPopups();
2179
- this.logger.error("verify text exist in page failed " + info.log);
2340
+ this.logger.error("verify page title failed " + info.log);
2180
2341
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2181
2342
  info.screenshotPath = screenshotPath;
2182
2343
  Object.assign(e, { info: info });
2183
2344
  error = e;
2184
- throw e;
2345
+ // throw e;
2346
+ await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
2185
2347
  }
2186
2348
  finally {
2187
2349
  const endTime = Date.now();
2188
- this._reportToWorld(world, {
2189
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2190
- text: "Verify text exists in page",
2350
+ _reportToWorld(world, {
2351
+ type: Types.VERIFY_PAGE_PATH,
2352
+ text: "Verify page title",
2353
+ _text: "Verify the page title contains " + title,
2191
2354
  screenshotId,
2192
2355
  result: error
2193
2356
  ? {
2194
2357
  status: "FAILED",
2195
2358
  startTime,
2196
2359
  endTime,
2197
- message: error === null || error === void 0 ? void 0 : error.message,
2360
+ message: error?.message,
2198
2361
  }
2199
2362
  : {
2200
2363
  status: "PASSED",
@@ -2205,15 +2368,317 @@ class StableBrowser {
2205
2368
  });
2206
2369
  }
2207
2370
  }
2208
- _getServerUrl() {
2209
- let serviceUrl = "https://api.blinq.io";
2210
- if (process.env.NODE_ENV_BLINQ === "dev") {
2211
- serviceUrl = "https://dev.api.blinq.io";
2371
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
2372
+ const frames = this.page.frames();
2373
+ let results = [];
2374
+ // let ignoreCase = false;
2375
+ for (let i = 0; i < frames.length; i++) {
2376
+ if (dateAlternatives.date) {
2377
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2378
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2379
+ result.frame = frames[i];
2380
+ results.push(result);
2381
+ }
2382
+ }
2383
+ else if (numberAlternatives.number) {
2384
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2385
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2386
+ result.frame = frames[i];
2387
+ results.push(result);
2388
+ }
2389
+ }
2390
+ else {
2391
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
2392
+ result.frame = frames[i];
2393
+ results.push(result);
2394
+ }
2395
+ }
2396
+ state.info.results = results;
2397
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2398
+ return resultWithElementsFound;
2399
+ }
2400
+ async verifyTextExistInPage(text, options = {}, world = null) {
2401
+ text = unEscapeString(text);
2402
+ const state = {
2403
+ text_search: text,
2404
+ options,
2405
+ world,
2406
+ locate: false,
2407
+ scroll: false,
2408
+ highlight: false,
2409
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2410
+ text: `Verify the text '${text}' exists in page`,
2411
+ _text: `Verify the text '${text}' exists in page`,
2412
+ operation: "verifyTextExistInPage",
2413
+ log: "***** verify text " + text + " exists in page *****\n",
2414
+ };
2415
+ if (testForRegex(text)) {
2416
+ text = text.replace(/\\"/g, '"');
2417
+ }
2418
+ const timeout = this._getFindElementTimeout(options);
2419
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2420
+ const newValue = await this._replaceWithLocalData(text, world);
2421
+ if (newValue !== text) {
2422
+ this.logger.info(text + "=" + newValue);
2423
+ text = newValue;
2424
+ }
2425
+ let dateAlternatives = findDateAlternatives(text);
2426
+ let numberAlternatives = findNumberAlternatives(text);
2427
+ try {
2428
+ await _preCommand(state, this);
2429
+ state.info.text = text;
2430
+ while (true) {
2431
+ let resultWithElementsFound = {
2432
+ length: 0,
2433
+ };
2434
+ try {
2435
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2436
+ }
2437
+ catch (error) {
2438
+ // ignore
2439
+ }
2440
+ if (resultWithElementsFound.length === 0) {
2441
+ if (Date.now() - state.startTime > timeout) {
2442
+ throw new Error(`Text ${text} not found in page`);
2443
+ }
2444
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2445
+ continue;
2446
+ }
2447
+ try {
2448
+ if (resultWithElementsFound[0].randomToken) {
2449
+ const frame = resultWithElementsFound[0].frame;
2450
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2451
+ await this._highlightElements(frame, dataAttribute);
2452
+ // if (world && world.screenshot && !world.screenshotPath) {
2453
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2454
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2455
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2456
+ // this._unhighlightElements(frame, dataAttribute)
2457
+ // .then(async () => {
2458
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2459
+ // })
2460
+ // .catch(
2461
+ // (e) => {}
2462
+ // console.error(e)
2463
+ // );
2464
+ // });
2465
+ // }
2466
+ const element = await frame.locator(dataAttribute).first();
2467
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2468
+ // await this._unhighlightElements(frame, dataAttribute);
2469
+ if (element) {
2470
+ await this.scrollIfNeeded(element, state.info);
2471
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2472
+ // await _screenshot(state, this, element);
2473
+ }
2474
+ }
2475
+ await _screenshot(state, this);
2476
+ return state.info;
2477
+ }
2478
+ catch (error) {
2479
+ console.error(error);
2480
+ }
2481
+ }
2482
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2483
+ }
2484
+ catch (e) {
2485
+ await _commandError(state, e, this);
2486
+ }
2487
+ finally {
2488
+ _commandFinally(state, this);
2489
+ }
2490
+ }
2491
+ async waitForTextToDisappear(text, options = {}, world = null) {
2492
+ text = unEscapeString(text);
2493
+ const state = {
2494
+ text_search: text,
2495
+ options,
2496
+ world,
2497
+ locate: false,
2498
+ scroll: false,
2499
+ highlight: false,
2500
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2501
+ text: `Verify the text '${text}' does not exist in page`,
2502
+ _text: `Verify the text '${text}' does not exist in page`,
2503
+ operation: "verifyTextNotExistInPage",
2504
+ log: "***** verify text " + text + " does not exist in page *****\n",
2505
+ };
2506
+ if (testForRegex(text)) {
2507
+ text = text.replace(/\\"/g, '"');
2508
+ }
2509
+ const timeout = this._getFindElementTimeout(options);
2510
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2511
+ const newValue = await this._replaceWithLocalData(text, world);
2512
+ if (newValue !== text) {
2513
+ this.logger.info(text + "=" + newValue);
2514
+ text = newValue;
2515
+ }
2516
+ let dateAlternatives = findDateAlternatives(text);
2517
+ let numberAlternatives = findNumberAlternatives(text);
2518
+ try {
2519
+ await _preCommand(state, this);
2520
+ state.info.text = text;
2521
+ let resultWithElementsFound = {
2522
+ length: null, // initial cannot be 0
2523
+ };
2524
+ while (true) {
2525
+ try {
2526
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2527
+ }
2528
+ catch (error) {
2529
+ // ignore
2530
+ }
2531
+ if (resultWithElementsFound.length === 0) {
2532
+ await _screenshot(state, this);
2533
+ return state.info;
2534
+ }
2535
+ if (Date.now() - state.startTime > timeout) {
2536
+ throw new Error(`Text ${text} found in page`);
2537
+ }
2538
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2539
+ }
2540
+ }
2541
+ catch (e) {
2542
+ await _commandError(state, e, this);
2543
+ }
2544
+ finally {
2545
+ _commandFinally(state, this);
2546
+ }
2547
+ }
2548
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2549
+ textAnchor = unEscapeString(textAnchor);
2550
+ textToVerify = unEscapeString(textToVerify);
2551
+ const state = {
2552
+ text_search: textToVerify,
2553
+ options,
2554
+ world,
2555
+ locate: false,
2556
+ scroll: false,
2557
+ highlight: false,
2558
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2559
+ text: `Verify text with relation to another text`,
2560
+ _text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
2561
+ operation: "verify_text_with_relation",
2562
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2563
+ };
2564
+ const timeout = this._getFindElementTimeout(options);
2565
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2566
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2567
+ if (newValue !== textAnchor) {
2568
+ this.logger.info(textAnchor + "=" + newValue);
2569
+ textAnchor = newValue;
2570
+ }
2571
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2572
+ if (newValue !== textToVerify) {
2573
+ this.logger.info(textToVerify + "=" + newValue);
2574
+ textToVerify = newValue;
2575
+ }
2576
+ let dateAlternatives = findDateAlternatives(textToVerify);
2577
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2578
+ let foundAncore = false;
2579
+ try {
2580
+ await _preCommand(state, this);
2581
+ state.info.text = textToVerify;
2582
+ let resultWithElementsFound = {
2583
+ length: 0,
2584
+ };
2585
+ while (true) {
2586
+ try {
2587
+ resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
2588
+ }
2589
+ catch (error) {
2590
+ // ignore
2591
+ }
2592
+ if (resultWithElementsFound.length === 0) {
2593
+ if (Date.now() - state.startTime > timeout) {
2594
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2595
+ }
2596
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2597
+ continue;
2598
+ }
2599
+ try {
2600
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2601
+ foundAncore = true;
2602
+ const result = resultWithElementsFound[i];
2603
+ const token = result.randomToken;
2604
+ const frame = result.frame;
2605
+ let css = `[data-blinq-id-${token}]`;
2606
+ const climbArray1 = [];
2607
+ for (let i = 0; i < climb; i++) {
2608
+ climbArray1.push("..");
2609
+ }
2610
+ let climbXpath = "xpath=" + climbArray1.join("/");
2611
+ css = css + " >> " + climbXpath;
2612
+ const count = await frame.locator(css).count();
2613
+ for (let j = 0; j < count; j++) {
2614
+ const continer = await frame.locator(css).nth(j);
2615
+ const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
2616
+ if (result.elementCount > 0) {
2617
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2618
+ await this._highlightElements(frame, dataAttribute);
2619
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2620
+ // if (world && world.screenshot && !world.screenshotPath) {
2621
+ // console.log(`Highlighting for vtrt while running from recorder`);
2622
+ // this._highlightElements(frame, dataAttribute)
2623
+ // .then(async () => {
2624
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2625
+ // this._unhighlightElements(frame, dataAttribute).then(
2626
+ // () => {}
2627
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2628
+ // );
2629
+ // })
2630
+ // .catch(e);
2631
+ // }
2632
+ //await this._highlightElements(frame, cssAnchor);
2633
+ const element = await frame.locator(dataAttribute).first();
2634
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2635
+ // await this._unhighlightElements(frame, dataAttribute);
2636
+ if (element) {
2637
+ await this.scrollIfNeeded(element, state.info);
2638
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2639
+ }
2640
+ await _screenshot(state, this);
2641
+ return state.info;
2642
+ }
2643
+ }
2644
+ }
2645
+ }
2646
+ catch (error) {
2647
+ console.error(error);
2648
+ }
2649
+ }
2650
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2651
+ }
2652
+ catch (e) {
2653
+ await _commandError(state, e, this);
2654
+ }
2655
+ finally {
2656
+ _commandFinally(state, this);
2212
2657
  }
2213
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2214
- serviceUrl = "https://stage.api.blinq.io";
2658
+ }
2659
+ async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
2660
+ const frames = this.page.frames();
2661
+ let results = [];
2662
+ let ignoreCase = false;
2663
+ for (let i = 0; i < frames.length; i++) {
2664
+ const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
2665
+ result.frame = frames[i];
2666
+ const climbArray = [];
2667
+ for (let i = 0; i < climb; i++) {
2668
+ climbArray.push("..");
2669
+ }
2670
+ let climbXpath = "xpath=" + climbArray.join("/");
2671
+ const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
2672
+ const count = await frames[i].locator(newLocator).count();
2673
+ if (count > 0) {
2674
+ result.elementCount = count;
2675
+ result.locator = newLocator;
2676
+ results.push(result);
2677
+ }
2215
2678
  }
2216
- return serviceUrl;
2679
+ // state.info.results = results;
2680
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2681
+ return resultWithElementsFound;
2217
2682
  }
2218
2683
  async visualVerification(text, options = {}, world = null) {
2219
2684
  const startTime = Date.now();
@@ -2229,14 +2694,17 @@ class StableBrowser {
2229
2694
  throw new Error("TOKEN is not set");
2230
2695
  }
2231
2696
  try {
2232
- let serviceUrl = this._getServerUrl();
2697
+ let serviceUrl = _getServerUrl();
2233
2698
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2234
2699
  info.screenshotPath = screenshotPath;
2235
2700
  const screenshot = await this.takeScreenshot();
2236
- const request = {
2237
- method: "POST",
2701
+ let request = {
2702
+ method: "post",
2703
+ maxBodyLength: Infinity,
2238
2704
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2239
2705
  headers: {
2706
+ "x-bvt-project-id": path.basename(this.project_path),
2707
+ "x-source": "aaa",
2240
2708
  "Content-Type": "application/json",
2241
2709
  Authorization: `Bearer ${process.env.TOKEN}`,
2242
2710
  },
@@ -2245,7 +2713,7 @@ class StableBrowser {
2245
2713
  screenshot: screenshot,
2246
2714
  }),
2247
2715
  };
2248
- let result = await this.context.api.request(request);
2716
+ const result = await axios.request(request);
2249
2717
  if (result.data.status !== true) {
2250
2718
  throw new Error("Visual validation failed");
2251
2719
  }
@@ -2265,20 +2733,22 @@ class StableBrowser {
2265
2733
  info.screenshotPath = screenshotPath;
2266
2734
  Object.assign(e, { info: info });
2267
2735
  error = e;
2268
- throw e;
2736
+ // throw e;
2737
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2269
2738
  }
2270
2739
  finally {
2271
2740
  const endTime = Date.now();
2272
- this._reportToWorld(world, {
2741
+ _reportToWorld(world, {
2273
2742
  type: Types.VERIFY_VISUAL,
2274
2743
  text: "Visual verification",
2744
+ _text: "Visual verification of " + text,
2275
2745
  screenshotId,
2276
2746
  result: error
2277
2747
  ? {
2278
2748
  status: "FAILED",
2279
2749
  startTime,
2280
2750
  endTime,
2281
- message: error === null || error === void 0 ? void 0 : error.message,
2751
+ message: error?.message,
2282
2752
  }
2283
2753
  : {
2284
2754
  status: "PASSED",
@@ -2310,13 +2780,14 @@ class StableBrowser {
2310
2780
  this.logger.info("Table data verified");
2311
2781
  }
2312
2782
  async getTableData(selectors, _params = null, options = {}, world = null) {
2313
- this._validateSelectors(selectors);
2783
+ _validateSelectors(selectors);
2314
2784
  const startTime = Date.now();
2315
2785
  let error = null;
2316
2786
  let screenshotId = null;
2317
2787
  let screenshotPath = null;
2318
2788
  const info = {};
2319
2789
  info.log = "";
2790
+ info.locatorLog = new LocatorLog(selectors);
2320
2791
  info.operation = "getTableData";
2321
2792
  info.selectors = selectors;
2322
2793
  try {
@@ -2332,11 +2803,12 @@ class StableBrowser {
2332
2803
  info.screenshotPath = screenshotPath;
2333
2804
  Object.assign(e, { info: info });
2334
2805
  error = e;
2335
- throw e;
2806
+ // throw e;
2807
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2336
2808
  }
2337
2809
  finally {
2338
2810
  const endTime = Date.now();
2339
- this._reportToWorld(world, {
2811
+ _reportToWorld(world, {
2340
2812
  element_name: selectors.element_name,
2341
2813
  type: Types.GET_TABLE_DATA,
2342
2814
  text: "Get table data",
@@ -2346,7 +2818,7 @@ class StableBrowser {
2346
2818
  status: "FAILED",
2347
2819
  startTime,
2348
2820
  endTime,
2349
- message: error === null || error === void 0 ? void 0 : error.message,
2821
+ message: error?.message,
2350
2822
  }
2351
2823
  : {
2352
2824
  status: "PASSED",
@@ -2358,7 +2830,7 @@ class StableBrowser {
2358
2830
  }
2359
2831
  }
2360
2832
  async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
2361
- this._validateSelectors(selectors);
2833
+ _validateSelectors(selectors);
2362
2834
  if (!query) {
2363
2835
  throw new Error("query is null");
2364
2836
  }
@@ -2391,7 +2863,7 @@ class StableBrowser {
2391
2863
  info.operation = "analyzeTable";
2392
2864
  info.selectors = selectors;
2393
2865
  info.query = query;
2394
- query = this._fixUsingParams(query, _params);
2866
+ query = _fixUsingParams(query, _params);
2395
2867
  info.query_fixed = query;
2396
2868
  info.operator = operator;
2397
2869
  info.value = value;
@@ -2497,11 +2969,12 @@ class StableBrowser {
2497
2969
  info.screenshotPath = screenshotPath;
2498
2970
  Object.assign(e, { info: info });
2499
2971
  error = e;
2500
- throw e;
2972
+ // throw e;
2973
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2501
2974
  }
2502
2975
  finally {
2503
2976
  const endTime = Date.now();
2504
- this._reportToWorld(world, {
2977
+ _reportToWorld(world, {
2505
2978
  element_name: selectors.element_name,
2506
2979
  type: Types.ANALYZE_TABLE,
2507
2980
  text: "Analyze table",
@@ -2511,7 +2984,7 @@ class StableBrowser {
2511
2984
  status: "FAILED",
2512
2985
  startTime,
2513
2986
  endTime,
2514
- message: error === null || error === void 0 ? void 0 : error.message,
2987
+ message: error?.message,
2515
2988
  }
2516
2989
  : {
2517
2990
  status: "PASSED",
@@ -2523,27 +2996,7 @@ class StableBrowser {
2523
2996
  }
2524
2997
  }
2525
2998
  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;
2999
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2547
3000
  }
2548
3001
  _getLoadTimeout(options) {
2549
3002
  let timeout = 15000;
@@ -2555,6 +3008,32 @@ class StableBrowser {
2555
3008
  }
2556
3009
  return timeout;
2557
3010
  }
3011
+ _getFindElementTimeout(options) {
3012
+ if (options && options.timeout) {
3013
+ return options.timeout;
3014
+ }
3015
+ if (this.configuration.find_element_timeout) {
3016
+ return this.configuration.find_element_timeout;
3017
+ }
3018
+ return 30000;
3019
+ }
3020
+ async saveStoreState(path = null, world = null) {
3021
+ const storageState = await this.page.context().storageState();
3022
+ //const testDataFile = _getDataFile(world, this.context, this);
3023
+ if (path) {
3024
+ // save { storageState: storageState } into the path
3025
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
3026
+ }
3027
+ else {
3028
+ await this.setTestData({ storageState: storageState }, world);
3029
+ }
3030
+ }
3031
+ async restoreSaveState(path = null, world = null) {
3032
+ await refreshBrowser(this, path, world);
3033
+ this.registerEventListeners(this.context);
3034
+ registerNetworkEvents(this.world, this, this.context, this.page);
3035
+ registerDownloadEvent(this.page, this.world, this.context);
3036
+ }
2558
3037
  async waitForPageLoad(options = {}, world = null) {
2559
3038
  let timeout = this._getLoadTimeout(options);
2560
3039
  const promiseArray = [];
@@ -2580,13 +3059,13 @@ class StableBrowser {
2580
3059
  }
2581
3060
  catch (e) {
2582
3061
  if (e.label === "networkidle") {
2583
- console.log("waitted for the network to be idle timeout");
3062
+ console.log("waited for the network to be idle timeout");
2584
3063
  }
2585
3064
  else if (e.label === "load") {
2586
- console.log("waitted for the load timeout");
3065
+ console.log("waited for the load timeout");
2587
3066
  }
2588
3067
  else if (e.label === "domcontentloaded") {
2589
- console.log("waitted for the domcontent loaded timeout");
3068
+ console.log("waited for the domcontent loaded timeout");
2590
3069
  }
2591
3070
  console.log(".");
2592
3071
  }
@@ -2594,7 +3073,7 @@ class StableBrowser {
2594
3073
  await new Promise((resolve) => setTimeout(resolve, 2000));
2595
3074
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2596
3075
  const endTime = Date.now();
2597
- this._reportToWorld(world, {
3076
+ _reportToWorld(world, {
2598
3077
  type: Types.GET_PAGE_STATUS,
2599
3078
  text: "Wait for page load",
2600
3079
  screenshotId,
@@ -2603,7 +3082,7 @@ class StableBrowser {
2603
3082
  status: "FAILED",
2604
3083
  startTime,
2605
3084
  endTime,
2606
- message: error === null || error === void 0 ? void 0 : error.message,
3085
+ message: error?.message,
2607
3086
  }
2608
3087
  : {
2609
3088
  status: "PASSED",
@@ -2614,41 +3093,123 @@ class StableBrowser {
2614
3093
  }
2615
3094
  }
2616
3095
  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 = {};
3096
+ const state = {
3097
+ options,
3098
+ world,
3099
+ locate: false,
3100
+ scroll: false,
3101
+ highlight: false,
3102
+ type: Types.CLOSE_PAGE,
3103
+ text: `Close page`,
3104
+ _text: `Close the page`,
3105
+ operation: "closePage",
3106
+ log: "***** close page *****\n",
3107
+ throwError: false,
3108
+ };
2622
3109
  try {
3110
+ await _preCommand(state, this);
2623
3111
  await this.page.close();
2624
3112
  }
2625
3113
  catch (e) {
2626
3114
  console.log(".");
3115
+ await _commandError(state, e, this);
2627
3116
  }
2628
3117
  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,
3118
+ _commandFinally(state, this);
3119
+ }
3120
+ }
3121
+ async tableCellOperation(headerText, rowText, options, _params, world = null) {
3122
+ let operation = null;
3123
+ if (!options || !options.operation) {
3124
+ throw new Error("operation is not defined");
3125
+ }
3126
+ operation = options.operation;
3127
+ // validate operation is one of the supported operations
3128
+ if (operation != "click" && operation != "hover+click") {
3129
+ throw new Error("operation is not supported");
3130
+ }
3131
+ const state = {
3132
+ options,
3133
+ world,
3134
+ locate: false,
3135
+ scroll: false,
3136
+ highlight: false,
3137
+ type: Types.TABLE_OPERATION,
3138
+ text: `Table operation`,
3139
+ _text: `Table ${operation} operation`,
3140
+ operation: operation,
3141
+ log: "***** Table operation *****\n",
3142
+ };
3143
+ const timeout = this._getFindElementTimeout(options);
3144
+ try {
3145
+ await _preCommand(state, this);
3146
+ const start = Date.now();
3147
+ let cellArea = null;
3148
+ while (true) {
3149
+ try {
3150
+ cellArea = await _findCellArea(headerText, rowText, this, state);
3151
+ if (cellArea) {
3152
+ break;
2642
3153
  }
2643
- : {
2644
- status: "PASSED",
2645
- startTime,
2646
- endTime,
2647
- },
2648
- info: info,
2649
- });
3154
+ }
3155
+ catch (e) {
3156
+ // ignore
3157
+ }
3158
+ if (Date.now() - start > timeout) {
3159
+ throw new Error(`Cell not found in table`);
3160
+ }
3161
+ await new Promise((resolve) => setTimeout(resolve, 1000));
3162
+ }
3163
+ switch (operation) {
3164
+ case "click":
3165
+ if (!options.css) {
3166
+ // will click in the center of the cell
3167
+ let xOffset = 0;
3168
+ let yOffset = 0;
3169
+ if (options.xOffset) {
3170
+ xOffset = options.xOffset;
3171
+ }
3172
+ if (options.yOffset) {
3173
+ yOffset = options.yOffset;
3174
+ }
3175
+ await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
3176
+ }
3177
+ else {
3178
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3179
+ if (results.length === 0) {
3180
+ throw new Error(`Element not found in cell area`);
3181
+ }
3182
+ state.element = results[0];
3183
+ await performAction("click", state.element, options, this, state, _params);
3184
+ }
3185
+ break;
3186
+ case "hover+click":
3187
+ if (!options.css) {
3188
+ throw new Error("css is not defined");
3189
+ }
3190
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3191
+ if (results.length === 0) {
3192
+ throw new Error(`Element not found in cell area`);
3193
+ }
3194
+ state.element = results[0];
3195
+ await performAction("hover+click", state.element, options, this, state, _params);
3196
+ break;
3197
+ default:
3198
+ throw new Error("operation is not supported");
3199
+ }
3200
+ }
3201
+ catch (e) {
3202
+ await _commandError(state, e, this);
3203
+ }
3204
+ finally {
3205
+ _commandFinally(state, this);
2650
3206
  }
2651
3207
  }
3208
+ saveTestDataAsGlobal(options, world) {
3209
+ const dataFile = _getDataFile(world, this.context, this);
3210
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
3211
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
3212
+ }
2652
3213
  async setViewportSize(width, hight, options = {}, world = null) {
2653
3214
  const startTime = Date.now();
2654
3215
  let error = null;
@@ -2666,21 +3227,23 @@ class StableBrowser {
2666
3227
  }
2667
3228
  catch (e) {
2668
3229
  console.log(".");
3230
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2669
3231
  }
2670
3232
  finally {
2671
3233
  await new Promise((resolve) => setTimeout(resolve, 2000));
2672
3234
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2673
3235
  const endTime = Date.now();
2674
- this._reportToWorld(world, {
3236
+ _reportToWorld(world, {
2675
3237
  type: Types.SET_VIEWPORT,
2676
3238
  text: "set viewport size to " + width + "x" + hight,
3239
+ _text: "Set the viewport size to " + width + "x" + hight,
2677
3240
  screenshotId,
2678
3241
  result: error
2679
3242
  ? {
2680
3243
  status: "FAILED",
2681
3244
  startTime,
2682
3245
  endTime,
2683
- message: error === null || error === void 0 ? void 0 : error.message,
3246
+ message: error?.message,
2684
3247
  }
2685
3248
  : {
2686
3249
  status: "PASSED",
@@ -2702,12 +3265,13 @@ class StableBrowser {
2702
3265
  }
2703
3266
  catch (e) {
2704
3267
  console.log(".");
3268
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2705
3269
  }
2706
3270
  finally {
2707
3271
  await new Promise((resolve) => setTimeout(resolve, 2000));
2708
3272
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2709
3273
  const endTime = Date.now();
2710
- this._reportToWorld(world, {
3274
+ _reportToWorld(world, {
2711
3275
  type: Types.GET_PAGE_STATUS,
2712
3276
  text: "page relaod",
2713
3277
  screenshotId,
@@ -2716,7 +3280,7 @@ class StableBrowser {
2716
3280
  status: "FAILED",
2717
3281
  startTime,
2718
3282
  endTime,
2719
- message: error === null || error === void 0 ? void 0 : error.message,
3283
+ message: error?.message,
2720
3284
  }
2721
3285
  : {
2722
3286
  status: "PASSED",
@@ -2729,40 +3293,112 @@ class StableBrowser {
2729
3293
  }
2730
3294
  async scrollIfNeeded(element, info) {
2731
3295
  try {
2732
- let didScroll = await element.evaluate((node) => {
2733
- const rect = node.getBoundingClientRect();
2734
- if (rect &&
2735
- rect.top >= 0 &&
2736
- rect.left >= 0 &&
2737
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
2738
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
2739
- return false;
2740
- }
2741
- else {
2742
- node.scrollIntoView({
2743
- behavior: "smooth",
2744
- block: "center",
2745
- inline: "center",
2746
- });
2747
- return true;
2748
- }
3296
+ await element.scrollIntoViewIfNeeded({
3297
+ timeout: 2000,
2749
3298
  });
2750
- if (didScroll) {
2751
- await new Promise((resolve) => setTimeout(resolve, 500));
2752
- if (info) {
2753
- info.box = await element.boundingBox();
3299
+ await new Promise((resolve) => setTimeout(resolve, 500));
3300
+ if (info) {
3301
+ info.box = await element.boundingBox({
3302
+ timeout: 1000,
3303
+ });
3304
+ }
3305
+ }
3306
+ catch (e) {
3307
+ console.log("#-#");
3308
+ }
3309
+ }
3310
+ async beforeStep(world, step) {
3311
+ if (this.stepIndex === undefined) {
3312
+ this.stepIndex = 0;
3313
+ }
3314
+ else {
3315
+ this.stepIndex++;
3316
+ }
3317
+ if (step && step.pickleStep && step.pickleStep.text) {
3318
+ this.stepName = step.pickleStep.text;
3319
+ this.logger.info("step: " + this.stepName);
3320
+ }
3321
+ else if (step && step.text) {
3322
+ this.stepName = step.text;
3323
+ }
3324
+ else {
3325
+ this.stepName = "step " + this.stepIndex;
3326
+ }
3327
+ if (this.context) {
3328
+ this.context.examplesRow = extractStepExampleParameters(step);
3329
+ }
3330
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
3331
+ if (this.context.browserObject.context) {
3332
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
3333
+ }
3334
+ }
3335
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
3336
+ this.tags = step.pickle.tags.map((tag) => tag.name);
3337
+ // check if @global_test_data tag is present
3338
+ if (this.tags.includes("@global_test_data")) {
3339
+ this.saveTestDataAsGlobal({}, world);
3340
+ }
3341
+ }
3342
+ if (this.initSnapshotTaken === false) {
3343
+ this.initSnapshotTaken = true;
3344
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3345
+ const snapshot = await this.getAriaSnapshot();
3346
+ if (snapshot) {
3347
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
2754
3348
  }
2755
3349
  }
2756
3350
  }
3351
+ }
3352
+ async getAriaSnapshot() {
3353
+ try {
3354
+ // find the page url
3355
+ const url = await this.page.url();
3356
+ // extract the path from the url
3357
+ const path = new URL(url).pathname;
3358
+ // get the page title
3359
+ const title = await this.page.title();
3360
+ // go over other frams
3361
+ const frames = this.page.frames();
3362
+ const snapshots = [];
3363
+ const content = [`- path: ${path}`, `- title: ${title}`];
3364
+ const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
3365
+ for (let i = 0; i < frames.length; i++) {
3366
+ content.push(`- frame: ${i}`);
3367
+ const frame = frames[i];
3368
+ const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
3369
+ content.push(snapshot);
3370
+ }
3371
+ return content.join("\n");
3372
+ }
2757
3373
  catch (e) {
2758
- console.log("scroll failed");
3374
+ console.error(e);
2759
3375
  }
3376
+ return null;
2760
3377
  }
2761
- _reportToWorld(world, properties) {
2762
- if (!world || !world.attach) {
2763
- return;
3378
+ async afterStep(world, step) {
3379
+ this.stepName = null;
3380
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
3381
+ if (this.context.browserObject.context) {
3382
+ await this.context.browserObject.context.tracing.stopChunk({
3383
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
3384
+ });
3385
+ await world.attach(JSON.stringify({
3386
+ type: "trace",
3387
+ traceFilePath: `trace-${this.stepIndex}.zip`,
3388
+ }), "application/json+trace");
3389
+ // console.log("trace file created", `trace-${this.stepIndex}.zip`);
3390
+ }
3391
+ }
3392
+ if (this.context) {
3393
+ this.context.examplesRow = null;
3394
+ }
3395
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3396
+ const snapshot = await this.getAriaSnapshot();
3397
+ if (snapshot) {
3398
+ const obj = {};
3399
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
3400
+ }
2764
3401
  }
2765
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2766
3402
  }
2767
3403
  }
2768
3404
  function createTimedPromise(promise, label) {
@@ -2770,151 +3406,5 @@ function createTimedPromise(promise, label) {
2770
3406
  .then((result) => ({ status: "fulfilled", label, result }))
2771
3407
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2772
3408
  }
2773
- const KEYBOARD_EVENTS = [
2774
- "ALT",
2775
- "AltGraph",
2776
- "CapsLock",
2777
- "Control",
2778
- "Fn",
2779
- "FnLock",
2780
- "Hyper",
2781
- "Meta",
2782
- "NumLock",
2783
- "ScrollLock",
2784
- "Shift",
2785
- "Super",
2786
- "Symbol",
2787
- "SymbolLock",
2788
- "Enter",
2789
- "Tab",
2790
- "ArrowDown",
2791
- "ArrowLeft",
2792
- "ArrowRight",
2793
- "ArrowUp",
2794
- "End",
2795
- "Home",
2796
- "PageDown",
2797
- "PageUp",
2798
- "Backspace",
2799
- "Clear",
2800
- "Copy",
2801
- "CrSel",
2802
- "Cut",
2803
- "Delete",
2804
- "EraseEof",
2805
- "ExSel",
2806
- "Insert",
2807
- "Paste",
2808
- "Redo",
2809
- "Undo",
2810
- "Accept",
2811
- "Again",
2812
- "Attn",
2813
- "Cancel",
2814
- "ContextMenu",
2815
- "Escape",
2816
- "Execute",
2817
- "Find",
2818
- "Finish",
2819
- "Help",
2820
- "Pause",
2821
- "Play",
2822
- "Props",
2823
- "Select",
2824
- "ZoomIn",
2825
- "ZoomOut",
2826
- "BrightnessDown",
2827
- "BrightnessUp",
2828
- "Eject",
2829
- "LogOff",
2830
- "Power",
2831
- "PowerOff",
2832
- "PrintScreen",
2833
- "Hibernate",
2834
- "Standby",
2835
- "WakeUp",
2836
- "AllCandidates",
2837
- "Alphanumeric",
2838
- "CodeInput",
2839
- "Compose",
2840
- "Convert",
2841
- "Dead",
2842
- "FinalMode",
2843
- "GroupFirst",
2844
- "GroupLast",
2845
- "GroupNext",
2846
- "GroupPrevious",
2847
- "ModeChange",
2848
- "NextCandidate",
2849
- "NonConvert",
2850
- "PreviousCandidate",
2851
- "Process",
2852
- "SingleCandidate",
2853
- "HangulMode",
2854
- "HanjaMode",
2855
- "JunjaMode",
2856
- "Eisu",
2857
- "Hankaku",
2858
- "Hiragana",
2859
- "HiraganaKatakana",
2860
- "KanaMode",
2861
- "KanjiMode",
2862
- "Katakana",
2863
- "Romaji",
2864
- "Zenkaku",
2865
- "ZenkakuHanaku",
2866
- "F1",
2867
- "F2",
2868
- "F3",
2869
- "F4",
2870
- "F5",
2871
- "F6",
2872
- "F7",
2873
- "F8",
2874
- "F9",
2875
- "F10",
2876
- "F11",
2877
- "F12",
2878
- "Soft1",
2879
- "Soft2",
2880
- "Soft3",
2881
- "Soft4",
2882
- "ChannelDown",
2883
- "ChannelUp",
2884
- "Close",
2885
- "MailForward",
2886
- "MailReply",
2887
- "MailSend",
2888
- "MediaFastForward",
2889
- "MediaPause",
2890
- "MediaPlay",
2891
- "MediaPlayPause",
2892
- "MediaRecord",
2893
- "MediaRewind",
2894
- "MediaStop",
2895
- "MediaTrackNext",
2896
- "MediaTrackPrevious",
2897
- "AudioBalanceLeft",
2898
- "AudioBalanceRight",
2899
- "AudioBassBoostDown",
2900
- "AudioBassBoostToggle",
2901
- "AudioBassBoostUp",
2902
- "AudioFaderFront",
2903
- "AudioFaderRear",
2904
- "AudioSurroundModeNext",
2905
- "AudioTrebleDown",
2906
- "AudioTrebleUp",
2907
- "AudioVolumeDown",
2908
- "AudioVolumeMute",
2909
- "AudioVolumeUp",
2910
- "MicrophoneToggle",
2911
- "MicrophoneVolumeDown",
2912
- "MicrophoneVolumeMute",
2913
- "MicrophoneVolumeUp",
2914
- "TV",
2915
- "TV3DMode",
2916
- "TVAntennaCable",
2917
- "TVAudioDescription",
2918
- ];
2919
3409
  export { StableBrowser };
2920
3410
  //# sourceMappingURL=stable_browser.js.map