automation_model 1.0.429-dev → 1.0.429-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,107 @@ 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
+ type: Types.SELECT,
988
+ text: `Select option: ${values}`,
989
+ operation: "selectOption",
990
+ log: "***** select option " + selectors.element_name + " *****\n",
991
+ };
869
992
  try {
870
- let element = await this._locate(selectors, info, _params);
871
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
993
+ await _preCommand(state, this);
872
994
  try {
873
- await this._highlightElements(element);
874
- await element.selectOption(values);
995
+ await state.element.selectOption(values);
875
996
  }
876
997
  catch (e) {
877
998
  //await this.closeUnexpectedPopups();
878
- info.log += "selectOption failed, will try force" + "\n";
879
- await element.selectOption(values, { timeout: 10000, force: true });
999
+ state.info.log += "selectOption failed, will try force" + "\n";
1000
+ await state.element.selectOption(values, { timeout: 10000, force: true });
880
1001
  }
881
1002
  await this.waitForPageLoad();
882
- return info;
1003
+ return state.info;
883
1004
  }
884
1005
  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;
1006
+ await _commandError(state, e, this);
892
1007
  }
893
1008
  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
- });
1009
+ _commandFinally(state, this);
915
1010
  }
916
1011
  }
917
1012
  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;
1013
+ const state = {
1014
+ value: _value,
1015
+ _params,
1016
+ options,
1017
+ world,
1018
+ locate: false,
1019
+ scroll: false,
1020
+ highlight: false,
1021
+ type: Types.TYPE_PRESS,
1022
+ text: `Type value: ${_value}`,
1023
+ operation: "type",
1024
+ log: "",
1025
+ };
927
1026
  try {
928
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
929
- const valueSegment = _value.split("&&");
1027
+ await _preCommand(state, this);
1028
+ const valueSegment = state.value.split("&&");
930
1029
  for (let i = 0; i < valueSegment.length; i++) {
931
1030
  if (i > 0) {
932
1031
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -946,108 +1045,53 @@ class StableBrowser {
946
1045
  await this.page.keyboard.type(value);
947
1046
  }
948
1047
  }
949
- return info;
1048
+ return state.info;
950
1049
  }
951
1050
  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;
1051
+ await _commandError(state, e, this);
959
1052
  }
960
1053
  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
- });
1054
+ _commandFinally(state, this);
981
1055
  }
982
1056
  }
983
1057
  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;
1058
+ const state = {
1059
+ selectors,
1060
+ _params,
1061
+ value,
1062
+ options,
1063
+ world,
1064
+ type: Types.SET_INPUT,
1065
+ text: `Set input value`,
1066
+ operation: "setInputValue",
1067
+ log: "***** set input value " + selectors.element_name + " *****\n",
1068
+ };
996
1069
  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);
1070
+ await _preCommand(state, this);
1071
+ let value = await this._replaceWithLocalData(state.value, this);
1002
1072
  try {
1003
- await element.evaluateHandle((el, value) => {
1073
+ await state.element.evaluateHandle((el, value) => {
1004
1074
  el.value = value;
1005
1075
  }, value);
1006
1076
  }
1007
1077
  catch (error) {
1008
1078
  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) => {
1079
+ await _screenshot(state, this);
1080
+ Object.assign(error, { info: state.info });
1081
+ await state.element.evaluateHandle((el, value) => {
1013
1082
  el.value = value;
1014
1083
  });
1015
1084
  }
1016
1085
  }
1017
1086
  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;
1087
+ await _commandError(state, e, this);
1024
1088
  }
1025
1089
  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
- });
1090
+ _commandFinally(state, this);
1047
1091
  }
1048
1092
  }
1049
1093
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1050
- this._validateSelectors(selectors);
1094
+ _validateSelectors(selectors);
1051
1095
  const startTime = Date.now();
1052
1096
  let error = null;
1053
1097
  let screenshotId = null;
@@ -1140,30 +1184,28 @@ class StableBrowser {
1140
1184
  }
1141
1185
  }
1142
1186
  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);
1187
+ const state = {
1188
+ selectors,
1189
+ _params,
1190
+ value: unEscapeString(_value),
1191
+ options,
1192
+ world,
1193
+ type: Types.FILL,
1194
+ text: `Click type input with value: ${_value}`,
1195
+ operation: "clickType",
1196
+ log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
1197
+ };
1198
+ const newValue = await this._replaceWithLocalData(state.value, world);
1153
1199
  if (newValue !== _value) {
1154
1200
  //this.logger.info(_value + "=" + newValue);
1155
1201
  _value = newValue;
1156
1202
  }
1157
- info.value = _value;
1158
1203
  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);
1204
+ await _preCommand(state, this);
1205
+ state.info.value = _value;
1164
1206
  if (options === null || options === undefined || !options.press) {
1165
1207
  try {
1166
- let currentValue = await element.inputValue();
1208
+ let currentValue = await state.element.inputValue();
1167
1209
  if (currentValue) {
1168
1210
  await element.fill("");
1169
1211
  }
@@ -1174,22 +1216,22 @@ class StableBrowser {
1174
1216
  }
1175
1217
  if (options === null || options === undefined || options.press) {
1176
1218
  try {
1177
- await element.click({ timeout: 5000 });
1219
+ await state.element.click({ timeout: 5000 });
1178
1220
  }
1179
1221
  catch (e) {
1180
- await element.dispatchEvent("click");
1222
+ await state.element.dispatchEvent("click");
1181
1223
  }
1182
1224
  }
1183
1225
  else {
1184
1226
  try {
1185
- await element.focus();
1227
+ await state.element.focus();
1186
1228
  }
1187
1229
  catch (e) {
1188
- await element.dispatchEvent("focus");
1230
+ await state.element.dispatchEvent("focus");
1189
1231
  }
1190
1232
  }
1191
1233
  await new Promise((resolve) => setTimeout(resolve, 500));
1192
- const valueSegment = _value.split("&&");
1234
+ const valueSegment = state.value.split("&&");
1193
1235
  for (let i = 0; i < valueSegment.length; i++) {
1194
1236
  if (i > 0) {
1195
1237
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1209,13 +1251,14 @@ class StableBrowser {
1209
1251
  await new Promise((resolve) => setTimeout(resolve, 500));
1210
1252
  }
1211
1253
  }
1254
+ await _screenshot(state, this);
1212
1255
  if (enter === true) {
1213
1256
  await new Promise((resolve) => setTimeout(resolve, 2000));
1214
1257
  await this.page.keyboard.press("Enter");
1215
1258
  await this.waitForPageLoad();
1216
1259
  }
1217
1260
  else if (enter === false) {
1218
- await element.dispatchEvent("change");
1261
+ await state.element.dispatchEvent("change");
1219
1262
  //await this.page.keyboard.press("Tab");
1220
1263
  }
1221
1264
  else {
@@ -1224,103 +1267,50 @@ class StableBrowser {
1224
1267
  await this.waitForPageLoad();
1225
1268
  }
1226
1269
  }
1227
- return info;
1270
+ return state.info;
1228
1271
  }
1229
1272
  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;
1273
+ await _commandError(state, e, this);
1237
1274
  }
1238
1275
  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
- });
1276
+ _commandFinally(state, this);
1260
1277
  }
1261
1278
  }
1262
1279
  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;
1280
+ const state = {
1281
+ selectors,
1282
+ _params,
1283
+ value: unEscapeString(value),
1284
+ options,
1285
+ world,
1286
+ type: Types.FILL,
1287
+ text: `Fill input with value: ${value}`,
1288
+ operation: "fill",
1289
+ log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
1290
+ };
1273
1291
  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");
1292
+ await _preCommand(state, this);
1293
+ await state.element.fill(value);
1294
+ await state.element.dispatchEvent("change");
1279
1295
  if (enter) {
1280
1296
  await new Promise((resolve) => setTimeout(resolve, 2000));
1281
1297
  await this.page.keyboard.press("Enter");
1282
1298
  }
1283
1299
  await this.waitForPageLoad();
1284
- return info;
1300
+ return state.info;
1285
1301
  }
1286
1302
  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;
1303
+ await _commandError(state, e, this);
1294
1304
  }
1295
1305
  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
- });
1306
+ _commandFinally(state, this);
1317
1307
  }
1318
1308
  }
1319
1309
  async getText(selectors, _params = null, options = {}, info = {}, world = null) {
1320
1310
  return await this._getText(selectors, 0, _params, options, info, world);
1321
1311
  }
1322
1312
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1323
- this._validateSelectors(selectors);
1313
+ _validateSelectors(selectors);
1324
1314
  let screenshotId = null;
1325
1315
  let screenshotPath = null;
1326
1316
  if (!info.log) {
@@ -1364,165 +1354,124 @@ class StableBrowser {
1364
1354
  }
1365
1355
  }
1366
1356
  async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
1367
- var _a;
1368
- this._validateSelectors(selectors);
1369
1357
  if (!pattern) {
1370
1358
  throw new Error("pattern is null");
1371
1359
  }
1372
1360
  if (!text) {
1373
1361
  throw new Error("text is null");
1374
1362
  }
1363
+ const state = {
1364
+ selectors,
1365
+ _params,
1366
+ pattern,
1367
+ value: text,
1368
+ options,
1369
+ world,
1370
+ locate: false,
1371
+ scroll: false,
1372
+ screenshot: false,
1373
+ highlight: false,
1374
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1375
+ text: `Verify element contains pattern: ${pattern}`,
1376
+ operation: "containsPattern",
1377
+ log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1378
+ };
1375
1379
  const newValue = await this._replaceWithLocalData(text, world);
1376
1380
  if (newValue !== text) {
1377
1381
  this.logger.info(text + "=" + newValue);
1378
1382
  text = newValue;
1379
1383
  }
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
1384
  let foundObj = null;
1392
1385
  try {
1393
- foundObj = await this._getText(selectors, 0, _params, options, info, world);
1386
+ await _preCommand(state, this);
1387
+ state.info.pattern = pattern;
1388
+ foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
1394
1389
  if (foundObj && foundObj.element) {
1395
- await this.scrollIfNeeded(foundObj.element, info);
1390
+ await this.scrollIfNeeded(foundObj.element, state.info);
1396
1391
  }
1397
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1392
+ await _screenshot(state, this);
1398
1393
  let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1399
1394
  pattern = pattern.replace("{text}", escapedText);
1400
1395
  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;
1396
+ if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
1397
+ state.info.foundText = foundObj?.text;
1403
1398
  throw new Error("element doesn't contain text " + text);
1404
1399
  }
1405
- return info;
1400
+ return state.info;
1406
1401
  }
1407
1402
  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;
1403
+ this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
1404
+ await _commandError(state, e, this);
1416
1405
  }
1417
1406
  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
- });
1407
+ _commandFinally(state, this);
1439
1408
  }
1440
1409
  }
1441
1410
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1442
- var _a, _b, _c;
1443
- this._validateSelectors(selectors);
1411
+ const state = {
1412
+ selectors,
1413
+ _params,
1414
+ value: text,
1415
+ options,
1416
+ world,
1417
+ locate: false,
1418
+ scroll: false,
1419
+ screenshot: false,
1420
+ highlight: false,
1421
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1422
+ text: `Verify element contains text: ${text}`,
1423
+ operation: "containsText",
1424
+ log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
1425
+ };
1444
1426
  if (!text) {
1445
1427
  throw new Error("text is null");
1446
1428
  }
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;
1429
+ text = unEscapeString(text);
1455
1430
  const newValue = await this._replaceWithLocalData(text, world);
1456
1431
  if (newValue !== text) {
1457
1432
  this.logger.info(text + "=" + newValue);
1458
1433
  text = newValue;
1459
1434
  }
1460
- info.value = text;
1461
1435
  let foundObj = null;
1462
1436
  try {
1463
- foundObj = await this._getText(selectors, climb, _params, options, info, world);
1437
+ await _preCommand(state, this);
1438
+ foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1464
1439
  if (foundObj && foundObj.element) {
1465
- await this.scrollIfNeeded(foundObj.element, info);
1440
+ await this.scrollIfNeeded(foundObj.element, state.info);
1466
1441
  }
1467
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1442
+ await _screenshot(state, this);
1468
1443
  const dateAlternatives = findDateAlternatives(text);
1469
1444
  const numberAlternatives = findNumberAlternatives(text);
1470
1445
  if (dateAlternatives.date) {
1471
1446
  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;
1447
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1448
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1449
+ return state.info;
1475
1450
  }
1476
1451
  }
1477
1452
  throw new Error("element doesn't contain text " + text);
1478
1453
  }
1479
1454
  else if (numberAlternatives.number) {
1480
1455
  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;
1456
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1457
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1458
+ return state.info;
1484
1459
  }
1485
1460
  }
1486
1461
  throw new Error("element doesn't contain text " + text);
1487
1462
  }
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;
1463
+ else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1464
+ state.info.foundText = foundObj?.text;
1465
+ state.info.value = foundObj?.value;
1491
1466
  throw new Error("element doesn't contain text " + text);
1492
1467
  }
1493
- return info;
1468
+ return state.info;
1494
1469
  }
1495
1470
  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;
1471
+ await _commandError(state, e, this);
1503
1472
  }
1504
1473
  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
- });
1474
+ _commandFinally(state, this);
1526
1475
  }
1527
1476
  }
1528
1477
  _getDataFile(world = null) {
@@ -1541,6 +1490,29 @@ class StableBrowser {
1541
1490
  }
1542
1491
  return dataFile;
1543
1492
  }
1493
+ async waitForUserInput(message, world = null) {
1494
+ if (!message) {
1495
+ message = "# Wait for user input. Press any key to continue";
1496
+ }
1497
+ else {
1498
+ message = "# Wait for user input. " + message;
1499
+ }
1500
+ message += "\n";
1501
+ const value = await new Promise((resolve) => {
1502
+ const rl = readline.createInterface({
1503
+ input: process.stdin,
1504
+ output: process.stdout,
1505
+ });
1506
+ rl.question(message, (answer) => {
1507
+ rl.close();
1508
+ resolve(answer);
1509
+ });
1510
+ });
1511
+ if (value) {
1512
+ this.logger.info(`{{userInput}} was set to: ${value}`);
1513
+ }
1514
+ this.setTestData({ userInput: value }, world);
1515
+ }
1544
1516
  setTestData(testData, world = null) {
1545
1517
  if (!testData) {
1546
1518
  return;
@@ -1728,7 +1700,6 @@ class StableBrowser {
1728
1700
  }
1729
1701
  async takeScreenshot(screenshotPath) {
1730
1702
  const playContext = this.context.playContext;
1731
- const client = await playContext.newCDPSession(this.page);
1732
1703
  // Using CDP to capture the screenshot
1733
1704
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
1734
1705
  document.body.scrollWidth,
@@ -1738,97 +1709,67 @@ class StableBrowser {
1738
1709
  document.body.clientWidth,
1739
1710
  document.documentElement.clientWidth,
1740
1711
  ])));
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();
1712
+ let screenshotBuffer = null;
1713
+ if (this.context.browserName === "chromium") {
1714
+ const client = await playContext.newCDPSession(this.page);
1715
+ const { data } = await client.send("Page.captureScreenshot", {
1716
+ format: "png",
1717
+ // clip: {
1718
+ // x: 0,
1719
+ // y: 0,
1720
+ // width: viewportWidth,
1721
+ // height: viewportHeight,
1722
+ // scale: 1,
1723
+ // },
1724
+ });
1725
+ await client.detach();
1726
+ if (!screenshotPath) {
1727
+ return data;
1728
+ }
1729
+ screenshotBuffer = Buffer.from(data, "base64");
1730
+ }
1731
+ else {
1732
+ screenshotBuffer = await this.page.screenshot();
1733
+ }
1734
+ let image = await Jimp.read(screenshotBuffer);
1735
+ // Get the image dimensions
1736
+ const { width, height } = image.bitmap;
1737
+ const resizeRatio = viewportWidth / width;
1738
+ // Resize the image to fit within the viewport dimensions without enlarging
1739
+ if (width > viewportWidth) {
1740
+ image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
1741
+ await image.write(screenshotPath);
1742
+ }
1743
+ else {
1744
+ fs.writeFileSync(screenshotPath, screenshotBuffer);
1745
+ }
1776
1746
  }
1777
1747
  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;
1748
+ const state = {
1749
+ selectors,
1750
+ _params,
1751
+ options,
1752
+ world,
1753
+ type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1754
+ text: `Verify element exists in page`,
1755
+ operation: "verifyElementExistInPage",
1756
+ log: "***** verify element " + selectors.element_name + " exists in page *****\n",
1757
+ };
1783
1758
  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
1759
  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;
1760
+ await _preCommand(state, this);
1761
+ await expect(state.element).toHaveCount(1, { timeout: 10000 });
1762
+ return state.info;
1797
1763
  }
1798
1764
  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;
1765
+ await _commandError(state, e, this);
1806
1766
  }
1807
1767
  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
- });
1768
+ _commandFinally(state, this);
1828
1769
  }
1829
1770
  }
1830
1771
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1831
- this._validateSelectors(selectors);
1772
+ _validateSelectors(selectors);
1832
1773
  const startTime = Date.now();
1833
1774
  let error = null;
1834
1775
  let screenshotId = null;
@@ -1887,7 +1828,7 @@ class StableBrowser {
1887
1828
  status: "FAILED",
1888
1829
  startTime,
1889
1830
  endTime,
1890
- message: error === null || error === void 0 ? void 0 : error.message,
1831
+ message: error?.message,
1891
1832
  }
1892
1833
  : {
1893
1834
  status: "PASSED",
@@ -2096,7 +2037,7 @@ class StableBrowser {
2096
2037
  status: "FAILED",
2097
2038
  startTime,
2098
2039
  endTime,
2099
- message: error === null || error === void 0 ? void 0 : error.message,
2040
+ message: error?.message,
2100
2041
  }
2101
2042
  : {
2102
2043
  status: "PASSED",
@@ -2132,20 +2073,20 @@ class StableBrowser {
2132
2073
  for (let i = 0; i < frames.length; i++) {
2133
2074
  if (dateAlternatives.date) {
2134
2075
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2135
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
2076
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2136
2077
  result.frame = frames[i];
2137
2078
  results.push(result);
2138
2079
  }
2139
2080
  }
2140
2081
  else if (numberAlternatives.number) {
2141
2082
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2142
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
2083
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2143
2084
  result.frame = frames[i];
2144
2085
  results.push(result);
2145
2086
  }
2146
2087
  }
2147
2088
  else {
2148
- const result = await this._locateElementByText(frames[i], text, "*", true, {});
2089
+ const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2149
2090
  result.frame = frames[i];
2150
2091
  results.push(result);
2151
2092
  }
@@ -2194,7 +2135,7 @@ class StableBrowser {
2194
2135
  status: "FAILED",
2195
2136
  startTime,
2196
2137
  endTime,
2197
- message: error === null || error === void 0 ? void 0 : error.message,
2138
+ message: error?.message,
2198
2139
  }
2199
2140
  : {
2200
2141
  status: "PASSED",
@@ -2278,7 +2219,7 @@ class StableBrowser {
2278
2219
  status: "FAILED",
2279
2220
  startTime,
2280
2221
  endTime,
2281
- message: error === null || error === void 0 ? void 0 : error.message,
2222
+ message: error?.message,
2282
2223
  }
2283
2224
  : {
2284
2225
  status: "PASSED",
@@ -2310,7 +2251,7 @@ class StableBrowser {
2310
2251
  this.logger.info("Table data verified");
2311
2252
  }
2312
2253
  async getTableData(selectors, _params = null, options = {}, world = null) {
2313
- this._validateSelectors(selectors);
2254
+ _validateSelectors(selectors);
2314
2255
  const startTime = Date.now();
2315
2256
  let error = null;
2316
2257
  let screenshotId = null;
@@ -2346,7 +2287,7 @@ class StableBrowser {
2346
2287
  status: "FAILED",
2347
2288
  startTime,
2348
2289
  endTime,
2349
- message: error === null || error === void 0 ? void 0 : error.message,
2290
+ message: error?.message,
2350
2291
  }
2351
2292
  : {
2352
2293
  status: "PASSED",
@@ -2358,7 +2299,7 @@ class StableBrowser {
2358
2299
  }
2359
2300
  }
2360
2301
  async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
2361
- this._validateSelectors(selectors);
2302
+ _validateSelectors(selectors);
2362
2303
  if (!query) {
2363
2304
  throw new Error("query is null");
2364
2305
  }
@@ -2511,7 +2452,7 @@ class StableBrowser {
2511
2452
  status: "FAILED",
2512
2453
  startTime,
2513
2454
  endTime,
2514
- message: error === null || error === void 0 ? void 0 : error.message,
2455
+ message: error?.message,
2515
2456
  }
2516
2457
  : {
2517
2458
  status: "PASSED",
@@ -2603,7 +2544,7 @@ class StableBrowser {
2603
2544
  status: "FAILED",
2604
2545
  startTime,
2605
2546
  endTime,
2606
- message: error === null || error === void 0 ? void 0 : error.message,
2547
+ message: error?.message,
2607
2548
  }
2608
2549
  : {
2609
2550
  status: "PASSED",
@@ -2638,7 +2579,7 @@ class StableBrowser {
2638
2579
  status: "FAILED",
2639
2580
  startTime,
2640
2581
  endTime,
2641
- message: error === null || error === void 0 ? void 0 : error.message,
2582
+ message: error?.message,
2642
2583
  }
2643
2584
  : {
2644
2585
  status: "PASSED",
@@ -2680,7 +2621,7 @@ class StableBrowser {
2680
2621
  status: "FAILED",
2681
2622
  startTime,
2682
2623
  endTime,
2683
- message: error === null || error === void 0 ? void 0 : error.message,
2624
+ message: error?.message,
2684
2625
  }
2685
2626
  : {
2686
2627
  status: "PASSED",
@@ -2716,7 +2657,7 @@ class StableBrowser {
2716
2657
  status: "FAILED",
2717
2658
  startTime,
2718
2659
  endTime,
2719
- message: error === null || error === void 0 ? void 0 : error.message,
2660
+ message: error?.message,
2720
2661
  }
2721
2662
  : {
2722
2663
  status: "PASSED",
@@ -2729,14 +2670,18 @@ class StableBrowser {
2729
2670
  }
2730
2671
  async scrollIfNeeded(element, info) {
2731
2672
  try {
2732
- await element.scrollIntoViewIfNeeded();
2673
+ await element.scrollIntoViewIfNeeded({
2674
+ timeout: 2000,
2675
+ });
2733
2676
  await new Promise((resolve) => setTimeout(resolve, 500));
2734
2677
  if (info) {
2735
- info.box = await element.boundingBox();
2678
+ info.box = await element.boundingBox({
2679
+ timeout: 1000,
2680
+ });
2736
2681
  }
2737
2682
  }
2738
2683
  catch (e) {
2739
- console.log("scroll failed");
2684
+ console.log("#-#");
2740
2685
  }
2741
2686
  }
2742
2687
  _reportToWorld(world, properties) {
@@ -2897,5 +2842,10 @@ const KEYBOARD_EVENTS = [
2897
2842
  "TVAntennaCable",
2898
2843
  "TVAudioDescription",
2899
2844
  ];
2845
+ function unEscapeString(str) {
2846
+ const placeholder = "__NEWLINE__";
2847
+ str = str.replace(new RegExp(placeholder, "g"), "\n");
2848
+ return str;
2849
+ }
2900
2850
  export { StableBrowser };
2901
2851
  //# sourceMappingURL=stable_browser.js.map