automation_model 1.0.415-dev → 1.0.415

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