automation_model 1.0.432-dev → 1.0.432-stage

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,19 +2,21 @@
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";
16
15
  import { Readable } from "node:stream";
17
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";
18
20
  const Types = {
19
21
  CLICK: "click_element",
20
22
  NAVIGATE: "navigate",
@@ -42,15 +44,24 @@ const Types = {
42
44
  LOAD_DATA: "load_data",
43
45
  SET_INPUT: "set_input",
44
46
  };
47
+ export const apps = {};
45
48
  class StableBrowser {
46
- constructor(browser, page, logger = null, context = null) {
49
+ browser;
50
+ page;
51
+ logger;
52
+ context;
53
+ world;
54
+ project_path = null;
55
+ webLogFile = null;
56
+ networkLogger = null;
57
+ configuration = null;
58
+ appName = "main";
59
+ constructor(browser, page, logger = null, context = null, world = null) {
47
60
  this.browser = browser;
48
61
  this.page = page;
49
62
  this.logger = logger;
50
63
  this.context = context;
51
- this.project_path = null;
52
- this.webLogFile = null;
53
- this.configuration = null;
64
+ this.world = world;
54
65
  if (!this.logger) {
55
66
  this.logger = console;
56
67
  }
@@ -76,11 +87,17 @@ class StableBrowser {
76
87
  this.logger.error("unable to read ai_config.json");
77
88
  }
78
89
  const logFolder = path.join(this.project_path, "logs", "web");
79
- this.webLogFile = this.getWebLogFile(logFolder);
80
- this.registerConsoleLogListener(page, context, this.webLogFile);
81
- this.registerRequestListener();
90
+ this.world = world;
82
91
  context.pages = [this.page];
83
92
  context.pageLoading = { status: false };
93
+ this.registerEventListeners(this.context);
94
+ }
95
+ registerEventListeners(context) {
96
+ this.registerConsoleLogListener(this.page, context);
97
+ this.registerRequestListener(this.page, context, this.webLogFile);
98
+ if (!context.pageLoading) {
99
+ context.pageLoading = { status: false };
100
+ }
84
101
  context.playContext.on("page", async function (page) {
85
102
  context.pageLoading.status = true;
86
103
  this.page = page;
@@ -110,6 +127,36 @@ class StableBrowser {
110
127
  context.pageLoading.status = false;
111
128
  }.bind(this));
112
129
  }
130
+ async switchApp(appName) {
131
+ // check if the current app (this.appName) is the same as the new app
132
+ if (this.appName === appName) {
133
+ return;
134
+ }
135
+ let navigate = false;
136
+ if (!apps[appName]) {
137
+ let newContext = await getContext(null, false, this.logger, appName, false, this);
138
+ navigate = true;
139
+ apps[appName] = {
140
+ context: newContext,
141
+ browser: newContext.browser,
142
+ page: newContext.page,
143
+ };
144
+ }
145
+ const tempContext = {};
146
+ this._copyContext(this, tempContext);
147
+ this._copyContext(apps[appName], this);
148
+ apps[this.appName] = tempContext;
149
+ this.appName = appName;
150
+ if (navigate) {
151
+ await this.goto(this.context.environment.baseUrl);
152
+ await this.waitForPageLoad();
153
+ }
154
+ }
155
+ _copyContext(from, to) {
156
+ to.browser = from.browser;
157
+ to.page = from.page;
158
+ to.context = from.context;
159
+ }
113
160
  getWebLogFile(logFolder) {
114
161
  if (!fs.existsSync(logFolder)) {
115
162
  fs.mkdirSync(logFolder, { recursive: true });
@@ -121,37 +168,63 @@ class StableBrowser {
121
168
  const fileName = nextIndex + ".json";
122
169
  return path.join(logFolder, fileName);
123
170
  }
124
- registerConsoleLogListener(page, context, logFile) {
171
+ registerConsoleLogListener(page, context) {
125
172
  if (!this.context.webLogger) {
126
173
  this.context.webLogger = [];
127
174
  }
128
175
  page.on("console", async (msg) => {
129
- this.context.webLogger.push({
176
+ const obj = {
130
177
  type: msg.type(),
131
178
  text: msg.text(),
132
179
  location: msg.location(),
133
180
  time: new Date().toISOString(),
134
- });
135
- await fs.promises.writeFile(logFile, JSON.stringify(this.context.webLogger, null, 2));
181
+ };
182
+ this.context.webLogger.push(obj);
183
+ if (msg.type() === "error") {
184
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
185
+ }
136
186
  });
137
187
  }
138
- registerRequestListener() {
139
- this.page.on("request", async (data) => {
188
+ registerRequestListener(page, context, logFile) {
189
+ if (!this.context.networkLogger) {
190
+ this.context.networkLogger = [];
191
+ }
192
+ page.on("request", async (data) => {
193
+ const startTime = new Date().getTime();
140
194
  try {
141
- const pageUrl = new URL(this.page.url());
195
+ const pageUrl = new URL(page.url());
142
196
  const requestUrl = new URL(data.url());
143
197
  if (pageUrl.hostname === requestUrl.hostname) {
144
198
  const method = data.method();
145
- if (method === "POST" || method === "GET" || method === "PUT" || method === "DELETE" || method === "PATCH") {
199
+ if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
146
200
  const token = await data.headerValue("Authorization");
147
201
  if (token) {
148
- this.context.authtoken = token;
202
+ context.authtoken = token;
149
203
  }
150
204
  }
151
205
  }
206
+ const response = await data.response();
207
+ const endTime = new Date().getTime();
208
+ const obj = {
209
+ url: data.url(),
210
+ method: data.method(),
211
+ postData: data.postData(),
212
+ error: data.failure() ? data.failure().errorText : null,
213
+ duration: endTime - startTime,
214
+ startTime,
215
+ };
216
+ context.networkLogger.push(obj);
217
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
152
218
  }
153
219
  catch (error) {
154
220
  console.error("Error in request listener", error);
221
+ context.networkLogger.push({
222
+ error: "not able to listen",
223
+ message: error.message,
224
+ stack: error.stack,
225
+ time: new Date().toISOString(),
226
+ });
227
+ // await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
155
228
  }
156
229
  });
157
230
  }
@@ -166,20 +239,6 @@ class StableBrowser {
166
239
  timeout: 60000,
167
240
  });
168
241
  }
169
- _validateSelectors(selectors) {
170
- if (!selectors) {
171
- throw new Error("selectors is null");
172
- }
173
- if (!selectors.locators) {
174
- throw new Error("selectors.locators is null");
175
- }
176
- if (!Array.isArray(selectors.locators)) {
177
- throw new Error("selectors.locators expected to be array");
178
- }
179
- if (selectors.locators.length === 0) {
180
- throw new Error("selectors.locators expected to be non empty array");
181
- }
182
- }
183
242
  _fixUsingParams(text, _params) {
184
243
  if (!_params || typeof text !== "string") {
185
244
  return text;
@@ -247,7 +306,7 @@ class StableBrowser {
247
306
  locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
248
307
  }
249
308
  }
250
- if (locator === null || locator === void 0 ? void 0 : locator.engine) {
309
+ if (locator?.engine) {
251
310
  if (locator.engine === "css") {
252
311
  locatorReturn = scope.locator(locator.selector);
253
312
  }
@@ -268,7 +327,10 @@ class StableBrowser {
268
327
  return locatorReturn;
269
328
  }
270
329
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
271
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
330
+ if (css && css.locator) {
331
+ css = css.locator;
332
+ }
333
+ let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
272
334
  if (result.elementCount === 0) {
273
335
  return;
274
336
  }
@@ -283,7 +345,7 @@ class StableBrowser {
283
345
  }
284
346
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
285
347
  //const stringifyText = JSON.stringify(text);
286
- return await scope.evaluate(([text, tag, regex, partial]) => {
348
+ return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
287
349
  function isParent(parent, child) {
288
350
  let currentNode = child.parentNode;
289
351
  while (currentNode !== null) {
@@ -295,6 +357,15 @@ class StableBrowser {
295
357
  return false;
296
358
  }
297
359
  document.isParent = isParent;
360
+ function getRegex(str) {
361
+ const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
362
+ if (!match) {
363
+ return null;
364
+ }
365
+ let [_, pattern, flags] = match;
366
+ return new RegExp(pattern, flags);
367
+ }
368
+ document.getRegex = getRegex;
298
369
  function collectAllShadowDomElements(element, result = []) {
299
370
  // Check and add the element if it has a shadow root
300
371
  if (element.shadowRoot) {
@@ -313,6 +384,10 @@ class StableBrowser {
313
384
  if (!tag) {
314
385
  tag = "*";
315
386
  }
387
+ let regexpSearch = document.getRegex(text);
388
+ if (regexpSearch) {
389
+ regex = true;
390
+ }
316
391
  let elements = Array.from(document.querySelectorAll(tag));
317
392
  let shadowHosts = [];
318
393
  document.collectAllShadowDomElements(document, shadowHosts);
@@ -328,7 +403,9 @@ class StableBrowser {
328
403
  let randomToken = null;
329
404
  const foundElements = [];
330
405
  if (regex) {
331
- let regexpSearch = new RegExp(text, "im");
406
+ if (!regexpSearch) {
407
+ regexpSearch = new RegExp(text, "im");
408
+ }
332
409
  for (let i = 0; i < elements.length; i++) {
333
410
  const element = elements[i];
334
411
  if ((element.innerText && regexpSearch.test(element.innerText)) ||
@@ -342,8 +419,8 @@ class StableBrowser {
342
419
  for (let i = 0; i < elements.length; i++) {
343
420
  const element = elements[i];
344
421
  if (partial) {
345
- if ((element.innerText && element.innerText.trim().includes(text)) ||
346
- (element.value && element.value.includes(text))) {
422
+ if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
423
+ (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
347
424
  foundElements.push(element);
348
425
  }
349
426
  }
@@ -388,6 +465,12 @@ class StableBrowser {
388
465
  }
389
466
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
390
467
  let locatorSearch = selectorHierarchy[index];
468
+ try {
469
+ locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
470
+ }
471
+ catch (e) {
472
+ console.error(e);
473
+ }
391
474
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
392
475
  let locator = null;
393
476
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
@@ -476,13 +559,18 @@ class StableBrowser {
476
559
  if (result.foundElements.length > 0) {
477
560
  let dialogCloseLocator = result.foundElements[0].locator;
478
561
  await dialogCloseLocator.click();
562
+ // wait for the dialog to close
563
+ await dialogCloseLocator.waitFor({ state: "hidden" });
479
564
  return { rerun: true };
480
565
  }
481
566
  }
482
567
  }
483
568
  return { rerun: false };
484
569
  }
485
- async _locate(selectors, info, _params, timeout = 30000) {
570
+ async _locate(selectors, info, _params, timeout) {
571
+ if (!timeout) {
572
+ timeout = 30000;
573
+ }
486
574
  for (let i = 0; i < 3; i++) {
487
575
  info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
488
576
  for (let j = 0; j < selectors.locators.length; j++) {
@@ -496,17 +584,44 @@ class StableBrowser {
496
584
  }
497
585
  throw new Error("unable to locate element " + JSON.stringify(selectors));
498
586
  }
499
- async _locate_internal(selectors, info, _params, timeout = 30000) {
500
- let highPriorityTimeout = 5000;
501
- let visibleOnlyTimeout = 6000;
502
- let startTime = performance.now();
503
- let locatorsCount = 0;
504
- //let arrayMode = Array.isArray(selectors);
587
+ async _findFrameScope(selectors, timeout = 30000) {
505
588
  let scope = this.page;
589
+ if (selectors.frame) {
590
+ return selectors.frame;
591
+ }
506
592
  if (selectors.iframe_src || selectors.frameLocators) {
507
- info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
593
+ const findFrame = async (frame, framescope) => {
594
+ for (let i = 0; i < frame.selectors.length; i++) {
595
+ let frameLocator = frame.selectors[i];
596
+ if (frameLocator.css) {
597
+ let testframescope = framescope.frameLocator(frameLocator.css);
598
+ if (frameLocator.index) {
599
+ testframescope = framescope.nth(frameLocator.index);
600
+ }
601
+ try {
602
+ await testframescope.owner().evaluateHandle(() => true, null, {
603
+ timeout: 5000,
604
+ });
605
+ framescope = testframescope;
606
+ break;
607
+ }
608
+ catch (error) {
609
+ console.error("frame not found " + frameLocator.css);
610
+ }
611
+ }
612
+ }
613
+ if (frame.children) {
614
+ return await findFrame(frame.children, framescope);
615
+ }
616
+ return framescope;
617
+ };
508
618
  while (true) {
509
619
  let frameFound = false;
620
+ if (selectors.nestFrmLoc) {
621
+ scope = await findFrame(selectors.nestFrmLoc, scope);
622
+ frameFound = true;
623
+ break;
624
+ }
510
625
  if (selectors.frameLocators) {
511
626
  for (let i = 0; i < selectors.frameLocators.length; i++) {
512
627
  let frameLocator = selectors.frameLocators[i];
@@ -532,6 +647,25 @@ class StableBrowser {
532
647
  }
533
648
  }
534
649
  }
650
+ if (!scope) {
651
+ scope = this.page;
652
+ }
653
+ return scope;
654
+ }
655
+ async _getDocumentBody(selectors, timeout = 30000) {
656
+ let scope = await this._findFrameScope(selectors, timeout);
657
+ return scope.evaluate(() => {
658
+ var bodyContent = document.body.innerHTML;
659
+ return bodyContent;
660
+ });
661
+ }
662
+ async _locate_internal(selectors, info, _params, timeout = 30000) {
663
+ let highPriorityTimeout = 5000;
664
+ let visibleOnlyTimeout = 6000;
665
+ let startTime = performance.now();
666
+ let locatorsCount = 0;
667
+ //let arrayMode = Array.isArray(selectors);
668
+ let scope = await this._findFrameScope(selectors, timeout);
535
669
  let selectorsLocators = null;
536
670
  selectorsLocators = selectors.locators;
537
671
  // group selectors by priority
@@ -667,86 +801,121 @@ class StableBrowser {
667
801
  }
668
802
  return result;
669
803
  }
670
- async click(selectors, _params, options = {}, world = null) {
671
- this._validateSelectors(selectors);
804
+ async simpleClick(elementDescription, _params, options = {}, world = null) {
672
805
  const startTime = Date.now();
673
- if (options && options.context) {
674
- selectors.locators[0].text = options.context;
806
+ let timeout = 30000;
807
+ if (options && options.timeout) {
808
+ timeout = options.timeout;
675
809
  }
676
- const info = {};
677
- info.log = "***** click on " + selectors.element_name + " *****\n";
678
- info.operation = "click";
679
- info.selectors = selectors;
680
- let error = null;
681
- let screenshotId = null;
682
- let screenshotPath = null;
810
+ while (true) {
811
+ try {
812
+ const result = await locate_element(this.context, elementDescription, "click");
813
+ if (result?.elementNumber >= 0) {
814
+ const selectors = {
815
+ frame: result?.frame,
816
+ locators: [
817
+ {
818
+ css: result?.css,
819
+ },
820
+ ],
821
+ };
822
+ await this.click(selectors, _params, options, world);
823
+ return;
824
+ }
825
+ }
826
+ catch (e) {
827
+ if (performance.now() - startTime > timeout) {
828
+ throw e;
829
+ }
830
+ }
831
+ await new Promise((resolve) => setTimeout(resolve, 3000));
832
+ }
833
+ }
834
+ async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
835
+ const startTime = Date.now();
836
+ let timeout = 30000;
837
+ if (options && options.timeout) {
838
+ timeout = options.timeout;
839
+ }
840
+ while (true) {
841
+ try {
842
+ const result = await locate_element(this.context, elementDescription, "fill", value);
843
+ if (result?.elementNumber >= 0) {
844
+ const selectors = {
845
+ frame: result?.frame,
846
+ locators: [
847
+ {
848
+ css: result?.css,
849
+ },
850
+ ],
851
+ };
852
+ await this.clickType(selectors, value, false, _params, options, world);
853
+ return;
854
+ }
855
+ }
856
+ catch (e) {
857
+ if (performance.now() - startTime > timeout) {
858
+ throw e;
859
+ }
860
+ }
861
+ await new Promise((resolve) => setTimeout(resolve, 3000));
862
+ }
863
+ }
864
+ async click(selectors, _params, options = {}, world = null) {
865
+ const state = {
866
+ selectors,
867
+ _params,
868
+ options,
869
+ world,
870
+ text: "Click element",
871
+ type: Types.CLICK,
872
+ operation: "click",
873
+ log: "***** click on " + selectors.element_name + " *****\n",
874
+ };
683
875
  try {
684
- let element = await this._locate(selectors, info, _params);
685
- await this.scrollIfNeeded(element, info);
686
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
876
+ await _preCommand(state, this);
877
+ if (state.options && state.options.context) {
878
+ state.selectors.locators[0].text = state.options.context;
879
+ }
687
880
  try {
688
- await this._highlightElements(element);
689
- await element.click();
881
+ await state.element.click();
690
882
  await new Promise((resolve) => setTimeout(resolve, 1000));
691
883
  }
692
884
  catch (e) {
693
885
  // await this.closeUnexpectedPopups();
694
- info.log += "click failed, will try again" + "\n";
695
- element = await this._locate(selectors, info, _params);
696
- await element.dispatchEvent("click");
886
+ state.element = await this._locate(selectors, state.info, _params);
887
+ await state.element.dispatchEvent("click");
697
888
  await new Promise((resolve) => setTimeout(resolve, 1000));
698
889
  }
699
890
  await this.waitForPageLoad();
700
- return info;
891
+ return state.info;
701
892
  }
702
893
  catch (e) {
703
- this.logger.error("click failed " + info.log);
704
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
705
- info.screenshotPath = screenshotPath;
706
- Object.assign(e, { info: info });
707
- error = e;
708
- throw e;
894
+ await _commandError(state, e, this);
709
895
  }
710
896
  finally {
711
- const endTime = Date.now();
712
- this._reportToWorld(world, {
713
- element_name: selectors.element_name,
714
- type: Types.CLICK,
715
- text: `Click element`,
716
- screenshotId,
717
- result: error
718
- ? {
719
- status: "FAILED",
720
- startTime,
721
- endTime,
722
- message: error === null || error === void 0 ? void 0 : error.message,
723
- }
724
- : {
725
- status: "PASSED",
726
- startTime,
727
- endTime,
728
- },
729
- info: info,
730
- });
897
+ _commandFinally(state, this);
731
898
  }
732
899
  }
733
900
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
734
- this._validateSelectors(selectors);
735
- const startTime = Date.now();
736
- const info = {};
737
- info.log = "";
738
- info.operation = "setCheck";
739
- info.checked = checked;
740
- info.selectors = selectors;
741
- let error = null;
742
- let screenshotId = null;
743
- let screenshotPath = null;
901
+ const state = {
902
+ selectors,
903
+ _params,
904
+ options,
905
+ world,
906
+ type: checked ? Types.CHECK : Types.UNCHECK,
907
+ text: checked ? `Check element` : `Uncheck element`,
908
+ operation: "setCheck",
909
+ log: "***** check " + selectors.element_name + " *****\n",
910
+ };
744
911
  try {
745
- let element = await this._locate(selectors, info, _params);
746
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
912
+ await _preCommand(state, this);
913
+ state.info.checked = checked;
914
+ // let element = await this._locate(selectors, info, _params);
915
+ // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
747
916
  try {
748
- await this._highlightElements(element);
749
- await element.setChecked(checked);
917
+ // await this._highlightElements(element);
918
+ await state.element.setChecked(checked);
750
919
  await new Promise((resolve) => setTimeout(resolve, 1000));
751
920
  }
752
921
  catch (e) {
@@ -755,179 +924,108 @@ class StableBrowser {
755
924
  }
756
925
  else {
757
926
  //await this.closeUnexpectedPopups();
758
- info.log += "setCheck failed, will try again" + "\n";
759
- element = await this._locate(selectors, info, _params);
760
- await element.setChecked(checked, { timeout: 5000, force: true });
927
+ state.info.log += "setCheck failed, will try again" + "\n";
928
+ state.element = await this._locate(selectors, state.info, _params);
929
+ await state.element.setChecked(checked, { timeout: 5000, force: true });
761
930
  await new Promise((resolve) => setTimeout(resolve, 1000));
762
931
  }
763
932
  }
764
933
  await this.waitForPageLoad();
765
- return info;
934
+ return state.info;
766
935
  }
767
936
  catch (e) {
768
- this.logger.error("setCheck failed " + info.log);
769
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
770
- info.screenshotPath = screenshotPath;
771
- Object.assign(e, { info: info });
772
- error = e;
773
- throw e;
937
+ await _commandError(state, e, this);
774
938
  }
775
939
  finally {
776
- const endTime = Date.now();
777
- this._reportToWorld(world, {
778
- element_name: selectors.element_name,
779
- type: checked ? Types.CHECK : Types.UNCHECK,
780
- text: checked ? `Check element` : `Uncheck element`,
781
- screenshotId,
782
- result: error
783
- ? {
784
- status: "FAILED",
785
- startTime,
786
- endTime,
787
- message: error === null || error === void 0 ? void 0 : error.message,
788
- }
789
- : {
790
- status: "PASSED",
791
- startTime,
792
- endTime,
793
- },
794
- info: info,
795
- });
940
+ _commandFinally(state, this);
796
941
  }
797
942
  }
798
943
  async hover(selectors, _params, options = {}, world = null) {
799
- this._validateSelectors(selectors);
800
- const startTime = Date.now();
801
- const info = {};
802
- info.log = "";
803
- info.operation = "hover";
804
- info.selectors = selectors;
805
- let error = null;
806
- let screenshotId = null;
807
- let screenshotPath = null;
944
+ const state = {
945
+ selectors,
946
+ _params,
947
+ options,
948
+ world,
949
+ type: Types.HOVER,
950
+ text: `Hover element`,
951
+ operation: "hover",
952
+ log: "***** hover " + selectors.element_name + " *****\n",
953
+ };
808
954
  try {
809
- let element = await this._locate(selectors, info, _params);
810
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
955
+ await _preCommand(state, this);
811
956
  try {
812
- await this._highlightElements(element);
813
- await element.hover();
957
+ await state.element.hover();
814
958
  await new Promise((resolve) => setTimeout(resolve, 1000));
815
959
  }
816
960
  catch (e) {
817
961
  //await this.closeUnexpectedPopups();
818
- info.log += "hover failed, will try again" + "\n";
819
- element = await this._locate(selectors, info, _params);
820
- await element.hover({ timeout: 10000 });
962
+ state.info.log += "hover failed, will try again" + "\n";
963
+ state.element = await this._locate(selectors, state.info, _params);
964
+ await state.element.hover({ timeout: 10000 });
821
965
  await new Promise((resolve) => setTimeout(resolve, 1000));
822
966
  }
823
967
  await this.waitForPageLoad();
824
- return info;
968
+ return state.info;
825
969
  }
826
970
  catch (e) {
827
- this.logger.error("hover failed " + info.log);
828
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
829
- info.screenshotPath = screenshotPath;
830
- Object.assign(e, { info: info });
831
- error = e;
832
- throw e;
971
+ await _commandError(state, e, this);
833
972
  }
834
973
  finally {
835
- const endTime = Date.now();
836
- this._reportToWorld(world, {
837
- element_name: selectors.element_name,
838
- type: Types.HOVER,
839
- text: `Hover element`,
840
- screenshotId,
841
- result: error
842
- ? {
843
- status: "FAILED",
844
- startTime,
845
- endTime,
846
- message: error === null || error === void 0 ? void 0 : error.message,
847
- }
848
- : {
849
- status: "PASSED",
850
- startTime,
851
- endTime,
852
- },
853
- info: info,
854
- });
974
+ _commandFinally(state, this);
855
975
  }
856
976
  }
857
977
  async selectOption(selectors, values, _params = null, options = {}, world = null) {
858
- this._validateSelectors(selectors);
859
978
  if (!values) {
860
979
  throw new Error("values is null");
861
980
  }
862
- const startTime = Date.now();
863
- let error = null;
864
- let screenshotId = null;
865
- let screenshotPath = null;
866
- const info = {};
867
- info.log = "";
868
- info.operation = "selectOptions";
869
- info.selectors = selectors;
981
+ const state = {
982
+ selectors,
983
+ _params,
984
+ options,
985
+ world,
986
+ value: values.toString(),
987
+ type: Types.SELECT,
988
+ text: `Select option: ${values}`,
989
+ operation: "selectOption",
990
+ log: "***** select option " + selectors.element_name + " *****\n",
991
+ };
870
992
  try {
871
- let element = await this._locate(selectors, info, _params);
872
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
993
+ await _preCommand(state, this);
873
994
  try {
874
- await this._highlightElements(element);
875
- await element.selectOption(values);
995
+ await state.element.selectOption(values);
876
996
  }
877
997
  catch (e) {
878
998
  //await this.closeUnexpectedPopups();
879
- info.log += "selectOption failed, will try force" + "\n";
880
- await element.selectOption(values, { timeout: 10000, force: true });
999
+ state.info.log += "selectOption failed, will try force" + "\n";
1000
+ await state.element.selectOption(values, { timeout: 10000, force: true });
881
1001
  }
882
1002
  await this.waitForPageLoad();
883
- return info;
1003
+ return state.info;
884
1004
  }
885
1005
  catch (e) {
886
- this.logger.error("selectOption failed " + info.log);
887
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
888
- info.screenshotPath = screenshotPath;
889
- Object.assign(e, { info: info });
890
- this.logger.info("click failed, will try next selector");
891
- error = e;
892
- throw e;
1006
+ await _commandError(state, e, this);
893
1007
  }
894
1008
  finally {
895
- const endTime = Date.now();
896
- this._reportToWorld(world, {
897
- element_name: selectors.element_name,
898
- type: Types.SELECT,
899
- text: `Select option: ${values}`,
900
- value: values.toString(),
901
- screenshotId,
902
- result: error
903
- ? {
904
- status: "FAILED",
905
- startTime,
906
- endTime,
907
- message: error === null || error === void 0 ? void 0 : error.message,
908
- }
909
- : {
910
- status: "PASSED",
911
- startTime,
912
- endTime,
913
- },
914
- info: info,
915
- });
1009
+ _commandFinally(state, this);
916
1010
  }
917
1011
  }
918
1012
  async type(_value, _params = null, options = {}, world = null) {
919
- const startTime = Date.now();
920
- let error = null;
921
- let screenshotId = null;
922
- let screenshotPath = null;
923
- const info = {};
924
- info.log = "";
925
- info.operation = "type";
926
- _value = this._fixUsingParams(_value, _params);
927
- info.value = _value;
1013
+ const state = {
1014
+ value: _value,
1015
+ _params,
1016
+ options,
1017
+ world,
1018
+ locate: false,
1019
+ scroll: false,
1020
+ highlight: false,
1021
+ type: Types.TYPE_PRESS,
1022
+ text: `Type value: ${_value}`,
1023
+ operation: "type",
1024
+ log: "",
1025
+ };
928
1026
  try {
929
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
930
- const valueSegment = _value.split("&&");
1027
+ await _preCommand(state, this);
1028
+ const valueSegment = state.value.split("&&");
931
1029
  for (let i = 0; i < valueSegment.length; i++) {
932
1030
  if (i > 0) {
933
1031
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -947,108 +1045,53 @@ class StableBrowser {
947
1045
  await this.page.keyboard.type(value);
948
1046
  }
949
1047
  }
950
- return info;
1048
+ return state.info;
951
1049
  }
952
1050
  catch (e) {
953
- //await this.closeUnexpectedPopups();
954
- this.logger.error("type failed " + info.log);
955
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
956
- info.screenshotPath = screenshotPath;
957
- Object.assign(e, { info: info });
958
- error = e;
959
- throw e;
1051
+ await _commandError(state, e, this);
960
1052
  }
961
1053
  finally {
962
- const endTime = Date.now();
963
- this._reportToWorld(world, {
964
- type: Types.TYPE_PRESS,
965
- screenshotId,
966
- value: _value,
967
- text: `type value: ${_value}`,
968
- result: error
969
- ? {
970
- status: "FAILED",
971
- startTime,
972
- endTime,
973
- message: error === null || error === void 0 ? void 0 : error.message,
974
- }
975
- : {
976
- status: "PASSED",
977
- startTime,
978
- endTime,
979
- },
980
- info: info,
981
- });
1054
+ _commandFinally(state, this);
982
1055
  }
983
1056
  }
984
1057
  async setInputValue(selectors, value, _params = null, options = {}, world = null) {
985
- // set input value for non fillable inputs like date, time, range, color, etc.
986
- this._validateSelectors(selectors);
987
- const startTime = Date.now();
988
- const info = {};
989
- info.log = "***** set input value " + selectors.element_name + " *****\n";
990
- info.operation = "setInputValue";
991
- info.selectors = selectors;
992
- value = this._fixUsingParams(value, _params);
993
- info.value = value;
994
- let error = null;
995
- let screenshotId = null;
996
- let screenshotPath = null;
1058
+ const state = {
1059
+ selectors,
1060
+ _params,
1061
+ value,
1062
+ options,
1063
+ world,
1064
+ type: Types.SET_INPUT,
1065
+ text: `Set input value`,
1066
+ operation: "setInputValue",
1067
+ log: "***** set input value " + selectors.element_name + " *****\n",
1068
+ };
997
1069
  try {
998
- value = await this._replaceWithLocalData(value, this);
999
- let element = await this._locate(selectors, info, _params);
1000
- await this.scrollIfNeeded(element, info);
1001
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1002
- await this._highlightElements(element);
1070
+ await _preCommand(state, this);
1071
+ let value = await this._replaceWithLocalData(state.value, this);
1003
1072
  try {
1004
- await element.evaluateHandle((el, value) => {
1073
+ await state.element.evaluateHandle((el, value) => {
1005
1074
  el.value = value;
1006
1075
  }, value);
1007
1076
  }
1008
1077
  catch (error) {
1009
1078
  this.logger.error("setInputValue failed, will try again");
1010
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1011
- info.screenshotPath = screenshotPath;
1012
- Object.assign(error, { info: info });
1013
- await element.evaluateHandle((el, value) => {
1079
+ await _screenshot(state, this);
1080
+ Object.assign(error, { info: state.info });
1081
+ await state.element.evaluateHandle((el, value) => {
1014
1082
  el.value = value;
1015
1083
  });
1016
1084
  }
1017
1085
  }
1018
1086
  catch (e) {
1019
- this.logger.error("setInputValue failed " + info.log);
1020
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1021
- info.screenshotPath = screenshotPath;
1022
- Object.assign(e, { info: info });
1023
- error = e;
1024
- throw e;
1087
+ await _commandError(state, e, this);
1025
1088
  }
1026
1089
  finally {
1027
- const endTime = Date.now();
1028
- this._reportToWorld(world, {
1029
- element_name: selectors.element_name,
1030
- type: Types.SET_INPUT,
1031
- text: `Set input value`,
1032
- value: value,
1033
- screenshotId,
1034
- result: error
1035
- ? {
1036
- status: "FAILED",
1037
- startTime,
1038
- endTime,
1039
- message: error === null || error === void 0 ? void 0 : error.message,
1040
- }
1041
- : {
1042
- status: "PASSED",
1043
- startTime,
1044
- endTime,
1045
- },
1046
- info: info,
1047
- });
1090
+ _commandFinally(state, this);
1048
1091
  }
1049
1092
  }
1050
1093
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1051
- this._validateSelectors(selectors);
1094
+ _validateSelectors(selectors);
1052
1095
  const startTime = Date.now();
1053
1096
  let error = null;
1054
1097
  let screenshotId = null;
@@ -1141,32 +1184,32 @@ class StableBrowser {
1141
1184
  }
1142
1185
  }
1143
1186
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1144
- this._validateSelectors(selectors);
1145
- const startTime = Date.now();
1146
- let error = null;
1147
- let screenshotId = null;
1148
- let screenshotPath = null;
1149
- const info = {};
1150
- info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
1151
- info.operation = "clickType";
1152
- info.selectors = selectors;
1187
+ _value = unEscapeString(_value);
1153
1188
  const newValue = await this._replaceWithLocalData(_value, world);
1189
+ const state = {
1190
+ selectors,
1191
+ _params,
1192
+ value: newValue,
1193
+ originalValue: _value,
1194
+ options,
1195
+ world,
1196
+ type: Types.FILL,
1197
+ text: `Click type input with value: ${_value}`,
1198
+ operation: "clickType",
1199
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1200
+ };
1154
1201
  if (newValue !== _value) {
1155
1202
  //this.logger.info(_value + "=" + newValue);
1156
1203
  _value = newValue;
1157
1204
  }
1158
- info.value = _value;
1159
1205
  try {
1160
- let element = await this._locate(selectors, info, _params);
1161
- //insert red border around the element
1162
- await this.scrollIfNeeded(element, info);
1163
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1164
- await this._highlightElements(element);
1206
+ await _preCommand(state, this);
1207
+ state.info.value = _value;
1165
1208
  if (options === null || options === undefined || !options.press) {
1166
1209
  try {
1167
- let currentValue = await element.inputValue();
1210
+ let currentValue = await state.element.inputValue();
1168
1211
  if (currentValue) {
1169
- await element.fill("");
1212
+ await state.element.fill("");
1170
1213
  }
1171
1214
  }
1172
1215
  catch (e) {
@@ -1175,22 +1218,22 @@ class StableBrowser {
1175
1218
  }
1176
1219
  if (options === null || options === undefined || options.press) {
1177
1220
  try {
1178
- await element.click({ timeout: 5000 });
1221
+ await state.element.click({ timeout: 5000 });
1179
1222
  }
1180
1223
  catch (e) {
1181
- await element.dispatchEvent("click");
1224
+ await state.element.dispatchEvent("click");
1182
1225
  }
1183
1226
  }
1184
1227
  else {
1185
1228
  try {
1186
- await element.focus();
1229
+ await state.element.focus();
1187
1230
  }
1188
1231
  catch (e) {
1189
- await element.dispatchEvent("focus");
1232
+ await state.element.dispatchEvent("focus");
1190
1233
  }
1191
1234
  }
1192
1235
  await new Promise((resolve) => setTimeout(resolve, 500));
1193
- const valueSegment = _value.split("&&");
1236
+ const valueSegment = state.value.split("&&");
1194
1237
  for (let i = 0; i < valueSegment.length; i++) {
1195
1238
  if (i > 0) {
1196
1239
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1210,13 +1253,14 @@ class StableBrowser {
1210
1253
  await new Promise((resolve) => setTimeout(resolve, 500));
1211
1254
  }
1212
1255
  }
1256
+ await _screenshot(state, this);
1213
1257
  if (enter === true) {
1214
1258
  await new Promise((resolve) => setTimeout(resolve, 2000));
1215
1259
  await this.page.keyboard.press("Enter");
1216
1260
  await this.waitForPageLoad();
1217
1261
  }
1218
1262
  else if (enter === false) {
1219
- await element.dispatchEvent("change");
1263
+ await state.element.dispatchEvent("change");
1220
1264
  //await this.page.keyboard.press("Tab");
1221
1265
  }
1222
1266
  else {
@@ -1225,103 +1269,50 @@ class StableBrowser {
1225
1269
  await this.waitForPageLoad();
1226
1270
  }
1227
1271
  }
1228
- return info;
1272
+ return state.info;
1229
1273
  }
1230
1274
  catch (e) {
1231
- //await this.closeUnexpectedPopups();
1232
- this.logger.error("fill failed " + JSON.stringify(info));
1233
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1234
- info.screenshotPath = screenshotPath;
1235
- Object.assign(e, { info: info });
1236
- error = e;
1237
- throw e;
1275
+ await _commandError(state, e, this);
1238
1276
  }
1239
1277
  finally {
1240
- const endTime = Date.now();
1241
- this._reportToWorld(world, {
1242
- element_name: selectors.element_name,
1243
- type: Types.FILL,
1244
- screenshotId,
1245
- value: _value,
1246
- text: `clickType input with value: ${_value}`,
1247
- result: error
1248
- ? {
1249
- status: "FAILED",
1250
- startTime,
1251
- endTime,
1252
- message: error === null || error === void 0 ? void 0 : error.message,
1253
- }
1254
- : {
1255
- status: "PASSED",
1256
- startTime,
1257
- endTime,
1258
- },
1259
- info: info,
1260
- });
1278
+ _commandFinally(state, this);
1261
1279
  }
1262
1280
  }
1263
1281
  async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
1264
- this._validateSelectors(selectors);
1265
- const startTime = Date.now();
1266
- let error = null;
1267
- let screenshotId = null;
1268
- let screenshotPath = null;
1269
- const info = {};
1270
- info.log = "***** fill on " + selectors.element_name + " with value " + value + "*****\n";
1271
- info.operation = "fill";
1272
- info.selectors = selectors;
1273
- info.value = value;
1282
+ const state = {
1283
+ selectors,
1284
+ _params,
1285
+ value: unEscapeString(value),
1286
+ options,
1287
+ world,
1288
+ type: Types.FILL,
1289
+ text: `Fill input with value: ${value}`,
1290
+ operation: "fill",
1291
+ log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
1292
+ };
1274
1293
  try {
1275
- let element = await this._locate(selectors, info, _params);
1276
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1277
- await this._highlightElements(element);
1278
- await element.fill(value);
1279
- await element.dispatchEvent("change");
1294
+ await _preCommand(state, this);
1295
+ await state.element.fill(value);
1296
+ await state.element.dispatchEvent("change");
1280
1297
  if (enter) {
1281
1298
  await new Promise((resolve) => setTimeout(resolve, 2000));
1282
1299
  await this.page.keyboard.press("Enter");
1283
1300
  }
1284
1301
  await this.waitForPageLoad();
1285
- return info;
1302
+ return state.info;
1286
1303
  }
1287
1304
  catch (e) {
1288
- //await this.closeUnexpectedPopups();
1289
- this.logger.error("fill failed " + info.log);
1290
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1291
- info.screenshotPath = screenshotPath;
1292
- Object.assign(e, { info: info });
1293
- error = e;
1294
- throw e;
1305
+ await _commandError(state, e, this);
1295
1306
  }
1296
1307
  finally {
1297
- const endTime = Date.now();
1298
- this._reportToWorld(world, {
1299
- element_name: selectors.element_name,
1300
- type: Types.FILL,
1301
- screenshotId,
1302
- value,
1303
- text: `Fill input with value: ${value}`,
1304
- result: error
1305
- ? {
1306
- status: "FAILED",
1307
- startTime,
1308
- endTime,
1309
- message: error === null || error === void 0 ? void 0 : error.message,
1310
- }
1311
- : {
1312
- status: "PASSED",
1313
- startTime,
1314
- endTime,
1315
- },
1316
- info: info,
1317
- });
1308
+ _commandFinally(state, this);
1318
1309
  }
1319
1310
  }
1320
1311
  async getText(selectors, _params = null, options = {}, info = {}, world = null) {
1321
1312
  return await this._getText(selectors, 0, _params, options, info, world);
1322
1313
  }
1323
1314
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1324
- this._validateSelectors(selectors);
1315
+ _validateSelectors(selectors);
1325
1316
  let screenshotId = null;
1326
1317
  let screenshotPath = null;
1327
1318
  if (!info.log) {
@@ -1365,165 +1356,124 @@ class StableBrowser {
1365
1356
  }
1366
1357
  }
1367
1358
  async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
1368
- var _a;
1369
- this._validateSelectors(selectors);
1370
1359
  if (!pattern) {
1371
1360
  throw new Error("pattern is null");
1372
1361
  }
1373
1362
  if (!text) {
1374
1363
  throw new Error("text is null");
1375
1364
  }
1365
+ const state = {
1366
+ selectors,
1367
+ _params,
1368
+ pattern,
1369
+ value: pattern,
1370
+ options,
1371
+ world,
1372
+ locate: false,
1373
+ scroll: false,
1374
+ screenshot: false,
1375
+ highlight: false,
1376
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1377
+ text: `Verify element contains pattern: ${pattern}`,
1378
+ operation: "containsPattern",
1379
+ log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1380
+ };
1376
1381
  const newValue = await this._replaceWithLocalData(text, world);
1377
1382
  if (newValue !== text) {
1378
1383
  this.logger.info(text + "=" + newValue);
1379
1384
  text = newValue;
1380
1385
  }
1381
- const startTime = Date.now();
1382
- let error = null;
1383
- let screenshotId = null;
1384
- let screenshotPath = null;
1385
- const info = {};
1386
- info.log =
1387
- "***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
1388
- info.operation = "containsPattern";
1389
- info.selectors = selectors;
1390
- info.value = text;
1391
- info.pattern = pattern;
1392
1386
  let foundObj = null;
1393
1387
  try {
1394
- foundObj = await this._getText(selectors, 0, _params, options, info, world);
1388
+ await _preCommand(state, this);
1389
+ state.info.pattern = pattern;
1390
+ foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
1395
1391
  if (foundObj && foundObj.element) {
1396
- await this.scrollIfNeeded(foundObj.element, info);
1392
+ await this.scrollIfNeeded(foundObj.element, state.info);
1397
1393
  }
1398
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1394
+ await _screenshot(state, this);
1399
1395
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1400
1396
  pattern = pattern.replace("{text}", escapedText);
1401
1397
  let regex = new RegExp(pattern, "im");
1402
- 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))) {
1403
- info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1398
+ if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
1399
+ state.info.foundText = foundObj?.text;
1404
1400
  throw new Error("element doesn't contain text " + text);
1405
1401
  }
1406
- return info;
1402
+ return state.info;
1407
1403
  }
1408
1404
  catch (e) {
1409
- //await this.closeUnexpectedPopups();
1410
- this.logger.error("verify element contains text failed " + info.log);
1411
- this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
1412
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1413
- info.screenshotPath = screenshotPath;
1414
- Object.assign(e, { info: info });
1415
- error = e;
1416
- throw e;
1405
+ this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
1406
+ await _commandError(state, e, this);
1417
1407
  }
1418
1408
  finally {
1419
- const endTime = Date.now();
1420
- this._reportToWorld(world, {
1421
- element_name: selectors.element_name,
1422
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1423
- value: pattern,
1424
- text: `Verify element contains pattern: ${pattern}`,
1425
- screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
1426
- result: error
1427
- ? {
1428
- status: "FAILED",
1429
- startTime,
1430
- endTime,
1431
- message: error === null || error === void 0 ? void 0 : error.message,
1432
- }
1433
- : {
1434
- status: "PASSED",
1435
- startTime,
1436
- endTime,
1437
- },
1438
- info: info,
1439
- });
1409
+ _commandFinally(state, this);
1440
1410
  }
1441
1411
  }
1442
1412
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1443
- var _a, _b, _c;
1444
- this._validateSelectors(selectors);
1413
+ const state = {
1414
+ selectors,
1415
+ _params,
1416
+ value: text,
1417
+ options,
1418
+ world,
1419
+ locate: false,
1420
+ scroll: false,
1421
+ screenshot: false,
1422
+ highlight: false,
1423
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1424
+ text: `Verify element contains text: ${text}`,
1425
+ operation: "containsText",
1426
+ log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
1427
+ };
1445
1428
  if (!text) {
1446
1429
  throw new Error("text is null");
1447
1430
  }
1448
- const startTime = Date.now();
1449
- let error = null;
1450
- let screenshotId = null;
1451
- let screenshotPath = null;
1452
- const info = {};
1453
- info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
1454
- info.operation = "containsText";
1455
- info.selectors = selectors;
1431
+ text = unEscapeString(text);
1456
1432
  const newValue = await this._replaceWithLocalData(text, world);
1457
1433
  if (newValue !== text) {
1458
1434
  this.logger.info(text + "=" + newValue);
1459
1435
  text = newValue;
1460
1436
  }
1461
- info.value = text;
1462
1437
  let foundObj = null;
1463
1438
  try {
1464
- foundObj = await this._getText(selectors, climb, _params, options, info, world);
1439
+ await _preCommand(state, this);
1440
+ foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1465
1441
  if (foundObj && foundObj.element) {
1466
- await this.scrollIfNeeded(foundObj.element, info);
1442
+ await this.scrollIfNeeded(foundObj.element, state.info);
1467
1443
  }
1468
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1444
+ await _screenshot(state, this);
1469
1445
  const dateAlternatives = findDateAlternatives(text);
1470
1446
  const numberAlternatives = findNumberAlternatives(text);
1471
1447
  if (dateAlternatives.date) {
1472
1448
  for (let i = 0; i < dateAlternatives.dates.length; i++) {
1473
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(dateAlternatives.dates[i])) ||
1474
- ((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(dateAlternatives.dates[i]))) {
1475
- return info;
1449
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1450
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1451
+ return state.info;
1476
1452
  }
1477
1453
  }
1478
1454
  throw new Error("element doesn't contain text " + text);
1479
1455
  }
1480
1456
  else if (numberAlternatives.number) {
1481
1457
  for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1482
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(numberAlternatives.numbers[i])) ||
1483
- ((_b = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _b === void 0 ? void 0 : _b.includes(numberAlternatives.numbers[i]))) {
1484
- return info;
1458
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1459
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1460
+ return state.info;
1485
1461
  }
1486
1462
  }
1487
1463
  throw new Error("element doesn't contain text " + text);
1488
1464
  }
1489
- 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))) {
1490
- info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1491
- info.value = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value;
1465
+ else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1466
+ state.info.foundText = foundObj?.text;
1467
+ state.info.value = foundObj?.value;
1492
1468
  throw new Error("element doesn't contain text " + text);
1493
1469
  }
1494
- return info;
1470
+ return state.info;
1495
1471
  }
1496
1472
  catch (e) {
1497
- //await this.closeUnexpectedPopups();
1498
- this.logger.error("verify element contains text failed " + info.log);
1499
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1500
- info.screenshotPath = screenshotPath;
1501
- Object.assign(e, { info: info });
1502
- error = e;
1503
- throw e;
1473
+ await _commandError(state, e, this);
1504
1474
  }
1505
1475
  finally {
1506
- const endTime = Date.now();
1507
- this._reportToWorld(world, {
1508
- element_name: selectors.element_name,
1509
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1510
- text: `Verify element contains text: ${text}`,
1511
- value: text,
1512
- screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
1513
- result: error
1514
- ? {
1515
- status: "FAILED",
1516
- startTime,
1517
- endTime,
1518
- message: error === null || error === void 0 ? void 0 : error.message,
1519
- }
1520
- : {
1521
- status: "PASSED",
1522
- startTime,
1523
- endTime,
1524
- },
1525
- info: info,
1526
- });
1476
+ _commandFinally(state, this);
1527
1477
  }
1528
1478
  }
1529
1479
  _getDataFile(world = null) {
@@ -1544,7 +1494,10 @@ class StableBrowser {
1544
1494
  }
1545
1495
  async waitForUserInput(message, world = null) {
1546
1496
  if (!message) {
1547
- message = "Press any key to continue";
1497
+ message = "# Wait for user input. Press any key to continue";
1498
+ }
1499
+ else {
1500
+ message = "# Wait for user input. " + message;
1548
1501
  }
1549
1502
  message += "\n";
1550
1503
  const value = await new Promise((resolve) => {
@@ -1749,7 +1702,6 @@ class StableBrowser {
1749
1702
  }
1750
1703
  async takeScreenshot(screenshotPath) {
1751
1704
  const playContext = this.context.playContext;
1752
- const client = await playContext.newCDPSession(this.page);
1753
1705
  // Using CDP to capture the screenshot
1754
1706
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
1755
1707
  document.body.scrollWidth,
@@ -1759,97 +1711,67 @@ class StableBrowser {
1759
1711
  document.body.clientWidth,
1760
1712
  document.documentElement.clientWidth,
1761
1713
  ])));
1762
- const viewportHeight = Math.max(...(await this.page.evaluate(() => [
1763
- document.body.scrollHeight,
1764
- document.documentElement.scrollHeight,
1765
- document.body.offsetHeight,
1766
- document.documentElement.offsetHeight,
1767
- document.body.clientHeight,
1768
- document.documentElement.clientHeight,
1769
- ])));
1770
- const { data } = await client.send("Page.captureScreenshot", {
1771
- format: "png",
1772
- // clip: {
1773
- // x: 0,
1774
- // y: 0,
1775
- // width: viewportWidth,
1776
- // height: viewportHeight,
1777
- // scale: 1,
1778
- // },
1779
- });
1780
- if (!screenshotPath) {
1781
- return data;
1782
- }
1783
- let screenshotBuffer = Buffer.from(data, "base64");
1784
- const sharpBuffer = sharp(screenshotBuffer);
1785
- const metadata = await sharpBuffer.metadata();
1786
- //check if you are on retina display and reduce the quality of the image
1787
- if (metadata.width > viewportWidth || metadata.height > viewportHeight) {
1788
- screenshotBuffer = await sharpBuffer
1789
- .resize(viewportWidth, viewportHeight, {
1790
- fit: sharp.fit.inside,
1791
- withoutEnlargement: true,
1792
- })
1793
- .toBuffer();
1794
- }
1795
- fs.writeFileSync(screenshotPath, screenshotBuffer);
1796
- await client.detach();
1714
+ let screenshotBuffer = null;
1715
+ if (this.context.browserName === "chromium") {
1716
+ const client = await playContext.newCDPSession(this.page);
1717
+ const { data } = await client.send("Page.captureScreenshot", {
1718
+ format: "png",
1719
+ // clip: {
1720
+ // x: 0,
1721
+ // y: 0,
1722
+ // width: viewportWidth,
1723
+ // height: viewportHeight,
1724
+ // scale: 1,
1725
+ // },
1726
+ });
1727
+ await client.detach();
1728
+ if (!screenshotPath) {
1729
+ return data;
1730
+ }
1731
+ screenshotBuffer = Buffer.from(data, "base64");
1732
+ }
1733
+ else {
1734
+ screenshotBuffer = await this.page.screenshot();
1735
+ }
1736
+ let image = await Jimp.read(screenshotBuffer);
1737
+ // Get the image dimensions
1738
+ const { width, height } = image.bitmap;
1739
+ const resizeRatio = viewportWidth / width;
1740
+ // Resize the image to fit within the viewport dimensions without enlarging
1741
+ if (width > viewportWidth) {
1742
+ image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
1743
+ await image.write(screenshotPath);
1744
+ }
1745
+ else {
1746
+ fs.writeFileSync(screenshotPath, screenshotBuffer);
1747
+ }
1797
1748
  }
1798
1749
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1799
- this._validateSelectors(selectors);
1800
- const startTime = Date.now();
1801
- let error = null;
1802
- let screenshotId = null;
1803
- let screenshotPath = null;
1750
+ const state = {
1751
+ selectors,
1752
+ _params,
1753
+ options,
1754
+ world,
1755
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1756
+ text: `Verify element exists in page`,
1757
+ operation: "verifyElementExistInPage",
1758
+ log: "***** verify element " + selectors.element_name + " exists in page *****\n",
1759
+ };
1804
1760
  await new Promise((resolve) => setTimeout(resolve, 2000));
1805
- const info = {};
1806
- info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
1807
- info.operation = "verify";
1808
- info.selectors = selectors;
1809
1761
  try {
1810
- const element = await this._locate(selectors, info, _params);
1811
- if (element) {
1812
- await this.scrollIfNeeded(element, info);
1813
- }
1814
- await this._highlightElements(element);
1815
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1816
- await expect(element).toHaveCount(1, { timeout: 10000 });
1817
- return info;
1762
+ await _preCommand(state, this);
1763
+ await expect(state.element).toHaveCount(1, { timeout: 10000 });
1764
+ return state.info;
1818
1765
  }
1819
1766
  catch (e) {
1820
- //await this.closeUnexpectedPopups();
1821
- this.logger.error("verify failed " + info.log);
1822
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1823
- info.screenshotPath = screenshotPath;
1824
- Object.assign(e, { info: info });
1825
- error = e;
1826
- throw e;
1767
+ await _commandError(state, e, this);
1827
1768
  }
1828
1769
  finally {
1829
- const endTime = Date.now();
1830
- this._reportToWorld(world, {
1831
- element_name: selectors.element_name,
1832
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1833
- text: "Verify element exists in page",
1834
- screenshotId,
1835
- result: error
1836
- ? {
1837
- status: "FAILED",
1838
- startTime,
1839
- endTime,
1840
- message: error === null || error === void 0 ? void 0 : error.message,
1841
- }
1842
- : {
1843
- status: "PASSED",
1844
- startTime,
1845
- endTime,
1846
- },
1847
- info: info,
1848
- });
1770
+ _commandFinally(state, this);
1849
1771
  }
1850
1772
  }
1851
1773
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1852
- this._validateSelectors(selectors);
1774
+ _validateSelectors(selectors);
1853
1775
  const startTime = Date.now();
1854
1776
  let error = null;
1855
1777
  let screenshotId = null;
@@ -1908,7 +1830,7 @@ class StableBrowser {
1908
1830
  status: "FAILED",
1909
1831
  startTime,
1910
1832
  endTime,
1911
- message: error === null || error === void 0 ? void 0 : error.message,
1833
+ message: error?.message,
1912
1834
  }
1913
1835
  : {
1914
1836
  status: "PASSED",
@@ -2117,7 +2039,7 @@ class StableBrowser {
2117
2039
  status: "FAILED",
2118
2040
  startTime,
2119
2041
  endTime,
2120
- message: error === null || error === void 0 ? void 0 : error.message,
2042
+ message: error?.message,
2121
2043
  }
2122
2044
  : {
2123
2045
  status: "PASSED",
@@ -2153,20 +2075,20 @@ class StableBrowser {
2153
2075
  for (let i = 0; i < frames.length; i++) {
2154
2076
  if (dateAlternatives.date) {
2155
2077
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2156
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
2078
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2157
2079
  result.frame = frames[i];
2158
2080
  results.push(result);
2159
2081
  }
2160
2082
  }
2161
2083
  else if (numberAlternatives.number) {
2162
2084
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2163
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
2085
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2164
2086
  result.frame = frames[i];
2165
2087
  results.push(result);
2166
2088
  }
2167
2089
  }
2168
2090
  else {
2169
- const result = await this._locateElementByText(frames[i], text, "*", true, {});
2091
+ const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2170
2092
  result.frame = frames[i];
2171
2093
  results.push(result);
2172
2094
  }
@@ -2215,7 +2137,7 @@ class StableBrowser {
2215
2137
  status: "FAILED",
2216
2138
  startTime,
2217
2139
  endTime,
2218
- message: error === null || error === void 0 ? void 0 : error.message,
2140
+ message: error?.message,
2219
2141
  }
2220
2142
  : {
2221
2143
  status: "PASSED",
@@ -2299,7 +2221,7 @@ class StableBrowser {
2299
2221
  status: "FAILED",
2300
2222
  startTime,
2301
2223
  endTime,
2302
- message: error === null || error === void 0 ? void 0 : error.message,
2224
+ message: error?.message,
2303
2225
  }
2304
2226
  : {
2305
2227
  status: "PASSED",
@@ -2331,7 +2253,7 @@ class StableBrowser {
2331
2253
  this.logger.info("Table data verified");
2332
2254
  }
2333
2255
  async getTableData(selectors, _params = null, options = {}, world = null) {
2334
- this._validateSelectors(selectors);
2256
+ _validateSelectors(selectors);
2335
2257
  const startTime = Date.now();
2336
2258
  let error = null;
2337
2259
  let screenshotId = null;
@@ -2367,7 +2289,7 @@ class StableBrowser {
2367
2289
  status: "FAILED",
2368
2290
  startTime,
2369
2291
  endTime,
2370
- message: error === null || error === void 0 ? void 0 : error.message,
2292
+ message: error?.message,
2371
2293
  }
2372
2294
  : {
2373
2295
  status: "PASSED",
@@ -2379,7 +2301,7 @@ class StableBrowser {
2379
2301
  }
2380
2302
  }
2381
2303
  async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
2382
- this._validateSelectors(selectors);
2304
+ _validateSelectors(selectors);
2383
2305
  if (!query) {
2384
2306
  throw new Error("query is null");
2385
2307
  }
@@ -2532,7 +2454,7 @@ class StableBrowser {
2532
2454
  status: "FAILED",
2533
2455
  startTime,
2534
2456
  endTime,
2535
- message: error === null || error === void 0 ? void 0 : error.message,
2457
+ message: error?.message,
2536
2458
  }
2537
2459
  : {
2538
2460
  status: "PASSED",
@@ -2544,27 +2466,7 @@ class StableBrowser {
2544
2466
  }
2545
2467
  }
2546
2468
  async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
2547
- if (!value) {
2548
- return value;
2549
- }
2550
- // find all the accurance of {{(.*?)}} and replace with the value
2551
- let regex = /{{(.*?)}}/g;
2552
- let matches = value.match(regex);
2553
- if (matches) {
2554
- const testData = this.getTestData(world);
2555
- for (let i = 0; i < matches.length; i++) {
2556
- let match = matches[i];
2557
- let key = match.substring(2, match.length - 2);
2558
- let newValue = objectPath.get(testData, key, null);
2559
- if (newValue !== null) {
2560
- value = value.replace(match, newValue);
2561
- }
2562
- }
2563
- }
2564
- if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
2565
- return await decrypt(value, null, totpWait);
2566
- }
2567
- return value;
2469
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2568
2470
  }
2569
2471
  _getLoadTimeout(options) {
2570
2472
  let timeout = 15000;
@@ -2624,7 +2526,7 @@ class StableBrowser {
2624
2526
  status: "FAILED",
2625
2527
  startTime,
2626
2528
  endTime,
2627
- message: error === null || error === void 0 ? void 0 : error.message,
2529
+ message: error?.message,
2628
2530
  }
2629
2531
  : {
2630
2532
  status: "PASSED",
@@ -2659,7 +2561,7 @@ class StableBrowser {
2659
2561
  status: "FAILED",
2660
2562
  startTime,
2661
2563
  endTime,
2662
- message: error === null || error === void 0 ? void 0 : error.message,
2564
+ message: error?.message,
2663
2565
  }
2664
2566
  : {
2665
2567
  status: "PASSED",
@@ -2701,7 +2603,7 @@ class StableBrowser {
2701
2603
  status: "FAILED",
2702
2604
  startTime,
2703
2605
  endTime,
2704
- message: error === null || error === void 0 ? void 0 : error.message,
2606
+ message: error?.message,
2705
2607
  }
2706
2608
  : {
2707
2609
  status: "PASSED",
@@ -2737,7 +2639,7 @@ class StableBrowser {
2737
2639
  status: "FAILED",
2738
2640
  startTime,
2739
2641
  endTime,
2740
- message: error === null || error === void 0 ? void 0 : error.message,
2642
+ message: error?.message,
2741
2643
  }
2742
2644
  : {
2743
2645
  status: "PASSED",
@@ -2922,5 +2824,10 @@ const KEYBOARD_EVENTS = [
2922
2824
  "TVAntennaCable",
2923
2825
  "TVAudioDescription",
2924
2826
  ];
2827
+ function unEscapeString(str) {
2828
+ const placeholder = "__NEWLINE__";
2829
+ str = str.replace(new RegExp(placeholder, "g"), "\n");
2830
+ return str;
2831
+ }
2925
2832
  export { StableBrowser };
2926
2833
  //# sourceMappingURL=stable_browser.js.map