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