automation_model 1.0.0-amdocs

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