automation_model 1.0.409-dev → 1.0.409

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