automation_model 1.0.411-dev → 1.0.411

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