automation_model 1.0.401-dev → 1.0.401-main

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.
@@ -2,17 +2,22 @@
2
2
  import { expect } from "@playwright/test";
3
3
  import dayjs from "dayjs";
4
4
  import fs from "fs";
5
+ import { Jimp } from "jimp";
5
6
  import path from "path";
6
7
  import reg_parser from "regex-parser";
7
- import sharp from "sharp";
8
8
  import { findDateAlternatives, findNumberAlternatives } from "./analyze_helper.js";
9
9
  import { getDateTimeValue } from "./date_time.js";
10
10
  import drawRectangle from "./drawRect.js";
11
11
  //import { closeUnexpectedPopups } from "./popups.js";
12
12
  import { getTableCells, getTableData } from "./table_analyze.js";
13
- import objectPath from "object-path";
14
- import { decrypt } from "./utils.js";
13
+ import { maskValue, replaceWithLocalTestData } from "./utils.js";
15
14
  import csv from "csv-parser";
15
+ import { Readable } from "node:stream";
16
+ import readline from "readline";
17
+ import { getContext } from "./init_browser.js";
18
+ import { locate_element } from "./locate_element.js";
19
+ import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot } from "./command_common.js";
20
+ import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
16
21
  const Types = {
17
22
  CLICK: "click_element",
18
23
  NAVIGATE: "navigate",
@@ -37,16 +42,27 @@ const Types = {
37
42
  SET_DATE_TIME: "set_date_time",
38
43
  SET_VIEWPORT: "set_viewport",
39
44
  VERIFY_VISUAL: "verify_visual",
45
+ LOAD_DATA: "load_data",
46
+ SET_INPUT: "set_input",
40
47
  };
48
+ export const apps = {};
41
49
  class StableBrowser {
42
- constructor(browser, page, logger = null, context = null) {
50
+ browser;
51
+ page;
52
+ logger;
53
+ context;
54
+ world;
55
+ project_path = null;
56
+ webLogFile = null;
57
+ networkLogger = null;
58
+ configuration = null;
59
+ appName = "main";
60
+ constructor(browser, page, logger = null, context = null, world = null) {
43
61
  this.browser = browser;
44
62
  this.page = page;
45
63
  this.logger = logger;
46
64
  this.context = context;
47
- this.project_path = null;
48
- this.webLogFile = null;
49
- this.configuration = null;
65
+ this.world = world;
50
66
  if (!this.logger) {
51
67
  this.logger = console;
52
68
  }
@@ -72,19 +88,45 @@ class StableBrowser {
72
88
  this.logger.error("unable to read ai_config.json");
73
89
  }
74
90
  const logFolder = path.join(this.project_path, "logs", "web");
75
- this.webLogFile = this.getWebLogFile(logFolder);
76
- this.registerConsoleLogListener(page, context, this.webLogFile);
77
- this.registerRequestListener();
91
+ this.world = world;
78
92
  context.pages = [this.page];
79
93
  context.pageLoading = { status: false };
94
+ this.registerEventListeners(this.context);
95
+ registerNetworkEvents(this.world, this, this.context, this.page);
96
+ registerDownloadEvent(this.page, this.world, this.context);
97
+ }
98
+ registerEventListeners(context) {
99
+ this.registerConsoleLogListener(this.page, context);
100
+ this.registerRequestListener(this.page, context, this.webLogFile);
101
+ if (!context.pageLoading) {
102
+ context.pageLoading = { status: false };
103
+ }
80
104
  context.playContext.on("page", async function (page) {
105
+ if (this.configuration && this.configuration.closePopups === true) {
106
+ console.log("close unexpected popups");
107
+ await page.close();
108
+ return;
109
+ }
81
110
  context.pageLoading.status = true;
82
111
  this.page = page;
83
112
  context.page = page;
84
113
  context.pages.push(page);
85
- this.webLogFile = this.getWebLogFile(logFolder);
86
- this.registerConsoleLogListener(page, context, this.webLogFile);
87
- this.registerRequestListener();
114
+ registerNetworkEvents(this.world, this, context, this.page);
115
+ registerDownloadEvent(this.page, this.world, context);
116
+ page.on("close", async () => {
117
+ if (this.context && this.context.pages && this.context.pages.length > 1) {
118
+ this.context.pages.pop();
119
+ this.page = this.context.pages[this.context.pages.length - 1];
120
+ this.context.page = this.page;
121
+ try {
122
+ let title = await this.page.title();
123
+ console.log("Switched to page " + title);
124
+ }
125
+ catch (error) {
126
+ console.error("Error on page close", error);
127
+ }
128
+ }
129
+ });
88
130
  try {
89
131
  await this.waitForPageLoad();
90
132
  console.log("Switch page: " + (await page.title()));
@@ -95,6 +137,36 @@ class StableBrowser {
95
137
  context.pageLoading.status = false;
96
138
  }.bind(this));
97
139
  }
140
+ async switchApp(appName) {
141
+ // check if the current app (this.appName) is the same as the new app
142
+ if (this.appName === appName) {
143
+ return;
144
+ }
145
+ let navigate = false;
146
+ if (!apps[appName]) {
147
+ let newContext = await getContext(null, false, this.logger, appName, false, this);
148
+ navigate = true;
149
+ apps[appName] = {
150
+ context: newContext,
151
+ browser: newContext.browser,
152
+ page: newContext.page,
153
+ };
154
+ }
155
+ const tempContext = {};
156
+ this._copyContext(this, tempContext);
157
+ this._copyContext(apps[appName], this);
158
+ apps[this.appName] = tempContext;
159
+ this.appName = appName;
160
+ if (navigate) {
161
+ await this.goto(this.context.environment.baseUrl);
162
+ await this.waitForPageLoad();
163
+ }
164
+ }
165
+ _copyContext(from, to) {
166
+ to.browser = from.browser;
167
+ to.page = from.page;
168
+ to.context = from.context;
169
+ }
98
170
  getWebLogFile(logFolder) {
99
171
  if (!fs.existsSync(logFolder)) {
100
172
  fs.mkdirSync(logFolder, { recursive: true });
@@ -106,37 +178,63 @@ class StableBrowser {
106
178
  const fileName = nextIndex + ".json";
107
179
  return path.join(logFolder, fileName);
108
180
  }
109
- registerConsoleLogListener(page, context, logFile) {
181
+ registerConsoleLogListener(page, context) {
110
182
  if (!this.context.webLogger) {
111
183
  this.context.webLogger = [];
112
184
  }
113
185
  page.on("console", async (msg) => {
114
- this.context.webLogger.push({
186
+ const obj = {
115
187
  type: msg.type(),
116
188
  text: msg.text(),
117
189
  location: msg.location(),
118
190
  time: new Date().toISOString(),
119
- });
120
- await fs.promises.writeFile(logFile, JSON.stringify(this.context.webLogger, null, 2));
191
+ };
192
+ this.context.webLogger.push(obj);
193
+ if (msg.type() === "error") {
194
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
195
+ }
121
196
  });
122
197
  }
123
- registerRequestListener() {
124
- this.page.on("request", async (data) => {
198
+ registerRequestListener(page, context, logFile) {
199
+ if (!this.context.networkLogger) {
200
+ this.context.networkLogger = [];
201
+ }
202
+ page.on("request", async (data) => {
203
+ const startTime = new Date().getTime();
125
204
  try {
126
- const pageUrl = new URL(this.page.url());
205
+ const pageUrl = new URL(page.url());
127
206
  const requestUrl = new URL(data.url());
128
207
  if (pageUrl.hostname === requestUrl.hostname) {
129
208
  const method = data.method();
130
- if (method === "POST" || method === "GET" || method === "PUT" || method === "DELETE" || method === "PATCH") {
209
+ if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
131
210
  const token = await data.headerValue("Authorization");
132
211
  if (token) {
133
- this.context.authtoken = token;
212
+ context.authtoken = token;
134
213
  }
135
214
  }
136
215
  }
216
+ const response = await data.response();
217
+ const endTime = new Date().getTime();
218
+ const obj = {
219
+ url: data.url(),
220
+ method: data.method(),
221
+ postData: data.postData(),
222
+ error: data.failure() ? data.failure().errorText : null,
223
+ duration: endTime - startTime,
224
+ startTime,
225
+ };
226
+ context.networkLogger.push(obj);
227
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
137
228
  }
138
229
  catch (error) {
139
230
  console.error("Error in request listener", error);
231
+ context.networkLogger.push({
232
+ error: "not able to listen",
233
+ message: error.message,
234
+ stack: error.stack,
235
+ time: new Date().toISOString(),
236
+ });
237
+ // await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
140
238
  }
141
239
  });
142
240
  }
@@ -151,20 +249,6 @@ class StableBrowser {
151
249
  timeout: 60000,
152
250
  });
153
251
  }
154
- _validateSelectors(selectors) {
155
- if (!selectors) {
156
- throw new Error("selectors is null");
157
- }
158
- if (!selectors.locators) {
159
- throw new Error("selectors.locators is null");
160
- }
161
- if (!Array.isArray(selectors.locators)) {
162
- throw new Error("selectors.locators expected to be array");
163
- }
164
- if (selectors.locators.length === 0) {
165
- throw new Error("selectors.locators expected to be non empty array");
166
- }
167
- }
168
252
  _fixUsingParams(text, _params) {
169
253
  if (!_params || typeof text !== "string") {
170
254
  return text;
@@ -179,35 +263,84 @@ class StableBrowser {
179
263
  }
180
264
  return text;
181
265
  }
182
- _getLocator(locator, scope, _params) {
183
- if (locator.type === "pw_selector") {
184
- return scope.locator(locator.selector);
266
+ _fixLocatorUsingParams(locator, _params) {
267
+ // check if not null
268
+ if (!locator) {
269
+ return locator;
270
+ }
271
+ // clone the locator
272
+ locator = JSON.parse(JSON.stringify(locator));
273
+ this.scanAndManipulate(locator, _params);
274
+ return locator;
275
+ }
276
+ _isObject(value) {
277
+ return value && typeof value === "object" && value.constructor === Object;
278
+ }
279
+ scanAndManipulate(currentObj, _params) {
280
+ for (const key in currentObj) {
281
+ if (typeof currentObj[key] === "string") {
282
+ // Perform string manipulation
283
+ currentObj[key] = this._fixUsingParams(currentObj[key], _params);
284
+ }
285
+ else if (this._isObject(currentObj[key])) {
286
+ // Recursively scan nested objects
287
+ this.scanAndManipulate(currentObj[key], _params);
288
+ }
185
289
  }
290
+ }
291
+ _getLocator(locator, scope, _params) {
292
+ locator = this._fixLocatorUsingParams(locator, _params);
293
+ let locatorReturn;
186
294
  if (locator.role) {
187
295
  if (locator.role[1].nameReg) {
188
296
  locator.role[1].name = reg_parser(locator.role[1].nameReg);
189
297
  delete locator.role[1].nameReg;
190
298
  }
191
- if (locator.role[1].name) {
192
- locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
193
- }
194
- return scope.getByRole(locator.role[0], locator.role[1]);
299
+ // if (locator.role[1].name) {
300
+ // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
301
+ // }
302
+ locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
195
303
  }
196
304
  if (locator.css) {
197
- return scope.locator(this._fixUsingParams(locator.css, _params));
305
+ locatorReturn = scope.locator(locator.css);
198
306
  }
199
- if ((locator === null || locator === void 0 ? void 0 : locator.engine) && (locator === null || locator === void 0 ? void 0 : locator.score) <= 520) {
200
- let selector = locator.selector.replace(/"/g, "\\\"");
201
- if (locator.engine === "internal:att") {
202
- selector = `[${selector}]`;
307
+ // handle role/name locators
308
+ // locator.selector will be something like: textbox[name="Username"i]
309
+ if (locator.engine === "internal:role") {
310
+ // extract the role, name and the i/s flags using regex
311
+ const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
312
+ if (match) {
313
+ const role = match[1];
314
+ const name = match[3];
315
+ const flags = match[4];
316
+ locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
203
317
  }
204
- const locator = scope.locator(`${locator.engine}="${selector}"`);
205
- return locator;
206
318
  }
207
- throw new Error("unknown locator type");
319
+ if (locator?.engine) {
320
+ if (locator.engine === "css") {
321
+ locatorReturn = scope.locator(locator.selector);
322
+ }
323
+ else {
324
+ let selector = locator.selector;
325
+ if (locator.engine === "internal:attr") {
326
+ if (!selector.startsWith("[")) {
327
+ selector = `[${selector}]`;
328
+ }
329
+ }
330
+ locatorReturn = scope.locator(`${locator.engine}=${selector}`);
331
+ }
332
+ }
333
+ if (!locatorReturn) {
334
+ console.error(locator);
335
+ throw new Error("Locator undefined");
336
+ }
337
+ return locatorReturn;
208
338
  }
209
339
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
210
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
340
+ if (css && css.locator) {
341
+ css = css.locator;
342
+ }
343
+ let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
211
344
  if (result.elementCount === 0) {
212
345
  return;
213
346
  }
@@ -222,7 +355,7 @@ class StableBrowser {
222
355
  }
223
356
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
224
357
  //const stringifyText = JSON.stringify(text);
225
- return await scope.evaluate(([text, tag, regex, partial]) => {
358
+ return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
226
359
  function isParent(parent, child) {
227
360
  let currentNode = child.parentNode;
228
361
  while (currentNode !== null) {
@@ -234,6 +367,15 @@ class StableBrowser {
234
367
  return false;
235
368
  }
236
369
  document.isParent = isParent;
370
+ function getRegex(str) {
371
+ const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
372
+ if (!match) {
373
+ return null;
374
+ }
375
+ let [_, pattern, flags] = match;
376
+ return new RegExp(pattern, flags);
377
+ }
378
+ document.getRegex = getRegex;
237
379
  function collectAllShadowDomElements(element, result = []) {
238
380
  // Check and add the element if it has a shadow root
239
381
  if (element.shadowRoot) {
@@ -250,7 +392,11 @@ class StableBrowser {
250
392
  }
251
393
  document.collectAllShadowDomElements = collectAllShadowDomElements;
252
394
  if (!tag) {
253
- tag = "*";
395
+ tag = "*:not(script, style, head)";
396
+ }
397
+ let regexpSearch = document.getRegex(text);
398
+ if (regexpSearch) {
399
+ regex = true;
254
400
  }
255
401
  let elements = Array.from(document.querySelectorAll(tag));
256
402
  let shadowHosts = [];
@@ -267,7 +413,9 @@ class StableBrowser {
267
413
  let randomToken = null;
268
414
  const foundElements = [];
269
415
  if (regex) {
270
- let regexpSearch = new RegExp(text, "im");
416
+ if (!regexpSearch) {
417
+ regexpSearch = new RegExp(text, "im");
418
+ }
271
419
  for (let i = 0; i < elements.length; i++) {
272
420
  const element = elements[i];
273
421
  if ((element.innerText && regexpSearch.test(element.innerText)) ||
@@ -281,8 +429,8 @@ class StableBrowser {
281
429
  for (let i = 0; i < elements.length; i++) {
282
430
  const element = elements[i];
283
431
  if (partial) {
284
- if ((element.innerText && element.innerText.trim().includes(text)) ||
285
- (element.value && element.value.includes(text))) {
432
+ if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
433
+ (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
286
434
  foundElements.push(element);
287
435
  }
288
436
  }
@@ -327,18 +475,29 @@ class StableBrowser {
327
475
  }
328
476
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
329
477
  let locatorSearch = selectorHierarchy[index];
478
+ try {
479
+ locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
480
+ }
481
+ catch (e) {
482
+ console.error(e);
483
+ }
330
484
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
331
485
  let locator = null;
332
486
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
333
487
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
334
488
  if (!locatorString) {
489
+ info.failCause.textNotFound = true;
490
+ info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
335
491
  return;
336
492
  }
337
493
  locator = this._getLocator({ css: locatorString }, scope, _params);
338
494
  }
339
495
  else if (locatorSearch.text) {
340
- let result = await this._locateElementByText(scope, this._fixUsingParams(locatorSearch.text, _params), locatorSearch.tag, false, locatorSearch.partial === true, _params);
496
+ let text = this._fixUsingParams(locatorSearch.text, _params);
497
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
341
498
  if (result.elementCount === 0) {
499
+ info.failCause.textNotFound = true;
500
+ info.failCause.lastError = "failed to locate element by text: " + text;
342
501
  return;
343
502
  }
344
503
  locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
@@ -355,6 +514,9 @@ class StableBrowser {
355
514
  // cssHref = true;
356
515
  // }
357
516
  let count = await locator.count();
517
+ if (count > 0 && !info.failCause.count) {
518
+ info.failCause.count = count;
519
+ }
358
520
  //info.log += "total elements found " + count + "\n";
359
521
  //let visibleCount = 0;
360
522
  let visibleLocator = null;
@@ -372,6 +534,8 @@ class StableBrowser {
372
534
  foundLocators.push(locator.nth(j));
373
535
  }
374
536
  else {
537
+ info.failCause.visible = visible;
538
+ info.failCause.enabled = enabled;
375
539
  if (!info.printMessages) {
376
540
  info.printMessages = {};
377
541
  }
@@ -383,6 +547,11 @@ class StableBrowser {
383
547
  }
384
548
  }
385
549
  async closeUnexpectedPopups(info, _params) {
550
+ if (!info) {
551
+ info = {};
552
+ info.failCause = {};
553
+ info.log = "";
554
+ }
386
555
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
387
556
  if (!info) {
388
557
  info = {};
@@ -415,15 +584,20 @@ class StableBrowser {
415
584
  if (result.foundElements.length > 0) {
416
585
  let dialogCloseLocator = result.foundElements[0].locator;
417
586
  await dialogCloseLocator.click();
587
+ // wait for the dialog to close
588
+ await dialogCloseLocator.waitFor({ state: "hidden" });
418
589
  return { rerun: true };
419
590
  }
420
591
  }
421
592
  }
422
593
  return { rerun: false };
423
594
  }
424
- async _locate(selectors, info, _params, timeout = 30000) {
595
+ async _locate(selectors, info, _params, timeout) {
596
+ if (!timeout) {
597
+ timeout = 30000;
598
+ }
425
599
  for (let i = 0; i < 3; i++) {
426
- info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
600
+ info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
427
601
  for (let j = 0; j < selectors.locators.length; j++) {
428
602
  let selector = selectors.locators[j];
429
603
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
@@ -435,17 +609,49 @@ class StableBrowser {
435
609
  }
436
610
  throw new Error("unable to locate element " + JSON.stringify(selectors));
437
611
  }
438
- async _locate_internal(selectors, info, _params, timeout = 30000) {
439
- let highPriorityTimeout = 5000;
440
- let visibleOnlyTimeout = 6000;
441
- let startTime = performance.now();
442
- let locatorsCount = 0;
443
- //let arrayMode = Array.isArray(selectors);
612
+ async _findFrameScope(selectors, timeout = 30000, info) {
613
+ if (!info) {
614
+ info = {};
615
+ info.failCause = {};
616
+ info.log = "";
617
+ }
444
618
  let scope = this.page;
619
+ if (selectors.frame) {
620
+ return selectors.frame;
621
+ }
445
622
  if (selectors.iframe_src || selectors.frameLocators) {
446
- info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
623
+ const findFrame = async (frame, framescope) => {
624
+ for (let i = 0; i < frame.selectors.length; i++) {
625
+ let frameLocator = frame.selectors[i];
626
+ if (frameLocator.css) {
627
+ let testframescope = framescope.frameLocator(frameLocator.css);
628
+ if (frameLocator.index) {
629
+ testframescope = framescope.nth(frameLocator.index);
630
+ }
631
+ try {
632
+ await testframescope.owner().evaluateHandle(() => true, null, {
633
+ timeout: 5000,
634
+ });
635
+ framescope = testframescope;
636
+ break;
637
+ }
638
+ catch (error) {
639
+ console.error("frame not found " + frameLocator.css);
640
+ }
641
+ }
642
+ }
643
+ if (frame.children) {
644
+ return await findFrame(frame.children, framescope);
645
+ }
646
+ return framescope;
647
+ };
447
648
  while (true) {
448
649
  let frameFound = false;
650
+ if (selectors.nestFrmLoc) {
651
+ scope = await findFrame(selectors.nestFrmLoc, scope);
652
+ frameFound = true;
653
+ break;
654
+ }
449
655
  if (selectors.frameLocators) {
450
656
  for (let i = 0; i < selectors.frameLocators.length; i++) {
451
657
  let frameLocator = selectors.frameLocators[i];
@@ -462,6 +668,8 @@ class StableBrowser {
462
668
  if (!scope) {
463
669
  info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
464
670
  if (performance.now() - startTime > timeout) {
671
+ info.failCause.iframeNotFound = true;
672
+ info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
465
673
  throw new Error("unable to locate iframe " + selectors.iframe_src);
466
674
  }
467
675
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -471,6 +679,30 @@ class StableBrowser {
471
679
  }
472
680
  }
473
681
  }
682
+ if (!scope) {
683
+ scope = this.page;
684
+ }
685
+ return scope;
686
+ }
687
+ async _getDocumentBody(selectors, timeout = 30000, info) {
688
+ let scope = await this._findFrameScope(selectors, timeout, info);
689
+ return scope.evaluate(() => {
690
+ var bodyContent = document.body.innerHTML;
691
+ return bodyContent;
692
+ });
693
+ }
694
+ async _locate_internal(selectors, info, _params, timeout = 30000) {
695
+ if (!info) {
696
+ info = {};
697
+ info.failCause = {};
698
+ info.log = "";
699
+ }
700
+ let highPriorityTimeout = 5000;
701
+ let visibleOnlyTimeout = 6000;
702
+ let startTime = performance.now();
703
+ let locatorsCount = 0;
704
+ //let arrayMode = Array.isArray(selectors);
705
+ let scope = await this._findFrameScope(selectors, timeout, info);
474
706
  let selectorsLocators = null;
475
707
  selectorsLocators = selectors.locators;
476
708
  // group selectors by priority
@@ -572,6 +804,8 @@ class StableBrowser {
572
804
  }
573
805
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
574
806
  info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
807
+ info.failCause.locatorNotFound = true;
808
+ info.failCause.lastError = "failed to locate unique element";
575
809
  throw new Error("failed to locate first element no elements found, " + info.log);
576
810
  }
577
811
  async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
@@ -603,86 +837,129 @@ class StableBrowser {
603
837
  });
604
838
  result.locatorIndex = i;
605
839
  }
840
+ if (foundLocators.length > 1) {
841
+ info.failCause.foundMultiple = true;
842
+ }
606
843
  }
607
844
  return result;
608
845
  }
609
- async click(selectors, _params, options = {}, world = null) {
610
- this._validateSelectors(selectors);
846
+ async simpleClick(elementDescription, _params, options = {}, world = null) {
611
847
  const startTime = Date.now();
612
- const info = {};
613
- info.log = "***** click on " + selectors.element_name + " *****\n";
614
- info.operation = "click";
615
- info.selectors = selectors;
616
- let error = null;
617
- let screenshotId = null;
618
- let screenshotPath = null;
848
+ let timeout = 30000;
849
+ if (options && options.timeout) {
850
+ timeout = options.timeout;
851
+ }
852
+ while (true) {
853
+ try {
854
+ const result = await locate_element(this.context, elementDescription, "click");
855
+ if (result?.elementNumber >= 0) {
856
+ const selectors = {
857
+ frame: result?.frame,
858
+ locators: [
859
+ {
860
+ css: result?.css,
861
+ },
862
+ ],
863
+ };
864
+ await this.click(selectors, _params, options, world);
865
+ return;
866
+ }
867
+ }
868
+ catch (e) {
869
+ if (performance.now() - startTime > timeout) {
870
+ // throw e;
871
+ await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
872
+ }
873
+ }
874
+ await new Promise((resolve) => setTimeout(resolve, 3000));
875
+ }
876
+ }
877
+ async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
878
+ const startTime = Date.now();
879
+ let timeout = 30000;
880
+ if (options && options.timeout) {
881
+ timeout = options.timeout;
882
+ }
883
+ while (true) {
884
+ try {
885
+ const result = await locate_element(this.context, elementDescription, "fill", value);
886
+ if (result?.elementNumber >= 0) {
887
+ const selectors = {
888
+ frame: result?.frame,
889
+ locators: [
890
+ {
891
+ css: result?.css,
892
+ },
893
+ ],
894
+ };
895
+ await this.clickType(selectors, value, false, _params, options, world);
896
+ return;
897
+ }
898
+ }
899
+ catch (e) {
900
+ if (performance.now() - startTime > timeout) {
901
+ // throw e;
902
+ await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
903
+ }
904
+ }
905
+ await new Promise((resolve) => setTimeout(resolve, 3000));
906
+ }
907
+ }
908
+ async click(selectors, _params, options = {}, world = null) {
909
+ const state = {
910
+ selectors,
911
+ _params,
912
+ options,
913
+ world,
914
+ text: "Click element",
915
+ type: Types.CLICK,
916
+ operation: "click",
917
+ log: "***** click on " + selectors.element_name + " *****\n",
918
+ };
619
919
  try {
620
- let element = await this._locate(selectors, info, _params);
621
- await this.scrollIfNeeded(element, info);
622
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
920
+ await _preCommand(state, this);
921
+ if (state.options && state.options.context) {
922
+ state.selectors.locators[0].text = state.options.context;
923
+ }
623
924
  try {
624
- await this._highlightElements(element);
625
- await element.click({ timeout: 5000 });
925
+ await state.element.click();
626
926
  await new Promise((resolve) => setTimeout(resolve, 1000));
627
927
  }
628
928
  catch (e) {
629
929
  // await this.closeUnexpectedPopups();
630
- info.log += "click failed, will try again" + "\n";
631
- element = await this._locate(selectors, info, _params);
632
- await element.click({ timeout: 10000, force: true });
930
+ state.element = await this._locate(selectors, state.info, _params);
931
+ await state.element.dispatchEvent("click");
633
932
  await new Promise((resolve) => setTimeout(resolve, 1000));
634
933
  }
635
934
  await this.waitForPageLoad();
636
- return info;
935
+ return state.info;
637
936
  }
638
937
  catch (e) {
639
- this.logger.error("click failed " + info.log);
640
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
641
- info.screenshotPath = screenshotPath;
642
- Object.assign(e, { info: info });
643
- error = e;
644
- throw e;
938
+ await _commandError(state, e, this);
645
939
  }
646
940
  finally {
647
- const endTime = Date.now();
648
- this._reportToWorld(world, {
649
- element_name: selectors.element_name,
650
- type: Types.CLICK,
651
- text: `Click element`,
652
- screenshotId,
653
- result: error
654
- ? {
655
- status: "FAILED",
656
- startTime,
657
- endTime,
658
- message: error === null || error === void 0 ? void 0 : error.message,
659
- }
660
- : {
661
- status: "PASSED",
662
- startTime,
663
- endTime,
664
- },
665
- info: info,
666
- });
941
+ _commandFinally(state, this);
667
942
  }
668
943
  }
669
944
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
670
- this._validateSelectors(selectors);
671
- const startTime = Date.now();
672
- const info = {};
673
- info.log = "";
674
- info.operation = "setCheck";
675
- info.checked = checked;
676
- info.selectors = selectors;
677
- let error = null;
678
- let screenshotId = null;
679
- let screenshotPath = null;
945
+ const state = {
946
+ selectors,
947
+ _params,
948
+ options,
949
+ world,
950
+ type: checked ? Types.CHECK : Types.UNCHECK,
951
+ text: checked ? `Check element` : `Uncheck element`,
952
+ operation: "setCheck",
953
+ log: "***** check " + selectors.element_name + " *****\n",
954
+ };
680
955
  try {
681
- let element = await this._locate(selectors, info, _params);
682
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
956
+ await _preCommand(state, this);
957
+ state.info.checked = checked;
958
+ // let element = await this._locate(selectors, info, _params);
959
+ // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
683
960
  try {
684
- await this._highlightElements(element);
685
- await element.setChecked(checked, { timeout: 5000 });
961
+ // await this._highlightElements(element);
962
+ await state.element.setChecked(checked);
686
963
  await new Promise((resolve) => setTimeout(resolve, 1000));
687
964
  }
688
965
  catch (e) {
@@ -691,179 +968,108 @@ class StableBrowser {
691
968
  }
692
969
  else {
693
970
  //await this.closeUnexpectedPopups();
694
- info.log += "setCheck failed, will try again" + "\n";
695
- element = await this._locate(selectors, info, _params);
696
- await element.setChecked(checked, { timeout: 5000, force: true });
971
+ state.info.log += "setCheck failed, will try again" + "\n";
972
+ state.element = await this._locate(selectors, state.info, _params);
973
+ await state.element.setChecked(checked, { timeout: 5000, force: true });
697
974
  await new Promise((resolve) => setTimeout(resolve, 1000));
698
975
  }
699
976
  }
700
977
  await this.waitForPageLoad();
701
- return info;
978
+ return state.info;
702
979
  }
703
980
  catch (e) {
704
- this.logger.error("setCheck failed " + info.log);
705
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
706
- info.screenshotPath = screenshotPath;
707
- Object.assign(e, { info: info });
708
- error = e;
709
- throw e;
981
+ await _commandError(state, e, this);
710
982
  }
711
983
  finally {
712
- const endTime = Date.now();
713
- this._reportToWorld(world, {
714
- element_name: selectors.element_name,
715
- type: checked ? Types.CHECK : Types.UNCHECK,
716
- text: checked ? `Check element` : `Uncheck element`,
717
- screenshotId,
718
- result: error
719
- ? {
720
- status: "FAILED",
721
- startTime,
722
- endTime,
723
- message: error === null || error === void 0 ? void 0 : error.message,
724
- }
725
- : {
726
- status: "PASSED",
727
- startTime,
728
- endTime,
729
- },
730
- info: info,
731
- });
984
+ _commandFinally(state, this);
732
985
  }
733
986
  }
734
987
  async hover(selectors, _params, options = {}, world = null) {
735
- this._validateSelectors(selectors);
736
- const startTime = Date.now();
737
- const info = {};
738
- info.log = "";
739
- info.operation = "hover";
740
- info.selectors = selectors;
741
- let error = null;
742
- let screenshotId = null;
743
- let screenshotPath = null;
988
+ const state = {
989
+ selectors,
990
+ _params,
991
+ options,
992
+ world,
993
+ type: Types.HOVER,
994
+ text: `Hover element`,
995
+ operation: "hover",
996
+ log: "***** hover " + selectors.element_name + " *****\n",
997
+ };
744
998
  try {
745
- let element = await this._locate(selectors, info, _params);
746
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
999
+ await _preCommand(state, this);
747
1000
  try {
748
- await this._highlightElements(element);
749
- await element.hover({ timeout: 10000 });
1001
+ await state.element.hover();
750
1002
  await new Promise((resolve) => setTimeout(resolve, 1000));
751
1003
  }
752
1004
  catch (e) {
753
1005
  //await this.closeUnexpectedPopups();
754
- info.log += "hover failed, will try again" + "\n";
755
- element = await this._locate(selectors, info, _params);
756
- await element.hover({ timeout: 10000 });
1006
+ state.info.log += "hover failed, will try again" + "\n";
1007
+ state.element = await this._locate(selectors, state.info, _params);
1008
+ await state.element.hover({ timeout: 10000 });
757
1009
  await new Promise((resolve) => setTimeout(resolve, 1000));
758
1010
  }
759
1011
  await this.waitForPageLoad();
760
- return info;
1012
+ return state.info;
761
1013
  }
762
1014
  catch (e) {
763
- this.logger.error("hover failed " + info.log);
764
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
765
- info.screenshotPath = screenshotPath;
766
- Object.assign(e, { info: info });
767
- error = e;
768
- throw e;
1015
+ await _commandError(state, e, this);
769
1016
  }
770
1017
  finally {
771
- const endTime = Date.now();
772
- this._reportToWorld(world, {
773
- element_name: selectors.element_name,
774
- type: Types.HOVER,
775
- text: `Hover element`,
776
- screenshotId,
777
- result: error
778
- ? {
779
- status: "FAILED",
780
- startTime,
781
- endTime,
782
- message: error === null || error === void 0 ? void 0 : error.message,
783
- }
784
- : {
785
- status: "PASSED",
786
- startTime,
787
- endTime,
788
- },
789
- info: info,
790
- });
1018
+ _commandFinally(state, this);
791
1019
  }
792
1020
  }
793
1021
  async selectOption(selectors, values, _params = null, options = {}, world = null) {
794
- this._validateSelectors(selectors);
795
1022
  if (!values) {
796
1023
  throw new Error("values is null");
797
1024
  }
798
- const startTime = Date.now();
799
- let error = null;
800
- let screenshotId = null;
801
- let screenshotPath = null;
802
- const info = {};
803
- info.log = "";
804
- info.operation = "selectOptions";
805
- info.selectors = selectors;
1025
+ const state = {
1026
+ selectors,
1027
+ _params,
1028
+ options,
1029
+ world,
1030
+ value: values.toString(),
1031
+ type: Types.SELECT,
1032
+ text: `Select option: ${values}`,
1033
+ operation: "selectOption",
1034
+ log: "***** select option " + selectors.element_name + " *****\n",
1035
+ };
806
1036
  try {
807
- let element = await this._locate(selectors, info, _params);
808
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1037
+ await _preCommand(state, this);
809
1038
  try {
810
- await this._highlightElements(element);
811
- await element.selectOption(values, { timeout: 5000 });
1039
+ await state.element.selectOption(values);
812
1040
  }
813
1041
  catch (e) {
814
1042
  //await this.closeUnexpectedPopups();
815
- info.log += "selectOption failed, will try force" + "\n";
816
- await element.selectOption(values, { timeout: 10000, force: true });
1043
+ state.info.log += "selectOption failed, will try force" + "\n";
1044
+ await state.element.selectOption(values, { timeout: 10000, force: true });
817
1045
  }
818
1046
  await this.waitForPageLoad();
819
- return info;
1047
+ return state.info;
820
1048
  }
821
1049
  catch (e) {
822
- this.logger.error("selectOption failed " + info.log);
823
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
824
- info.screenshotPath = screenshotPath;
825
- Object.assign(e, { info: info });
826
- this.logger.info("click failed, will try next selector");
827
- error = e;
828
- throw e;
1050
+ await _commandError(state, e, this);
829
1051
  }
830
1052
  finally {
831
- const endTime = Date.now();
832
- this._reportToWorld(world, {
833
- element_name: selectors.element_name,
834
- type: Types.SELECT,
835
- text: `Select option: ${values}`,
836
- value: values.toString(),
837
- screenshotId,
838
- result: error
839
- ? {
840
- status: "FAILED",
841
- startTime,
842
- endTime,
843
- message: error === null || error === void 0 ? void 0 : error.message,
844
- }
845
- : {
846
- status: "PASSED",
847
- startTime,
848
- endTime,
849
- },
850
- info: info,
851
- });
1053
+ _commandFinally(state, this);
852
1054
  }
853
1055
  }
854
1056
  async type(_value, _params = null, options = {}, world = null) {
855
- const startTime = Date.now();
856
- let error = null;
857
- let screenshotId = null;
858
- let screenshotPath = null;
859
- const info = {};
860
- info.log = "";
861
- info.operation = "type";
862
- _value = this._fixUsingParams(_value, _params);
863
- info.value = _value;
1057
+ const state = {
1058
+ value: _value,
1059
+ _params,
1060
+ options,
1061
+ world,
1062
+ locate: false,
1063
+ scroll: false,
1064
+ highlight: false,
1065
+ type: Types.TYPE_PRESS,
1066
+ text: `Type value: ${_value}`,
1067
+ operation: "type",
1068
+ log: "",
1069
+ };
864
1070
  try {
865
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
866
- const valueSegment = _value.split("&&");
1071
+ await _preCommand(state, this);
1072
+ const valueSegment = state.value.split("&&");
867
1073
  for (let i = 0; i < valueSegment.length; i++) {
868
1074
  if (i > 0) {
869
1075
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -883,68 +1089,76 @@ class StableBrowser {
883
1089
  await this.page.keyboard.type(value);
884
1090
  }
885
1091
  }
886
- return info;
1092
+ return state.info;
887
1093
  }
888
1094
  catch (e) {
889
- //await this.closeUnexpectedPopups();
890
- this.logger.error("type failed " + info.log);
891
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
892
- info.screenshotPath = screenshotPath;
893
- Object.assign(e, { info: info });
894
- error = e;
895
- throw e;
1095
+ await _commandError(state, e, this);
896
1096
  }
897
1097
  finally {
898
- const endTime = Date.now();
899
- this._reportToWorld(world, {
900
- type: Types.TYPE_PRESS,
901
- screenshotId,
902
- value: _value,
903
- text: `type value: ${_value}`,
904
- result: error
905
- ? {
906
- status: "FAILED",
907
- startTime,
908
- endTime,
909
- message: error === null || error === void 0 ? void 0 : error.message,
910
- }
911
- : {
912
- status: "PASSED",
913
- startTime,
914
- endTime,
915
- },
916
- info: info,
917
- });
1098
+ _commandFinally(state, this);
1099
+ }
1100
+ }
1101
+ async setInputValue(selectors, value, _params = null, options = {}, world = null) {
1102
+ const state = {
1103
+ selectors,
1104
+ _params,
1105
+ value,
1106
+ options,
1107
+ world,
1108
+ type: Types.SET_INPUT,
1109
+ text: `Set input value`,
1110
+ operation: "setInputValue",
1111
+ log: "***** set input value " + selectors.element_name + " *****\n",
1112
+ };
1113
+ try {
1114
+ await _preCommand(state, this);
1115
+ let value = await this._replaceWithLocalData(state.value, this);
1116
+ try {
1117
+ await state.element.evaluateHandle((el, value) => {
1118
+ el.value = value;
1119
+ }, value);
1120
+ }
1121
+ catch (error) {
1122
+ this.logger.error("setInputValue failed, will try again");
1123
+ await _screenshot(state, this);
1124
+ Object.assign(error, { info: state.info });
1125
+ await state.element.evaluateHandle((el, value) => {
1126
+ el.value = value;
1127
+ });
1128
+ }
1129
+ }
1130
+ catch (e) {
1131
+ await _commandError(state, e, this);
1132
+ }
1133
+ finally {
1134
+ _commandFinally(state, this);
918
1135
  }
919
1136
  }
920
1137
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
921
- this._validateSelectors(selectors);
922
- const startTime = Date.now();
923
- let error = null;
924
- let screenshotId = null;
925
- let screenshotPath = null;
926
- const info = {};
927
- info.log = "";
928
- info.operation = Types.SET_DATE_TIME;
929
- info.selectors = selectors;
930
- info.value = value;
1138
+ const state = {
1139
+ selectors,
1140
+ _params,
1141
+ value: await this._replaceWithLocalData(value, this),
1142
+ options,
1143
+ world,
1144
+ type: Types.SET_DATE_TIME,
1145
+ text: `Set date time value: ${value}`,
1146
+ operation: "setDateTime",
1147
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1148
+ throwError: false,
1149
+ };
931
1150
  try {
932
- value = await this._replaceWithLocalData(value, this);
933
- let element = await this._locate(selectors, info, _params);
934
- //insert red border around the element
935
- await this.scrollIfNeeded(element, info);
936
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
937
- await this._highlightElements(element);
1151
+ await _preCommand(state, this);
938
1152
  try {
939
- await element.click();
1153
+ await state.element.click();
940
1154
  await new Promise((resolve) => setTimeout(resolve, 500));
941
1155
  if (format) {
942
- value = dayjs(value).format(format);
943
- await element.fill(value);
1156
+ state.value = dayjs(state.value).format(format);
1157
+ await state.element.fill(state.value);
944
1158
  }
945
1159
  else {
946
- const dateTimeValue = await getDateTimeValue({ value, element });
947
- await element.evaluateHandle((el, dateTimeValue) => {
1160
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1161
+ await state.element.evaluateHandle((el, dateTimeValue) => {
948
1162
  el.value = ""; // clear input
949
1163
  el.value = dateTimeValue;
950
1164
  }, dateTimeValue);
@@ -955,21 +1169,21 @@ class StableBrowser {
955
1169
  await this.waitForPageLoad();
956
1170
  }
957
1171
  }
958
- catch (error) {
1172
+ catch (err) {
959
1173
  //await this.closeUnexpectedPopups();
960
- this.logger.error("setting date time input failed " + JSON.stringify(info));
961
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
962
- info.screenshotPath = screenshotPath;
963
- Object.assign(error, { info: info });
1174
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1175
+ this.logger.info("Trying again");
1176
+ await _screenshot(state, this);
1177
+ Object.assign(err, { info: state.info });
964
1178
  await element.click();
965
1179
  await new Promise((resolve) => setTimeout(resolve, 500));
966
1180
  if (format) {
967
- value = dayjs(value).format(format);
968
- await element.fill(value);
1181
+ state.value = dayjs(state.value).format(format);
1182
+ await state.element.fill(state.value);
969
1183
  }
970
1184
  else {
971
- const dateTimeValue = await getDateTimeValue({ value, element });
972
- await element.evaluateHandle((el, dateTimeValue) => {
1185
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1186
+ await state.element.evaluateHandle((el, dateTimeValue) => {
973
1187
  el.value = ""; // clear input
974
1188
  el.value = dateTimeValue;
975
1189
  }, dateTimeValue);
@@ -981,130 +1195,40 @@ class StableBrowser {
981
1195
  }
982
1196
  }
983
1197
  }
984
- catch (error) {
985
- error = e;
986
- throw e;
987
- }
988
- finally {
989
- const endTime = Date.now();
990
- this._reportToWorld(world, {
991
- element_name: selectors.element_name,
992
- type: Types.SET_DATE_TIME,
993
- screenshotId,
994
- value: value,
995
- text: `setDateTime input with value: ${value}`,
996
- result: error
997
- ? {
998
- status: "FAILED",
999
- startTime,
1000
- endTime,
1001
- message: error === null || error === void 0 ? void 0 : error.message,
1002
- }
1003
- : {
1004
- status: "PASSED",
1005
- startTime,
1006
- endTime,
1007
- },
1008
- info: info,
1009
- });
1010
- }
1011
- }
1012
- async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
1013
- this._validateSelectors(selectors);
1014
- const startTime = Date.now();
1015
- let error = null;
1016
- let screenshotId = null;
1017
- let screenshotPath = null;
1018
- const info = {};
1019
- info.log = "";
1020
- info.operation = Types.SET_DATE_TIME;
1021
- info.selectors = selectors;
1022
- info.value = value;
1023
- try {
1024
- let element = await this._locate(selectors, info, _params);
1025
- //insert red border around the element
1026
- await this.scrollIfNeeded(element, info);
1027
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1028
- await this._highlightElements(element);
1029
- try {
1030
- await element.click();
1031
- await new Promise((resolve) => setTimeout(resolve, 500));
1032
- const dateTimeValue = await getDateTimeValue({ value, element });
1033
- await element.evaluateHandle((el, dateTimeValue) => {
1034
- el.value = ""; // clear input
1035
- el.value = dateTimeValue;
1036
- }, dateTimeValue);
1037
- }
1038
- catch (error) {
1039
- //await this.closeUnexpectedPopups();
1040
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1041
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1042
- info.screenshotPath = screenshotPath;
1043
- Object.assign(error, { info: info });
1044
- await element.click();
1045
- await new Promise((resolve) => setTimeout(resolve, 500));
1046
- const dateTimeValue = await getDateTimeValue({ value, element });
1047
- await element.evaluateHandle((el, dateTimeValue) => {
1048
- el.value = ""; // clear input
1049
- el.value = dateTimeValue;
1050
- }, dateTimeValue);
1051
- }
1052
- }
1053
- catch (error) {
1054
- error = e;
1055
- throw e;
1198
+ catch (e) {
1199
+ await _commandError(state, e, this);
1056
1200
  }
1057
1201
  finally {
1058
- const endTime = Date.now();
1059
- this._reportToWorld(world, {
1060
- element_name: selectors.element_name,
1061
- type: Types.SET_DATE_TIME,
1062
- screenshotId,
1063
- value: value,
1064
- text: `setDateTime input with value: ${value}`,
1065
- result: error
1066
- ? {
1067
- status: "FAILED",
1068
- startTime,
1069
- endTime,
1070
- message: error === null || error === void 0 ? void 0 : error.message,
1071
- }
1072
- : {
1073
- status: "PASSED",
1074
- startTime,
1075
- endTime,
1076
- },
1077
- info: info,
1078
- });
1202
+ _commandFinally(state, this);
1079
1203
  }
1080
1204
  }
1081
1205
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1082
- this._validateSelectors(selectors);
1083
- const startTime = Date.now();
1084
- let error = null;
1085
- let screenshotId = null;
1086
- let screenshotPath = null;
1087
- const info = {};
1088
- info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
1089
- info.operation = "clickType";
1090
- info.selectors = selectors;
1206
+ _value = unEscapeString(_value);
1091
1207
  const newValue = await this._replaceWithLocalData(_value, world);
1208
+ const state = {
1209
+ selectors,
1210
+ _params,
1211
+ value: newValue,
1212
+ originalValue: _value,
1213
+ options,
1214
+ world,
1215
+ type: Types.FILL,
1216
+ text: `Click type input with value: ${_value}`,
1217
+ operation: "clickType",
1218
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1219
+ };
1092
1220
  if (newValue !== _value) {
1093
1221
  //this.logger.info(_value + "=" + newValue);
1094
1222
  _value = newValue;
1095
1223
  }
1096
- info.value = _value;
1097
1224
  try {
1098
- let element = await this._locate(selectors, info, _params);
1099
- //insert red border around the element
1100
- await this.scrollIfNeeded(element, info);
1101
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1102
- await this._highlightElements(element);
1225
+ await _preCommand(state, this);
1226
+ state.info.value = _value;
1103
1227
  if (options === null || options === undefined || !options.press) {
1104
1228
  try {
1105
- let currentValue = await element.inputValue();
1229
+ let currentValue = await state.element.inputValue();
1106
1230
  if (currentValue) {
1107
- await element.fill("");
1231
+ await state.element.fill("");
1108
1232
  }
1109
1233
  }
1110
1234
  catch (e) {
@@ -1113,22 +1237,22 @@ class StableBrowser {
1113
1237
  }
1114
1238
  if (options === null || options === undefined || options.press) {
1115
1239
  try {
1116
- await element.click({ timeout: 5000 });
1240
+ await state.element.click({ timeout: 5000 });
1117
1241
  }
1118
1242
  catch (e) {
1119
- await element.dispatchEvent("click");
1243
+ await state.element.dispatchEvent("click");
1120
1244
  }
1121
1245
  }
1122
1246
  else {
1123
1247
  try {
1124
- await element.focus();
1248
+ await state.element.focus();
1125
1249
  }
1126
1250
  catch (e) {
1127
- await element.dispatchEvent("focus");
1251
+ await state.element.dispatchEvent("focus");
1128
1252
  }
1129
1253
  }
1130
1254
  await new Promise((resolve) => setTimeout(resolve, 500));
1131
- const valueSegment = _value.split("&&");
1255
+ const valueSegment = state.value.split("&&");
1132
1256
  for (let i = 0; i < valueSegment.length; i++) {
1133
1257
  if (i > 0) {
1134
1258
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1148,118 +1272,66 @@ class StableBrowser {
1148
1272
  await new Promise((resolve) => setTimeout(resolve, 500));
1149
1273
  }
1150
1274
  }
1275
+ await _screenshot(state, this);
1151
1276
  if (enter === true) {
1152
1277
  await new Promise((resolve) => setTimeout(resolve, 2000));
1153
1278
  await this.page.keyboard.press("Enter");
1154
1279
  await this.waitForPageLoad();
1155
1280
  }
1156
1281
  else if (enter === false) {
1157
- await element.dispatchEvent("change");
1282
+ await state.element.dispatchEvent("change");
1158
1283
  //await this.page.keyboard.press("Tab");
1159
1284
  }
1160
1285
  else {
1161
1286
  if (enter !== "" && enter !== null && enter !== undefined) {
1162
1287
  await this.page.keyboard.press(enter);
1163
- await this.waitForPageLoad();
1164
- }
1165
- }
1166
- return info;
1167
- }
1168
- catch (e) {
1169
- //await this.closeUnexpectedPopups();
1170
- this.logger.error("fill failed " + JSON.stringify(info));
1171
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1172
- info.screenshotPath = screenshotPath;
1173
- Object.assign(e, { info: info });
1174
- error = e;
1175
- throw e;
1176
- }
1177
- finally {
1178
- const endTime = Date.now();
1179
- this._reportToWorld(world, {
1180
- element_name: selectors.element_name,
1181
- type: Types.FILL,
1182
- screenshotId,
1183
- value: _value,
1184
- text: `clickType input with value: ${_value}`,
1185
- result: error
1186
- ? {
1187
- status: "FAILED",
1188
- startTime,
1189
- endTime,
1190
- message: error === null || error === void 0 ? void 0 : error.message,
1191
- }
1192
- : {
1193
- status: "PASSED",
1194
- startTime,
1195
- endTime,
1196
- },
1197
- info: info,
1198
- });
1288
+ await this.waitForPageLoad();
1289
+ }
1290
+ }
1291
+ return state.info;
1292
+ }
1293
+ catch (e) {
1294
+ await _commandError(state, e, this);
1295
+ }
1296
+ finally {
1297
+ _commandFinally(state, this);
1199
1298
  }
1200
1299
  }
1201
1300
  async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
1202
- this._validateSelectors(selectors);
1203
- const startTime = Date.now();
1204
- let error = null;
1205
- let screenshotId = null;
1206
- let screenshotPath = null;
1207
- const info = {};
1208
- info.log = "***** fill on " + selectors.element_name + " with value " + value + "*****\n";
1209
- info.operation = "fill";
1210
- info.selectors = selectors;
1211
- info.value = value;
1301
+ const state = {
1302
+ selectors,
1303
+ _params,
1304
+ value: unEscapeString(value),
1305
+ options,
1306
+ world,
1307
+ type: Types.FILL,
1308
+ text: `Fill input with value: ${value}`,
1309
+ operation: "fill",
1310
+ log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
1311
+ };
1212
1312
  try {
1213
- let element = await this._locate(selectors, info, _params);
1214
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1215
- await this._highlightElements(element);
1216
- await element.fill(value, { timeout: 10000 });
1217
- await element.dispatchEvent("change");
1313
+ await _preCommand(state, this);
1314
+ await state.element.fill(value);
1315
+ await state.element.dispatchEvent("change");
1218
1316
  if (enter) {
1219
1317
  await new Promise((resolve) => setTimeout(resolve, 2000));
1220
1318
  await this.page.keyboard.press("Enter");
1221
1319
  }
1222
1320
  await this.waitForPageLoad();
1223
- return info;
1321
+ return state.info;
1224
1322
  }
1225
1323
  catch (e) {
1226
- //await this.closeUnexpectedPopups();
1227
- this.logger.error("fill failed " + info.log);
1228
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1229
- info.screenshotPath = screenshotPath;
1230
- Object.assign(e, { info: info });
1231
- error = e;
1232
- throw e;
1324
+ await _commandError(state, e, this);
1233
1325
  }
1234
1326
  finally {
1235
- const endTime = Date.now();
1236
- this._reportToWorld(world, {
1237
- element_name: selectors.element_name,
1238
- type: Types.FILL,
1239
- screenshotId,
1240
- value,
1241
- text: `Fill input with value: ${value}`,
1242
- result: error
1243
- ? {
1244
- status: "FAILED",
1245
- startTime,
1246
- endTime,
1247
- message: error === null || error === void 0 ? void 0 : error.message,
1248
- }
1249
- : {
1250
- status: "PASSED",
1251
- startTime,
1252
- endTime,
1253
- },
1254
- info: info,
1255
- });
1327
+ _commandFinally(state, this);
1256
1328
  }
1257
1329
  }
1258
1330
  async getText(selectors, _params = null, options = {}, info = {}, world = null) {
1259
1331
  return await this._getText(selectors, 0, _params, options, info, world);
1260
1332
  }
1261
1333
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1262
- this._validateSelectors(selectors);
1334
+ _validateSelectors(selectors);
1263
1335
  let screenshotId = null;
1264
1336
  let screenshotPath = null;
1265
1337
  if (!info.log) {
@@ -1303,165 +1375,124 @@ class StableBrowser {
1303
1375
  }
1304
1376
  }
1305
1377
  async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
1306
- var _a;
1307
- this._validateSelectors(selectors);
1308
1378
  if (!pattern) {
1309
1379
  throw new Error("pattern is null");
1310
1380
  }
1311
1381
  if (!text) {
1312
1382
  throw new Error("text is null");
1313
1383
  }
1384
+ const state = {
1385
+ selectors,
1386
+ _params,
1387
+ pattern,
1388
+ value: pattern,
1389
+ options,
1390
+ world,
1391
+ locate: false,
1392
+ scroll: false,
1393
+ screenshot: false,
1394
+ highlight: false,
1395
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1396
+ text: `Verify element contains pattern: ${pattern}`,
1397
+ operation: "containsPattern",
1398
+ log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1399
+ };
1314
1400
  const newValue = await this._replaceWithLocalData(text, world);
1315
1401
  if (newValue !== text) {
1316
1402
  this.logger.info(text + "=" + newValue);
1317
1403
  text = newValue;
1318
1404
  }
1319
- const startTime = Date.now();
1320
- let error = null;
1321
- let screenshotId = null;
1322
- let screenshotPath = null;
1323
- const info = {};
1324
- info.log =
1325
- "***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
1326
- info.operation = "containsPattern";
1327
- info.selectors = selectors;
1328
- info.value = text;
1329
- info.pattern = pattern;
1330
1405
  let foundObj = null;
1331
1406
  try {
1332
- foundObj = await this._getText(selectors, 0, _params, options, info, world);
1407
+ await _preCommand(state, this);
1408
+ state.info.pattern = pattern;
1409
+ foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
1333
1410
  if (foundObj && foundObj.element) {
1334
- await this.scrollIfNeeded(foundObj.element, info);
1411
+ await this.scrollIfNeeded(foundObj.element, state.info);
1335
1412
  }
1336
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1413
+ await _screenshot(state, this);
1337
1414
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1338
1415
  pattern = pattern.replace("{text}", escapedText);
1339
1416
  let regex = new RegExp(pattern, "im");
1340
- if (!regex.test(foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) && !((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(text))) {
1341
- info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1417
+ if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
1418
+ state.info.foundText = foundObj?.text;
1342
1419
  throw new Error("element doesn't contain text " + text);
1343
1420
  }
1344
- return info;
1421
+ return state.info;
1345
1422
  }
1346
1423
  catch (e) {
1347
- //await this.closeUnexpectedPopups();
1348
- this.logger.error("verify element contains text failed " + info.log);
1349
- this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
1350
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1351
- info.screenshotPath = screenshotPath;
1352
- Object.assign(e, { info: info });
1353
- error = e;
1354
- throw e;
1424
+ this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
1425
+ await _commandError(state, e, this);
1355
1426
  }
1356
1427
  finally {
1357
- const endTime = Date.now();
1358
- this._reportToWorld(world, {
1359
- element_name: selectors.element_name,
1360
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1361
- value: pattern,
1362
- text: `Verify element contains pattern: ${pattern}`,
1363
- screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
1364
- result: error
1365
- ? {
1366
- status: "FAILED",
1367
- startTime,
1368
- endTime,
1369
- message: error === null || error === void 0 ? void 0 : error.message,
1370
- }
1371
- : {
1372
- status: "PASSED",
1373
- startTime,
1374
- endTime,
1375
- },
1376
- info: info,
1377
- });
1428
+ _commandFinally(state, this);
1378
1429
  }
1379
1430
  }
1380
1431
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1381
- var _a, _b, _c;
1382
- this._validateSelectors(selectors);
1432
+ const state = {
1433
+ selectors,
1434
+ _params,
1435
+ value: text,
1436
+ options,
1437
+ world,
1438
+ locate: false,
1439
+ scroll: false,
1440
+ screenshot: false,
1441
+ highlight: false,
1442
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1443
+ text: `Verify element contains text: ${text}`,
1444
+ operation: "containsText",
1445
+ log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
1446
+ };
1383
1447
  if (!text) {
1384
1448
  throw new Error("text is null");
1385
1449
  }
1386
- const startTime = Date.now();
1387
- let error = null;
1388
- let screenshotId = null;
1389
- let screenshotPath = null;
1390
- const info = {};
1391
- info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
1392
- info.operation = "containsText";
1393
- info.selectors = selectors;
1450
+ text = unEscapeString(text);
1394
1451
  const newValue = await this._replaceWithLocalData(text, world);
1395
1452
  if (newValue !== text) {
1396
1453
  this.logger.info(text + "=" + newValue);
1397
1454
  text = newValue;
1398
1455
  }
1399
- info.value = text;
1400
1456
  let foundObj = null;
1401
1457
  try {
1402
- foundObj = await this._getText(selectors, climb, _params, options, info, world);
1458
+ await _preCommand(state, this);
1459
+ foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1403
1460
  if (foundObj && foundObj.element) {
1404
- await this.scrollIfNeeded(foundObj.element, info);
1461
+ await this.scrollIfNeeded(foundObj.element, state.info);
1405
1462
  }
1406
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1463
+ await _screenshot(state, this);
1407
1464
  const dateAlternatives = findDateAlternatives(text);
1408
1465
  const numberAlternatives = findNumberAlternatives(text);
1409
1466
  if (dateAlternatives.date) {
1410
1467
  for (let i = 0; i < dateAlternatives.dates.length; i++) {
1411
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(dateAlternatives.dates[i])) ||
1412
- ((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(dateAlternatives.dates[i]))) {
1413
- return info;
1468
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1469
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1470
+ return state.info;
1414
1471
  }
1415
1472
  }
1416
1473
  throw new Error("element doesn't contain text " + text);
1417
1474
  }
1418
1475
  else if (numberAlternatives.number) {
1419
1476
  for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1420
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(numberAlternatives.numbers[i])) ||
1421
- ((_b = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _b === void 0 ? void 0 : _b.includes(numberAlternatives.numbers[i]))) {
1422
- return info;
1477
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1478
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1479
+ return state.info;
1423
1480
  }
1424
1481
  }
1425
1482
  throw new Error("element doesn't contain text " + text);
1426
1483
  }
1427
- else if (!(foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(text)) && !((_c = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _c === void 0 ? void 0 : _c.includes(text))) {
1428
- info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1429
- info.value = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value;
1484
+ else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1485
+ state.info.foundText = foundObj?.text;
1486
+ state.info.value = foundObj?.value;
1430
1487
  throw new Error("element doesn't contain text " + text);
1431
1488
  }
1432
- return info;
1489
+ return state.info;
1433
1490
  }
1434
1491
  catch (e) {
1435
- //await this.closeUnexpectedPopups();
1436
- this.logger.error("verify element contains text failed " + info.log);
1437
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1438
- info.screenshotPath = screenshotPath;
1439
- Object.assign(e, { info: info });
1440
- error = e;
1441
- throw e;
1492
+ await _commandError(state, e, this);
1442
1493
  }
1443
1494
  finally {
1444
- const endTime = Date.now();
1445
- this._reportToWorld(world, {
1446
- element_name: selectors.element_name,
1447
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1448
- text: `Verify element contains text: ${text}`,
1449
- value: text,
1450
- screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
1451
- result: error
1452
- ? {
1453
- status: "FAILED",
1454
- startTime,
1455
- endTime,
1456
- message: error === null || error === void 0 ? void 0 : error.message,
1457
- }
1458
- : {
1459
- status: "PASSED",
1460
- startTime,
1461
- endTime,
1462
- },
1463
- info: info,
1464
- });
1495
+ _commandFinally(state, this);
1465
1496
  }
1466
1497
  }
1467
1498
  _getDataFile(world = null) {
@@ -1480,6 +1511,29 @@ class StableBrowser {
1480
1511
  }
1481
1512
  return dataFile;
1482
1513
  }
1514
+ async waitForUserInput(message, world = null) {
1515
+ if (!message) {
1516
+ message = "# Wait for user input. Press any key to continue";
1517
+ }
1518
+ else {
1519
+ message = "# Wait for user input. " + message;
1520
+ }
1521
+ message += "\n";
1522
+ const value = await new Promise((resolve) => {
1523
+ const rl = readline.createInterface({
1524
+ input: process.stdin,
1525
+ output: process.stdout,
1526
+ });
1527
+ rl.question(message, (answer) => {
1528
+ rl.close();
1529
+ resolve(answer);
1530
+ });
1531
+ });
1532
+ if (value) {
1533
+ this.logger.info(`{{userInput}} was set to: ${value}`);
1534
+ }
1535
+ this.setTestData({ userInput: value }, world);
1536
+ }
1483
1537
  setTestData(testData, world = null) {
1484
1538
  if (!testData) {
1485
1539
  return;
@@ -1507,7 +1561,7 @@ class StableBrowser {
1507
1561
  const data = fs.readFileSync(filePath, "utf8");
1508
1562
  const results = [];
1509
1563
  return new Promise((resolve, reject) => {
1510
- const readableStream = new stream.Readable();
1564
+ const readableStream = new Readable();
1511
1565
  readableStream._read = () => { }; // _read is required but you can noop it
1512
1566
  readableStream.push(data);
1513
1567
  readableStream.push(null);
@@ -1667,7 +1721,6 @@ class StableBrowser {
1667
1721
  }
1668
1722
  async takeScreenshot(screenshotPath) {
1669
1723
  const playContext = this.context.playContext;
1670
- const client = await playContext.newCDPSession(this.page);
1671
1724
  // Using CDP to capture the screenshot
1672
1725
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
1673
1726
  document.body.scrollWidth,
@@ -1677,164 +1730,105 @@ class StableBrowser {
1677
1730
  document.body.clientWidth,
1678
1731
  document.documentElement.clientWidth,
1679
1732
  ])));
1680
- const viewportHeight = Math.max(...(await this.page.evaluate(() => [
1681
- document.body.scrollHeight,
1682
- document.documentElement.scrollHeight,
1683
- document.body.offsetHeight,
1684
- document.documentElement.offsetHeight,
1685
- document.body.clientHeight,
1686
- document.documentElement.clientHeight,
1687
- ])));
1688
- const { data } = await client.send("Page.captureScreenshot", {
1689
- format: "png",
1690
- clip: {
1691
- x: 0,
1692
- y: 0,
1693
- width: viewportWidth,
1694
- height: viewportHeight,
1695
- scale: 1,
1696
- },
1697
- });
1698
- if (!screenshotPath) {
1699
- return data;
1700
- }
1701
- let screenshotBuffer = Buffer.from(data, "base64");
1702
- const sharpBuffer = sharp(screenshotBuffer);
1703
- const metadata = await sharpBuffer.metadata();
1704
- //check if you are on retina display and reduce the quality of the image
1705
- if (metadata.width > viewportWidth || metadata.height > viewportHeight) {
1706
- screenshotBuffer = await sharpBuffer
1707
- .resize(viewportWidth, viewportHeight, {
1708
- fit: sharp.fit.inside,
1709
- withoutEnlargement: true,
1710
- })
1711
- .toBuffer();
1712
- }
1713
- fs.writeFileSync(screenshotPath, screenshotBuffer);
1714
- await client.detach();
1733
+ let screenshotBuffer = null;
1734
+ if (this.context.browserName === "chromium") {
1735
+ const client = await playContext.newCDPSession(this.page);
1736
+ const { data } = await client.send("Page.captureScreenshot", {
1737
+ format: "png",
1738
+ // clip: {
1739
+ // x: 0,
1740
+ // y: 0,
1741
+ // width: viewportWidth,
1742
+ // height: viewportHeight,
1743
+ // scale: 1,
1744
+ // },
1745
+ });
1746
+ await client.detach();
1747
+ if (!screenshotPath) {
1748
+ return data;
1749
+ }
1750
+ screenshotBuffer = Buffer.from(data, "base64");
1751
+ }
1752
+ else {
1753
+ screenshotBuffer = await this.page.screenshot();
1754
+ }
1755
+ let image = await Jimp.read(screenshotBuffer);
1756
+ // Get the image dimensions
1757
+ const { width, height } = image.bitmap;
1758
+ const resizeRatio = viewportWidth / width;
1759
+ // Resize the image to fit within the viewport dimensions without enlarging
1760
+ if (width > viewportWidth) {
1761
+ image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
1762
+ await image.write(screenshotPath);
1763
+ }
1764
+ else {
1765
+ fs.writeFileSync(screenshotPath, screenshotBuffer);
1766
+ }
1715
1767
  }
1716
1768
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1717
- this._validateSelectors(selectors);
1718
- const startTime = Date.now();
1719
- let error = null;
1720
- let screenshotId = null;
1721
- let screenshotPath = null;
1769
+ const state = {
1770
+ selectors,
1771
+ _params,
1772
+ options,
1773
+ world,
1774
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1775
+ text: `Verify element exists in page`,
1776
+ operation: "verifyElementExistInPage",
1777
+ log: "***** verify element " + selectors.element_name + " exists in page *****\n",
1778
+ };
1722
1779
  await new Promise((resolve) => setTimeout(resolve, 2000));
1723
- const info = {};
1724
- info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
1725
- info.operation = "verify";
1726
- info.selectors = selectors;
1727
1780
  try {
1728
- const element = await this._locate(selectors, info, _params);
1729
- if (element) {
1730
- await this.scrollIfNeeded(element, info);
1731
- }
1732
- await this._highlightElements(element);
1733
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1734
- await expect(element).toHaveCount(1, { timeout: 10000 });
1735
- return info;
1781
+ await _preCommand(state, this);
1782
+ await expect(state.element).toHaveCount(1, { timeout: 10000 });
1783
+ return state.info;
1736
1784
  }
1737
1785
  catch (e) {
1738
- //await this.closeUnexpectedPopups();
1739
- this.logger.error("verify failed " + info.log);
1740
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1741
- info.screenshotPath = screenshotPath;
1742
- Object.assign(e, { info: info });
1743
- error = e;
1744
- throw e;
1786
+ await _commandError(state, e, this);
1745
1787
  }
1746
1788
  finally {
1747
- const endTime = Date.now();
1748
- this._reportToWorld(world, {
1749
- element_name: selectors.element_name,
1750
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1751
- text: "Verify element exists in page",
1752
- screenshotId,
1753
- result: error
1754
- ? {
1755
- status: "FAILED",
1756
- startTime,
1757
- endTime,
1758
- message: error === null || error === void 0 ? void 0 : error.message,
1759
- }
1760
- : {
1761
- status: "PASSED",
1762
- startTime,
1763
- endTime,
1764
- },
1765
- info: info,
1766
- });
1789
+ _commandFinally(state, this);
1767
1790
  }
1768
1791
  }
1769
1792
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1770
- this._validateSelectors(selectors);
1771
- const startTime = Date.now();
1772
- let error = null;
1773
- let screenshotId = null;
1774
- let screenshotPath = null;
1793
+ const state = {
1794
+ selectors,
1795
+ _params,
1796
+ attribute,
1797
+ variable,
1798
+ options,
1799
+ world,
1800
+ type: Types.EXTRACT,
1801
+ text: `Extract attribute from element`,
1802
+ operation: "extractAttribute",
1803
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1804
+ };
1775
1805
  await new Promise((resolve) => setTimeout(resolve, 2000));
1776
- const info = {};
1777
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1778
- info.operation = "extract";
1779
- info.selectors = selectors;
1780
1806
  try {
1781
- const element = await this._locate(selectors, info, _params);
1782
- await this._highlightElements(element);
1783
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1807
+ await _preCommand(state, this);
1784
1808
  switch (attribute) {
1785
1809
  case "inner_text":
1786
- info.value = await element.innerText();
1810
+ state.value = await state.element.innerText();
1787
1811
  break;
1788
1812
  case "href":
1789
- info.value = await element.getAttribute("href");
1813
+ state.value = await state.element.getAttribute("href");
1790
1814
  break;
1791
1815
  case "value":
1792
- info.value = await element.inputValue();
1816
+ state.value = await state.element.inputValue();
1793
1817
  break;
1794
1818
  default:
1795
- info.value = await element.getAttribute(attribute);
1819
+ state.value = await state.element.getAttribute(attribute);
1796
1820
  break;
1797
1821
  }
1798
- this[variable] = info.value;
1799
- if (world) {
1800
- world[variable] = info.value;
1801
- }
1802
- this.setTestData({ [variable]: info.value }, world);
1803
- this.logger.info("set test data: " + variable + "=" + info.value);
1804
- return info;
1822
+ state.info.value = state.value;
1823
+ this.setTestData({ [variable]: state.value }, world);
1824
+ this.logger.info("set test data: " + variable + "=" + state.value);
1825
+ return state.info;
1805
1826
  }
1806
1827
  catch (e) {
1807
- //await this.closeUnexpectedPopups();
1808
- this.logger.error("extract failed " + info.log);
1809
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1810
- info.screenshotPath = screenshotPath;
1811
- Object.assign(e, { info: info });
1812
- error = e;
1813
- throw e;
1828
+ await _commandError(state, e, this);
1814
1829
  }
1815
1830
  finally {
1816
- const endTime = Date.now();
1817
- this._reportToWorld(world, {
1818
- element_name: selectors.element_name,
1819
- type: Types.EXTRACT_ATTRIBUTE,
1820
- variable: variable,
1821
- value: info.value,
1822
- text: "Extract attribute from element",
1823
- screenshotId,
1824
- result: error
1825
- ? {
1826
- status: "FAILED",
1827
- startTime,
1828
- endTime,
1829
- message: error === null || error === void 0 ? void 0 : error.message,
1830
- }
1831
- : {
1832
- status: "PASSED",
1833
- startTime,
1834
- endTime,
1835
- },
1836
- info: info,
1837
- });
1831
+ _commandFinally(state, this);
1838
1832
  }
1839
1833
  }
1840
1834
  async extractEmailData(emailAddress, options, world) {
@@ -1911,7 +1905,8 @@ class StableBrowser {
1911
1905
  catch (e) {
1912
1906
  errorCount++;
1913
1907
  if (errorCount > 3) {
1914
- throw e;
1908
+ // throw e;
1909
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1915
1910
  }
1916
1911
  // ignore
1917
1912
  }
@@ -2022,7 +2017,8 @@ class StableBrowser {
2022
2017
  info.screenshotPath = screenshotPath;
2023
2018
  Object.assign(e, { info: info });
2024
2019
  error = e;
2025
- throw e;
2020
+ // throw e;
2021
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2026
2022
  }
2027
2023
  finally {
2028
2024
  const endTime = Date.now();
@@ -2035,7 +2031,7 @@ class StableBrowser {
2035
2031
  status: "FAILED",
2036
2032
  startTime,
2037
2033
  endTime,
2038
- message: error === null || error === void 0 ? void 0 : error.message,
2034
+ message: error?.message,
2039
2035
  }
2040
2036
  : {
2041
2037
  status: "PASSED",
@@ -2047,52 +2043,59 @@ class StableBrowser {
2047
2043
  }
2048
2044
  }
2049
2045
  async verifyTextExistInPage(text, options = {}, world = null) {
2050
- const startTime = Date.now();
2046
+ text = unEscapeString(text);
2047
+ const state = {
2048
+ text_search: text,
2049
+ options,
2050
+ world,
2051
+ locate: false,
2052
+ scroll: false,
2053
+ highlight: false,
2054
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2055
+ text: `Verify text exists in page`,
2056
+ operation: "verifyTextExistInPage",
2057
+ log: "***** verify text " + text + " exists in page *****\n",
2058
+ };
2051
2059
  const timeout = this._getLoadTimeout(options);
2052
- let error = null;
2053
- let screenshotId = null;
2054
- let screenshotPath = null;
2055
2060
  await new Promise((resolve) => setTimeout(resolve, 2000));
2056
- const info = {};
2057
- info.log = "***** verify text " + text + " exists in page *****\n";
2058
- info.operation = "verifyTextExistInPage";
2059
2061
  const newValue = await this._replaceWithLocalData(text, world);
2060
2062
  if (newValue !== text) {
2061
2063
  this.logger.info(text + "=" + newValue);
2062
2064
  text = newValue;
2063
2065
  }
2064
- info.text = text;
2065
2066
  let dateAlternatives = findDateAlternatives(text);
2066
2067
  let numberAlternatives = findNumberAlternatives(text);
2067
2068
  try {
2069
+ await _preCommand(state, this);
2070
+ state.info.text = text;
2068
2071
  while (true) {
2069
2072
  const frames = this.page.frames();
2070
2073
  let results = [];
2071
2074
  for (let i = 0; i < frames.length; i++) {
2072
2075
  if (dateAlternatives.date) {
2073
2076
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2074
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
2077
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2075
2078
  result.frame = frames[i];
2076
2079
  results.push(result);
2077
2080
  }
2078
2081
  }
2079
2082
  else if (numberAlternatives.number) {
2080
2083
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2081
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
2084
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2082
2085
  result.frame = frames[i];
2083
2086
  results.push(result);
2084
2087
  }
2085
2088
  }
2086
2089
  else {
2087
- const result = await this._locateElementByText(frames[i], text, "*", true, {});
2090
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2088
2091
  result.frame = frames[i];
2089
2092
  results.push(result);
2090
2093
  }
2091
2094
  }
2092
- info.results = results;
2095
+ state.info.results = results;
2093
2096
  const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2094
2097
  if (resultWithElementsFound.length === 0) {
2095
- if (Date.now() - startTime > timeout) {
2098
+ if (Date.now() - state.startTime > timeout) {
2096
2099
  throw new Error(`Text ${text} not found in page`);
2097
2100
  }
2098
2101
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2104,44 +2107,20 @@ class StableBrowser {
2104
2107
  await this._highlightElements(frame, dataAttribute);
2105
2108
  const element = await frame.$(dataAttribute);
2106
2109
  if (element) {
2107
- await this.scrollIfNeeded(element, info);
2110
+ await this.scrollIfNeeded(element, state.info);
2108
2111
  await element.dispatchEvent("bvt_verify_page_contains_text");
2109
2112
  }
2110
2113
  }
2111
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2112
- return info;
2114
+ await _screenshot(state, this);
2115
+ return state.info;
2113
2116
  }
2114
2117
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2115
2118
  }
2116
2119
  catch (e) {
2117
- //await this.closeUnexpectedPopups();
2118
- this.logger.error("verify text exist in page failed " + info.log);
2119
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2120
- info.screenshotPath = screenshotPath;
2121
- Object.assign(e, { info: info });
2122
- error = e;
2123
- throw e;
2120
+ await _commandError(state, e, this);
2124
2121
  }
2125
2122
  finally {
2126
- const endTime = Date.now();
2127
- this._reportToWorld(world, {
2128
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2129
- text: "Verify text exists in page",
2130
- screenshotId,
2131
- result: error
2132
- ? {
2133
- status: "FAILED",
2134
- startTime,
2135
- endTime,
2136
- message: error === null || error === void 0 ? void 0 : error.message,
2137
- }
2138
- : {
2139
- status: "PASSED",
2140
- startTime,
2141
- endTime,
2142
- },
2143
- info: info,
2144
- });
2123
+ _commandFinally(state, this);
2145
2124
  }
2146
2125
  }
2147
2126
  _getServerUrl() {
@@ -2204,7 +2183,8 @@ class StableBrowser {
2204
2183
  info.screenshotPath = screenshotPath;
2205
2184
  Object.assign(e, { info: info });
2206
2185
  error = e;
2207
- throw e;
2186
+ // throw e;
2187
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2208
2188
  }
2209
2189
  finally {
2210
2190
  const endTime = Date.now();
@@ -2217,7 +2197,7 @@ class StableBrowser {
2217
2197
  status: "FAILED",
2218
2198
  startTime,
2219
2199
  endTime,
2220
- message: error === null || error === void 0 ? void 0 : error.message,
2200
+ message: error?.message,
2221
2201
  }
2222
2202
  : {
2223
2203
  status: "PASSED",
@@ -2249,7 +2229,7 @@ class StableBrowser {
2249
2229
  this.logger.info("Table data verified");
2250
2230
  }
2251
2231
  async getTableData(selectors, _params = null, options = {}, world = null) {
2252
- this._validateSelectors(selectors);
2232
+ _validateSelectors(selectors);
2253
2233
  const startTime = Date.now();
2254
2234
  let error = null;
2255
2235
  let screenshotId = null;
@@ -2271,7 +2251,8 @@ class StableBrowser {
2271
2251
  info.screenshotPath = screenshotPath;
2272
2252
  Object.assign(e, { info: info });
2273
2253
  error = e;
2274
- throw e;
2254
+ // throw e;
2255
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2275
2256
  }
2276
2257
  finally {
2277
2258
  const endTime = Date.now();
@@ -2285,7 +2266,7 @@ class StableBrowser {
2285
2266
  status: "FAILED",
2286
2267
  startTime,
2287
2268
  endTime,
2288
- message: error === null || error === void 0 ? void 0 : error.message,
2269
+ message: error?.message,
2289
2270
  }
2290
2271
  : {
2291
2272
  status: "PASSED",
@@ -2297,7 +2278,7 @@ class StableBrowser {
2297
2278
  }
2298
2279
  }
2299
2280
  async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
2300
- this._validateSelectors(selectors);
2281
+ _validateSelectors(selectors);
2301
2282
  if (!query) {
2302
2283
  throw new Error("query is null");
2303
2284
  }
@@ -2436,7 +2417,8 @@ class StableBrowser {
2436
2417
  info.screenshotPath = screenshotPath;
2437
2418
  Object.assign(e, { info: info });
2438
2419
  error = e;
2439
- throw e;
2420
+ // throw e;
2421
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2440
2422
  }
2441
2423
  finally {
2442
2424
  const endTime = Date.now();
@@ -2450,7 +2432,7 @@ class StableBrowser {
2450
2432
  status: "FAILED",
2451
2433
  startTime,
2452
2434
  endTime,
2453
- message: error === null || error === void 0 ? void 0 : error.message,
2435
+ message: error?.message,
2454
2436
  }
2455
2437
  : {
2456
2438
  status: "PASSED",
@@ -2462,27 +2444,7 @@ class StableBrowser {
2462
2444
  }
2463
2445
  }
2464
2446
  async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
2465
- if (!value) {
2466
- return value;
2467
- }
2468
- // find all the accurance of {{(.*?)}} and replace with the value
2469
- let regex = /{{(.*?)}}/g;
2470
- let matches = value.match(regex);
2471
- if (matches) {
2472
- const testData = this.getTestData(world);
2473
- for (let i = 0; i < matches.length; i++) {
2474
- let match = matches[i];
2475
- let key = match.substring(2, match.length - 2);
2476
- let newValue = objectPath.get(testData, key, null);
2477
- if (newValue !== null) {
2478
- value = value.replace(match, newValue);
2479
- }
2480
- }
2481
- }
2482
- if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
2483
- return await decrypt(value, null, totpWait);
2484
- }
2485
- return value;
2447
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2486
2448
  }
2487
2449
  _getLoadTimeout(options) {
2488
2450
  let timeout = 15000;
@@ -2519,13 +2481,13 @@ class StableBrowser {
2519
2481
  }
2520
2482
  catch (e) {
2521
2483
  if (e.label === "networkidle") {
2522
- console.log("waitted for the network to be idle timeout");
2484
+ console.log("waited for the network to be idle timeout");
2523
2485
  }
2524
2486
  else if (e.label === "load") {
2525
- console.log("waitted for the load timeout");
2487
+ console.log("waited for the load timeout");
2526
2488
  }
2527
2489
  else if (e.label === "domcontentloaded") {
2528
- console.log("waitted for the domcontent loaded timeout");
2490
+ console.log("waited for the domcontent loaded timeout");
2529
2491
  }
2530
2492
  console.log(".");
2531
2493
  }
@@ -2542,7 +2504,7 @@ class StableBrowser {
2542
2504
  status: "FAILED",
2543
2505
  startTime,
2544
2506
  endTime,
2545
- message: error === null || error === void 0 ? void 0 : error.message,
2507
+ message: error?.message,
2546
2508
  }
2547
2509
  : {
2548
2510
  status: "PASSED",
@@ -2553,46 +2515,28 @@ class StableBrowser {
2553
2515
  }
2554
2516
  }
2555
2517
  async closePage(options = {}, world = null) {
2556
- const startTime = Date.now();
2557
- let error = null;
2558
- let screenshotId = null;
2559
- let screenshotPath = null;
2560
- const info = {};
2518
+ const state = {
2519
+ options,
2520
+ world,
2521
+ locate: false,
2522
+ scroll: false,
2523
+ highlight: false,
2524
+ type: Types.CLOSE_PAGE,
2525
+ text: `Close page`,
2526
+ operation: "closePage",
2527
+ log: "***** close page *****\n",
2528
+ throwError: false,
2529
+ };
2561
2530
  try {
2531
+ await _preCommand(state, this);
2562
2532
  await this.page.close();
2563
- if (this.context && this.context.pages && this.context.pages.length > 0) {
2564
- this.context.pages.pop();
2565
- this.page = this.context.pages[this.context.pages.length - 1];
2566
- this.context.page = this.page;
2567
- let title = await this.page.title();
2568
- console.log("Switched to page " + title);
2569
- }
2570
2533
  }
2571
2534
  catch (e) {
2572
2535
  console.log(".");
2536
+ await _commandError(state, e, this);
2573
2537
  }
2574
2538
  finally {
2575
- await new Promise((resolve) => setTimeout(resolve, 2000));
2576
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2577
- const endTime = Date.now();
2578
- this._reportToWorld(world, {
2579
- type: Types.CLOSE_PAGE,
2580
- text: "close page",
2581
- screenshotId,
2582
- result: error
2583
- ? {
2584
- status: "FAILED",
2585
- startTime,
2586
- endTime,
2587
- message: error === null || error === void 0 ? void 0 : error.message,
2588
- }
2589
- : {
2590
- status: "PASSED",
2591
- startTime,
2592
- endTime,
2593
- },
2594
- info: info,
2595
- });
2539
+ _commandFinally(state, this);
2596
2540
  }
2597
2541
  }
2598
2542
  async setViewportSize(width, hight, options = {}, world = null) {
@@ -2612,6 +2556,7 @@ class StableBrowser {
2612
2556
  }
2613
2557
  catch (e) {
2614
2558
  console.log(".");
2559
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2615
2560
  }
2616
2561
  finally {
2617
2562
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -2626,7 +2571,7 @@ class StableBrowser {
2626
2571
  status: "FAILED",
2627
2572
  startTime,
2628
2573
  endTime,
2629
- message: error === null || error === void 0 ? void 0 : error.message,
2574
+ message: error?.message,
2630
2575
  }
2631
2576
  : {
2632
2577
  status: "PASSED",
@@ -2648,6 +2593,7 @@ class StableBrowser {
2648
2593
  }
2649
2594
  catch (e) {
2650
2595
  console.log(".");
2596
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2651
2597
  }
2652
2598
  finally {
2653
2599
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -2662,7 +2608,7 @@ class StableBrowser {
2662
2608
  status: "FAILED",
2663
2609
  startTime,
2664
2610
  endTime,
2665
- message: error === null || error === void 0 ? void 0 : error.message,
2611
+ message: error?.message,
2666
2612
  }
2667
2613
  : {
2668
2614
  status: "PASSED",
@@ -2675,33 +2621,18 @@ class StableBrowser {
2675
2621
  }
2676
2622
  async scrollIfNeeded(element, info) {
2677
2623
  try {
2678
- let didScroll = await element.evaluate((node) => {
2679
- const rect = node.getBoundingClientRect();
2680
- if (rect &&
2681
- rect.top >= 0 &&
2682
- rect.left >= 0 &&
2683
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
2684
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
2685
- return false;
2686
- }
2687
- else {
2688
- node.scrollIntoView({
2689
- behavior: "smooth",
2690
- block: "center",
2691
- inline: "center",
2692
- });
2693
- return true;
2694
- }
2624
+ await element.scrollIntoViewIfNeeded({
2625
+ timeout: 2000,
2695
2626
  });
2696
- if (didScroll) {
2697
- await new Promise((resolve) => setTimeout(resolve, 500));
2698
- if (info) {
2699
- info.box = await element.boundingBox();
2700
- }
2627
+ await new Promise((resolve) => setTimeout(resolve, 500));
2628
+ if (info) {
2629
+ info.box = await element.boundingBox({
2630
+ timeout: 1000,
2631
+ });
2701
2632
  }
2702
2633
  }
2703
2634
  catch (e) {
2704
- console.log("scroll failed");
2635
+ console.log("#-#");
2705
2636
  }
2706
2637
  }
2707
2638
  _reportToWorld(world, properties) {
@@ -2862,5 +2793,10 @@ const KEYBOARD_EVENTS = [
2862
2793
  "TVAntennaCable",
2863
2794
  "TVAudioDescription",
2864
2795
  ];
2796
+ function unEscapeString(str) {
2797
+ const placeholder = "__NEWLINE__";
2798
+ str = str.replace(new RegExp(placeholder, "g"), "\n");
2799
+ return str;
2800
+ }
2865
2801
  export { StableBrowser };
2866
2802
  //# sourceMappingURL=stable_browser.js.map