automation_model 1.0.433-dev → 1.0.433-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,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";
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";
20
+ import { registerNetworkEvents } from "./network.js";
18
21
  const Types = {
19
22
  CLICK: "click_element",
20
23
  NAVIGATE: "navigate",
@@ -42,15 +45,24 @@ const Types = {
42
45
  LOAD_DATA: "load_data",
43
46
  SET_INPUT: "set_input",
44
47
  };
48
+ export const apps = {};
45
49
  class StableBrowser {
46
- constructor(browser, page, logger = null, context = null) {
50
+ browser;
51
+ page;
52
+ logger;
53
+ context;
54
+ world;
55
+ project_path = null;
56
+ webLogFile = null;
57
+ networkLogger = null;
58
+ configuration = null;
59
+ appName = "main";
60
+ constructor(browser, page, logger = null, context = null, world = null) {
47
61
  this.browser = browser;
48
62
  this.page = page;
49
63
  this.logger = logger;
50
64
  this.context = context;
51
- this.project_path = null;
52
- this.webLogFile = null;
53
- this.configuration = null;
65
+ this.world = world;
54
66
  if (!this.logger) {
55
67
  this.logger = console;
56
68
  }
@@ -76,16 +88,24 @@ class StableBrowser {
76
88
  this.logger.error("unable to read ai_config.json");
77
89
  }
78
90
  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();
91
+ this.world = world;
82
92
  context.pages = [this.page];
83
93
  context.pageLoading = { status: false };
94
+ this.registerEventListeners(this.context);
95
+ registerNetworkEvents(this.world, this, this.context, this.page);
96
+ }
97
+ registerEventListeners(context) {
98
+ this.registerConsoleLogListener(this.page, context);
99
+ this.registerRequestListener(this.page, context, this.webLogFile);
100
+ if (!context.pageLoading) {
101
+ context.pageLoading = { status: false };
102
+ }
84
103
  context.playContext.on("page", async function (page) {
85
104
  context.pageLoading.status = true;
86
105
  this.page = page;
87
106
  context.page = page;
88
107
  context.pages.push(page);
108
+ registerNetworkEvents(this.world, this, context, this.page);
89
109
  page.on("close", async () => {
90
110
  if (this.context && this.context.pages && this.context.pages.length > 1) {
91
111
  this.context.pages.pop();
@@ -110,6 +130,36 @@ class StableBrowser {
110
130
  context.pageLoading.status = false;
111
131
  }.bind(this));
112
132
  }
133
+ async switchApp(appName) {
134
+ // check if the current app (this.appName) is the same as the new app
135
+ if (this.appName === appName) {
136
+ return;
137
+ }
138
+ let navigate = false;
139
+ if (!apps[appName]) {
140
+ let newContext = await getContext(null, false, this.logger, appName, false, this);
141
+ navigate = true;
142
+ apps[appName] = {
143
+ context: newContext,
144
+ browser: newContext.browser,
145
+ page: newContext.page,
146
+ };
147
+ }
148
+ const tempContext = {};
149
+ this._copyContext(this, tempContext);
150
+ this._copyContext(apps[appName], this);
151
+ apps[this.appName] = tempContext;
152
+ this.appName = appName;
153
+ if (navigate) {
154
+ await this.goto(this.context.environment.baseUrl);
155
+ await this.waitForPageLoad();
156
+ }
157
+ }
158
+ _copyContext(from, to) {
159
+ to.browser = from.browser;
160
+ to.page = from.page;
161
+ to.context = from.context;
162
+ }
113
163
  getWebLogFile(logFolder) {
114
164
  if (!fs.existsSync(logFolder)) {
115
165
  fs.mkdirSync(logFolder, { recursive: true });
@@ -121,37 +171,63 @@ class StableBrowser {
121
171
  const fileName = nextIndex + ".json";
122
172
  return path.join(logFolder, fileName);
123
173
  }
124
- registerConsoleLogListener(page, context, logFile) {
174
+ registerConsoleLogListener(page, context) {
125
175
  if (!this.context.webLogger) {
126
176
  this.context.webLogger = [];
127
177
  }
128
178
  page.on("console", async (msg) => {
129
- this.context.webLogger.push({
179
+ const obj = {
130
180
  type: msg.type(),
131
181
  text: msg.text(),
132
182
  location: msg.location(),
133
183
  time: new Date().toISOString(),
134
- });
135
- await fs.promises.writeFile(logFile, JSON.stringify(this.context.webLogger, null, 2));
184
+ };
185
+ this.context.webLogger.push(obj);
186
+ if (msg.type() === "error") {
187
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
188
+ }
136
189
  });
137
190
  }
138
- registerRequestListener() {
139
- this.page.on("request", async (data) => {
191
+ registerRequestListener(page, context, logFile) {
192
+ if (!this.context.networkLogger) {
193
+ this.context.networkLogger = [];
194
+ }
195
+ page.on("request", async (data) => {
196
+ const startTime = new Date().getTime();
140
197
  try {
141
- const pageUrl = new URL(this.page.url());
198
+ const pageUrl = new URL(page.url());
142
199
  const requestUrl = new URL(data.url());
143
200
  if (pageUrl.hostname === requestUrl.hostname) {
144
201
  const method = data.method();
145
- if (method === "POST" || method === "GET" || method === "PUT" || method === "DELETE" || method === "PATCH") {
202
+ if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
146
203
  const token = await data.headerValue("Authorization");
147
204
  if (token) {
148
- this.context.authtoken = token;
205
+ context.authtoken = token;
149
206
  }
150
207
  }
151
208
  }
209
+ const response = await data.response();
210
+ const endTime = new Date().getTime();
211
+ const obj = {
212
+ url: data.url(),
213
+ method: data.method(),
214
+ postData: data.postData(),
215
+ error: data.failure() ? data.failure().errorText : null,
216
+ duration: endTime - startTime,
217
+ startTime,
218
+ };
219
+ context.networkLogger.push(obj);
220
+ this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
152
221
  }
153
222
  catch (error) {
154
223
  console.error("Error in request listener", error);
224
+ context.networkLogger.push({
225
+ error: "not able to listen",
226
+ message: error.message,
227
+ stack: error.stack,
228
+ time: new Date().toISOString(),
229
+ });
230
+ // await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
155
231
  }
156
232
  });
157
233
  }
@@ -166,20 +242,6 @@ class StableBrowser {
166
242
  timeout: 60000,
167
243
  });
168
244
  }
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
245
  _fixUsingParams(text, _params) {
184
246
  if (!_params || typeof text !== "string") {
185
247
  return text;
@@ -247,7 +309,7 @@ class StableBrowser {
247
309
  locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
248
310
  }
249
311
  }
250
- if (locator === null || locator === void 0 ? void 0 : locator.engine) {
312
+ if (locator?.engine) {
251
313
  if (locator.engine === "css") {
252
314
  locatorReturn = scope.locator(locator.selector);
253
315
  }
@@ -268,7 +330,10 @@ class StableBrowser {
268
330
  return locatorReturn;
269
331
  }
270
332
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
271
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
333
+ if (css && css.locator) {
334
+ css = css.locator;
335
+ }
336
+ let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
272
337
  if (result.elementCount === 0) {
273
338
  return;
274
339
  }
@@ -283,7 +348,7 @@ class StableBrowser {
283
348
  }
284
349
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
285
350
  //const stringifyText = JSON.stringify(text);
286
- return await scope.evaluate(([text, tag, regex, partial]) => {
351
+ return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
287
352
  function isParent(parent, child) {
288
353
  let currentNode = child.parentNode;
289
354
  while (currentNode !== null) {
@@ -295,6 +360,15 @@ class StableBrowser {
295
360
  return false;
296
361
  }
297
362
  document.isParent = isParent;
363
+ function getRegex(str) {
364
+ const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
365
+ if (!match) {
366
+ return null;
367
+ }
368
+ let [_, pattern, flags] = match;
369
+ return new RegExp(pattern, flags);
370
+ }
371
+ document.getRegex = getRegex;
298
372
  function collectAllShadowDomElements(element, result = []) {
299
373
  // Check and add the element if it has a shadow root
300
374
  if (element.shadowRoot) {
@@ -313,6 +387,10 @@ class StableBrowser {
313
387
  if (!tag) {
314
388
  tag = "*";
315
389
  }
390
+ let regexpSearch = document.getRegex(text);
391
+ if (regexpSearch) {
392
+ regex = true;
393
+ }
316
394
  let elements = Array.from(document.querySelectorAll(tag));
317
395
  let shadowHosts = [];
318
396
  document.collectAllShadowDomElements(document, shadowHosts);
@@ -328,7 +406,9 @@ class StableBrowser {
328
406
  let randomToken = null;
329
407
  const foundElements = [];
330
408
  if (regex) {
331
- let regexpSearch = new RegExp(text, "im");
409
+ if (!regexpSearch) {
410
+ regexpSearch = new RegExp(text, "im");
411
+ }
332
412
  for (let i = 0; i < elements.length; i++) {
333
413
  const element = elements[i];
334
414
  if ((element.innerText && regexpSearch.test(element.innerText)) ||
@@ -342,8 +422,8 @@ class StableBrowser {
342
422
  for (let i = 0; i < elements.length; i++) {
343
423
  const element = elements[i];
344
424
  if (partial) {
345
- if ((element.innerText && element.innerText.trim().includes(text)) ||
346
- (element.value && element.value.includes(text))) {
425
+ if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
426
+ (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
347
427
  foundElements.push(element);
348
428
  }
349
429
  }
@@ -388,6 +468,12 @@ class StableBrowser {
388
468
  }
389
469
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
390
470
  let locatorSearch = selectorHierarchy[index];
471
+ try {
472
+ locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
473
+ }
474
+ catch (e) {
475
+ console.error(e);
476
+ }
391
477
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
392
478
  let locator = null;
393
479
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
@@ -476,13 +562,18 @@ class StableBrowser {
476
562
  if (result.foundElements.length > 0) {
477
563
  let dialogCloseLocator = result.foundElements[0].locator;
478
564
  await dialogCloseLocator.click();
565
+ // wait for the dialog to close
566
+ await dialogCloseLocator.waitFor({ state: "hidden" });
479
567
  return { rerun: true };
480
568
  }
481
569
  }
482
570
  }
483
571
  return { rerun: false };
484
572
  }
485
- async _locate(selectors, info, _params, timeout = 30000) {
573
+ async _locate(selectors, info, _params, timeout) {
574
+ if (!timeout) {
575
+ timeout = 30000;
576
+ }
486
577
  for (let i = 0; i < 3; i++) {
487
578
  info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
488
579
  for (let j = 0; j < selectors.locators.length; j++) {
@@ -496,32 +587,41 @@ class StableBrowser {
496
587
  }
497
588
  throw new Error("unable to locate element " + JSON.stringify(selectors));
498
589
  }
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);
590
+ async _findFrameScope(selectors, timeout = 30000) {
505
591
  let scope = this.page;
592
+ if (selectors.frame) {
593
+ return selectors.frame;
594
+ }
506
595
  if (selectors.iframe_src || selectors.frameLocators) {
507
- const findFrame = (frame, framescope) => {
596
+ const findFrame = async (frame, framescope) => {
508
597
  for (let i = 0; i < frame.selectors.length; i++) {
509
598
  let frameLocator = frame.selectors[i];
510
599
  if (frameLocator.css) {
511
- framescope = framescope.frameLocator(frameLocator.css);
512
- break;
600
+ let testframescope = framescope.frameLocator(frameLocator.css);
601
+ if (frameLocator.index) {
602
+ testframescope = framescope.nth(frameLocator.index);
603
+ }
604
+ try {
605
+ await testframescope.owner().evaluateHandle(() => true, null, {
606
+ timeout: 5000,
607
+ });
608
+ framescope = testframescope;
609
+ break;
610
+ }
611
+ catch (error) {
612
+ console.error("frame not found " + frameLocator.css);
613
+ }
513
614
  }
514
615
  }
515
616
  if (frame.children) {
516
- return findFrame(frame.children, framescope);
617
+ return await findFrame(frame.children, framescope);
517
618
  }
518
619
  return framescope;
519
620
  };
520
- info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
521
621
  while (true) {
522
622
  let frameFound = false;
523
623
  if (selectors.nestFrmLoc) {
524
- scope = findFrame(selectors.nestFrmLoc, scope);
624
+ scope = await findFrame(selectors.nestFrmLoc, scope);
525
625
  frameFound = true;
526
626
  break;
527
627
  }
@@ -550,6 +650,25 @@ class StableBrowser {
550
650
  }
551
651
  }
552
652
  }
653
+ if (!scope) {
654
+ scope = this.page;
655
+ }
656
+ return scope;
657
+ }
658
+ async _getDocumentBody(selectors, timeout = 30000) {
659
+ let scope = await this._findFrameScope(selectors, timeout);
660
+ return scope.evaluate(() => {
661
+ var bodyContent = document.body.innerHTML;
662
+ return bodyContent;
663
+ });
664
+ }
665
+ async _locate_internal(selectors, info, _params, timeout = 30000) {
666
+ let highPriorityTimeout = 5000;
667
+ let visibleOnlyTimeout = 6000;
668
+ let startTime = performance.now();
669
+ let locatorsCount = 0;
670
+ //let arrayMode = Array.isArray(selectors);
671
+ let scope = await this._findFrameScope(selectors, timeout);
553
672
  let selectorsLocators = null;
554
673
  selectorsLocators = selectors.locators;
555
674
  // group selectors by priority
@@ -685,86 +804,121 @@ class StableBrowser {
685
804
  }
686
805
  return result;
687
806
  }
688
- async click(selectors, _params, options = {}, world = null) {
689
- this._validateSelectors(selectors);
807
+ async simpleClick(elementDescription, _params, options = {}, world = null) {
690
808
  const startTime = Date.now();
691
- if (options && options.context) {
692
- selectors.locators[0].text = options.context;
809
+ let timeout = 30000;
810
+ if (options && options.timeout) {
811
+ timeout = options.timeout;
693
812
  }
694
- const info = {};
695
- info.log = "***** click on " + selectors.element_name + " *****\n";
696
- info.operation = "click";
697
- info.selectors = selectors;
698
- let error = null;
699
- let screenshotId = null;
700
- let screenshotPath = null;
813
+ while (true) {
814
+ try {
815
+ const result = await locate_element(this.context, elementDescription, "click");
816
+ if (result?.elementNumber >= 0) {
817
+ const selectors = {
818
+ frame: result?.frame,
819
+ locators: [
820
+ {
821
+ css: result?.css,
822
+ },
823
+ ],
824
+ };
825
+ await this.click(selectors, _params, options, world);
826
+ return;
827
+ }
828
+ }
829
+ catch (e) {
830
+ if (performance.now() - startTime > timeout) {
831
+ throw e;
832
+ }
833
+ }
834
+ await new Promise((resolve) => setTimeout(resolve, 3000));
835
+ }
836
+ }
837
+ async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
838
+ const startTime = Date.now();
839
+ let timeout = 30000;
840
+ if (options && options.timeout) {
841
+ timeout = options.timeout;
842
+ }
843
+ while (true) {
844
+ try {
845
+ const result = await locate_element(this.context, elementDescription, "fill", value);
846
+ if (result?.elementNumber >= 0) {
847
+ const selectors = {
848
+ frame: result?.frame,
849
+ locators: [
850
+ {
851
+ css: result?.css,
852
+ },
853
+ ],
854
+ };
855
+ await this.clickType(selectors, value, false, _params, options, world);
856
+ return;
857
+ }
858
+ }
859
+ catch (e) {
860
+ if (performance.now() - startTime > timeout) {
861
+ throw e;
862
+ }
863
+ }
864
+ await new Promise((resolve) => setTimeout(resolve, 3000));
865
+ }
866
+ }
867
+ async click(selectors, _params, options = {}, world = null) {
868
+ const state = {
869
+ selectors,
870
+ _params,
871
+ options,
872
+ world,
873
+ text: "Click element",
874
+ type: Types.CLICK,
875
+ operation: "click",
876
+ log: "***** click on " + selectors.element_name + " *****\n",
877
+ };
701
878
  try {
702
- let element = await this._locate(selectors, info, _params);
703
- await this.scrollIfNeeded(element, info);
704
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
879
+ await _preCommand(state, this);
880
+ if (state.options && state.options.context) {
881
+ state.selectors.locators[0].text = state.options.context;
882
+ }
705
883
  try {
706
- await this._highlightElements(element);
707
- await element.click();
884
+ await state.element.click();
708
885
  await new Promise((resolve) => setTimeout(resolve, 1000));
709
886
  }
710
887
  catch (e) {
711
888
  // await this.closeUnexpectedPopups();
712
- info.log += "click failed, will try again" + "\n";
713
- element = await this._locate(selectors, info, _params);
714
- await element.dispatchEvent("click");
889
+ state.element = await this._locate(selectors, state.info, _params);
890
+ await state.element.dispatchEvent("click");
715
891
  await new Promise((resolve) => setTimeout(resolve, 1000));
716
892
  }
717
893
  await this.waitForPageLoad();
718
- return info;
894
+ return state.info;
719
895
  }
720
896
  catch (e) {
721
- this.logger.error("click failed " + info.log);
722
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
723
- info.screenshotPath = screenshotPath;
724
- Object.assign(e, { info: info });
725
- error = e;
726
- throw e;
897
+ await _commandError(state, e, this);
727
898
  }
728
899
  finally {
729
- const endTime = Date.now();
730
- this._reportToWorld(world, {
731
- element_name: selectors.element_name,
732
- type: Types.CLICK,
733
- text: `Click element`,
734
- screenshotId,
735
- result: error
736
- ? {
737
- status: "FAILED",
738
- startTime,
739
- endTime,
740
- message: error === null || error === void 0 ? void 0 : error.message,
741
- }
742
- : {
743
- status: "PASSED",
744
- startTime,
745
- endTime,
746
- },
747
- info: info,
748
- });
900
+ _commandFinally(state, this);
749
901
  }
750
902
  }
751
903
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
752
- this._validateSelectors(selectors);
753
- const startTime = Date.now();
754
- const info = {};
755
- info.log = "";
756
- info.operation = "setCheck";
757
- info.checked = checked;
758
- info.selectors = selectors;
759
- let error = null;
760
- let screenshotId = null;
761
- let screenshotPath = null;
904
+ const state = {
905
+ selectors,
906
+ _params,
907
+ options,
908
+ world,
909
+ type: checked ? Types.CHECK : Types.UNCHECK,
910
+ text: checked ? `Check element` : `Uncheck element`,
911
+ operation: "setCheck",
912
+ log: "***** check " + selectors.element_name + " *****\n",
913
+ };
762
914
  try {
763
- let element = await this._locate(selectors, info, _params);
764
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
915
+ await _preCommand(state, this);
916
+ state.info.checked = checked;
917
+ // let element = await this._locate(selectors, info, _params);
918
+ // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
765
919
  try {
766
- await this._highlightElements(element);
767
- await element.setChecked(checked);
920
+ // await this._highlightElements(element);
921
+ await state.element.setChecked(checked);
768
922
  await new Promise((resolve) => setTimeout(resolve, 1000));
769
923
  }
770
924
  catch (e) {
@@ -773,179 +927,108 @@ class StableBrowser {
773
927
  }
774
928
  else {
775
929
  //await this.closeUnexpectedPopups();
776
- info.log += "setCheck failed, will try again" + "\n";
777
- element = await this._locate(selectors, info, _params);
778
- await element.setChecked(checked, { timeout: 5000, force: true });
930
+ state.info.log += "setCheck failed, will try again" + "\n";
931
+ state.element = await this._locate(selectors, state.info, _params);
932
+ await state.element.setChecked(checked, { timeout: 5000, force: true });
779
933
  await new Promise((resolve) => setTimeout(resolve, 1000));
780
934
  }
781
935
  }
782
936
  await this.waitForPageLoad();
783
- return info;
937
+ return state.info;
784
938
  }
785
939
  catch (e) {
786
- this.logger.error("setCheck failed " + info.log);
787
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
788
- info.screenshotPath = screenshotPath;
789
- Object.assign(e, { info: info });
790
- error = e;
791
- throw e;
940
+ await _commandError(state, e, this);
792
941
  }
793
942
  finally {
794
- const endTime = Date.now();
795
- this._reportToWorld(world, {
796
- element_name: selectors.element_name,
797
- type: checked ? Types.CHECK : Types.UNCHECK,
798
- text: checked ? `Check element` : `Uncheck element`,
799
- screenshotId,
800
- result: error
801
- ? {
802
- status: "FAILED",
803
- startTime,
804
- endTime,
805
- message: error === null || error === void 0 ? void 0 : error.message,
806
- }
807
- : {
808
- status: "PASSED",
809
- startTime,
810
- endTime,
811
- },
812
- info: info,
813
- });
943
+ _commandFinally(state, this);
814
944
  }
815
945
  }
816
946
  async hover(selectors, _params, options = {}, world = null) {
817
- this._validateSelectors(selectors);
818
- const startTime = Date.now();
819
- const info = {};
820
- info.log = "";
821
- info.operation = "hover";
822
- info.selectors = selectors;
823
- let error = null;
824
- let screenshotId = null;
825
- let screenshotPath = null;
947
+ const state = {
948
+ selectors,
949
+ _params,
950
+ options,
951
+ world,
952
+ type: Types.HOVER,
953
+ text: `Hover element`,
954
+ operation: "hover",
955
+ log: "***** hover " + selectors.element_name + " *****\n",
956
+ };
826
957
  try {
827
- let element = await this._locate(selectors, info, _params);
828
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
958
+ await _preCommand(state, this);
829
959
  try {
830
- await this._highlightElements(element);
831
- await element.hover();
960
+ await state.element.hover();
832
961
  await new Promise((resolve) => setTimeout(resolve, 1000));
833
962
  }
834
963
  catch (e) {
835
964
  //await this.closeUnexpectedPopups();
836
- info.log += "hover failed, will try again" + "\n";
837
- element = await this._locate(selectors, info, _params);
838
- await element.hover({ timeout: 10000 });
965
+ state.info.log += "hover failed, will try again" + "\n";
966
+ state.element = await this._locate(selectors, state.info, _params);
967
+ await state.element.hover({ timeout: 10000 });
839
968
  await new Promise((resolve) => setTimeout(resolve, 1000));
840
969
  }
841
970
  await this.waitForPageLoad();
842
- return info;
971
+ return state.info;
843
972
  }
844
973
  catch (e) {
845
- this.logger.error("hover failed " + info.log);
846
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
847
- info.screenshotPath = screenshotPath;
848
- Object.assign(e, { info: info });
849
- error = e;
850
- throw e;
974
+ await _commandError(state, e, this);
851
975
  }
852
976
  finally {
853
- const endTime = Date.now();
854
- this._reportToWorld(world, {
855
- element_name: selectors.element_name,
856
- type: Types.HOVER,
857
- text: `Hover element`,
858
- screenshotId,
859
- result: error
860
- ? {
861
- status: "FAILED",
862
- startTime,
863
- endTime,
864
- message: error === null || error === void 0 ? void 0 : error.message,
865
- }
866
- : {
867
- status: "PASSED",
868
- startTime,
869
- endTime,
870
- },
871
- info: info,
872
- });
977
+ _commandFinally(state, this);
873
978
  }
874
979
  }
875
980
  async selectOption(selectors, values, _params = null, options = {}, world = null) {
876
- this._validateSelectors(selectors);
877
981
  if (!values) {
878
982
  throw new Error("values is null");
879
983
  }
880
- const startTime = Date.now();
881
- let error = null;
882
- let screenshotId = null;
883
- let screenshotPath = null;
884
- const info = {};
885
- info.log = "";
886
- info.operation = "selectOptions";
887
- info.selectors = selectors;
984
+ const state = {
985
+ selectors,
986
+ _params,
987
+ options,
988
+ world,
989
+ value: values.toString(),
990
+ type: Types.SELECT,
991
+ text: `Select option: ${values}`,
992
+ operation: "selectOption",
993
+ log: "***** select option " + selectors.element_name + " *****\n",
994
+ };
888
995
  try {
889
- let element = await this._locate(selectors, info, _params);
890
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
996
+ await _preCommand(state, this);
891
997
  try {
892
- await this._highlightElements(element);
893
- await element.selectOption(values);
998
+ await state.element.selectOption(values);
894
999
  }
895
1000
  catch (e) {
896
1001
  //await this.closeUnexpectedPopups();
897
- info.log += "selectOption failed, will try force" + "\n";
898
- await element.selectOption(values, { timeout: 10000, force: true });
1002
+ state.info.log += "selectOption failed, will try force" + "\n";
1003
+ await state.element.selectOption(values, { timeout: 10000, force: true });
899
1004
  }
900
1005
  await this.waitForPageLoad();
901
- return info;
1006
+ return state.info;
902
1007
  }
903
1008
  catch (e) {
904
- this.logger.error("selectOption failed " + info.log);
905
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
906
- info.screenshotPath = screenshotPath;
907
- Object.assign(e, { info: info });
908
- this.logger.info("click failed, will try next selector");
909
- error = e;
910
- throw e;
1009
+ await _commandError(state, e, this);
911
1010
  }
912
1011
  finally {
913
- const endTime = Date.now();
914
- this._reportToWorld(world, {
915
- element_name: selectors.element_name,
916
- type: Types.SELECT,
917
- text: `Select option: ${values}`,
918
- value: values.toString(),
919
- screenshotId,
920
- result: error
921
- ? {
922
- status: "FAILED",
923
- startTime,
924
- endTime,
925
- message: error === null || error === void 0 ? void 0 : error.message,
926
- }
927
- : {
928
- status: "PASSED",
929
- startTime,
930
- endTime,
931
- },
932
- info: info,
933
- });
1012
+ _commandFinally(state, this);
934
1013
  }
935
1014
  }
936
1015
  async type(_value, _params = null, options = {}, world = null) {
937
- const startTime = Date.now();
938
- let error = null;
939
- let screenshotId = null;
940
- let screenshotPath = null;
941
- const info = {};
942
- info.log = "";
943
- info.operation = "type";
944
- _value = this._fixUsingParams(_value, _params);
945
- info.value = _value;
1016
+ const state = {
1017
+ value: _value,
1018
+ _params,
1019
+ options,
1020
+ world,
1021
+ locate: false,
1022
+ scroll: false,
1023
+ highlight: false,
1024
+ type: Types.TYPE_PRESS,
1025
+ text: `Type value: ${_value}`,
1026
+ operation: "type",
1027
+ log: "",
1028
+ };
946
1029
  try {
947
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
948
- const valueSegment = _value.split("&&");
1030
+ await _preCommand(state, this);
1031
+ const valueSegment = state.value.split("&&");
949
1032
  for (let i = 0; i < valueSegment.length; i++) {
950
1033
  if (i > 0) {
951
1034
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -965,108 +1048,53 @@ class StableBrowser {
965
1048
  await this.page.keyboard.type(value);
966
1049
  }
967
1050
  }
968
- return info;
1051
+ return state.info;
969
1052
  }
970
1053
  catch (e) {
971
- //await this.closeUnexpectedPopups();
972
- this.logger.error("type failed " + info.log);
973
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
974
- info.screenshotPath = screenshotPath;
975
- Object.assign(e, { info: info });
976
- error = e;
977
- throw e;
1054
+ await _commandError(state, e, this);
978
1055
  }
979
1056
  finally {
980
- const endTime = Date.now();
981
- this._reportToWorld(world, {
982
- type: Types.TYPE_PRESS,
983
- screenshotId,
984
- value: _value,
985
- text: `type value: ${_value}`,
986
- result: error
987
- ? {
988
- status: "FAILED",
989
- startTime,
990
- endTime,
991
- message: error === null || error === void 0 ? void 0 : error.message,
992
- }
993
- : {
994
- status: "PASSED",
995
- startTime,
996
- endTime,
997
- },
998
- info: info,
999
- });
1057
+ _commandFinally(state, this);
1000
1058
  }
1001
1059
  }
1002
1060
  async setInputValue(selectors, value, _params = null, options = {}, world = null) {
1003
- // set input value for non fillable inputs like date, time, range, color, etc.
1004
- this._validateSelectors(selectors);
1005
- const startTime = Date.now();
1006
- const info = {};
1007
- info.log = "***** set input value " + selectors.element_name + " *****\n";
1008
- info.operation = "setInputValue";
1009
- info.selectors = selectors;
1010
- value = this._fixUsingParams(value, _params);
1011
- info.value = value;
1012
- let error = null;
1013
- let screenshotId = null;
1014
- let screenshotPath = null;
1061
+ const state = {
1062
+ selectors,
1063
+ _params,
1064
+ value,
1065
+ options,
1066
+ world,
1067
+ type: Types.SET_INPUT,
1068
+ text: `Set input value`,
1069
+ operation: "setInputValue",
1070
+ log: "***** set input value " + selectors.element_name + " *****\n",
1071
+ };
1015
1072
  try {
1016
- value = await this._replaceWithLocalData(value, this);
1017
- let element = await this._locate(selectors, info, _params);
1018
- await this.scrollIfNeeded(element, info);
1019
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1020
- await this._highlightElements(element);
1073
+ await _preCommand(state, this);
1074
+ let value = await this._replaceWithLocalData(state.value, this);
1021
1075
  try {
1022
- await element.evaluateHandle((el, value) => {
1076
+ await state.element.evaluateHandle((el, value) => {
1023
1077
  el.value = value;
1024
1078
  }, value);
1025
1079
  }
1026
1080
  catch (error) {
1027
1081
  this.logger.error("setInputValue failed, will try again");
1028
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1029
- info.screenshotPath = screenshotPath;
1030
- Object.assign(error, { info: info });
1031
- await element.evaluateHandle((el, value) => {
1082
+ await _screenshot(state, this);
1083
+ Object.assign(error, { info: state.info });
1084
+ await state.element.evaluateHandle((el, value) => {
1032
1085
  el.value = value;
1033
1086
  });
1034
1087
  }
1035
1088
  }
1036
1089
  catch (e) {
1037
- this.logger.error("setInputValue failed " + info.log);
1038
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1039
- info.screenshotPath = screenshotPath;
1040
- Object.assign(e, { info: info });
1041
- error = e;
1042
- throw e;
1090
+ await _commandError(state, e, this);
1043
1091
  }
1044
1092
  finally {
1045
- const endTime = Date.now();
1046
- this._reportToWorld(world, {
1047
- element_name: selectors.element_name,
1048
- type: Types.SET_INPUT,
1049
- text: `Set input value`,
1050
- value: value,
1051
- screenshotId,
1052
- result: error
1053
- ? {
1054
- status: "FAILED",
1055
- startTime,
1056
- endTime,
1057
- message: error === null || error === void 0 ? void 0 : error.message,
1058
- }
1059
- : {
1060
- status: "PASSED",
1061
- startTime,
1062
- endTime,
1063
- },
1064
- info: info,
1065
- });
1093
+ _commandFinally(state, this);
1066
1094
  }
1067
1095
  }
1068
1096
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1069
- this._validateSelectors(selectors);
1097
+ _validateSelectors(selectors);
1070
1098
  const startTime = Date.now();
1071
1099
  let error = null;
1072
1100
  let screenshotId = null;
@@ -1159,32 +1187,32 @@ class StableBrowser {
1159
1187
  }
1160
1188
  }
1161
1189
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1162
- this._validateSelectors(selectors);
1163
- const startTime = Date.now();
1164
- let error = null;
1165
- let screenshotId = null;
1166
- let screenshotPath = null;
1167
- const info = {};
1168
- info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
1169
- info.operation = "clickType";
1170
- info.selectors = selectors;
1190
+ _value = unEscapeString(_value);
1171
1191
  const newValue = await this._replaceWithLocalData(_value, world);
1192
+ const state = {
1193
+ selectors,
1194
+ _params,
1195
+ value: newValue,
1196
+ originalValue: _value,
1197
+ options,
1198
+ world,
1199
+ type: Types.FILL,
1200
+ text: `Click type input with value: ${_value}`,
1201
+ operation: "clickType",
1202
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1203
+ };
1172
1204
  if (newValue !== _value) {
1173
1205
  //this.logger.info(_value + "=" + newValue);
1174
1206
  _value = newValue;
1175
1207
  }
1176
- info.value = _value;
1177
1208
  try {
1178
- let element = await this._locate(selectors, info, _params);
1179
- //insert red border around the element
1180
- await this.scrollIfNeeded(element, info);
1181
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1182
- await this._highlightElements(element);
1209
+ await _preCommand(state, this);
1210
+ state.info.value = _value;
1183
1211
  if (options === null || options === undefined || !options.press) {
1184
1212
  try {
1185
- let currentValue = await element.inputValue();
1213
+ let currentValue = await state.element.inputValue();
1186
1214
  if (currentValue) {
1187
- await element.fill("");
1215
+ await state.element.fill("");
1188
1216
  }
1189
1217
  }
1190
1218
  catch (e) {
@@ -1193,22 +1221,22 @@ class StableBrowser {
1193
1221
  }
1194
1222
  if (options === null || options === undefined || options.press) {
1195
1223
  try {
1196
- await element.click({ timeout: 5000 });
1224
+ await state.element.click({ timeout: 5000 });
1197
1225
  }
1198
1226
  catch (e) {
1199
- await element.dispatchEvent("click");
1227
+ await state.element.dispatchEvent("click");
1200
1228
  }
1201
1229
  }
1202
1230
  else {
1203
1231
  try {
1204
- await element.focus();
1232
+ await state.element.focus();
1205
1233
  }
1206
1234
  catch (e) {
1207
- await element.dispatchEvent("focus");
1235
+ await state.element.dispatchEvent("focus");
1208
1236
  }
1209
1237
  }
1210
1238
  await new Promise((resolve) => setTimeout(resolve, 500));
1211
- const valueSegment = _value.split("&&");
1239
+ const valueSegment = state.value.split("&&");
1212
1240
  for (let i = 0; i < valueSegment.length; i++) {
1213
1241
  if (i > 0) {
1214
1242
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1228,13 +1256,14 @@ class StableBrowser {
1228
1256
  await new Promise((resolve) => setTimeout(resolve, 500));
1229
1257
  }
1230
1258
  }
1259
+ await _screenshot(state, this);
1231
1260
  if (enter === true) {
1232
1261
  await new Promise((resolve) => setTimeout(resolve, 2000));
1233
1262
  await this.page.keyboard.press("Enter");
1234
1263
  await this.waitForPageLoad();
1235
1264
  }
1236
1265
  else if (enter === false) {
1237
- await element.dispatchEvent("change");
1266
+ await state.element.dispatchEvent("change");
1238
1267
  //await this.page.keyboard.press("Tab");
1239
1268
  }
1240
1269
  else {
@@ -1243,103 +1272,50 @@ class StableBrowser {
1243
1272
  await this.waitForPageLoad();
1244
1273
  }
1245
1274
  }
1246
- return info;
1275
+ return state.info;
1247
1276
  }
1248
1277
  catch (e) {
1249
- //await this.closeUnexpectedPopups();
1250
- this.logger.error("fill failed " + JSON.stringify(info));
1251
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1252
- info.screenshotPath = screenshotPath;
1253
- Object.assign(e, { info: info });
1254
- error = e;
1255
- throw e;
1278
+ await _commandError(state, e, this);
1256
1279
  }
1257
1280
  finally {
1258
- const endTime = Date.now();
1259
- this._reportToWorld(world, {
1260
- element_name: selectors.element_name,
1261
- type: Types.FILL,
1262
- screenshotId,
1263
- value: _value,
1264
- text: `clickType input with value: ${_value}`,
1265
- result: error
1266
- ? {
1267
- status: "FAILED",
1268
- startTime,
1269
- endTime,
1270
- message: error === null || error === void 0 ? void 0 : error.message,
1271
- }
1272
- : {
1273
- status: "PASSED",
1274
- startTime,
1275
- endTime,
1276
- },
1277
- info: info,
1278
- });
1281
+ _commandFinally(state, this);
1279
1282
  }
1280
1283
  }
1281
1284
  async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
1282
- this._validateSelectors(selectors);
1283
- const startTime = Date.now();
1284
- let error = null;
1285
- let screenshotId = null;
1286
- let screenshotPath = null;
1287
- const info = {};
1288
- info.log = "***** fill on " + selectors.element_name + " with value " + value + "*****\n";
1289
- info.operation = "fill";
1290
- info.selectors = selectors;
1291
- info.value = value;
1285
+ const state = {
1286
+ selectors,
1287
+ _params,
1288
+ value: unEscapeString(value),
1289
+ options,
1290
+ world,
1291
+ type: Types.FILL,
1292
+ text: `Fill input with value: ${value}`,
1293
+ operation: "fill",
1294
+ log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
1295
+ };
1292
1296
  try {
1293
- let element = await this._locate(selectors, info, _params);
1294
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1295
- await this._highlightElements(element);
1296
- await element.fill(value);
1297
- await element.dispatchEvent("change");
1297
+ await _preCommand(state, this);
1298
+ await state.element.fill(value);
1299
+ await state.element.dispatchEvent("change");
1298
1300
  if (enter) {
1299
1301
  await new Promise((resolve) => setTimeout(resolve, 2000));
1300
1302
  await this.page.keyboard.press("Enter");
1301
1303
  }
1302
1304
  await this.waitForPageLoad();
1303
- return info;
1305
+ return state.info;
1304
1306
  }
1305
1307
  catch (e) {
1306
- //await this.closeUnexpectedPopups();
1307
- this.logger.error("fill failed " + info.log);
1308
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1309
- info.screenshotPath = screenshotPath;
1310
- Object.assign(e, { info: info });
1311
- error = e;
1312
- throw e;
1308
+ await _commandError(state, e, this);
1313
1309
  }
1314
1310
  finally {
1315
- const endTime = Date.now();
1316
- this._reportToWorld(world, {
1317
- element_name: selectors.element_name,
1318
- type: Types.FILL,
1319
- screenshotId,
1320
- value,
1321
- text: `Fill input with value: ${value}`,
1322
- result: error
1323
- ? {
1324
- status: "FAILED",
1325
- startTime,
1326
- endTime,
1327
- message: error === null || error === void 0 ? void 0 : error.message,
1328
- }
1329
- : {
1330
- status: "PASSED",
1331
- startTime,
1332
- endTime,
1333
- },
1334
- info: info,
1335
- });
1311
+ _commandFinally(state, this);
1336
1312
  }
1337
1313
  }
1338
1314
  async getText(selectors, _params = null, options = {}, info = {}, world = null) {
1339
1315
  return await this._getText(selectors, 0, _params, options, info, world);
1340
1316
  }
1341
1317
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1342
- this._validateSelectors(selectors);
1318
+ _validateSelectors(selectors);
1343
1319
  let screenshotId = null;
1344
1320
  let screenshotPath = null;
1345
1321
  if (!info.log) {
@@ -1383,165 +1359,124 @@ class StableBrowser {
1383
1359
  }
1384
1360
  }
1385
1361
  async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
1386
- var _a;
1387
- this._validateSelectors(selectors);
1388
1362
  if (!pattern) {
1389
1363
  throw new Error("pattern is null");
1390
1364
  }
1391
1365
  if (!text) {
1392
1366
  throw new Error("text is null");
1393
1367
  }
1368
+ const state = {
1369
+ selectors,
1370
+ _params,
1371
+ pattern,
1372
+ value: pattern,
1373
+ options,
1374
+ world,
1375
+ locate: false,
1376
+ scroll: false,
1377
+ screenshot: false,
1378
+ highlight: false,
1379
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1380
+ text: `Verify element contains pattern: ${pattern}`,
1381
+ operation: "containsPattern",
1382
+ log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1383
+ };
1394
1384
  const newValue = await this._replaceWithLocalData(text, world);
1395
1385
  if (newValue !== text) {
1396
1386
  this.logger.info(text + "=" + newValue);
1397
1387
  text = newValue;
1398
1388
  }
1399
- const startTime = Date.now();
1400
- let error = null;
1401
- let screenshotId = null;
1402
- let screenshotPath = null;
1403
- const info = {};
1404
- info.log =
1405
- "***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
1406
- info.operation = "containsPattern";
1407
- info.selectors = selectors;
1408
- info.value = text;
1409
- info.pattern = pattern;
1410
1389
  let foundObj = null;
1411
1390
  try {
1412
- foundObj = await this._getText(selectors, 0, _params, options, info, world);
1391
+ await _preCommand(state, this);
1392
+ state.info.pattern = pattern;
1393
+ foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
1413
1394
  if (foundObj && foundObj.element) {
1414
- await this.scrollIfNeeded(foundObj.element, info);
1395
+ await this.scrollIfNeeded(foundObj.element, state.info);
1415
1396
  }
1416
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1397
+ await _screenshot(state, this);
1417
1398
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1418
1399
  pattern = pattern.replace("{text}", escapedText);
1419
1400
  let regex = new RegExp(pattern, "im");
1420
- 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))) {
1421
- info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1401
+ if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
1402
+ state.info.foundText = foundObj?.text;
1422
1403
  throw new Error("element doesn't contain text " + text);
1423
1404
  }
1424
- return info;
1405
+ return state.info;
1425
1406
  }
1426
1407
  catch (e) {
1427
- //await this.closeUnexpectedPopups();
1428
- this.logger.error("verify element contains text failed " + info.log);
1429
- this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
1430
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1431
- info.screenshotPath = screenshotPath;
1432
- Object.assign(e, { info: info });
1433
- error = e;
1434
- throw e;
1408
+ this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
1409
+ await _commandError(state, e, this);
1435
1410
  }
1436
1411
  finally {
1437
- const endTime = Date.now();
1438
- this._reportToWorld(world, {
1439
- element_name: selectors.element_name,
1440
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1441
- value: pattern,
1442
- text: `Verify element contains pattern: ${pattern}`,
1443
- screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
1444
- result: error
1445
- ? {
1446
- status: "FAILED",
1447
- startTime,
1448
- endTime,
1449
- message: error === null || error === void 0 ? void 0 : error.message,
1450
- }
1451
- : {
1452
- status: "PASSED",
1453
- startTime,
1454
- endTime,
1455
- },
1456
- info: info,
1457
- });
1412
+ _commandFinally(state, this);
1458
1413
  }
1459
1414
  }
1460
1415
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1461
- var _a, _b, _c;
1462
- this._validateSelectors(selectors);
1416
+ const state = {
1417
+ selectors,
1418
+ _params,
1419
+ value: text,
1420
+ options,
1421
+ world,
1422
+ locate: false,
1423
+ scroll: false,
1424
+ screenshot: false,
1425
+ highlight: false,
1426
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1427
+ text: `Verify element contains text: ${text}`,
1428
+ operation: "containsText",
1429
+ log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
1430
+ };
1463
1431
  if (!text) {
1464
1432
  throw new Error("text is null");
1465
1433
  }
1466
- const startTime = Date.now();
1467
- let error = null;
1468
- let screenshotId = null;
1469
- let screenshotPath = null;
1470
- const info = {};
1471
- info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
1472
- info.operation = "containsText";
1473
- info.selectors = selectors;
1434
+ text = unEscapeString(text);
1474
1435
  const newValue = await this._replaceWithLocalData(text, world);
1475
1436
  if (newValue !== text) {
1476
1437
  this.logger.info(text + "=" + newValue);
1477
1438
  text = newValue;
1478
1439
  }
1479
- info.value = text;
1480
1440
  let foundObj = null;
1481
1441
  try {
1482
- foundObj = await this._getText(selectors, climb, _params, options, info, world);
1442
+ await _preCommand(state, this);
1443
+ foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1483
1444
  if (foundObj && foundObj.element) {
1484
- await this.scrollIfNeeded(foundObj.element, info);
1445
+ await this.scrollIfNeeded(foundObj.element, state.info);
1485
1446
  }
1486
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1447
+ await _screenshot(state, this);
1487
1448
  const dateAlternatives = findDateAlternatives(text);
1488
1449
  const numberAlternatives = findNumberAlternatives(text);
1489
1450
  if (dateAlternatives.date) {
1490
1451
  for (let i = 0; i < dateAlternatives.dates.length; i++) {
1491
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(dateAlternatives.dates[i])) ||
1492
- ((_a = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _a === void 0 ? void 0 : _a.includes(dateAlternatives.dates[i]))) {
1493
- return info;
1452
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1453
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1454
+ return state.info;
1494
1455
  }
1495
1456
  }
1496
1457
  throw new Error("element doesn't contain text " + text);
1497
1458
  }
1498
1459
  else if (numberAlternatives.number) {
1499
1460
  for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1500
- if ((foundObj === null || foundObj === void 0 ? void 0 : foundObj.text.includes(numberAlternatives.numbers[i])) ||
1501
- ((_b = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value) === null || _b === void 0 ? void 0 : _b.includes(numberAlternatives.numbers[i]))) {
1502
- return info;
1461
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1462
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1463
+ return state.info;
1503
1464
  }
1504
1465
  }
1505
1466
  throw new Error("element doesn't contain text " + text);
1506
1467
  }
1507
- 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))) {
1508
- info.foundText = foundObj === null || foundObj === void 0 ? void 0 : foundObj.text;
1509
- info.value = foundObj === null || foundObj === void 0 ? void 0 : foundObj.value;
1468
+ else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1469
+ state.info.foundText = foundObj?.text;
1470
+ state.info.value = foundObj?.value;
1510
1471
  throw new Error("element doesn't contain text " + text);
1511
1472
  }
1512
- return info;
1473
+ return state.info;
1513
1474
  }
1514
1475
  catch (e) {
1515
- //await this.closeUnexpectedPopups();
1516
- this.logger.error("verify element contains text failed " + info.log);
1517
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1518
- info.screenshotPath = screenshotPath;
1519
- Object.assign(e, { info: info });
1520
- error = e;
1521
- throw e;
1476
+ await _commandError(state, e, this);
1522
1477
  }
1523
1478
  finally {
1524
- const endTime = Date.now();
1525
- this._reportToWorld(world, {
1526
- element_name: selectors.element_name,
1527
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1528
- text: `Verify element contains text: ${text}`,
1529
- value: text,
1530
- screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
1531
- result: error
1532
- ? {
1533
- status: "FAILED",
1534
- startTime,
1535
- endTime,
1536
- message: error === null || error === void 0 ? void 0 : error.message,
1537
- }
1538
- : {
1539
- status: "PASSED",
1540
- startTime,
1541
- endTime,
1542
- },
1543
- info: info,
1544
- });
1479
+ _commandFinally(state, this);
1545
1480
  }
1546
1481
  }
1547
1482
  _getDataFile(world = null) {
@@ -1562,7 +1497,10 @@ class StableBrowser {
1562
1497
  }
1563
1498
  async waitForUserInput(message, world = null) {
1564
1499
  if (!message) {
1565
- message = "Press any key to continue";
1500
+ message = "# Wait for user input. Press any key to continue";
1501
+ }
1502
+ else {
1503
+ message = "# Wait for user input. " + message;
1566
1504
  }
1567
1505
  message += "\n";
1568
1506
  const value = await new Promise((resolve) => {
@@ -1767,7 +1705,6 @@ class StableBrowser {
1767
1705
  }
1768
1706
  async takeScreenshot(screenshotPath) {
1769
1707
  const playContext = this.context.playContext;
1770
- const client = await playContext.newCDPSession(this.page);
1771
1708
  // Using CDP to capture the screenshot
1772
1709
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
1773
1710
  document.body.scrollWidth,
@@ -1777,97 +1714,67 @@ class StableBrowser {
1777
1714
  document.body.clientWidth,
1778
1715
  document.documentElement.clientWidth,
1779
1716
  ])));
1780
- const viewportHeight = Math.max(...(await this.page.evaluate(() => [
1781
- document.body.scrollHeight,
1782
- document.documentElement.scrollHeight,
1783
- document.body.offsetHeight,
1784
- document.documentElement.offsetHeight,
1785
- document.body.clientHeight,
1786
- document.documentElement.clientHeight,
1787
- ])));
1788
- const { data } = await client.send("Page.captureScreenshot", {
1789
- format: "png",
1790
- // clip: {
1791
- // x: 0,
1792
- // y: 0,
1793
- // width: viewportWidth,
1794
- // height: viewportHeight,
1795
- // scale: 1,
1796
- // },
1797
- });
1798
- if (!screenshotPath) {
1799
- return data;
1800
- }
1801
- let screenshotBuffer = Buffer.from(data, "base64");
1802
- const sharpBuffer = sharp(screenshotBuffer);
1803
- const metadata = await sharpBuffer.metadata();
1804
- //check if you are on retina display and reduce the quality of the image
1805
- if (metadata.width > viewportWidth || metadata.height > viewportHeight) {
1806
- screenshotBuffer = await sharpBuffer
1807
- .resize(viewportWidth, viewportHeight, {
1808
- fit: sharp.fit.inside,
1809
- withoutEnlargement: true,
1810
- })
1811
- .toBuffer();
1812
- }
1813
- fs.writeFileSync(screenshotPath, screenshotBuffer);
1814
- await client.detach();
1717
+ let screenshotBuffer = null;
1718
+ if (this.context.browserName === "chromium") {
1719
+ const client = await playContext.newCDPSession(this.page);
1720
+ const { data } = await client.send("Page.captureScreenshot", {
1721
+ format: "png",
1722
+ // clip: {
1723
+ // x: 0,
1724
+ // y: 0,
1725
+ // width: viewportWidth,
1726
+ // height: viewportHeight,
1727
+ // scale: 1,
1728
+ // },
1729
+ });
1730
+ await client.detach();
1731
+ if (!screenshotPath) {
1732
+ return data;
1733
+ }
1734
+ screenshotBuffer = Buffer.from(data, "base64");
1735
+ }
1736
+ else {
1737
+ screenshotBuffer = await this.page.screenshot();
1738
+ }
1739
+ let image = await Jimp.read(screenshotBuffer);
1740
+ // Get the image dimensions
1741
+ const { width, height } = image.bitmap;
1742
+ const resizeRatio = viewportWidth / width;
1743
+ // Resize the image to fit within the viewport dimensions without enlarging
1744
+ if (width > viewportWidth) {
1745
+ image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
1746
+ await image.write(screenshotPath);
1747
+ }
1748
+ else {
1749
+ fs.writeFileSync(screenshotPath, screenshotBuffer);
1750
+ }
1815
1751
  }
1816
1752
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1817
- this._validateSelectors(selectors);
1818
- const startTime = Date.now();
1819
- let error = null;
1820
- let screenshotId = null;
1821
- let screenshotPath = null;
1753
+ const state = {
1754
+ selectors,
1755
+ _params,
1756
+ options,
1757
+ world,
1758
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1759
+ text: `Verify element exists in page`,
1760
+ operation: "verifyElementExistInPage",
1761
+ log: "***** verify element " + selectors.element_name + " exists in page *****\n",
1762
+ };
1822
1763
  await new Promise((resolve) => setTimeout(resolve, 2000));
1823
- const info = {};
1824
- info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
1825
- info.operation = "verify";
1826
- info.selectors = selectors;
1827
1764
  try {
1828
- const element = await this._locate(selectors, info, _params);
1829
- if (element) {
1830
- await this.scrollIfNeeded(element, info);
1831
- }
1832
- await this._highlightElements(element);
1833
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1834
- await expect(element).toHaveCount(1, { timeout: 10000 });
1835
- return info;
1765
+ await _preCommand(state, this);
1766
+ await expect(state.element).toHaveCount(1, { timeout: 10000 });
1767
+ return state.info;
1836
1768
  }
1837
1769
  catch (e) {
1838
- //await this.closeUnexpectedPopups();
1839
- this.logger.error("verify failed " + info.log);
1840
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1841
- info.screenshotPath = screenshotPath;
1842
- Object.assign(e, { info: info });
1843
- error = e;
1844
- throw e;
1770
+ await _commandError(state, e, this);
1845
1771
  }
1846
1772
  finally {
1847
- const endTime = Date.now();
1848
- this._reportToWorld(world, {
1849
- element_name: selectors.element_name,
1850
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1851
- text: "Verify element exists in page",
1852
- screenshotId,
1853
- result: error
1854
- ? {
1855
- status: "FAILED",
1856
- startTime,
1857
- endTime,
1858
- message: error === null || error === void 0 ? void 0 : error.message,
1859
- }
1860
- : {
1861
- status: "PASSED",
1862
- startTime,
1863
- endTime,
1864
- },
1865
- info: info,
1866
- });
1773
+ _commandFinally(state, this);
1867
1774
  }
1868
1775
  }
1869
1776
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1870
- this._validateSelectors(selectors);
1777
+ _validateSelectors(selectors);
1871
1778
  const startTime = Date.now();
1872
1779
  let error = null;
1873
1780
  let screenshotId = null;
@@ -1926,7 +1833,7 @@ class StableBrowser {
1926
1833
  status: "FAILED",
1927
1834
  startTime,
1928
1835
  endTime,
1929
- message: error === null || error === void 0 ? void 0 : error.message,
1836
+ message: error?.message,
1930
1837
  }
1931
1838
  : {
1932
1839
  status: "PASSED",
@@ -2135,7 +2042,7 @@ class StableBrowser {
2135
2042
  status: "FAILED",
2136
2043
  startTime,
2137
2044
  endTime,
2138
- message: error === null || error === void 0 ? void 0 : error.message,
2045
+ message: error?.message,
2139
2046
  }
2140
2047
  : {
2141
2048
  status: "PASSED",
@@ -2171,20 +2078,20 @@ class StableBrowser {
2171
2078
  for (let i = 0; i < frames.length; i++) {
2172
2079
  if (dateAlternatives.date) {
2173
2080
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2174
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
2081
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2175
2082
  result.frame = frames[i];
2176
2083
  results.push(result);
2177
2084
  }
2178
2085
  }
2179
2086
  else if (numberAlternatives.number) {
2180
2087
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2181
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
2088
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2182
2089
  result.frame = frames[i];
2183
2090
  results.push(result);
2184
2091
  }
2185
2092
  }
2186
2093
  else {
2187
- const result = await this._locateElementByText(frames[i], text, "*", true, {});
2094
+ const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2188
2095
  result.frame = frames[i];
2189
2096
  results.push(result);
2190
2097
  }
@@ -2233,7 +2140,7 @@ class StableBrowser {
2233
2140
  status: "FAILED",
2234
2141
  startTime,
2235
2142
  endTime,
2236
- message: error === null || error === void 0 ? void 0 : error.message,
2143
+ message: error?.message,
2237
2144
  }
2238
2145
  : {
2239
2146
  status: "PASSED",
@@ -2317,7 +2224,7 @@ class StableBrowser {
2317
2224
  status: "FAILED",
2318
2225
  startTime,
2319
2226
  endTime,
2320
- message: error === null || error === void 0 ? void 0 : error.message,
2227
+ message: error?.message,
2321
2228
  }
2322
2229
  : {
2323
2230
  status: "PASSED",
@@ -2349,7 +2256,7 @@ class StableBrowser {
2349
2256
  this.logger.info("Table data verified");
2350
2257
  }
2351
2258
  async getTableData(selectors, _params = null, options = {}, world = null) {
2352
- this._validateSelectors(selectors);
2259
+ _validateSelectors(selectors);
2353
2260
  const startTime = Date.now();
2354
2261
  let error = null;
2355
2262
  let screenshotId = null;
@@ -2385,7 +2292,7 @@ class StableBrowser {
2385
2292
  status: "FAILED",
2386
2293
  startTime,
2387
2294
  endTime,
2388
- message: error === null || error === void 0 ? void 0 : error.message,
2295
+ message: error?.message,
2389
2296
  }
2390
2297
  : {
2391
2298
  status: "PASSED",
@@ -2397,7 +2304,7 @@ class StableBrowser {
2397
2304
  }
2398
2305
  }
2399
2306
  async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
2400
- this._validateSelectors(selectors);
2307
+ _validateSelectors(selectors);
2401
2308
  if (!query) {
2402
2309
  throw new Error("query is null");
2403
2310
  }
@@ -2550,7 +2457,7 @@ class StableBrowser {
2550
2457
  status: "FAILED",
2551
2458
  startTime,
2552
2459
  endTime,
2553
- message: error === null || error === void 0 ? void 0 : error.message,
2460
+ message: error?.message,
2554
2461
  }
2555
2462
  : {
2556
2463
  status: "PASSED",
@@ -2562,27 +2469,7 @@ class StableBrowser {
2562
2469
  }
2563
2470
  }
2564
2471
  async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
2565
- if (!value) {
2566
- return value;
2567
- }
2568
- // find all the accurance of {{(.*?)}} and replace with the value
2569
- let regex = /{{(.*?)}}/g;
2570
- let matches = value.match(regex);
2571
- if (matches) {
2572
- const testData = this.getTestData(world);
2573
- for (let i = 0; i < matches.length; i++) {
2574
- let match = matches[i];
2575
- let key = match.substring(2, match.length - 2);
2576
- let newValue = objectPath.get(testData, key, null);
2577
- if (newValue !== null) {
2578
- value = value.replace(match, newValue);
2579
- }
2580
- }
2581
- }
2582
- if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
2583
- return await decrypt(value, null, totpWait);
2584
- }
2585
- return value;
2472
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2586
2473
  }
2587
2474
  _getLoadTimeout(options) {
2588
2475
  let timeout = 15000;
@@ -2642,7 +2529,7 @@ class StableBrowser {
2642
2529
  status: "FAILED",
2643
2530
  startTime,
2644
2531
  endTime,
2645
- message: error === null || error === void 0 ? void 0 : error.message,
2532
+ message: error?.message,
2646
2533
  }
2647
2534
  : {
2648
2535
  status: "PASSED",
@@ -2677,7 +2564,7 @@ class StableBrowser {
2677
2564
  status: "FAILED",
2678
2565
  startTime,
2679
2566
  endTime,
2680
- message: error === null || error === void 0 ? void 0 : error.message,
2567
+ message: error?.message,
2681
2568
  }
2682
2569
  : {
2683
2570
  status: "PASSED",
@@ -2719,7 +2606,7 @@ class StableBrowser {
2719
2606
  status: "FAILED",
2720
2607
  startTime,
2721
2608
  endTime,
2722
- message: error === null || error === void 0 ? void 0 : error.message,
2609
+ message: error?.message,
2723
2610
  }
2724
2611
  : {
2725
2612
  status: "PASSED",
@@ -2755,7 +2642,7 @@ class StableBrowser {
2755
2642
  status: "FAILED",
2756
2643
  startTime,
2757
2644
  endTime,
2758
- message: error === null || error === void 0 ? void 0 : error.message,
2645
+ message: error?.message,
2759
2646
  }
2760
2647
  : {
2761
2648
  status: "PASSED",
@@ -2940,5 +2827,10 @@ const KEYBOARD_EVENTS = [
2940
2827
  "TVAntennaCable",
2941
2828
  "TVAudioDescription",
2942
2829
  ];
2830
+ function unEscapeString(str) {
2831
+ const placeholder = "__NEWLINE__";
2832
+ str = str.replace(new RegExp(placeholder, "g"), "\n");
2833
+ return str;
2834
+ }
2943
2835
  export { StableBrowser };
2944
2836
  //# sourceMappingURL=stable_browser.js.map