automation_model 1.0.408-dev → 1.0.408

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 (54) hide show
  1. package/lib/api.d.ts +43 -1
  2. package/lib/api.js +225 -41
  3. package/lib/api.js.map +1 -1
  4. package/lib/auto_page.d.ts +2 -1
  5. package/lib/auto_page.js +43 -17
  6. package/lib/auto_page.js.map +1 -1
  7. package/lib/browser_manager.d.ts +7 -3
  8. package/lib/browser_manager.js +120 -39
  9. package/lib/browser_manager.js.map +1 -1
  10. package/lib/command_common.d.ts +6 -0
  11. package/lib/command_common.js +160 -0
  12. package/lib/command_common.js.map +1 -0
  13. package/lib/environment.d.ts +3 -0
  14. package/lib/environment.js +5 -2
  15. package/lib/environment.js.map +1 -1
  16. package/lib/error-messages.d.ts +6 -0
  17. package/lib/error-messages.js +188 -0
  18. package/lib/error-messages.js.map +1 -0
  19. package/lib/generation_scripts.d.ts +4 -0
  20. package/lib/generation_scripts.js +2 -0
  21. package/lib/generation_scripts.js.map +1 -0
  22. package/lib/index.d.ts +1 -0
  23. package/lib/index.js +1 -0
  24. package/lib/index.js.map +1 -1
  25. package/lib/init_browser.d.ts +3 -1
  26. package/lib/init_browser.js +67 -4
  27. package/lib/init_browser.js.map +1 -1
  28. package/lib/locate_element.d.ts +7 -0
  29. package/lib/locate_element.js +215 -0
  30. package/lib/locate_element.js.map +1 -0
  31. package/lib/locator.d.ts +36 -0
  32. package/lib/locator.js +165 -0
  33. package/lib/locator.js.map +1 -1
  34. package/lib/locator_log.d.ts +26 -0
  35. package/lib/locator_log.js +69 -0
  36. package/lib/locator_log.js.map +1 -0
  37. package/lib/network.d.ts +3 -0
  38. package/lib/network.js +157 -0
  39. package/lib/network.js.map +1 -0
  40. package/lib/scripts/axe.mini.js +12 -0
  41. package/lib/scripts/find_text.js +126 -0
  42. package/lib/stable_browser.d.ts +75 -32
  43. package/lib/stable_browser.js +1188 -1187
  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/test_context.d.ts +4 -0
  49. package/lib/test_context.js +12 -9
  50. package/lib/test_context.js.map +1 -1
  51. package/lib/utils.d.ts +14 -1
  52. package/lib/utils.js +304 -5
  53. package/lib/utils.js.map +1 -1
  54. package/package.json +13 -8
@@ -2,19 +2,25 @@
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 { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } 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 } 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
+ export const Types = {
18
24
  CLICK: "click_element",
19
25
  NAVIGATE: "navigate",
20
26
  FILL: "fill_element",
@@ -25,6 +31,8 @@ const Types = {
25
31
  GET_PAGE_STATUS: "get_page_status",
26
32
  CLICK_ROW_ACTION: "click_row_action",
27
33
  VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
34
+ VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
35
+ VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
28
36
  ANALYZE_TABLE: "analyze_table",
29
37
  SELECT: "select_combobox",
30
38
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -40,16 +48,29 @@ const Types = {
40
48
  VERIFY_VISUAL: "verify_visual",
41
49
  LOAD_DATA: "load_data",
42
50
  SET_INPUT: "set_input",
51
+ WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
52
+ VERIFY_ATTRIBUTE: "verify_element_attribute",
53
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
43
54
  };
55
+ export const apps = {};
44
56
  class StableBrowser {
45
- constructor(browser, page, logger = null, context = null) {
57
+ browser;
58
+ page;
59
+ logger;
60
+ context;
61
+ world;
62
+ project_path = null;
63
+ webLogFile = null;
64
+ networkLogger = null;
65
+ configuration = null;
66
+ appName = "main";
67
+ tags = null;
68
+ constructor(browser, page, logger = null, context = null, world = null) {
46
69
  this.browser = browser;
47
70
  this.page = page;
48
71
  this.logger = logger;
49
72
  this.context = context;
50
- this.project_path = null;
51
- this.webLogFile = null;
52
- this.configuration = null;
73
+ this.world = world;
53
74
  if (!this.logger) {
54
75
  this.logger = console;
55
76
  }
@@ -75,23 +96,43 @@ class StableBrowser {
75
96
  this.logger.error("unable to read ai_config.json");
76
97
  }
77
98
  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();
99
+ this.world = world;
81
100
  context.pages = [this.page];
82
101
  context.pageLoading = { status: false };
102
+ this.registerEventListeners(this.context);
103
+ registerNetworkEvents(this.world, this, this.context, this.page);
104
+ registerDownloadEvent(this.page, this.world, this.context);
105
+ }
106
+ registerEventListeners(context) {
107
+ this.registerConsoleLogListener(this.page, context);
108
+ // this.registerRequestListener(this.page, context, this.webLogFile);
109
+ if (!context.pageLoading) {
110
+ context.pageLoading = { status: false };
111
+ }
83
112
  context.playContext.on("page", async function (page) {
113
+ if (this.configuration && this.configuration.closePopups === true) {
114
+ console.log("close unexpected popups");
115
+ await page.close();
116
+ return;
117
+ }
84
118
  context.pageLoading.status = true;
85
119
  this.page = page;
86
120
  context.page = page;
87
121
  context.pages.push(page);
122
+ registerNetworkEvents(this.world, this, context, this.page);
123
+ registerDownloadEvent(this.page, this.world, context);
88
124
  page.on("close", async () => {
89
- if (this.context && this.context.pages && this.context.pages.length > 0) {
125
+ if (this.context && this.context.pages && this.context.pages.length > 1) {
90
126
  this.context.pages.pop();
91
127
  this.page = this.context.pages[this.context.pages.length - 1];
92
128
  this.context.page = this.page;
93
- let title = await this.page.title();
94
- console.log("Switched to page " + title);
129
+ try {
130
+ let title = await this.page.title();
131
+ console.log("Switched to page " + title);
132
+ }
133
+ catch (error) {
134
+ console.error("Error on page close", error);
135
+ }
95
136
  }
96
137
  });
97
138
  try {
@@ -104,145 +145,179 @@ class StableBrowser {
104
145
  context.pageLoading.status = false;
105
146
  }.bind(this));
106
147
  }
107
- getWebLogFile(logFolder) {
108
- if (!fs.existsSync(logFolder)) {
109
- fs.mkdirSync(logFolder, { recursive: true });
148
+ async switchApp(appName) {
149
+ // check if the current app (this.appName) is the same as the new app
150
+ if (this.appName === appName) {
151
+ return;
152
+ }
153
+ let navigate = false;
154
+ if (!apps[appName]) {
155
+ let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
156
+ newContextCreated = true;
157
+ apps[appName] = {
158
+ context: newContext,
159
+ browser: newContext.browser,
160
+ page: newContext.page,
161
+ };
110
162
  }
111
- let nextIndex = 1;
112
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
113
- nextIndex++;
163
+ const tempContext = {};
164
+ _copyContext(this, tempContext);
165
+ _copyContext(apps[appName], this);
166
+ apps[this.appName] = tempContext;
167
+ this.appName = appName;
168
+ if (navigate) {
169
+ await this.goto(this.context.environment.baseUrl);
170
+ await this.waitForPageLoad();
114
171
  }
115
- const fileName = nextIndex + ".json";
116
- return path.join(logFolder, fileName);
117
172
  }
118
- registerConsoleLogListener(page, context, logFile) {
173
+ registerConsoleLogListener(page, context) {
119
174
  if (!this.context.webLogger) {
120
175
  this.context.webLogger = [];
121
176
  }
122
177
  page.on("console", async (msg) => {
123
- this.context.webLogger.push({
178
+ const obj = {
124
179
  type: msg.type(),
125
180
  text: msg.text(),
126
181
  location: msg.location(),
127
182
  time: new Date().toISOString(),
128
- });
129
- await fs.promises.writeFile(logFile, JSON.stringify(this.context.webLogger, null, 2));
183
+ };
184
+ this.context.webLogger.push(obj);
185
+ if (msg.type() === "error") {
186
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
187
+ }
130
188
  });
131
189
  }
132
- registerRequestListener() {
133
- this.page.on("request", async (data) => {
190
+ registerRequestListener(page, context, logFile) {
191
+ if (!this.context.networkLogger) {
192
+ this.context.networkLogger = [];
193
+ }
194
+ page.on("request", async (data) => {
195
+ const startTime = new Date().getTime();
134
196
  try {
135
- const pageUrl = new URL(this.page.url());
197
+ const pageUrl = new URL(page.url());
136
198
  const requestUrl = new URL(data.url());
137
199
  if (pageUrl.hostname === requestUrl.hostname) {
138
200
  const method = data.method();
139
- if (method === "POST" || method === "GET" || method === "PUT" || method === "DELETE" || method === "PATCH") {
201
+ if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
140
202
  const token = await data.headerValue("Authorization");
141
203
  if (token) {
142
- this.context.authtoken = token;
204
+ context.authtoken = token;
143
205
  }
144
206
  }
145
207
  }
208
+ const response = await data.response();
209
+ const endTime = new Date().getTime();
210
+ const obj = {
211
+ url: data.url(),
212
+ method: data.method(),
213
+ postData: data.postData(),
214
+ error: data.failure() ? data.failure().errorText : null,
215
+ duration: endTime - startTime,
216
+ startTime,
217
+ };
218
+ context.networkLogger.push(obj);
219
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
146
220
  }
147
221
  catch (error) {
148
- console.error("Error in request listener", error);
222
+ // console.error("Error in request listener", error);
223
+ context.networkLogger.push({
224
+ error: "not able to listen",
225
+ message: error.message,
226
+ stack: error.stack,
227
+ time: new Date().toISOString(),
228
+ });
229
+ // await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
149
230
  }
150
231
  });
151
232
  }
152
233
  // async closeUnexpectedPopups() {
153
234
  // await closeUnexpectedPopups(this.page);
154
235
  // }
155
- async goto(url) {
236
+ async goto(url, world = null) {
156
237
  if (!url.startsWith("http")) {
157
238
  url = "https://" + url;
158
239
  }
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;
180
- }
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]);
240
+ const state = {
241
+ value: url,
242
+ world: world,
243
+ type: Types.NAVIGATE,
244
+ text: `Navigate Page to: ${url}`,
245
+ operation: "goto",
246
+ log: "***** navigate page to " + url + " *****\n",
247
+ info: {},
248
+ locate: false,
249
+ scroll: false,
250
+ screenshot: false,
251
+ highlight: false,
252
+ };
253
+ try {
254
+ await _preCommand(state, this);
255
+ await this.page.goto(url, {
256
+ timeout: 60000,
257
+ });
258
+ await _screenshot(state, this);
188
259
  }
189
- return text;
190
- }
191
- _fixLocatorUsingParams(locator, _params) {
192
- // check if not null
193
- if (!locator) {
194
- return locator;
260
+ catch (error) {
261
+ console.error("Error on goto", error);
262
+ _commandError(state, error, this);
195
263
  }
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
- }
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);
213
- }
264
+ finally {
265
+ _commandFinally(state, this);
214
266
  }
215
267
  }
216
268
  _getLocator(locator, scope, _params) {
217
- locator = this._fixLocatorUsingParams(locator, _params);
218
- if (locator.type === "pw_selector") {
219
- return scope.locator(locator.selector);
220
- }
269
+ locator = _fixLocatorUsingParams(locator, _params);
270
+ let locatorReturn;
221
271
  if (locator.role) {
222
272
  if (locator.role[1].nameReg) {
223
273
  locator.role[1].name = reg_parser(locator.role[1].nameReg);
224
274
  delete locator.role[1].nameReg;
225
275
  }
226
276
  // if (locator.role[1].name) {
227
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
277
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
228
278
  // }
229
- return scope.getByRole(locator.role[0], locator.role[1]);
279
+ locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
230
280
  }
231
281
  if (locator.css) {
232
- return scope.locator(locator.css);
282
+ locatorReturn = scope.locator(locator.css);
283
+ }
284
+ // handle role/name locators
285
+ // locator.selector will be something like: textbox[name="Username"i]
286
+ if (locator.engine === "internal:role") {
287
+ // extract the role, name and the i/s flags using regex
288
+ const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
289
+ if (match) {
290
+ const role = match[1];
291
+ const name = match[3];
292
+ const flags = match[4];
293
+ locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
294
+ }
233
295
  }
234
- if ((locator === null || locator === void 0 ? void 0 : locator.engine) && (locator === null || locator === void 0 ? void 0 : locator.score) <= 520) {
235
- let selector = locator.selector.replace(/"/g, '\\"');
236
- if (locator.engine === "internal:att") {
237
- selector = `[${selector}]`;
296
+ if (locator?.engine) {
297
+ if (locator.engine === "css") {
298
+ locatorReturn = scope.locator(locator.selector);
299
+ }
300
+ else {
301
+ let selector = locator.selector;
302
+ if (locator.engine === "internal:attr") {
303
+ if (!selector.startsWith("[")) {
304
+ selector = `[${selector}]`;
305
+ }
306
+ }
307
+ locatorReturn = scope.locator(`${locator.engine}=${selector}`);
238
308
  }
239
- const locator = scope.locator(`${locator.engine}="${selector}"`);
240
- return locator;
241
309
  }
242
- throw new Error("unknown locator type");
310
+ if (!locatorReturn) {
311
+ console.error(locator);
312
+ throw new Error("Locator undefined");
313
+ }
314
+ return locatorReturn;
243
315
  }
244
316
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
245
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
317
+ if (css && css.locator) {
318
+ css = css.locator;
319
+ }
320
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
246
321
  if (result.elementCount === 0) {
247
322
  return;
248
323
  }
@@ -255,125 +330,90 @@ class StableBrowser {
255
330
  let climbXpath = "xpath=" + climbArray.join("/");
256
331
  return textElementCss + " >> " + climbXpath + " >> " + css;
257
332
  }
258
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
333
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
259
334
  //const stringifyText = JSON.stringify(text);
260
- return await scope.evaluate(([text, tag, regex, partial]) => {
261
- function isParent(parent, child) {
262
- let currentNode = child.parentNode;
263
- while (currentNode !== null) {
264
- if (currentNode === parent) {
265
- return true;
266
- }
267
- currentNode = currentNode.parentNode;
268
- }
269
- return false;
270
- }
271
- document.isParent = isParent;
272
- function collectAllShadowDomElements(element, result = []) {
273
- // Check and add the element if it has a shadow root
274
- if (element.shadowRoot) {
275
- result.push(element);
276
- // Also search within the shadow root
277
- document.collectAllShadowDomElements(element.shadowRoot, result);
278
- }
279
- // Iterate over child nodes
280
- element.childNodes.forEach((child) => {
281
- // Recursively call the function for each child node
282
- document.collectAllShadowDomElements(child, result);
283
- });
284
- return result;
285
- }
286
- document.collectAllShadowDomElements = collectAllShadowDomElements;
287
- if (!tag) {
288
- tag = "*";
289
- }
290
- let elements = Array.from(document.querySelectorAll(tag));
291
- let shadowHosts = [];
292
- document.collectAllShadowDomElements(document, shadowHosts);
293
- for (let i = 0; i < shadowHosts.length; i++) {
294
- let shadowElement = shadowHosts[i].shadowRoot;
295
- if (!shadowElement) {
296
- console.log("shadowElement is null, for host " + shadowHosts[i]);
297
- continue;
298
- }
299
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
300
- elements = elements.concat(shadowElements);
301
- }
302
- let randomToken = null;
303
- const foundElements = [];
335
+ return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial, ignoreCase]) => {
336
+ const options = {
337
+ innerText: true,
338
+ ignoreCase: ignoreCase,
339
+ };
304
340
  if (regex) {
305
- let regexpSearch = new RegExp(text, "im");
306
- for (let i = 0; i < elements.length; i++) {
307
- const element = elements[i];
308
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
309
- (element.value && regexpSearch.test(element.value))) {
310
- foundElements.push(element);
311
- }
312
- }
341
+ options.singleRegex = true;
313
342
  }
314
- else {
315
- text = text.trim();
316
- for (let i = 0; i < elements.length; i++) {
317
- const element = elements[i];
318
- if (partial) {
319
- if ((element.innerText && element.innerText.trim().includes(text)) ||
320
- (element.value && element.value.includes(text))) {
321
- foundElements.push(element);
322
- }
323
- }
324
- else {
325
- if ((element.innerText && element.innerText.trim() === text) ||
326
- (element.value && element.value === text)) {
327
- foundElements.push(element);
328
- }
329
- }
330
- }
343
+ if (tag) {
344
+ options.tag = tag;
331
345
  }
332
- let noChildElements = [];
333
- for (let i = 0; i < foundElements.length; i++) {
334
- let element = foundElements[i];
335
- let hasChild = false;
336
- for (let j = 0; j < foundElements.length; j++) {
337
- if (i === j) {
338
- continue;
339
- }
340
- if (isParent(element, foundElements[j])) {
341
- hasChild = true;
342
- break;
343
- }
346
+ if (!(partial === true)) {
347
+ options.exactMatch = true;
348
+ }
349
+ if (text.startsWith("/") && text.endsWith("/")) {
350
+ if (text.length < 3) {
351
+ throw new Error("Invalid regex pattern: empty pattern");
352
+ }
353
+ try {
354
+ const pattern = text.slice(1, -1);
355
+ new RegExp(pattern); // Validate pattern
356
+ text = pattern;
357
+ options.singleRegex = true;
344
358
  }
345
- if (!hasChild) {
346
- noChildElements.push(element);
359
+ catch (e) {
360
+ throw new Error(`Invalid regex pattern: ${e.message}`);
347
361
  }
348
362
  }
363
+ const elements = window.findMatchingElements(text, options);
364
+ let randomToken = null;
365
+ const foundElements = [];
349
366
  let elementCount = 0;
350
- if (noChildElements.length > 0) {
351
- for (let i = 0; i < noChildElements.length; i++) {
367
+ if (elements.length > 0) {
368
+ for (let i = 0; i < elements.length; i++) {
352
369
  if (randomToken === null) {
353
370
  randomToken = Math.random().toString(36).substring(7);
354
371
  }
355
- let element = noChildElements[i];
372
+ let element = elements[i];
356
373
  element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
357
374
  elementCount++;
358
375
  }
359
376
  }
360
377
  return { elementCount: elementCount, randomToken: randomToken };
361
- }, [text1, tag1, regex1, partial1]);
378
+ }, [text1, tag1, regex1, partial1, ignoreCase]);
362
379
  }
363
380
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
381
+ if (!info) {
382
+ info = {};
383
+ }
384
+ if (!info.failCause) {
385
+ info.failCause = {};
386
+ }
387
+ if (!info.log) {
388
+ info.log = "";
389
+ info.locatorLog = new LocatorLog(selectorHierarchy);
390
+ }
364
391
  let locatorSearch = selectorHierarchy[index];
392
+ let originalLocatorSearch = "";
393
+ try {
394
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
395
+ locatorSearch = JSON.parse(originalLocatorSearch);
396
+ }
397
+ catch (e) {
398
+ console.error(e);
399
+ }
365
400
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
366
401
  let locator = null;
367
402
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
368
403
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
369
404
  if (!locatorString) {
405
+ info.failCause.textNotFound = true;
406
+ info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
370
407
  return;
371
408
  }
372
409
  locator = this._getLocator({ css: locatorString }, scope, _params);
373
410
  }
374
411
  else if (locatorSearch.text) {
375
- let result = await this._locateElementByText(scope, this._fixUsingParams(locatorSearch.text, _params), locatorSearch.tag, false, locatorSearch.partial === true, _params);
412
+ let text = _fixUsingParams(locatorSearch.text, _params);
413
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
376
414
  if (result.elementCount === 0) {
415
+ info.failCause.textNotFound = true;
416
+ info.failCause.lastError = "failed to locate element by text: " + text;
377
417
  return;
378
418
  }
379
419
  locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
@@ -390,13 +430,22 @@ class StableBrowser {
390
430
  // cssHref = true;
391
431
  // }
392
432
  let count = await locator.count();
433
+ if (count > 0 && !info.failCause.count) {
434
+ info.failCause.count = count;
435
+ }
393
436
  //info.log += "total elements found " + count + "\n";
394
437
  //let visibleCount = 0;
395
438
  let visibleLocator = null;
396
- if (locatorSearch.index && locatorSearch.index < count) {
439
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
397
440
  foundLocators.push(locator.nth(locatorSearch.index));
441
+ if (info.locatorLog) {
442
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
443
+ }
398
444
  return;
399
445
  }
446
+ if (info.locatorLog && count === 0) {
447
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
448
+ }
400
449
  for (let j = 0; j < count; j++) {
401
450
  let visible = await locator.nth(j).isVisible();
402
451
  const enabled = await locator.nth(j).isEnabled();
@@ -405,24 +454,40 @@ class StableBrowser {
405
454
  }
406
455
  if (visible && enabled) {
407
456
  foundLocators.push(locator.nth(j));
457
+ if (info.locatorLog) {
458
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
459
+ }
408
460
  }
409
461
  else {
462
+ info.failCause.visible = visible;
463
+ info.failCause.enabled = enabled;
410
464
  if (!info.printMessages) {
411
465
  info.printMessages = {};
412
466
  }
467
+ if (info.locatorLog && !visible) {
468
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
469
+ }
470
+ if (info.locatorLog && !enabled) {
471
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
472
+ }
413
473
  if (!info.printMessages[j.toString()]) {
414
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
474
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
415
475
  info.printMessages[j.toString()] = true;
416
476
  }
417
477
  }
418
478
  }
419
479
  }
420
480
  async closeUnexpectedPopups(info, _params) {
481
+ if (!info) {
482
+ info = {};
483
+ info.failCause = {};
484
+ info.log = "";
485
+ }
421
486
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
422
487
  if (!info) {
423
488
  info = {};
424
489
  }
425
- info.log += "scan for popup handlers" + "\n";
490
+ //info.log += "scan for popup handlers" + "\n";
426
491
  const handlerGroup = [];
427
492
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
428
493
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -449,16 +514,33 @@ class StableBrowser {
449
514
  }
450
515
  if (result.foundElements.length > 0) {
451
516
  let dialogCloseLocator = result.foundElements[0].locator;
452
- await dialogCloseLocator.click();
517
+ try {
518
+ await scope?.evaluate(() => {
519
+ window.__isClosingPopups = true;
520
+ });
521
+ await dialogCloseLocator.click();
522
+ // wait for the dialog to close
523
+ await dialogCloseLocator.waitFor({ state: "hidden" });
524
+ }
525
+ catch (e) {
526
+ }
527
+ finally {
528
+ await scope?.evaluate(() => {
529
+ window.__isClosingPopups = false;
530
+ });
531
+ }
453
532
  return { rerun: true };
454
533
  }
455
534
  }
456
535
  }
457
536
  return { rerun: false };
458
537
  }
459
- async _locate(selectors, info, _params, timeout = 30000) {
538
+ async _locate(selectors, info, _params, timeout) {
539
+ if (!timeout) {
540
+ timeout = 30000;
541
+ }
460
542
  for (let i = 0; i < 3; i++) {
461
- info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
543
+ info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
462
544
  for (let j = 0; j < selectors.locators.length; j++) {
463
545
  let selector = selectors.locators[j];
464
546
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
@@ -470,21 +552,57 @@ class StableBrowser {
470
552
  }
471
553
  throw new Error("unable to locate element " + JSON.stringify(selectors));
472
554
  }
473
- async _locate_internal(selectors, info, _params, timeout = 30000) {
474
- let highPriorityTimeout = 5000;
475
- let visibleOnlyTimeout = 6000;
476
- let startTime = performance.now();
477
- let locatorsCount = 0;
478
- //let arrayMode = Array.isArray(selectors);
555
+ async _findFrameScope(selectors, timeout = 30000, info) {
556
+ if (!info) {
557
+ info = {};
558
+ info.failCause = {};
559
+ info.log = "";
560
+ }
561
+ let startTime = Date.now();
479
562
  let scope = this.page;
563
+ if (selectors.frame) {
564
+ return selectors.frame;
565
+ }
480
566
  if (selectors.iframe_src || selectors.frameLocators) {
481
- info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
567
+ const findFrame = async (frame, framescope) => {
568
+ for (let i = 0; i < frame.selectors.length; i++) {
569
+ let frameLocator = frame.selectors[i];
570
+ if (frameLocator.css) {
571
+ let testframescope = framescope.frameLocator(frameLocator.css);
572
+ if (frameLocator.index) {
573
+ testframescope = framescope.nth(frameLocator.index);
574
+ }
575
+ try {
576
+ await testframescope.owner().evaluateHandle(() => true, null, {
577
+ timeout: 5000,
578
+ });
579
+ framescope = testframescope;
580
+ break;
581
+ }
582
+ catch (error) {
583
+ console.error("frame not found " + frameLocator.css);
584
+ }
585
+ }
586
+ }
587
+ if (frame.children) {
588
+ return await findFrame(frame.children, framescope);
589
+ }
590
+ return framescope;
591
+ };
592
+ let fLocator = null;
482
593
  while (true) {
483
594
  let frameFound = false;
595
+ if (selectors.nestFrmLoc) {
596
+ fLocator = selectors.nestFrmLoc;
597
+ scope = await findFrame(selectors.nestFrmLoc, scope);
598
+ frameFound = true;
599
+ break;
600
+ }
484
601
  if (selectors.frameLocators) {
485
602
  for (let i = 0; i < selectors.frameLocators.length; i++) {
486
603
  let frameLocator = selectors.frameLocators[i];
487
604
  if (frameLocator.css) {
605
+ fLocator = frameLocator.css;
488
606
  scope = scope.frameLocator(frameLocator.css);
489
607
  frameFound = true;
490
608
  break;
@@ -492,20 +610,55 @@ class StableBrowser {
492
610
  }
493
611
  }
494
612
  if (!frameFound && selectors.iframe_src) {
613
+ fLocator = selectors.iframe_src;
495
614
  scope = this.page.frame({ url: selectors.iframe_src });
496
615
  }
497
616
  if (!scope) {
498
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
499
- if (performance.now() - startTime > timeout) {
617
+ if (info && info.locatorLog) {
618
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
619
+ }
620
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
621
+ if (Date.now() - startTime > timeout) {
622
+ info.failCause.iframeNotFound = true;
623
+ info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
500
624
  throw new Error("unable to locate iframe " + selectors.iframe_src);
501
625
  }
502
626
  await new Promise((resolve) => setTimeout(resolve, 1000));
503
627
  }
504
628
  else {
629
+ if (info && info.locatorLog) {
630
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
631
+ }
505
632
  break;
506
633
  }
507
634
  }
508
635
  }
636
+ if (!scope) {
637
+ scope = this.page;
638
+ }
639
+ return scope;
640
+ }
641
+ async _getDocumentBody(selectors, timeout = 30000, info) {
642
+ let scope = await this._findFrameScope(selectors, timeout, info);
643
+ return scope.evaluate(() => {
644
+ var bodyContent = document.body.innerHTML;
645
+ return bodyContent;
646
+ });
647
+ }
648
+ async _locate_internal(selectors, info, _params, timeout = 30000) {
649
+ if (!info) {
650
+ info = {};
651
+ info.failCause = {};
652
+ info.log = "";
653
+ info.locatorLog = new LocatorLog(selectors);
654
+ }
655
+ let highPriorityTimeout = 5000;
656
+ let visibleOnlyTimeout = 6000;
657
+ let startTime = Date.now();
658
+ let locatorsCount = 0;
659
+ let lazy_scroll = false;
660
+ //let arrayMode = Array.isArray(selectors);
661
+ let scope = await this._findFrameScope(selectors, timeout, info);
509
662
  let selectorsLocators = null;
510
663
  selectorsLocators = selectors.locators;
511
664
  // group selectors by priority
@@ -592,21 +745,33 @@ class StableBrowser {
592
745
  return maxCountElement.locator;
593
746
  }
594
747
  }
595
- if (performance.now() - startTime > timeout) {
748
+ if (Date.now() - startTime > timeout) {
596
749
  break;
597
750
  }
598
- if (performance.now() - startTime > highPriorityTimeout) {
751
+ if (Date.now() - startTime > highPriorityTimeout) {
599
752
  info.log += "high priority timeout, will try all elements" + "\n";
600
753
  highPriorityOnly = false;
754
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
755
+ lazy_scroll = true;
756
+ await scrollPageToLoadLazyElements(this.page);
757
+ }
601
758
  }
602
- if (performance.now() - startTime > visibleOnlyTimeout) {
759
+ if (Date.now() - startTime > visibleOnlyTimeout) {
603
760
  info.log += "visible only timeout, will try all elements" + "\n";
604
761
  visibleOnly = false;
605
762
  }
606
763
  await new Promise((resolve) => setTimeout(resolve, 1000));
607
764
  }
608
765
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
609
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
766
+ // if (info.locatorLog) {
767
+ // const lines = info.locatorLog.toString().split("\n");
768
+ // for (let line of lines) {
769
+ // this.logger.debug(line);
770
+ // }
771
+ // }
772
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
773
+ info.failCause.locatorNotFound = true;
774
+ info.failCause.lastError = "failed to locate unique element";
610
775
  throw new Error("failed to locate first element no elements found, " + info.log);
611
776
  }
612
777
  async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
@@ -620,8 +785,9 @@ class StableBrowser {
620
785
  await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
621
786
  }
622
787
  catch (e) {
623
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
624
- this.logger.debug(e);
788
+ // this call can fail it the browser is navigating
789
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
790
+ // this.logger.debug(e);
625
791
  foundLocators = [];
626
792
  try {
627
793
  await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
@@ -638,86 +804,168 @@ class StableBrowser {
638
804
  });
639
805
  result.locatorIndex = i;
640
806
  }
807
+ if (foundLocators.length > 1) {
808
+ info.failCause.foundMultiple = true;
809
+ if (info.locatorLog) {
810
+ info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
811
+ }
812
+ }
641
813
  }
642
814
  return result;
643
815
  }
644
- async click(selectors, _params, options = {}, world = null) {
645
- this._validateSelectors(selectors);
816
+ async simpleClick(elementDescription, _params, options = {}, world = null) {
817
+ const state = {
818
+ locate: false,
819
+ scroll: false,
820
+ highlight: false,
821
+ _params,
822
+ options,
823
+ world,
824
+ type: Types.CLICK,
825
+ text: "Click element",
826
+ operation: "simpleClick",
827
+ log: "***** click on " + elementDescription + " *****\n",
828
+ };
829
+ _preCommand(state, this);
646
830
  const startTime = Date.now();
647
- const info = {};
648
- info.log = "***** click on " + selectors.element_name + " *****\n";
649
- info.operation = "click";
650
- info.selectors = selectors;
651
- let error = null;
652
- let screenshotId = null;
653
- let screenshotPath = null;
831
+ let timeout = 30000;
832
+ if (options && options.timeout) {
833
+ timeout = options.timeout;
834
+ }
835
+ while (true) {
836
+ try {
837
+ const result = await locate_element(this.context, elementDescription, "click");
838
+ if (result?.elementNumber >= 0) {
839
+ const selectors = {
840
+ frame: result?.frame,
841
+ locators: [
842
+ {
843
+ css: result?.css,
844
+ },
845
+ ],
846
+ };
847
+ await this.click(selectors, _params, options, world);
848
+ return;
849
+ }
850
+ }
851
+ catch (e) {
852
+ if (performance.now() - startTime > timeout) {
853
+ // throw e;
854
+ try {
855
+ await _commandError(state, "timeout looking for " + elementDescription, this);
856
+ }
857
+ finally {
858
+ _commandFinally(state, this);
859
+ }
860
+ }
861
+ }
862
+ await new Promise((resolve) => setTimeout(resolve, 3000));
863
+ }
864
+ }
865
+ async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
866
+ const state = {
867
+ locate: false,
868
+ scroll: false,
869
+ highlight: false,
870
+ _params,
871
+ options,
872
+ world,
873
+ type: Types.FILL,
874
+ text: "Fill element",
875
+ operation: "simpleClickType",
876
+ log: "***** click type on " + elementDescription + " *****\n",
877
+ };
878
+ _preCommand(state, this);
879
+ const startTime = Date.now();
880
+ let timeout = 30000;
881
+ if (options && options.timeout) {
882
+ timeout = options.timeout;
883
+ }
884
+ while (true) {
885
+ try {
886
+ const result = await locate_element(this.context, elementDescription, "fill", value);
887
+ if (result?.elementNumber >= 0) {
888
+ const selectors = {
889
+ frame: result?.frame,
890
+ locators: [
891
+ {
892
+ css: result?.css,
893
+ },
894
+ ],
895
+ };
896
+ await this.clickType(selectors, value, false, _params, options, world);
897
+ return;
898
+ }
899
+ }
900
+ catch (e) {
901
+ if (performance.now() - startTime > timeout) {
902
+ // throw e;
903
+ try {
904
+ await _commandError(state, "timeout looking for " + elementDescription, this);
905
+ }
906
+ finally {
907
+ _commandFinally(state, this);
908
+ }
909
+ }
910
+ }
911
+ await new Promise((resolve) => setTimeout(resolve, 3000));
912
+ }
913
+ }
914
+ async click(selectors, _params, options = {}, world = null) {
915
+ const state = {
916
+ selectors,
917
+ _params,
918
+ options,
919
+ world,
920
+ text: "Click element",
921
+ type: Types.CLICK,
922
+ operation: "click",
923
+ log: "***** click on " + selectors.element_name + " *****\n",
924
+ };
654
925
  try {
655
- let element = await this._locate(selectors, info, _params);
656
- await this.scrollIfNeeded(element, info);
657
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
926
+ await _preCommand(state, this);
927
+ if (state.options && state.options.context) {
928
+ state.selectors.locators[0].text = state.options.context;
929
+ }
658
930
  try {
659
- await this._highlightElements(element);
660
- await element.click({ timeout: 5000 });
661
- await new Promise((resolve) => setTimeout(resolve, 1000));
931
+ await state.element.click();
932
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
662
933
  }
663
934
  catch (e) {
664
935
  // await this.closeUnexpectedPopups();
665
- info.log += "click failed, will try again" + "\n";
666
- element = await this._locate(selectors, info, _params);
667
- await element.click({ timeout: 10000, force: true });
668
- await new Promise((resolve) => setTimeout(resolve, 1000));
936
+ state.element = await this._locate(selectors, state.info, _params);
937
+ await state.element.dispatchEvent("click");
938
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
669
939
  }
670
940
  await this.waitForPageLoad();
671
- return info;
941
+ return state.info;
672
942
  }
673
943
  catch (e) {
674
- this.logger.error("click failed " + info.log);
675
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
676
- info.screenshotPath = screenshotPath;
677
- Object.assign(e, { info: info });
678
- error = e;
679
- throw e;
944
+ await _commandError(state, e, this);
680
945
  }
681
946
  finally {
682
- const endTime = Date.now();
683
- this._reportToWorld(world, {
684
- element_name: selectors.element_name,
685
- type: Types.CLICK,
686
- text: `Click element`,
687
- screenshotId,
688
- result: error
689
- ? {
690
- status: "FAILED",
691
- startTime,
692
- endTime,
693
- message: error === null || error === void 0 ? void 0 : error.message,
694
- }
695
- : {
696
- status: "PASSED",
697
- startTime,
698
- endTime,
699
- },
700
- info: info,
701
- });
947
+ _commandFinally(state, this);
702
948
  }
703
949
  }
704
950
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
705
- this._validateSelectors(selectors);
706
- const startTime = Date.now();
707
- const info = {};
708
- info.log = "";
709
- info.operation = "setCheck";
710
- info.checked = checked;
711
- info.selectors = selectors;
712
- let error = null;
713
- let screenshotId = null;
714
- let screenshotPath = null;
951
+ const state = {
952
+ selectors,
953
+ _params,
954
+ options,
955
+ world,
956
+ type: checked ? Types.CHECK : Types.UNCHECK,
957
+ text: checked ? `Check element` : `Uncheck element`,
958
+ operation: "setCheck",
959
+ log: "***** check " + selectors.element_name + " *****\n",
960
+ };
715
961
  try {
716
- let element = await this._locate(selectors, info, _params);
717
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
962
+ await _preCommand(state, this);
963
+ state.info.checked = checked;
964
+ // let element = await this._locate(selectors, info, _params);
965
+ // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
718
966
  try {
719
- await this._highlightElements(element);
720
- await element.setChecked(checked, { timeout: 5000 });
967
+ // await this._highlightElements(element);
968
+ await state.element.setChecked(checked);
721
969
  await new Promise((resolve) => setTimeout(resolve, 1000));
722
970
  }
723
971
  catch (e) {
@@ -726,179 +974,108 @@ class StableBrowser {
726
974
  }
727
975
  else {
728
976
  //await this.closeUnexpectedPopups();
729
- info.log += "setCheck failed, will try again" + "\n";
730
- element = await this._locate(selectors, info, _params);
731
- await element.setChecked(checked, { timeout: 5000, force: true });
977
+ state.info.log += "setCheck failed, will try again" + "\n";
978
+ state.element = await this._locate(selectors, state.info, _params);
979
+ await state.element.setChecked(checked, { timeout: 5000, force: true });
732
980
  await new Promise((resolve) => setTimeout(resolve, 1000));
733
981
  }
734
982
  }
735
983
  await this.waitForPageLoad();
736
- return info;
984
+ return state.info;
737
985
  }
738
986
  catch (e) {
739
- this.logger.error("setCheck failed " + info.log);
740
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
741
- info.screenshotPath = screenshotPath;
742
- Object.assign(e, { info: info });
743
- error = e;
744
- throw e;
987
+ await _commandError(state, e, this);
745
988
  }
746
989
  finally {
747
- const endTime = Date.now();
748
- this._reportToWorld(world, {
749
- element_name: selectors.element_name,
750
- type: checked ? Types.CHECK : Types.UNCHECK,
751
- text: checked ? `Check element` : `Uncheck element`,
752
- screenshotId,
753
- result: error
754
- ? {
755
- status: "FAILED",
756
- startTime,
757
- endTime,
758
- message: error === null || error === void 0 ? void 0 : error.message,
759
- }
760
- : {
761
- status: "PASSED",
762
- startTime,
763
- endTime,
764
- },
765
- info: info,
766
- });
990
+ _commandFinally(state, this);
767
991
  }
768
992
  }
769
993
  async hover(selectors, _params, options = {}, world = null) {
770
- this._validateSelectors(selectors);
771
- const startTime = Date.now();
772
- const info = {};
773
- info.log = "";
774
- info.operation = "hover";
775
- info.selectors = selectors;
776
- let error = null;
777
- let screenshotId = null;
778
- let screenshotPath = null;
994
+ const state = {
995
+ selectors,
996
+ _params,
997
+ options,
998
+ world,
999
+ type: Types.HOVER,
1000
+ text: `Hover element`,
1001
+ operation: "hover",
1002
+ log: "***** hover " + selectors.element_name + " *****\n",
1003
+ };
779
1004
  try {
780
- let element = await this._locate(selectors, info, _params);
781
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1005
+ await _preCommand(state, this);
782
1006
  try {
783
- await this._highlightElements(element);
784
- await element.hover({ timeout: 10000 });
1007
+ await state.element.hover();
785
1008
  await new Promise((resolve) => setTimeout(resolve, 1000));
786
1009
  }
787
1010
  catch (e) {
788
1011
  //await this.closeUnexpectedPopups();
789
- info.log += "hover failed, will try again" + "\n";
790
- element = await this._locate(selectors, info, _params);
791
- await element.hover({ timeout: 10000 });
1012
+ state.info.log += "hover failed, will try again" + "\n";
1013
+ state.element = await this._locate(selectors, state.info, _params);
1014
+ await state.element.hover({ timeout: 10000 });
792
1015
  await new Promise((resolve) => setTimeout(resolve, 1000));
793
1016
  }
794
1017
  await this.waitForPageLoad();
795
- return info;
1018
+ return state.info;
796
1019
  }
797
1020
  catch (e) {
798
- this.logger.error("hover failed " + info.log);
799
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
800
- info.screenshotPath = screenshotPath;
801
- Object.assign(e, { info: info });
802
- error = e;
803
- throw e;
1021
+ await _commandError(state, e, this);
804
1022
  }
805
1023
  finally {
806
- const endTime = Date.now();
807
- this._reportToWorld(world, {
808
- element_name: selectors.element_name,
809
- type: Types.HOVER,
810
- text: `Hover element`,
811
- screenshotId,
812
- result: error
813
- ? {
814
- status: "FAILED",
815
- startTime,
816
- endTime,
817
- message: error === null || error === void 0 ? void 0 : error.message,
818
- }
819
- : {
820
- status: "PASSED",
821
- startTime,
822
- endTime,
823
- },
824
- info: info,
825
- });
1024
+ _commandFinally(state, this);
826
1025
  }
827
1026
  }
828
1027
  async selectOption(selectors, values, _params = null, options = {}, world = null) {
829
- this._validateSelectors(selectors);
830
1028
  if (!values) {
831
1029
  throw new Error("values is null");
832
1030
  }
833
- const startTime = Date.now();
834
- let error = null;
835
- let screenshotId = null;
836
- let screenshotPath = null;
837
- const info = {};
838
- info.log = "";
839
- info.operation = "selectOptions";
840
- info.selectors = selectors;
1031
+ const state = {
1032
+ selectors,
1033
+ _params,
1034
+ options,
1035
+ world,
1036
+ value: values.toString(),
1037
+ type: Types.SELECT,
1038
+ text: `Select option: ${values}`,
1039
+ operation: "selectOption",
1040
+ log: "***** select option " + selectors.element_name + " *****\n",
1041
+ };
841
1042
  try {
842
- let element = await this._locate(selectors, info, _params);
843
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1043
+ await _preCommand(state, this);
844
1044
  try {
845
- await this._highlightElements(element);
846
- await element.selectOption(values, { timeout: 5000 });
1045
+ await state.element.selectOption(values);
847
1046
  }
848
1047
  catch (e) {
849
1048
  //await this.closeUnexpectedPopups();
850
- info.log += "selectOption failed, will try force" + "\n";
851
- await element.selectOption(values, { timeout: 10000, force: true });
1049
+ state.info.log += "selectOption failed, will try force" + "\n";
1050
+ await state.element.selectOption(values, { timeout: 10000, force: true });
852
1051
  }
853
1052
  await this.waitForPageLoad();
854
- return info;
1053
+ return state.info;
855
1054
  }
856
1055
  catch (e) {
857
- this.logger.error("selectOption failed " + info.log);
858
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
859
- info.screenshotPath = screenshotPath;
860
- Object.assign(e, { info: info });
861
- this.logger.info("click failed, will try next selector");
862
- error = e;
863
- throw e;
1056
+ await _commandError(state, e, this);
864
1057
  }
865
1058
  finally {
866
- const endTime = Date.now();
867
- this._reportToWorld(world, {
868
- element_name: selectors.element_name,
869
- type: Types.SELECT,
870
- text: `Select option: ${values}`,
871
- value: values.toString(),
872
- screenshotId,
873
- result: error
874
- ? {
875
- status: "FAILED",
876
- startTime,
877
- endTime,
878
- message: error === null || error === void 0 ? void 0 : error.message,
879
- }
880
- : {
881
- status: "PASSED",
882
- startTime,
883
- endTime,
884
- },
885
- info: info,
886
- });
1059
+ _commandFinally(state, this);
887
1060
  }
888
1061
  }
889
1062
  async type(_value, _params = null, options = {}, world = null) {
890
- const startTime = Date.now();
891
- let error = null;
892
- let screenshotId = null;
893
- let screenshotPath = null;
894
- const info = {};
895
- info.log = "";
896
- info.operation = "type";
897
- _value = this._fixUsingParams(_value, _params);
898
- info.value = _value;
1063
+ const state = {
1064
+ value: _value,
1065
+ _params,
1066
+ options,
1067
+ world,
1068
+ locate: false,
1069
+ scroll: false,
1070
+ highlight: false,
1071
+ type: Types.TYPE_PRESS,
1072
+ text: `Type value: ${_value}`,
1073
+ operation: "type",
1074
+ log: "",
1075
+ };
899
1076
  try {
900
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
901
- const valueSegment = _value.split("&&");
1077
+ await _preCommand(state, this);
1078
+ const valueSegment = state.value.split("&&");
902
1079
  for (let i = 0; i < valueSegment.length; i++) {
903
1080
  if (i > 0) {
904
1081
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -918,134 +1095,76 @@ class StableBrowser {
918
1095
  await this.page.keyboard.type(value);
919
1096
  }
920
1097
  }
921
- return info;
1098
+ return state.info;
922
1099
  }
923
1100
  catch (e) {
924
- //await this.closeUnexpectedPopups();
925
- this.logger.error("type failed " + info.log);
926
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
927
- info.screenshotPath = screenshotPath;
928
- Object.assign(e, { info: info });
929
- error = e;
930
- throw e;
1101
+ await _commandError(state, e, this);
931
1102
  }
932
1103
  finally {
933
- const endTime = Date.now();
934
- this._reportToWorld(world, {
935
- type: Types.TYPE_PRESS,
936
- screenshotId,
937
- value: _value,
938
- text: `type value: ${_value}`,
939
- result: error
940
- ? {
941
- status: "FAILED",
942
- startTime,
943
- endTime,
944
- message: error === null || error === void 0 ? void 0 : error.message,
945
- }
946
- : {
947
- status: "PASSED",
948
- startTime,
949
- endTime,
950
- },
951
- info: info,
952
- });
1104
+ _commandFinally(state, this);
953
1105
  }
954
1106
  }
955
1107
  async setInputValue(selectors, value, _params = null, options = {}, world = null) {
956
- // set input value for non fillable inputs like date, time, range, color, etc.
957
- this._validateSelectors(selectors);
958
- const startTime = Date.now();
959
- const info = {};
960
- info.log = "***** set input value " + selectors.element_name + " *****\n";
961
- info.operation = "setInputValue";
962
- info.selectors = selectors;
963
- value = this._fixUsingParams(value, _params);
964
- info.value = value;
965
- let error = null;
966
- let screenshotId = null;
967
- let screenshotPath = null;
1108
+ const state = {
1109
+ selectors,
1110
+ _params,
1111
+ value,
1112
+ options,
1113
+ world,
1114
+ type: Types.SET_INPUT,
1115
+ text: `Set input value`,
1116
+ operation: "setInputValue",
1117
+ log: "***** set input value " + selectors.element_name + " *****\n",
1118
+ };
968
1119
  try {
969
- value = await this._replaceWithLocalData(value, this);
970
- let element = await this._locate(selectors, info, _params);
971
- await this.scrollIfNeeded(element, info);
972
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
973
- await this._highlightElements(element);
1120
+ await _preCommand(state, this);
1121
+ let value = await this._replaceWithLocalData(state.value, this);
974
1122
  try {
975
- await element.evaluateHandle((el, value) => {
1123
+ await state.element.evaluateHandle((el, value) => {
976
1124
  el.value = value;
977
1125
  }, value);
978
1126
  }
979
1127
  catch (error) {
980
1128
  this.logger.error("setInputValue failed, will try again");
981
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
982
- info.screenshotPath = screenshotPath;
983
- Object.assign(error, { info: info });
984
- await element.evaluateHandle((el, value) => {
1129
+ await _screenshot(state, this);
1130
+ Object.assign(error, { info: state.info });
1131
+ await state.element.evaluateHandle((el, value) => {
985
1132
  el.value = value;
986
1133
  });
987
1134
  }
988
1135
  }
989
1136
  catch (e) {
990
- this.logger.error("setInputValue failed " + info.log);
991
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
992
- info.screenshotPath = screenshotPath;
993
- Object.assign(e, { info: info });
994
- error = e;
995
- throw e;
1137
+ await _commandError(state, e, this);
996
1138
  }
997
1139
  finally {
998
- const endTime = Date.now();
999
- this._reportToWorld(world, {
1000
- element_name: selectors.element_name,
1001
- type: Types.SET_INPUT,
1002
- text: `Set input value`,
1003
- value: value,
1004
- screenshotId,
1005
- result: error
1006
- ? {
1007
- status: "FAILED",
1008
- startTime,
1009
- endTime,
1010
- message: error === null || error === void 0 ? void 0 : error.message,
1011
- }
1012
- : {
1013
- status: "PASSED",
1014
- startTime,
1015
- endTime,
1016
- },
1017
- info: info,
1018
- });
1140
+ _commandFinally(state, this);
1019
1141
  }
1020
1142
  }
1021
1143
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1022
- this._validateSelectors(selectors);
1023
- const startTime = Date.now();
1024
- let error = null;
1025
- let screenshotId = null;
1026
- let screenshotPath = null;
1027
- const info = {};
1028
- info.log = "";
1029
- info.operation = Types.SET_DATE_TIME;
1030
- info.selectors = selectors;
1031
- info.value = value;
1144
+ const state = {
1145
+ selectors,
1146
+ _params,
1147
+ value: await this._replaceWithLocalData(value, this),
1148
+ options,
1149
+ world,
1150
+ type: Types.SET_DATE_TIME,
1151
+ text: `Set date time value: ${value}`,
1152
+ operation: "setDateTime",
1153
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1154
+ throwError: false,
1155
+ };
1032
1156
  try {
1033
- value = await this._replaceWithLocalData(value, this);
1034
- let element = await this._locate(selectors, info, _params);
1035
- //insert red border around the element
1036
- await this.scrollIfNeeded(element, info);
1037
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1038
- await this._highlightElements(element);
1157
+ await _preCommand(state, this);
1039
1158
  try {
1040
- await element.click();
1159
+ await state.element.click();
1041
1160
  await new Promise((resolve) => setTimeout(resolve, 500));
1042
1161
  if (format) {
1043
- value = dayjs(value).format(format);
1044
- await element.fill(value);
1162
+ state.value = dayjs(state.value).format(format);
1163
+ await state.element.fill(state.value);
1045
1164
  }
1046
1165
  else {
1047
- const dateTimeValue = await getDateTimeValue({ value, element });
1048
- await element.evaluateHandle((el, dateTimeValue) => {
1166
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1167
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1049
1168
  el.value = ""; // clear input
1050
1169
  el.value = dateTimeValue;
1051
1170
  }, dateTimeValue);
@@ -1058,20 +1177,19 @@ class StableBrowser {
1058
1177
  }
1059
1178
  catch (err) {
1060
1179
  //await this.closeUnexpectedPopups();
1061
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1180
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1062
1181
  this.logger.info("Trying again");
1063
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1064
- info.screenshotPath = screenshotPath;
1065
- Object.assign(err, { info: info });
1182
+ await _screenshot(state, this);
1183
+ Object.assign(err, { info: state.info });
1066
1184
  await element.click();
1067
1185
  await new Promise((resolve) => setTimeout(resolve, 500));
1068
1186
  if (format) {
1069
- value = dayjs(value).format(format);
1070
- await element.fill(value);
1187
+ state.value = dayjs(state.value).format(format);
1188
+ await state.element.fill(state.value);
1071
1189
  }
1072
1190
  else {
1073
- const dateTimeValue = await getDateTimeValue({ value, element });
1074
- await element.evaluateHandle((el, dateTimeValue) => {
1191
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1192
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1075
1193
  el.value = ""; // clear input
1076
1194
  el.value = dateTimeValue;
1077
1195
  }, dateTimeValue);
@@ -1084,60 +1202,39 @@ class StableBrowser {
1084
1202
  }
1085
1203
  }
1086
1204
  catch (e) {
1087
- error = e;
1088
- throw e;
1205
+ await _commandError(state, e, this);
1089
1206
  }
1090
1207
  finally {
1091
- const endTime = Date.now();
1092
- this._reportToWorld(world, {
1093
- element_name: selectors.element_name,
1094
- type: Types.SET_DATE_TIME,
1095
- screenshotId,
1096
- value: value,
1097
- text: `setDateTime input with value: ${value}`,
1098
- result: error
1099
- ? {
1100
- status: "FAILED",
1101
- startTime,
1102
- endTime,
1103
- message: error === null || error === void 0 ? void 0 : error.message,
1104
- }
1105
- : {
1106
- status: "PASSED",
1107
- startTime,
1108
- endTime,
1109
- },
1110
- info: info,
1111
- });
1208
+ _commandFinally(state, this);
1112
1209
  }
1113
1210
  }
1114
1211
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1115
- this._validateSelectors(selectors);
1116
- const startTime = Date.now();
1117
- let error = null;
1118
- let screenshotId = null;
1119
- let screenshotPath = null;
1120
- const info = {};
1121
- info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
1122
- info.operation = "clickType";
1123
- info.selectors = selectors;
1212
+ _value = unEscapeString(_value);
1124
1213
  const newValue = await this._replaceWithLocalData(_value, world);
1214
+ const state = {
1215
+ selectors,
1216
+ _params,
1217
+ value: newValue,
1218
+ originalValue: _value,
1219
+ options,
1220
+ world,
1221
+ type: Types.FILL,
1222
+ text: `Click type input with value: ${_value}`,
1223
+ operation: "clickType",
1224
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1225
+ };
1125
1226
  if (newValue !== _value) {
1126
1227
  //this.logger.info(_value + "=" + newValue);
1127
1228
  _value = newValue;
1128
1229
  }
1129
- info.value = _value;
1130
1230
  try {
1131
- let element = await this._locate(selectors, info, _params);
1132
- //insert red border around the element
1133
- await this.scrollIfNeeded(element, info);
1134
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1135
- await this._highlightElements(element);
1231
+ await _preCommand(state, this);
1232
+ state.info.value = _value;
1136
1233
  if (options === null || options === undefined || !options.press) {
1137
1234
  try {
1138
- let currentValue = await element.inputValue();
1235
+ let currentValue = await state.element.inputValue();
1139
1236
  if (currentValue) {
1140
- await element.fill("");
1237
+ await state.element.fill("");
1141
1238
  }
1142
1239
  }
1143
1240
  catch (e) {
@@ -1146,22 +1243,22 @@ class StableBrowser {
1146
1243
  }
1147
1244
  if (options === null || options === undefined || options.press) {
1148
1245
  try {
1149
- await element.click({ timeout: 5000 });
1246
+ await state.element.click({ timeout: 5000 });
1150
1247
  }
1151
1248
  catch (e) {
1152
- await element.dispatchEvent("click");
1249
+ await state.element.dispatchEvent("click");
1153
1250
  }
1154
1251
  }
1155
1252
  else {
1156
1253
  try {
1157
- await element.focus();
1254
+ await state.element.focus();
1158
1255
  }
1159
1256
  catch (e) {
1160
- await element.dispatchEvent("focus");
1257
+ await state.element.dispatchEvent("focus");
1161
1258
  }
1162
1259
  }
1163
1260
  await new Promise((resolve) => setTimeout(resolve, 500));
1164
- const valueSegment = _value.split("&&");
1261
+ const valueSegment = state.value.split("&&");
1165
1262
  for (let i = 0; i < valueSegment.length; i++) {
1166
1263
  if (i > 0) {
1167
1264
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1181,13 +1278,14 @@ class StableBrowser {
1181
1278
  await new Promise((resolve) => setTimeout(resolve, 500));
1182
1279
  }
1183
1280
  }
1281
+ await _screenshot(state, this);
1184
1282
  if (enter === true) {
1185
1283
  await new Promise((resolve) => setTimeout(resolve, 2000));
1186
1284
  await this.page.keyboard.press("Enter");
1187
1285
  await this.waitForPageLoad();
1188
1286
  }
1189
1287
  else if (enter === false) {
1190
- await element.dispatchEvent("change");
1288
+ await state.element.dispatchEvent("change");
1191
1289
  //await this.page.keyboard.press("Tab");
1192
1290
  }
1193
1291
  else {
@@ -1196,107 +1294,55 @@ class StableBrowser {
1196
1294
  await this.waitForPageLoad();
1197
1295
  }
1198
1296
  }
1199
- return info;
1297
+ return state.info;
1200
1298
  }
1201
1299
  catch (e) {
1202
- //await this.closeUnexpectedPopups();
1203
- this.logger.error("fill failed " + JSON.stringify(info));
1204
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1205
- info.screenshotPath = screenshotPath;
1206
- Object.assign(e, { info: info });
1207
- error = e;
1208
- throw e;
1300
+ await _commandError(state, e, this);
1209
1301
  }
1210
1302
  finally {
1211
- const endTime = Date.now();
1212
- this._reportToWorld(world, {
1213
- element_name: selectors.element_name,
1214
- type: Types.FILL,
1215
- screenshotId,
1216
- value: _value,
1217
- text: `clickType input with value: ${_value}`,
1218
- result: error
1219
- ? {
1220
- status: "FAILED",
1221
- startTime,
1222
- endTime,
1223
- message: error === null || error === void 0 ? void 0 : error.message,
1224
- }
1225
- : {
1226
- status: "PASSED",
1227
- startTime,
1228
- endTime,
1229
- },
1230
- info: info,
1231
- });
1303
+ _commandFinally(state, this);
1232
1304
  }
1233
1305
  }
1234
1306
  async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
1235
- this._validateSelectors(selectors);
1236
- const startTime = Date.now();
1237
- let error = null;
1238
- let screenshotId = null;
1239
- let screenshotPath = null;
1240
- const info = {};
1241
- info.log = "***** fill on " + selectors.element_name + " with value " + value + "*****\n";
1242
- info.operation = "fill";
1243
- info.selectors = selectors;
1244
- info.value = value;
1307
+ const state = {
1308
+ selectors,
1309
+ _params,
1310
+ value: unEscapeString(value),
1311
+ options,
1312
+ world,
1313
+ type: Types.FILL,
1314
+ text: `Fill input with value: ${value}`,
1315
+ operation: "fill",
1316
+ log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
1317
+ };
1245
1318
  try {
1246
- let element = await this._locate(selectors, info, _params);
1247
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1248
- await this._highlightElements(element);
1249
- await element.fill(value, { timeout: 10000 });
1250
- await element.dispatchEvent("change");
1319
+ await _preCommand(state, this);
1320
+ await state.element.fill(value);
1321
+ await state.element.dispatchEvent("change");
1251
1322
  if (enter) {
1252
1323
  await new Promise((resolve) => setTimeout(resolve, 2000));
1253
1324
  await this.page.keyboard.press("Enter");
1254
1325
  }
1255
1326
  await this.waitForPageLoad();
1256
- return info;
1327
+ return state.info;
1257
1328
  }
1258
1329
  catch (e) {
1259
- //await this.closeUnexpectedPopups();
1260
- this.logger.error("fill failed " + info.log);
1261
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1262
- info.screenshotPath = screenshotPath;
1263
- Object.assign(e, { info: info });
1264
- error = e;
1265
- throw e;
1330
+ await _commandError(state, e, this);
1266
1331
  }
1267
1332
  finally {
1268
- const endTime = Date.now();
1269
- this._reportToWorld(world, {
1270
- element_name: selectors.element_name,
1271
- type: Types.FILL,
1272
- screenshotId,
1273
- value,
1274
- text: `Fill input with value: ${value}`,
1275
- result: error
1276
- ? {
1277
- status: "FAILED",
1278
- startTime,
1279
- endTime,
1280
- message: error === null || error === void 0 ? void 0 : error.message,
1281
- }
1282
- : {
1283
- status: "PASSED",
1284
- startTime,
1285
- endTime,
1286
- },
1287
- info: info,
1288
- });
1333
+ _commandFinally(state, this);
1289
1334
  }
1290
1335
  }
1291
1336
  async getText(selectors, _params = null, options = {}, info = {}, world = null) {
1292
1337
  return await this._getText(selectors, 0, _params, options, info, world);
1293
1338
  }
1294
1339
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1295
- this._validateSelectors(selectors);
1340
+ _validateSelectors(selectors);
1296
1341
  let screenshotId = null;
1297
1342
  let screenshotPath = null;
1298
1343
  if (!info.log) {
1299
1344
  info.log = "";
1345
+ info.locatorLog = new LocatorLog(selectors);
1300
1346
  }
1301
1347
  info.operation = "getText";
1302
1348
  info.selectors = selectors;
@@ -1336,165 +1382,124 @@ class StableBrowser {
1336
1382
  }
1337
1383
  }
1338
1384
  async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
1339
- var _a;
1340
- this._validateSelectors(selectors);
1341
1385
  if (!pattern) {
1342
1386
  throw new Error("pattern is null");
1343
1387
  }
1344
1388
  if (!text) {
1345
1389
  throw new Error("text is null");
1346
1390
  }
1391
+ const state = {
1392
+ selectors,
1393
+ _params,
1394
+ pattern,
1395
+ value: pattern,
1396
+ options,
1397
+ world,
1398
+ locate: false,
1399
+ scroll: false,
1400
+ screenshot: false,
1401
+ highlight: false,
1402
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1403
+ text: `Verify element contains pattern: ${pattern}`,
1404
+ operation: "containsPattern",
1405
+ log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1406
+ };
1347
1407
  const newValue = await this._replaceWithLocalData(text, world);
1348
1408
  if (newValue !== text) {
1349
1409
  this.logger.info(text + "=" + newValue);
1350
1410
  text = newValue;
1351
1411
  }
1352
- const startTime = Date.now();
1353
- let error = null;
1354
- let screenshotId = null;
1355
- let screenshotPath = null;
1356
- const info = {};
1357
- info.log =
1358
- "***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
1359
- info.operation = "containsPattern";
1360
- info.selectors = selectors;
1361
- info.value = text;
1362
- info.pattern = pattern;
1363
1412
  let foundObj = null;
1364
1413
  try {
1365
- foundObj = await this._getText(selectors, 0, _params, options, info, world);
1414
+ await _preCommand(state, this);
1415
+ state.info.pattern = pattern;
1416
+ foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
1366
1417
  if (foundObj && foundObj.element) {
1367
- await this.scrollIfNeeded(foundObj.element, info);
1418
+ await this.scrollIfNeeded(foundObj.element, state.info);
1368
1419
  }
1369
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1420
+ await _screenshot(state, this);
1370
1421
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1371
1422
  pattern = pattern.replace("{text}", escapedText);
1372
1423
  let regex = new RegExp(pattern, "im");
1373
- 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))) {
1374
- info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1424
+ if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
1425
+ state.info.foundText = foundObj?.text;
1375
1426
  throw new Error("element doesn't contain text " + text);
1376
1427
  }
1377
- return info;
1428
+ return state.info;
1378
1429
  }
1379
1430
  catch (e) {
1380
- //await this.closeUnexpectedPopups();
1381
- this.logger.error("verify element contains text failed " + info.log);
1382
- this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
1383
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1384
- info.screenshotPath = screenshotPath;
1385
- Object.assign(e, { info: info });
1386
- error = e;
1387
- throw e;
1431
+ this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
1432
+ await _commandError(state, e, this);
1388
1433
  }
1389
1434
  finally {
1390
- const endTime = Date.now();
1391
- this._reportToWorld(world, {
1392
- element_name: selectors.element_name,
1393
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1394
- value: pattern,
1395
- text: `Verify element contains pattern: ${pattern}`,
1396
- screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
1397
- result: error
1398
- ? {
1399
- status: "FAILED",
1400
- startTime,
1401
- endTime,
1402
- message: error === null || error === void 0 ? void 0 : error.message,
1403
- }
1404
- : {
1405
- status: "PASSED",
1406
- startTime,
1407
- endTime,
1408
- },
1409
- info: info,
1410
- });
1435
+ _commandFinally(state, this);
1411
1436
  }
1412
1437
  }
1413
1438
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1414
- var _a, _b, _c;
1415
- this._validateSelectors(selectors);
1439
+ const state = {
1440
+ selectors,
1441
+ _params,
1442
+ value: text,
1443
+ options,
1444
+ world,
1445
+ locate: false,
1446
+ scroll: false,
1447
+ screenshot: false,
1448
+ highlight: false,
1449
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1450
+ text: `Verify element contains text: ${text}`,
1451
+ operation: "containsText",
1452
+ log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
1453
+ };
1416
1454
  if (!text) {
1417
1455
  throw new Error("text is null");
1418
1456
  }
1419
- const startTime = Date.now();
1420
- let error = null;
1421
- let screenshotId = null;
1422
- let screenshotPath = null;
1423
- const info = {};
1424
- info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
1425
- info.operation = "containsText";
1426
- info.selectors = selectors;
1457
+ text = unEscapeString(text);
1427
1458
  const newValue = await this._replaceWithLocalData(text, world);
1428
1459
  if (newValue !== text) {
1429
1460
  this.logger.info(text + "=" + newValue);
1430
1461
  text = newValue;
1431
1462
  }
1432
- info.value = text;
1433
1463
  let foundObj = null;
1434
1464
  try {
1435
- foundObj = await this._getText(selectors, climb, _params, options, info, world);
1465
+ await _preCommand(state, this);
1466
+ foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1436
1467
  if (foundObj && foundObj.element) {
1437
- await this.scrollIfNeeded(foundObj.element, info);
1468
+ await this.scrollIfNeeded(foundObj.element, state.info);
1438
1469
  }
1439
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1470
+ await _screenshot(state, this);
1440
1471
  const dateAlternatives = findDateAlternatives(text);
1441
1472
  const numberAlternatives = findNumberAlternatives(text);
1442
1473
  if (dateAlternatives.date) {
1443
1474
  for (let i = 0; i < dateAlternatives.dates.length; i++) {
1444
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(dateAlternatives.dates[i])) ||
1445
- ((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(dateAlternatives.dates[i]))) {
1446
- return info;
1475
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1476
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1477
+ return state.info;
1447
1478
  }
1448
1479
  }
1449
1480
  throw new Error("element doesn't contain text " + text);
1450
1481
  }
1451
1482
  else if (numberAlternatives.number) {
1452
1483
  for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1453
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(numberAlternatives.numbers[i])) ||
1454
- ((_b = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _b === void 0 ? void 0 : _b.includes(numberAlternatives.numbers[i]))) {
1455
- return info;
1484
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1485
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1486
+ return state.info;
1456
1487
  }
1457
1488
  }
1458
- throw new Error("element doesn't contain text " + text);
1459
- }
1460
- 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))) {
1461
- info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1462
- info.value = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value;
1463
- throw new Error("element doesn't contain text " + text);
1464
- }
1465
- return info;
1466
- }
1467
- catch (e) {
1468
- //await this.closeUnexpectedPopups();
1469
- this.logger.error("verify element contains text failed " + info.log);
1470
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1471
- info.screenshotPath = screenshotPath;
1472
- Object.assign(e, { info: info });
1473
- error = e;
1474
- throw e;
1475
- }
1476
- finally {
1477
- const endTime = Date.now();
1478
- this._reportToWorld(world, {
1479
- element_name: selectors.element_name,
1480
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1481
- text: `Verify element contains text: ${text}`,
1482
- value: text,
1483
- screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
1484
- result: error
1485
- ? {
1486
- status: "FAILED",
1487
- startTime,
1488
- endTime,
1489
- message: error === null || error === void 0 ? void 0 : error.message,
1490
- }
1491
- : {
1492
- status: "PASSED",
1493
- startTime,
1494
- endTime,
1495
- },
1496
- info: info,
1497
- });
1489
+ throw new Error("element doesn't contain text " + text);
1490
+ }
1491
+ else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1492
+ state.info.foundText = foundObj?.text;
1493
+ state.info.value = foundObj?.value;
1494
+ throw new Error("element doesn't contain text " + text);
1495
+ }
1496
+ return state.info;
1497
+ }
1498
+ catch (e) {
1499
+ await _commandError(state, e, this);
1500
+ }
1501
+ finally {
1502
+ _commandFinally(state, this);
1498
1503
  }
1499
1504
  }
1500
1505
  _getDataFile(world = null) {
@@ -1513,6 +1518,29 @@ class StableBrowser {
1513
1518
  }
1514
1519
  return dataFile;
1515
1520
  }
1521
+ async waitForUserInput(message, world = null) {
1522
+ if (!message) {
1523
+ message = "# Wait for user input. Press any key to continue";
1524
+ }
1525
+ else {
1526
+ message = "# Wait for user input. " + message;
1527
+ }
1528
+ message += "\n";
1529
+ const value = await new Promise((resolve) => {
1530
+ const rl = readline.createInterface({
1531
+ input: process.stdin,
1532
+ output: process.stdout,
1533
+ });
1534
+ rl.question(message, (answer) => {
1535
+ rl.close();
1536
+ resolve(answer);
1537
+ });
1538
+ });
1539
+ if (value) {
1540
+ this.logger.info(`{{userInput}} was set to: ${value}`);
1541
+ }
1542
+ this.setTestData({ userInput: value }, world);
1543
+ }
1516
1544
  setTestData(testData, world = null) {
1517
1545
  if (!testData) {
1518
1546
  return;
@@ -1653,11 +1681,9 @@ class StableBrowser {
1653
1681
  if (!fs.existsSync(world.screenshotPath)) {
1654
1682
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1655
1683
  }
1656
- let nextIndex = 1;
1657
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1658
- nextIndex++;
1659
- }
1660
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1684
+ // to make sure the path doesn't start with -
1685
+ const uuidStr = "id_" + randomUUID();
1686
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1661
1687
  try {
1662
1688
  await this.takeScreenshot(screenshotPath);
1663
1689
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1671,7 +1697,7 @@ class StableBrowser {
1671
1697
  catch (e) {
1672
1698
  this.logger.info("unable to take screenshot, ignored");
1673
1699
  }
1674
- result.screenshotId = nextIndex;
1700
+ result.screenshotId = uuidStr;
1675
1701
  result.screenshotPath = screenshotPath;
1676
1702
  if (info && info.box) {
1677
1703
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1700,7 +1726,6 @@ class StableBrowser {
1700
1726
  }
1701
1727
  async takeScreenshot(screenshotPath) {
1702
1728
  const playContext = this.context.playContext;
1703
- const client = await playContext.newCDPSession(this.page);
1704
1729
  // Using CDP to capture the screenshot
1705
1730
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
1706
1731
  document.body.scrollWidth,
@@ -1710,164 +1735,165 @@ class StableBrowser {
1710
1735
  document.body.clientWidth,
1711
1736
  document.documentElement.clientWidth,
1712
1737
  ])));
1713
- const viewportHeight = Math.max(...(await this.page.evaluate(() => [
1714
- document.body.scrollHeight,
1715
- document.documentElement.scrollHeight,
1716
- document.body.offsetHeight,
1717
- document.documentElement.offsetHeight,
1718
- document.body.clientHeight,
1719
- document.documentElement.clientHeight,
1720
- ])));
1721
- const { data } = await client.send("Page.captureScreenshot", {
1722
- format: "png",
1723
- clip: {
1724
- x: 0,
1725
- y: 0,
1726
- width: viewportWidth,
1727
- height: viewportHeight,
1728
- scale: 1,
1729
- },
1730
- });
1731
- if (!screenshotPath) {
1732
- return data;
1733
- }
1734
- let screenshotBuffer = Buffer.from(data, "base64");
1735
- const sharpBuffer = sharp(screenshotBuffer);
1736
- const metadata = await sharpBuffer.metadata();
1737
- //check if you are on retina display and reduce the quality of the image
1738
- if (metadata.width > viewportWidth || metadata.height > viewportHeight) {
1739
- screenshotBuffer = await sharpBuffer
1740
- .resize(viewportWidth, viewportHeight, {
1741
- fit: sharp.fit.inside,
1742
- withoutEnlargement: true,
1743
- })
1744
- .toBuffer();
1745
- }
1746
- fs.writeFileSync(screenshotPath, screenshotBuffer);
1747
- await client.detach();
1738
+ let screenshotBuffer = null;
1739
+ if (this.context.browserName === "chromium") {
1740
+ const client = await playContext.newCDPSession(this.page);
1741
+ const { data } = await client.send("Page.captureScreenshot", {
1742
+ format: "png",
1743
+ // clip: {
1744
+ // x: 0,
1745
+ // y: 0,
1746
+ // width: viewportWidth,
1747
+ // height: viewportHeight,
1748
+ // scale: 1,
1749
+ // },
1750
+ });
1751
+ await client.detach();
1752
+ if (!screenshotPath) {
1753
+ return data;
1754
+ }
1755
+ screenshotBuffer = Buffer.from(data, "base64");
1756
+ }
1757
+ else {
1758
+ screenshotBuffer = await this.page.screenshot();
1759
+ }
1760
+ let image = await Jimp.read(screenshotBuffer);
1761
+ // Get the image dimensions
1762
+ const { width, height } = image.bitmap;
1763
+ const resizeRatio = viewportWidth / width;
1764
+ // Resize the image to fit within the viewport dimensions without enlarging
1765
+ if (width > viewportWidth) {
1766
+ image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
1767
+ await image.write(screenshotPath);
1768
+ }
1769
+ else {
1770
+ fs.writeFileSync(screenshotPath, screenshotBuffer);
1771
+ }
1748
1772
  }
1749
1773
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1750
- this._validateSelectors(selectors);
1751
- const startTime = Date.now();
1752
- let error = null;
1753
- let screenshotId = null;
1754
- let screenshotPath = null;
1774
+ const state = {
1775
+ selectors,
1776
+ _params,
1777
+ options,
1778
+ world,
1779
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1780
+ text: `Verify element exists in page`,
1781
+ operation: "verifyElementExistInPage",
1782
+ log: "***** verify element " + selectors.element_name + " exists in page *****\n",
1783
+ };
1755
1784
  await new Promise((resolve) => setTimeout(resolve, 2000));
1756
- const info = {};
1757
- info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
1758
- info.operation = "verify";
1759
- info.selectors = selectors;
1760
1785
  try {
1761
- const element = await this._locate(selectors, info, _params);
1762
- if (element) {
1763
- await this.scrollIfNeeded(element, info);
1764
- }
1765
- await this._highlightElements(element);
1766
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1767
- await expect(element).toHaveCount(1, { timeout: 10000 });
1768
- return info;
1786
+ await _preCommand(state, this);
1787
+ await expect(state.element).toHaveCount(1, { timeout: 10000 });
1788
+ return state.info;
1769
1789
  }
1770
1790
  catch (e) {
1771
- //await this.closeUnexpectedPopups();
1772
- this.logger.error("verify failed " + info.log);
1773
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1774
- info.screenshotPath = screenshotPath;
1775
- Object.assign(e, { info: info });
1776
- error = e;
1777
- throw e;
1791
+ await _commandError(state, e, this);
1778
1792
  }
1779
1793
  finally {
1780
- const endTime = Date.now();
1781
- this._reportToWorld(world, {
1782
- element_name: selectors.element_name,
1783
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1784
- text: "Verify element exists in page",
1785
- screenshotId,
1786
- result: error
1787
- ? {
1788
- status: "FAILED",
1789
- startTime,
1790
- endTime,
1791
- message: error === null || error === void 0 ? void 0 : error.message,
1792
- }
1793
- : {
1794
- status: "PASSED",
1795
- startTime,
1796
- endTime,
1797
- },
1798
- info: info,
1799
- });
1794
+ _commandFinally(state, this);
1800
1795
  }
1801
1796
  }
1802
1797
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1803
- this._validateSelectors(selectors);
1804
- const startTime = Date.now();
1805
- let error = null;
1806
- let screenshotId = null;
1807
- let screenshotPath = null;
1798
+ const state = {
1799
+ selectors,
1800
+ _params,
1801
+ attribute,
1802
+ variable,
1803
+ options,
1804
+ world,
1805
+ type: Types.EXTRACT,
1806
+ text: `Extract attribute from element`,
1807
+ operation: "extractAttribute",
1808
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1809
+ };
1808
1810
  await new Promise((resolve) => setTimeout(resolve, 2000));
1809
- const info = {};
1810
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1811
- info.operation = "extract";
1812
- info.selectors = selectors;
1813
1811
  try {
1814
- const element = await this._locate(selectors, info, _params);
1815
- await this._highlightElements(element);
1816
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1812
+ await _preCommand(state, this);
1817
1813
  switch (attribute) {
1818
1814
  case "inner_text":
1819
- info.value = await element.innerText();
1815
+ state.value = await state.element.innerText();
1820
1816
  break;
1821
1817
  case "href":
1822
- info.value = await element.getAttribute("href");
1818
+ state.value = await state.element.getAttribute("href");
1819
+ break;
1820
+ case "value":
1821
+ state.value = await state.element.inputValue();
1822
+ break;
1823
+ default:
1824
+ state.value = await state.element.getAttribute(attribute);
1825
+ break;
1826
+ }
1827
+ state.info.value = state.value;
1828
+ this.setTestData({ [variable]: state.value }, world);
1829
+ this.logger.info("set test data: " + variable + "=" + state.value);
1830
+ return state.info;
1831
+ }
1832
+ catch (e) {
1833
+ await _commandError(state, e, this);
1834
+ }
1835
+ finally {
1836
+ _commandFinally(state, this);
1837
+ }
1838
+ }
1839
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1840
+ const state = {
1841
+ selectors,
1842
+ _params,
1843
+ attribute,
1844
+ value,
1845
+ options,
1846
+ world,
1847
+ type: Types.VERIFY_ATTRIBUTE,
1848
+ text: `Verify element attribute`,
1849
+ operation: "verifyAttribute",
1850
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1851
+ };
1852
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1853
+ let val;
1854
+ try {
1855
+ await _preCommand(state, this);
1856
+ switch (attribute) {
1857
+ case "innerText":
1858
+ val = String(await state.element.innerText());
1823
1859
  break;
1824
1860
  case "value":
1825
- info.value = await element.inputValue();
1861
+ val = String(await state.element.inputValue());
1862
+ break;
1863
+ case "checked":
1864
+ val = String(await state.element.isChecked());
1865
+ break;
1866
+ case "disabled":
1867
+ val = String(await state.element.isDisabled());
1868
+ break;
1869
+ case "readOnly":
1870
+ const isEditable = await state.element.isEditable();
1871
+ val = String(!isEditable);
1826
1872
  break;
1827
1873
  default:
1828
- info.value = await element.getAttribute(attribute);
1874
+ val = String(await state.element.getAttribute(attribute));
1829
1875
  break;
1830
1876
  }
1831
- this[variable] = info.value;
1832
- if (world) {
1833
- world[variable] = info.value;
1877
+ state.info.expectedValue = val;
1878
+ let regex;
1879
+ if (value.startsWith("/") && value.endsWith("/")) {
1880
+ const patternBody = value.slice(1, -1);
1881
+ regex = new RegExp(patternBody, "g");
1834
1882
  }
1835
- this.setTestData({ [variable]: info.value }, world);
1836
- this.logger.info("set test data: " + variable + "=" + info.value);
1837
- return info;
1883
+ else {
1884
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1885
+ regex = new RegExp(escapedPattern, "g");
1886
+ }
1887
+ if (!val.match(regex)) {
1888
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1889
+ }
1890
+ return state.info;
1838
1891
  }
1839
1892
  catch (e) {
1840
- //await this.closeUnexpectedPopups();
1841
- this.logger.error("extract failed " + info.log);
1842
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1843
- info.screenshotPath = screenshotPath;
1844
- Object.assign(e, { info: info });
1845
- error = e;
1846
- throw e;
1893
+ await _commandError(state, e, this);
1847
1894
  }
1848
1895
  finally {
1849
- const endTime = Date.now();
1850
- this._reportToWorld(world, {
1851
- element_name: selectors.element_name,
1852
- type: Types.EXTRACT_ATTRIBUTE,
1853
- variable: variable,
1854
- value: info.value,
1855
- text: "Extract attribute from element",
1856
- screenshotId,
1857
- result: error
1858
- ? {
1859
- status: "FAILED",
1860
- startTime,
1861
- endTime,
1862
- message: error === null || error === void 0 ? void 0 : error.message,
1863
- }
1864
- : {
1865
- status: "PASSED",
1866
- startTime,
1867
- endTime,
1868
- },
1869
- info: info,
1870
- });
1896
+ _commandFinally(state, this);
1871
1897
  }
1872
1898
  }
1873
1899
  async extractEmailData(emailAddress, options, world) {
@@ -1888,7 +1914,7 @@ class StableBrowser {
1888
1914
  if (options && options.timeout) {
1889
1915
  timeout = options.timeout;
1890
1916
  }
1891
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1917
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1892
1918
  const request = {
1893
1919
  method: "POST",
1894
1920
  url: serviceUrl,
@@ -1944,7 +1970,8 @@ class StableBrowser {
1944
1970
  catch (e) {
1945
1971
  errorCount++;
1946
1972
  if (errorCount > 3) {
1947
- throw e;
1973
+ // throw e;
1974
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1948
1975
  }
1949
1976
  // ignore
1950
1977
  }
@@ -2055,11 +2082,12 @@ class StableBrowser {
2055
2082
  info.screenshotPath = screenshotPath;
2056
2083
  Object.assign(e, { info: info });
2057
2084
  error = e;
2058
- throw e;
2085
+ // throw e;
2086
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2059
2087
  }
2060
2088
  finally {
2061
2089
  const endTime = Date.now();
2062
- this._reportToWorld(world, {
2090
+ _reportToWorld(world, {
2063
2091
  type: Types.VERIFY_PAGE_PATH,
2064
2092
  text: "Verify page path",
2065
2093
  screenshotId,
@@ -2068,7 +2096,7 @@ class StableBrowser {
2068
2096
  status: "FAILED",
2069
2097
  startTime,
2070
2098
  endTime,
2071
- message: error === null || error === void 0 ? void 0 : error.message,
2099
+ message: error?.message,
2072
2100
  }
2073
2101
  : {
2074
2102
  status: "PASSED",
@@ -2079,53 +2107,65 @@ class StableBrowser {
2079
2107
  });
2080
2108
  }
2081
2109
  }
2110
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2111
+ const frames = this.page.frames();
2112
+ let results = [];
2113
+ let ignoreCase = !(text.startsWith("/") && text.endsWith("/"));
2114
+ for (let i = 0; i < frames.length; i++) {
2115
+ if (dateAlternatives.date) {
2116
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2117
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2118
+ result.frame = frames[i];
2119
+ results.push(result);
2120
+ }
2121
+ }
2122
+ else if (numberAlternatives.number) {
2123
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2124
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2125
+ result.frame = frames[i];
2126
+ results.push(result);
2127
+ }
2128
+ }
2129
+ else {
2130
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2131
+ result.frame = frames[i];
2132
+ results.push(result);
2133
+ }
2134
+ }
2135
+ state.info.results = results;
2136
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2137
+ return resultWithElementsFound;
2138
+ }
2082
2139
  async verifyTextExistInPage(text, options = {}, world = null) {
2083
- const startTime = Date.now();
2140
+ text = unEscapeString(text);
2141
+ const state = {
2142
+ text_search: text,
2143
+ options,
2144
+ world,
2145
+ locate: false,
2146
+ scroll: false,
2147
+ highlight: false,
2148
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2149
+ text: `Verify text exists in page`,
2150
+ operation: "verifyTextExistInPage",
2151
+ log: "***** verify text " + text + " exists in page *****\n",
2152
+ };
2084
2153
  const timeout = this._getLoadTimeout(options);
2085
- let error = null;
2086
- let screenshotId = null;
2087
- let screenshotPath = null;
2088
2154
  await new Promise((resolve) => setTimeout(resolve, 2000));
2089
- const info = {};
2090
- info.log = "***** verify text " + text + " exists in page *****\n";
2091
- info.operation = "verifyTextExistInPage";
2092
2155
  const newValue = await this._replaceWithLocalData(text, world);
2093
2156
  if (newValue !== text) {
2094
2157
  this.logger.info(text + "=" + newValue);
2095
2158
  text = newValue;
2096
2159
  }
2097
- info.text = text;
2098
2160
  let dateAlternatives = findDateAlternatives(text);
2099
2161
  let numberAlternatives = findNumberAlternatives(text);
2100
2162
  try {
2163
+ await _preCommand(state, this);
2164
+ state.info.text = text;
2101
2165
  while (true) {
2102
- const frames = this.page.frames();
2103
- let results = [];
2104
- for (let i = 0; i < frames.length; i++) {
2105
- if (dateAlternatives.date) {
2106
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2107
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
2108
- result.frame = frames[i];
2109
- results.push(result);
2110
- }
2111
- }
2112
- else if (numberAlternatives.number) {
2113
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2114
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
2115
- result.frame = frames[i];
2116
- results.push(result);
2117
- }
2118
- }
2119
- else {
2120
- const result = await this._locateElementByText(frames[i], text, "*", true, {});
2121
- result.frame = frames[i];
2122
- results.push(result);
2123
- }
2124
- }
2125
- info.results = results;
2126
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2166
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2127
2167
  if (resultWithElementsFound.length === 0) {
2128
- if (Date.now() - startTime > timeout) {
2168
+ if (Date.now() - state.startTime > timeout) {
2129
2169
  throw new Error(`Text ${text} not found in page`);
2130
2170
  }
2131
2171
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2137,55 +2177,163 @@ class StableBrowser {
2137
2177
  await this._highlightElements(frame, dataAttribute);
2138
2178
  const element = await frame.$(dataAttribute);
2139
2179
  if (element) {
2140
- await this.scrollIfNeeded(element, info);
2180
+ await this.scrollIfNeeded(element, state.info);
2141
2181
  await element.dispatchEvent("bvt_verify_page_contains_text");
2142
2182
  }
2143
2183
  }
2144
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2145
- return info;
2184
+ await _screenshot(state, this);
2185
+ return state.info;
2146
2186
  }
2147
2187
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2148
2188
  }
2149
2189
  catch (e) {
2150
- //await this.closeUnexpectedPopups();
2151
- this.logger.error("verify text exist in page failed " + info.log);
2152
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2153
- info.screenshotPath = screenshotPath;
2154
- Object.assign(e, { info: info });
2155
- error = e;
2156
- throw e;
2190
+ await _commandError(state, e, this);
2157
2191
  }
2158
2192
  finally {
2159
- const endTime = Date.now();
2160
- this._reportToWorld(world, {
2161
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2162
- text: "Verify text exists in page",
2163
- screenshotId,
2164
- result: error
2165
- ? {
2166
- status: "FAILED",
2167
- startTime,
2168
- endTime,
2169
- message: error === null || error === void 0 ? void 0 : error.message,
2170
- }
2171
- : {
2172
- status: "PASSED",
2173
- startTime,
2174
- endTime,
2175
- },
2176
- info: info,
2177
- });
2193
+ _commandFinally(state, this);
2194
+ }
2195
+ }
2196
+ async waitForTextToDisappear(text, options = {}, world = null) {
2197
+ text = unEscapeString(text);
2198
+ const state = {
2199
+ text_search: text,
2200
+ options,
2201
+ world,
2202
+ locate: false,
2203
+ scroll: false,
2204
+ highlight: false,
2205
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2206
+ text: `Verify text does not exist in page`,
2207
+ operation: "verifyTextNotExistInPage",
2208
+ log: "***** verify text " + text + " does not exist in page *****\n",
2209
+ };
2210
+ const timeout = this._getLoadTimeout(options);
2211
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2212
+ const newValue = await this._replaceWithLocalData(text, world);
2213
+ if (newValue !== text) {
2214
+ this.logger.info(text + "=" + newValue);
2215
+ text = newValue;
2216
+ }
2217
+ let dateAlternatives = findDateAlternatives(text);
2218
+ let numberAlternatives = findNumberAlternatives(text);
2219
+ try {
2220
+ await _preCommand(state, this);
2221
+ state.info.text = text;
2222
+ while (true) {
2223
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2224
+ if (resultWithElementsFound.length === 0) {
2225
+ await _screenshot(state, this);
2226
+ return state.info;
2227
+ }
2228
+ if (Date.now() - state.startTime > timeout) {
2229
+ throw new Error(`Text ${text} found in page`);
2230
+ }
2231
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2232
+ }
2233
+ }
2234
+ catch (e) {
2235
+ await _commandError(state, e, this);
2236
+ }
2237
+ finally {
2238
+ _commandFinally(state, this);
2178
2239
  }
2179
2240
  }
2180
- _getServerUrl() {
2181
- let serviceUrl = "https://api.blinq.io";
2182
- if (process.env.NODE_ENV_BLINQ === "dev") {
2183
- serviceUrl = "https://dev.api.blinq.io";
2241
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2242
+ textAnchor = unEscapeString(textAnchor);
2243
+ textToVerify = unEscapeString(textToVerify);
2244
+ const state = {
2245
+ text_search: textToVerify,
2246
+ options,
2247
+ world,
2248
+ locate: false,
2249
+ scroll: false,
2250
+ highlight: false,
2251
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2252
+ text: `Verify text with relation to another text`,
2253
+ operation: "verify_text_with_relation",
2254
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2255
+ };
2256
+ const timeout = this._getLoadTimeout(options);
2257
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2258
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2259
+ if (newValue !== textAnchor) {
2260
+ this.logger.info(textAnchor + "=" + newValue);
2261
+ textAnchor = newValue;
2262
+ }
2263
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2264
+ if (newValue !== textToVerify) {
2265
+ this.logger.info(textToVerify + "=" + newValue);
2266
+ textToVerify = newValue;
2267
+ }
2268
+ let dateAlternatives = findDateAlternatives(textToVerify);
2269
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2270
+ let foundAncore = false;
2271
+ try {
2272
+ await _preCommand(state, this);
2273
+ state.info.text = textToVerify;
2274
+ while (true) {
2275
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2276
+ if (resultWithElementsFound.length === 0) {
2277
+ if (Date.now() - state.startTime > timeout) {
2278
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2279
+ }
2280
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2281
+ continue;
2282
+ }
2283
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2284
+ foundAncore = true;
2285
+ const result = resultWithElementsFound[i];
2286
+ const token = result.randomToken;
2287
+ const frame = result.frame;
2288
+ const css = `[data-blinq-id="blinq-id-${token}"]`;
2289
+ const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
2290
+ const elements = Array.from(document.querySelectorAll(css));
2291
+ for (let i = 0; i < elements.length; i++) {
2292
+ const element = elements[i];
2293
+ let climbParent = element;
2294
+ for (let j = 0; j < climb; j++) {
2295
+ climbParent = climbParent.parentElement;
2296
+ if (!climbParent) {
2297
+ break;
2298
+ }
2299
+ }
2300
+ if (!climbParent) {
2301
+ continue;
2302
+ }
2303
+ const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
2304
+ if (foundElements.length > 0) {
2305
+ // set the container element attribute
2306
+ element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
2307
+ climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
2308
+ foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
2309
+ return { found: true };
2310
+ }
2311
+ }
2312
+ return { found: false };
2313
+ }, [css, climb, textToVerify, result.randomToken]);
2314
+ if (findResult.found === true) {
2315
+ const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
2316
+ const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2317
+ await this._highlightElements(frame, dataAttribute);
2318
+ await this._highlightElements(frame, cssAnchor);
2319
+ const element = await frame.$(dataAttribute);
2320
+ if (element) {
2321
+ await this.scrollIfNeeded(element, state.info);
2322
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2323
+ }
2324
+ await _screenshot(state, this);
2325
+ return state.info;
2326
+ }
2327
+ }
2328
+ }
2329
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2184
2330
  }
2185
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2186
- serviceUrl = "https://stage.api.blinq.io";
2331
+ catch (e) {
2332
+ await _commandError(state, e, this);
2333
+ }
2334
+ finally {
2335
+ _commandFinally(state, this);
2187
2336
  }
2188
- return serviceUrl;
2189
2337
  }
2190
2338
  async visualVerification(text, options = {}, world = null) {
2191
2339
  const startTime = Date.now();
@@ -2201,7 +2349,7 @@ class StableBrowser {
2201
2349
  throw new Error("TOKEN is not set");
2202
2350
  }
2203
2351
  try {
2204
- let serviceUrl = this._getServerUrl();
2352
+ let serviceUrl = _getServerUrl();
2205
2353
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2206
2354
  info.screenshotPath = screenshotPath;
2207
2355
  const screenshot = await this.takeScreenshot();
@@ -2237,11 +2385,12 @@ class StableBrowser {
2237
2385
  info.screenshotPath = screenshotPath;
2238
2386
  Object.assign(e, { info: info });
2239
2387
  error = e;
2240
- throw e;
2388
+ // throw e;
2389
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2241
2390
  }
2242
2391
  finally {
2243
2392
  const endTime = Date.now();
2244
- this._reportToWorld(world, {
2393
+ _reportToWorld(world, {
2245
2394
  type: Types.VERIFY_VISUAL,
2246
2395
  text: "Visual verification",
2247
2396
  screenshotId,
@@ -2250,7 +2399,7 @@ class StableBrowser {
2250
2399
  status: "FAILED",
2251
2400
  startTime,
2252
2401
  endTime,
2253
- message: error === null || error === void 0 ? void 0 : error.message,
2402
+ message: error?.message,
2254
2403
  }
2255
2404
  : {
2256
2405
  status: "PASSED",
@@ -2282,13 +2431,14 @@ class StableBrowser {
2282
2431
  this.logger.info("Table data verified");
2283
2432
  }
2284
2433
  async getTableData(selectors, _params = null, options = {}, world = null) {
2285
- this._validateSelectors(selectors);
2434
+ _validateSelectors(selectors);
2286
2435
  const startTime = Date.now();
2287
2436
  let error = null;
2288
2437
  let screenshotId = null;
2289
2438
  let screenshotPath = null;
2290
2439
  const info = {};
2291
2440
  info.log = "";
2441
+ info.locatorLog = new LocatorLog(selectors);
2292
2442
  info.operation = "getTableData";
2293
2443
  info.selectors = selectors;
2294
2444
  try {
@@ -2304,11 +2454,12 @@ class StableBrowser {
2304
2454
  info.screenshotPath = screenshotPath;
2305
2455
  Object.assign(e, { info: info });
2306
2456
  error = e;
2307
- throw e;
2457
+ // throw e;
2458
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2308
2459
  }
2309
2460
  finally {
2310
2461
  const endTime = Date.now();
2311
- this._reportToWorld(world, {
2462
+ _reportToWorld(world, {
2312
2463
  element_name: selectors.element_name,
2313
2464
  type: Types.GET_TABLE_DATA,
2314
2465
  text: "Get table data",
@@ -2318,7 +2469,7 @@ class StableBrowser {
2318
2469
  status: "FAILED",
2319
2470
  startTime,
2320
2471
  endTime,
2321
- message: error === null || error === void 0 ? void 0 : error.message,
2472
+ message: error?.message,
2322
2473
  }
2323
2474
  : {
2324
2475
  status: "PASSED",
@@ -2330,7 +2481,7 @@ class StableBrowser {
2330
2481
  }
2331
2482
  }
2332
2483
  async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
2333
- this._validateSelectors(selectors);
2484
+ _validateSelectors(selectors);
2334
2485
  if (!query) {
2335
2486
  throw new Error("query is null");
2336
2487
  }
@@ -2363,7 +2514,7 @@ class StableBrowser {
2363
2514
  info.operation = "analyzeTable";
2364
2515
  info.selectors = selectors;
2365
2516
  info.query = query;
2366
- query = this._fixUsingParams(query, _params);
2517
+ query = _fixUsingParams(query, _params);
2367
2518
  info.query_fixed = query;
2368
2519
  info.operator = operator;
2369
2520
  info.value = value;
@@ -2469,11 +2620,12 @@ class StableBrowser {
2469
2620
  info.screenshotPath = screenshotPath;
2470
2621
  Object.assign(e, { info: info });
2471
2622
  error = e;
2472
- throw e;
2623
+ // throw e;
2624
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2473
2625
  }
2474
2626
  finally {
2475
2627
  const endTime = Date.now();
2476
- this._reportToWorld(world, {
2628
+ _reportToWorld(world, {
2477
2629
  element_name: selectors.element_name,
2478
2630
  type: Types.ANALYZE_TABLE,
2479
2631
  text: "Analyze table",
@@ -2483,7 +2635,7 @@ class StableBrowser {
2483
2635
  status: "FAILED",
2484
2636
  startTime,
2485
2637
  endTime,
2486
- message: error === null || error === void 0 ? void 0 : error.message,
2638
+ message: error?.message,
2487
2639
  }
2488
2640
  : {
2489
2641
  status: "PASSED",
@@ -2495,27 +2647,7 @@ class StableBrowser {
2495
2647
  }
2496
2648
  }
2497
2649
  async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
2498
- if (!value) {
2499
- return value;
2500
- }
2501
- // find all the accurance of {{(.*?)}} and replace with the value
2502
- let regex = /{{(.*?)}}/g;
2503
- let matches = value.match(regex);
2504
- if (matches) {
2505
- const testData = this.getTestData(world);
2506
- for (let i = 0; i < matches.length; i++) {
2507
- let match = matches[i];
2508
- let key = match.substring(2, match.length - 2);
2509
- let newValue = objectPath.get(testData, key, null);
2510
- if (newValue !== null) {
2511
- value = value.replace(match, newValue);
2512
- }
2513
- }
2514
- }
2515
- if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
2516
- return await decrypt(value, null, totpWait);
2517
- }
2518
- return value;
2650
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2519
2651
  }
2520
2652
  _getLoadTimeout(options) {
2521
2653
  let timeout = 15000;
@@ -2552,13 +2684,13 @@ class StableBrowser {
2552
2684
  }
2553
2685
  catch (e) {
2554
2686
  if (e.label === "networkidle") {
2555
- console.log("waitted for the network to be idle timeout");
2687
+ console.log("waited for the network to be idle timeout");
2556
2688
  }
2557
2689
  else if (e.label === "load") {
2558
- console.log("waitted for the load timeout");
2690
+ console.log("waited for the load timeout");
2559
2691
  }
2560
2692
  else if (e.label === "domcontentloaded") {
2561
- console.log("waitted for the domcontent loaded timeout");
2693
+ console.log("waited for the domcontent loaded timeout");
2562
2694
  }
2563
2695
  console.log(".");
2564
2696
  }
@@ -2566,7 +2698,7 @@ class StableBrowser {
2566
2698
  await new Promise((resolve) => setTimeout(resolve, 2000));
2567
2699
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2568
2700
  const endTime = Date.now();
2569
- this._reportToWorld(world, {
2701
+ _reportToWorld(world, {
2570
2702
  type: Types.GET_PAGE_STATUS,
2571
2703
  text: "Wait for page load",
2572
2704
  screenshotId,
@@ -2575,7 +2707,7 @@ class StableBrowser {
2575
2707
  status: "FAILED",
2576
2708
  startTime,
2577
2709
  endTime,
2578
- message: error === null || error === void 0 ? void 0 : error.message,
2710
+ message: error?.message,
2579
2711
  }
2580
2712
  : {
2581
2713
  status: "PASSED",
@@ -2586,41 +2718,35 @@ class StableBrowser {
2586
2718
  }
2587
2719
  }
2588
2720
  async closePage(options = {}, world = null) {
2589
- const startTime = Date.now();
2590
- let error = null;
2591
- let screenshotId = null;
2592
- let screenshotPath = null;
2593
- const info = {};
2721
+ const state = {
2722
+ options,
2723
+ world,
2724
+ locate: false,
2725
+ scroll: false,
2726
+ highlight: false,
2727
+ type: Types.CLOSE_PAGE,
2728
+ text: `Close page`,
2729
+ operation: "closePage",
2730
+ log: "***** close page *****\n",
2731
+ throwError: false,
2732
+ };
2594
2733
  try {
2734
+ await _preCommand(state, this);
2595
2735
  await this.page.close();
2596
2736
  }
2597
2737
  catch (e) {
2598
2738
  console.log(".");
2739
+ await _commandError(state, e, this);
2599
2740
  }
2600
2741
  finally {
2601
- await new Promise((resolve) => setTimeout(resolve, 2000));
2602
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2603
- const endTime = Date.now();
2604
- this._reportToWorld(world, {
2605
- type: Types.CLOSE_PAGE,
2606
- text: "close page",
2607
- screenshotId,
2608
- result: error
2609
- ? {
2610
- status: "FAILED",
2611
- startTime,
2612
- endTime,
2613
- message: error === null || error === void 0 ? void 0 : error.message,
2614
- }
2615
- : {
2616
- status: "PASSED",
2617
- startTime,
2618
- endTime,
2619
- },
2620
- info: info,
2621
- });
2742
+ _commandFinally(state, this);
2622
2743
  }
2623
2744
  }
2745
+ saveTestDataAsGlobal(options, world) {
2746
+ const dataFile = this._getDataFile(world);
2747
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2748
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2749
+ }
2624
2750
  async setViewportSize(width, hight, options = {}, world = null) {
2625
2751
  const startTime = Date.now();
2626
2752
  let error = null;
@@ -2638,12 +2764,13 @@ class StableBrowser {
2638
2764
  }
2639
2765
  catch (e) {
2640
2766
  console.log(".");
2767
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2641
2768
  }
2642
2769
  finally {
2643
2770
  await new Promise((resolve) => setTimeout(resolve, 2000));
2644
2771
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2645
2772
  const endTime = Date.now();
2646
- this._reportToWorld(world, {
2773
+ _reportToWorld(world, {
2647
2774
  type: Types.SET_VIEWPORT,
2648
2775
  text: "set viewport size to " + width + "x" + hight,
2649
2776
  screenshotId,
@@ -2652,7 +2779,7 @@ class StableBrowser {
2652
2779
  status: "FAILED",
2653
2780
  startTime,
2654
2781
  endTime,
2655
- message: error === null || error === void 0 ? void 0 : error.message,
2782
+ message: error?.message,
2656
2783
  }
2657
2784
  : {
2658
2785
  status: "PASSED",
@@ -2674,12 +2801,13 @@ class StableBrowser {
2674
2801
  }
2675
2802
  catch (e) {
2676
2803
  console.log(".");
2804
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2677
2805
  }
2678
2806
  finally {
2679
2807
  await new Promise((resolve) => setTimeout(resolve, 2000));
2680
2808
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2681
2809
  const endTime = Date.now();
2682
- this._reportToWorld(world, {
2810
+ _reportToWorld(world, {
2683
2811
  type: Types.GET_PAGE_STATUS,
2684
2812
  text: "page relaod",
2685
2813
  screenshotId,
@@ -2688,7 +2816,7 @@ class StableBrowser {
2688
2816
  status: "FAILED",
2689
2817
  startTime,
2690
2818
  endTime,
2691
- message: error === null || error === void 0 ? void 0 : error.message,
2819
+ message: error?.message,
2692
2820
  }
2693
2821
  : {
2694
2822
  status: "PASSED",
@@ -2701,40 +2829,59 @@ class StableBrowser {
2701
2829
  }
2702
2830
  async scrollIfNeeded(element, info) {
2703
2831
  try {
2704
- let didScroll = await element.evaluate((node) => {
2705
- const rect = node.getBoundingClientRect();
2706
- if (rect &&
2707
- rect.top >= 0 &&
2708
- rect.left >= 0 &&
2709
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
2710
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
2711
- return false;
2712
- }
2713
- else {
2714
- node.scrollIntoView({
2715
- behavior: "smooth",
2716
- block: "center",
2717
- inline: "center",
2718
- });
2719
- return true;
2720
- }
2832
+ await element.scrollIntoViewIfNeeded({
2833
+ timeout: 2000,
2721
2834
  });
2722
- if (didScroll) {
2723
- await new Promise((resolve) => setTimeout(resolve, 500));
2724
- if (info) {
2725
- info.box = await element.boundingBox();
2726
- }
2835
+ await new Promise((resolve) => setTimeout(resolve, 500));
2836
+ if (info) {
2837
+ info.box = await element.boundingBox({
2838
+ timeout: 1000,
2839
+ });
2727
2840
  }
2728
2841
  }
2729
2842
  catch (e) {
2730
- console.log("scroll failed");
2843
+ console.log("#-#");
2731
2844
  }
2732
2845
  }
2733
- _reportToWorld(world, properties) {
2734
- if (!world || !world.attach) {
2735
- return;
2846
+ async beforeStep(world, step) {
2847
+ if (this.stepIndex === undefined) {
2848
+ this.stepIndex = 0;
2849
+ }
2850
+ else {
2851
+ this.stepIndex++;
2852
+ }
2853
+ if (step && step.pickleStep && step.pickleStep.text) {
2854
+ this.stepName = step.pickleStep.text;
2855
+ this.logger.info("step: " + this.stepName);
2856
+ }
2857
+ else if (step && step.text) {
2858
+ this.stepName = step.text;
2859
+ }
2860
+ else {
2861
+ this.stepName = "step " + this.stepIndex;
2862
+ }
2863
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2864
+ if (this.context.browserObject.context) {
2865
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2866
+ }
2867
+ }
2868
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2869
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2870
+ // check if @global_test_data tag is present
2871
+ if (this.tags.includes("@global_test_data")) {
2872
+ this.saveTestDataAsGlobal({}, world);
2873
+ }
2874
+ }
2875
+ }
2876
+ async afterStep(world, step) {
2877
+ this.stepName = null;
2878
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2879
+ if (this.context.browserObject.context) {
2880
+ await this.context.browserObject.context.tracing.stopChunk({
2881
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2882
+ });
2883
+ }
2736
2884
  }
2737
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2738
2885
  }
2739
2886
  }
2740
2887
  function createTimedPromise(promise, label) {
@@ -2742,151 +2889,5 @@ function createTimedPromise(promise, label) {
2742
2889
  .then((result) => ({ status: "fulfilled", label, result }))
2743
2890
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2744
2891
  }
2745
- const KEYBOARD_EVENTS = [
2746
- "ALT",
2747
- "AltGraph",
2748
- "CapsLock",
2749
- "Control",
2750
- "Fn",
2751
- "FnLock",
2752
- "Hyper",
2753
- "Meta",
2754
- "NumLock",
2755
- "ScrollLock",
2756
- "Shift",
2757
- "Super",
2758
- "Symbol",
2759
- "SymbolLock",
2760
- "Enter",
2761
- "Tab",
2762
- "ArrowDown",
2763
- "ArrowLeft",
2764
- "ArrowRight",
2765
- "ArrowUp",
2766
- "End",
2767
- "Home",
2768
- "PageDown",
2769
- "PageUp",
2770
- "Backspace",
2771
- "Clear",
2772
- "Copy",
2773
- "CrSel",
2774
- "Cut",
2775
- "Delete",
2776
- "EraseEof",
2777
- "ExSel",
2778
- "Insert",
2779
- "Paste",
2780
- "Redo",
2781
- "Undo",
2782
- "Accept",
2783
- "Again",
2784
- "Attn",
2785
- "Cancel",
2786
- "ContextMenu",
2787
- "Escape",
2788
- "Execute",
2789
- "Find",
2790
- "Finish",
2791
- "Help",
2792
- "Pause",
2793
- "Play",
2794
- "Props",
2795
- "Select",
2796
- "ZoomIn",
2797
- "ZoomOut",
2798
- "BrightnessDown",
2799
- "BrightnessUp",
2800
- "Eject",
2801
- "LogOff",
2802
- "Power",
2803
- "PowerOff",
2804
- "PrintScreen",
2805
- "Hibernate",
2806
- "Standby",
2807
- "WakeUp",
2808
- "AllCandidates",
2809
- "Alphanumeric",
2810
- "CodeInput",
2811
- "Compose",
2812
- "Convert",
2813
- "Dead",
2814
- "FinalMode",
2815
- "GroupFirst",
2816
- "GroupLast",
2817
- "GroupNext",
2818
- "GroupPrevious",
2819
- "ModeChange",
2820
- "NextCandidate",
2821
- "NonConvert",
2822
- "PreviousCandidate",
2823
- "Process",
2824
- "SingleCandidate",
2825
- "HangulMode",
2826
- "HanjaMode",
2827
- "JunjaMode",
2828
- "Eisu",
2829
- "Hankaku",
2830
- "Hiragana",
2831
- "HiraganaKatakana",
2832
- "KanaMode",
2833
- "KanjiMode",
2834
- "Katakana",
2835
- "Romaji",
2836
- "Zenkaku",
2837
- "ZenkakuHanaku",
2838
- "F1",
2839
- "F2",
2840
- "F3",
2841
- "F4",
2842
- "F5",
2843
- "F6",
2844
- "F7",
2845
- "F8",
2846
- "F9",
2847
- "F10",
2848
- "F11",
2849
- "F12",
2850
- "Soft1",
2851
- "Soft2",
2852
- "Soft3",
2853
- "Soft4",
2854
- "ChannelDown",
2855
- "ChannelUp",
2856
- "Close",
2857
- "MailForward",
2858
- "MailReply",
2859
- "MailSend",
2860
- "MediaFastForward",
2861
- "MediaPause",
2862
- "MediaPlay",
2863
- "MediaPlayPause",
2864
- "MediaRecord",
2865
- "MediaRewind",
2866
- "MediaStop",
2867
- "MediaTrackNext",
2868
- "MediaTrackPrevious",
2869
- "AudioBalanceLeft",
2870
- "AudioBalanceRight",
2871
- "AudioBassBoostDown",
2872
- "AudioBassBoostToggle",
2873
- "AudioBassBoostUp",
2874
- "AudioFaderFront",
2875
- "AudioFaderRear",
2876
- "AudioSurroundModeNext",
2877
- "AudioTrebleDown",
2878
- "AudioTrebleUp",
2879
- "AudioVolumeDown",
2880
- "AudioVolumeMute",
2881
- "AudioVolumeUp",
2882
- "MicrophoneToggle",
2883
- "MicrophoneVolumeDown",
2884
- "MicrophoneVolumeMute",
2885
- "MicrophoneVolumeUp",
2886
- "TV",
2887
- "TV3DMode",
2888
- "TVAntennaCable",
2889
- "TVAudioDescription",
2890
- ];
2891
2892
  export { StableBrowser };
2892
2893
  //# sourceMappingURL=stable_browser.js.map