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