automation_model 1.0.418-dev → 1.0.418

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