automation_model 1.0.395-dev → 1.0.395-main

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,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";
@@ -13,6 +13,9 @@ import { getTableCells, getTableData } from "./table_analyze.js";
13
13
  import objectPath from "object-path";
14
14
  import { decrypt } from "./utils.js";
15
15
  import csv from "csv-parser";
16
+ import { Readable } from "node:stream";
17
+ import readline from "readline";
18
+ import { getContext } from "./init_browser.js";
16
19
  const Types = {
17
20
  CLICK: "click_element",
18
21
  NAVIGATE: "navigate",
@@ -28,6 +31,7 @@ const Types = {
28
31
  SELECT: "select_combobox",
29
32
  VERIFY_PAGE_PATH: "verify_page_path",
30
33
  TYPE_PRESS: "type_press",
34
+ PRESS: "press_key",
31
35
  HOVER: "hover_element",
32
36
  CHECK: "check_element",
33
37
  UNCHECK: "uncheck_element",
@@ -36,16 +40,22 @@ const Types = {
36
40
  SET_DATE_TIME: "set_date_time",
37
41
  SET_VIEWPORT: "set_viewport",
38
42
  VERIFY_VISUAL: "verify_visual",
43
+ LOAD_DATA: "load_data",
44
+ SET_INPUT: "set_input",
39
45
  };
46
+ export const apps = {};
40
47
  class StableBrowser {
41
- constructor(browser, page, logger = null, context = null) {
48
+ constructor(browser, page, logger = null, context = null, world = null) {
42
49
  this.browser = browser;
43
50
  this.page = page;
44
51
  this.logger = logger;
45
52
  this.context = context;
53
+ this.world = world;
46
54
  this.project_path = null;
47
55
  this.webLogFile = null;
56
+ this.networkLogger = null;
48
57
  this.configuration = null;
58
+ this.appName = "main";
49
59
  if (!this.logger) {
50
60
  this.logger = console;
51
61
  }
@@ -71,19 +81,36 @@ class StableBrowser {
71
81
  this.logger.error("unable to read ai_config.json");
72
82
  }
73
83
  const logFolder = path.join(this.project_path, "logs", "web");
74
- this.webLogFile = this.getWebLogFile(logFolder);
75
- this.registerConsoleLogListener(page, context, this.webLogFile);
76
- this.registerRequestListener();
84
+ this.world = world;
77
85
  context.pages = [this.page];
78
86
  context.pageLoading = { status: false };
87
+ this.registerEventListeners(this.context);
88
+ }
89
+ registerEventListeners(context) {
90
+ this.registerConsoleLogListener(this.page, context);
91
+ this.registerRequestListener(this.page, context, this.webLogFile);
92
+ if (!context.pageLoading) {
93
+ context.pageLoading = { status: false };
94
+ }
79
95
  context.playContext.on("page", async function (page) {
80
96
  context.pageLoading.status = true;
81
97
  this.page = page;
82
98
  context.page = page;
83
99
  context.pages.push(page);
84
- this.webLogFile = this.getWebLogFile(logFolder);
85
- this.registerConsoleLogListener(page, context, this.webLogFile);
86
- this.registerRequestListener();
100
+ page.on("close", async () => {
101
+ if (this.context && this.context.pages && this.context.pages.length > 1) {
102
+ this.context.pages.pop();
103
+ this.page = this.context.pages[this.context.pages.length - 1];
104
+ this.context.page = this.page;
105
+ try {
106
+ let title = await this.page.title();
107
+ console.log("Switched to page " + title);
108
+ }
109
+ catch (error) {
110
+ console.error("Error on page close", error);
111
+ }
112
+ }
113
+ });
87
114
  try {
88
115
  await this.waitForPageLoad();
89
116
  console.log("Switch page: " + (await page.title()));
@@ -94,6 +121,36 @@ class StableBrowser {
94
121
  context.pageLoading.status = false;
95
122
  }.bind(this));
96
123
  }
124
+ async switchApp(appName) {
125
+ // check if the current app (this.appName) is the same as the new app
126
+ if (this.appName === appName) {
127
+ return;
128
+ }
129
+ let navigate = false;
130
+ if (!apps[appName]) {
131
+ let newContext = await getContext(null, false, this.logger, appName, false, this);
132
+ navigate = true;
133
+ apps[appName] = {
134
+ context: newContext,
135
+ browser: newContext.browser,
136
+ page: newContext.page,
137
+ };
138
+ }
139
+ const tempContext = {};
140
+ this._copyContext(this, tempContext);
141
+ this._copyContext(apps[appName], this);
142
+ apps[this.appName] = tempContext;
143
+ this.appName = appName;
144
+ if (navigate) {
145
+ await this.goto(this.context.environment.baseUrl);
146
+ await this.waitForPageLoad();
147
+ }
148
+ }
149
+ _copyContext(from, to) {
150
+ to.browser = from.browser;
151
+ to.page = from.page;
152
+ to.context = from.context;
153
+ }
97
154
  getWebLogFile(logFolder) {
98
155
  if (!fs.existsSync(logFolder)) {
99
156
  fs.mkdirSync(logFolder, { recursive: true });
@@ -105,37 +162,63 @@ class StableBrowser {
105
162
  const fileName = nextIndex + ".json";
106
163
  return path.join(logFolder, fileName);
107
164
  }
108
- registerConsoleLogListener(page, context, logFile) {
165
+ registerConsoleLogListener(page, context) {
109
166
  if (!this.context.webLogger) {
110
167
  this.context.webLogger = [];
111
168
  }
112
169
  page.on("console", async (msg) => {
113
- this.context.webLogger.push({
170
+ var _a;
171
+ const obj = {
114
172
  type: msg.type(),
115
173
  text: msg.text(),
116
174
  location: msg.location(),
117
175
  time: new Date().toISOString(),
118
- });
119
- await fs.promises.writeFile(logFile, JSON.stringify(this.context.webLogger, null, 2));
176
+ };
177
+ this.context.webLogger.push(obj);
178
+ (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
120
179
  });
121
180
  }
122
- registerRequestListener() {
123
- this.page.on("request", async (data) => {
181
+ registerRequestListener(page, context, logFile) {
182
+ if (!this.context.networkLogger) {
183
+ this.context.networkLogger = [];
184
+ }
185
+ page.on("request", async (data) => {
186
+ var _a;
187
+ const startTime = new Date().getTime();
124
188
  try {
125
- const pageUrl = new URL(this.page.url());
189
+ const pageUrl = new URL(page.url());
126
190
  const requestUrl = new URL(data.url());
127
191
  if (pageUrl.hostname === requestUrl.hostname) {
128
192
  const method = data.method();
129
- if (method === "POST" || method === "GET" || method === "PUT" || method === "DELETE" || method === "PATCH") {
193
+ if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
130
194
  const token = await data.headerValue("Authorization");
131
195
  if (token) {
132
- this.context.authtoken = token;
196
+ context.authtoken = token;
133
197
  }
134
198
  }
135
199
  }
200
+ const response = await data.response();
201
+ const endTime = new Date().getTime();
202
+ const obj = {
203
+ url: data.url(),
204
+ method: data.method(),
205
+ postData: data.postData(),
206
+ error: data.failure() ? data.failure().errorText : null,
207
+ duration: endTime - startTime,
208
+ startTime,
209
+ };
210
+ context.networkLogger.push(obj);
211
+ (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
136
212
  }
137
213
  catch (error) {
138
214
  console.error("Error in request listener", error);
215
+ context.networkLogger.push({
216
+ error: "not able to listen",
217
+ message: error.message,
218
+ stack: error.stack,
219
+ time: new Date().toISOString(),
220
+ });
221
+ // await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
139
222
  }
140
223
  });
141
224
  }
@@ -178,24 +261,78 @@ class StableBrowser {
178
261
  }
179
262
  return text;
180
263
  }
181
- _getLocator(locator, scope, _params) {
182
- if (locator.type === "pw_selector") {
183
- return scope.locator(locator.selector);
264
+ _fixLocatorUsingParams(locator, _params) {
265
+ // check if not null
266
+ if (!locator) {
267
+ return locator;
268
+ }
269
+ // clone the locator
270
+ locator = JSON.parse(JSON.stringify(locator));
271
+ this.scanAndManipulate(locator, _params);
272
+ return locator;
273
+ }
274
+ _isObject(value) {
275
+ return value && typeof value === "object" && value.constructor === Object;
276
+ }
277
+ scanAndManipulate(currentObj, _params) {
278
+ for (const key in currentObj) {
279
+ if (typeof currentObj[key] === "string") {
280
+ // Perform string manipulation
281
+ currentObj[key] = this._fixUsingParams(currentObj[key], _params);
282
+ }
283
+ else if (this._isObject(currentObj[key])) {
284
+ // Recursively scan nested objects
285
+ this.scanAndManipulate(currentObj[key], _params);
286
+ }
184
287
  }
288
+ }
289
+ _getLocator(locator, scope, _params) {
290
+ locator = this._fixLocatorUsingParams(locator, _params);
291
+ let locatorReturn;
185
292
  if (locator.role) {
186
293
  if (locator.role[1].nameReg) {
187
294
  locator.role[1].name = reg_parser(locator.role[1].nameReg);
188
295
  delete locator.role[1].nameReg;
189
296
  }
190
- if (locator.role[1].name) {
191
- locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
192
- }
193
- return scope.getByRole(locator.role[0], locator.role[1]);
297
+ // if (locator.role[1].name) {
298
+ // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
299
+ // }
300
+ locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
194
301
  }
195
302
  if (locator.css) {
196
- return scope.locator(this._fixUsingParams(locator.css, _params));
303
+ locatorReturn = scope.locator(locator.css);
304
+ }
305
+ // handle role/name locators
306
+ // locator.selector will be something like: textbox[name="Username"i]
307
+ if (locator.engine === "internal:role") {
308
+ // extract the role, name and the i/s flags using regex
309
+ const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
310
+ if (match) {
311
+ const role = match[1];
312
+ const name = match[3];
313
+ const flags = match[4];
314
+ locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
315
+ }
316
+ }
317
+ if (locator === null || locator === void 0 ? void 0 : locator.engine) {
318
+ if (locator.engine === "css") {
319
+ locatorReturn = scope.locator(locator.selector);
320
+ }
321
+ else {
322
+ let selector = locator.selector;
323
+ if (locator.engine === "internal:attr") {
324
+ if (!selector.startsWith("[")) {
325
+ selector = `[${selector}]`;
326
+ }
327
+ }
328
+ locatorReturn = scope.locator(`${locator.engine}=${selector}`);
329
+ }
330
+ }
331
+ if (!locatorReturn) {
332
+ console.error(locator);
333
+ throw new Error("Locator undefined");
197
334
  }
198
- throw new Error("unknown locator type");
335
+ return locatorReturn;
199
336
  }
200
337
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
201
338
  let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
@@ -406,6 +543,8 @@ class StableBrowser {
406
543
  if (result.foundElements.length > 0) {
407
544
  let dialogCloseLocator = result.foundElements[0].locator;
408
545
  await dialogCloseLocator.click();
546
+ // wait for the dialog to close
547
+ await dialogCloseLocator.waitFor({ state: "hidden" });
409
548
  return { rerun: true };
410
549
  }
411
550
  }
@@ -414,7 +553,7 @@ class StableBrowser {
414
553
  }
415
554
  async _locate(selectors, info, _params, timeout = 30000) {
416
555
  for (let i = 0; i < 3; i++) {
417
- info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
556
+ info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
418
557
  for (let j = 0; j < selectors.locators.length; j++) {
419
558
  let selector = selectors.locators[j];
420
559
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
@@ -434,9 +573,30 @@ class StableBrowser {
434
573
  //let arrayMode = Array.isArray(selectors);
435
574
  let scope = this.page;
436
575
  if (selectors.iframe_src || selectors.frameLocators) {
576
+ const findFrame = (frame, framescope) => {
577
+ for (let i = 0; i < frame.selectors.length; i++) {
578
+ let frameLocator = frame.selectors[i];
579
+ if (frameLocator.css) {
580
+ framescope = framescope.frameLocator(frameLocator.css);
581
+ if (frameLocator.index) {
582
+ framescope = framescope.nth(frameLocator.index);
583
+ }
584
+ break;
585
+ }
586
+ }
587
+ if (frame.children) {
588
+ return findFrame(frame.children, framescope);
589
+ }
590
+ return framescope;
591
+ };
437
592
  info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
438
593
  while (true) {
439
594
  let frameFound = false;
595
+ if (selectors.nestFrmLoc) {
596
+ scope = findFrame(selectors.nestFrmLoc, scope);
597
+ frameFound = true;
598
+ break;
599
+ }
440
600
  if (selectors.frameLocators) {
441
601
  for (let i = 0; i < selectors.frameLocators.length; i++) {
442
602
  let frameLocator = selectors.frameLocators[i];
@@ -600,6 +760,9 @@ class StableBrowser {
600
760
  async click(selectors, _params, options = {}, world = null) {
601
761
  this._validateSelectors(selectors);
602
762
  const startTime = Date.now();
763
+ if (options && options.context) {
764
+ selectors.locators[0].text = options.context;
765
+ }
603
766
  const info = {};
604
767
  info.log = "***** click on " + selectors.element_name + " *****\n";
605
768
  info.operation = "click";
@@ -613,14 +776,14 @@ class StableBrowser {
613
776
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
614
777
  try {
615
778
  await this._highlightElements(element);
616
- await element.click({ timeout: 5000 });
779
+ await element.click();
617
780
  await new Promise((resolve) => setTimeout(resolve, 1000));
618
781
  }
619
782
  catch (e) {
620
783
  // await this.closeUnexpectedPopups();
621
784
  info.log += "click failed, will try again" + "\n";
622
785
  element = await this._locate(selectors, info, _params);
623
- await element.click({ timeout: 10000, force: true });
786
+ await element.dispatchEvent("click");
624
787
  await new Promise((resolve) => setTimeout(resolve, 1000));
625
788
  }
626
789
  await this.waitForPageLoad();
@@ -673,7 +836,7 @@ class StableBrowser {
673
836
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
674
837
  try {
675
838
  await this._highlightElements(element);
676
- await element.setChecked(checked, { timeout: 5000 });
839
+ await element.setChecked(checked);
677
840
  await new Promise((resolve) => setTimeout(resolve, 1000));
678
841
  }
679
842
  catch (e) {
@@ -737,7 +900,7 @@ class StableBrowser {
737
900
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
738
901
  try {
739
902
  await this._highlightElements(element);
740
- await element.hover({ timeout: 10000 });
903
+ await element.hover();
741
904
  await new Promise((resolve) => setTimeout(resolve, 1000));
742
905
  }
743
906
  catch (e) {
@@ -799,7 +962,7 @@ class StableBrowser {
799
962
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
800
963
  try {
801
964
  await this._highlightElements(element);
802
- await element.selectOption(values, { timeout: 5000 });
965
+ await element.selectOption(values);
803
966
  }
804
967
  catch (e) {
805
968
  //await this.closeUnexpectedPopups();
@@ -908,71 +1071,45 @@ class StableBrowser {
908
1071
  });
909
1072
  }
910
1073
  }
911
- async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1074
+ async setInputValue(selectors, value, _params = null, options = {}, world = null) {
1075
+ // set input value for non fillable inputs like date, time, range, color, etc.
912
1076
  this._validateSelectors(selectors);
913
1077
  const startTime = Date.now();
914
- let error = null;
915
- let screenshotId = null;
916
- let screenshotPath = null;
917
1078
  const info = {};
918
- info.log = "";
919
- info.operation = Types.SET_DATE_TIME;
1079
+ info.log = "***** set input value " + selectors.element_name + " *****\n";
1080
+ info.operation = "setInputValue";
920
1081
  info.selectors = selectors;
1082
+ value = this._fixUsingParams(value, _params);
921
1083
  info.value = value;
1084
+ let error = null;
1085
+ let screenshotId = null;
1086
+ let screenshotPath = null;
922
1087
  try {
923
1088
  value = await this._replaceWithLocalData(value, this);
924
1089
  let element = await this._locate(selectors, info, _params);
925
- //insert red border around the element
926
1090
  await this.scrollIfNeeded(element, info);
927
1091
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
928
1092
  await this._highlightElements(element);
929
1093
  try {
930
- await element.click();
931
- await new Promise((resolve) => setTimeout(resolve, 500));
932
- if (format) {
933
- value = dayjs(value).format(format);
934
- await element.fill(value);
935
- }
936
- else {
937
- const dateTimeValue = await getDateTimeValue({ value, element });
938
- await element.evaluateHandle((el, dateTimeValue) => {
939
- el.value = ""; // clear input
940
- el.value = dateTimeValue;
941
- }, dateTimeValue);
942
- }
943
- if (enter) {
944
- await new Promise((resolve) => setTimeout(resolve, 2000));
945
- await this.page.keyboard.press("Enter");
946
- await this.waitForPageLoad();
947
- }
1094
+ await element.evaluateHandle((el, value) => {
1095
+ el.value = value;
1096
+ }, value);
948
1097
  }
949
1098
  catch (error) {
950
- //await this.closeUnexpectedPopups();
951
- this.logger.error("setting date time input failed " + JSON.stringify(info));
952
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1099
+ this.logger.error("setInputValue failed, will try again");
1100
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
953
1101
  info.screenshotPath = screenshotPath;
954
1102
  Object.assign(error, { info: info });
955
- await element.click();
956
- await new Promise((resolve) => setTimeout(resolve, 500));
957
- if (format) {
958
- value = dayjs(value).format(format);
959
- await element.fill(value);
960
- }
961
- else {
962
- const dateTimeValue = await getDateTimeValue({ value, element });
963
- await element.evaluateHandle((el, dateTimeValue) => {
964
- el.value = ""; // clear input
965
- el.value = dateTimeValue;
966
- }, dateTimeValue);
967
- }
968
- if (enter) {
969
- await new Promise((resolve) => setTimeout(resolve, 2000));
970
- await this.page.keyboard.press("Enter");
971
- await this.waitForPageLoad();
972
- }
1103
+ await element.evaluateHandle((el, value) => {
1104
+ el.value = value;
1105
+ });
973
1106
  }
974
1107
  }
975
- catch (error) {
1108
+ catch (e) {
1109
+ this.logger.error("setInputValue failed " + info.log);
1110
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1111
+ info.screenshotPath = screenshotPath;
1112
+ Object.assign(e, { info: info });
976
1113
  error = e;
977
1114
  throw e;
978
1115
  }
@@ -980,10 +1117,10 @@ class StableBrowser {
980
1117
  const endTime = Date.now();
981
1118
  this._reportToWorld(world, {
982
1119
  element_name: selectors.element_name,
983
- type: Types.SET_DATE_TIME,
984
- screenshotId,
1120
+ type: Types.SET_INPUT,
1121
+ text: `Set input value`,
985
1122
  value: value,
986
- text: `setDateTime input with value: ${value}`,
1123
+ screenshotId,
987
1124
  result: error
988
1125
  ? {
989
1126
  status: "FAILED",
@@ -1000,7 +1137,7 @@ class StableBrowser {
1000
1137
  });
1001
1138
  }
1002
1139
  }
1003
- async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
1140
+ async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1004
1141
  this._validateSelectors(selectors);
1005
1142
  const startTime = Date.now();
1006
1143
  let error = null;
@@ -1012,6 +1149,7 @@ class StableBrowser {
1012
1149
  info.selectors = selectors;
1013
1150
  info.value = value;
1014
1151
  try {
1152
+ value = await this._replaceWithLocalData(value, this);
1015
1153
  let element = await this._locate(selectors, info, _params);
1016
1154
  //insert red border around the element
1017
1155
  await this.scrollIfNeeded(element, info);
@@ -1020,28 +1158,51 @@ class StableBrowser {
1020
1158
  try {
1021
1159
  await element.click();
1022
1160
  await new Promise((resolve) => setTimeout(resolve, 500));
1023
- const dateTimeValue = await getDateTimeValue({ value, element });
1024
- await element.evaluateHandle((el, dateTimeValue) => {
1025
- el.value = ""; // clear input
1026
- el.value = dateTimeValue;
1027
- }, dateTimeValue);
1161
+ if (format) {
1162
+ value = dayjs(value).format(format);
1163
+ await element.fill(value);
1164
+ }
1165
+ else {
1166
+ const dateTimeValue = await getDateTimeValue({ value, element });
1167
+ await element.evaluateHandle((el, dateTimeValue) => {
1168
+ el.value = ""; // clear input
1169
+ el.value = dateTimeValue;
1170
+ }, dateTimeValue);
1171
+ }
1172
+ if (enter) {
1173
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1174
+ await this.page.keyboard.press("Enter");
1175
+ await this.waitForPageLoad();
1176
+ }
1028
1177
  }
1029
- catch (error) {
1178
+ catch (err) {
1030
1179
  //await this.closeUnexpectedPopups();
1031
1180
  this.logger.error("setting date time input failed " + JSON.stringify(info));
1032
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1181
+ this.logger.info("Trying again");
1182
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1033
1183
  info.screenshotPath = screenshotPath;
1034
- Object.assign(error, { info: info });
1184
+ Object.assign(err, { info: info });
1035
1185
  await element.click();
1036
1186
  await new Promise((resolve) => setTimeout(resolve, 500));
1037
- const dateTimeValue = await getDateTimeValue({ value, element });
1038
- await element.evaluateHandle((el, dateTimeValue) => {
1039
- el.value = ""; // clear input
1040
- el.value = dateTimeValue;
1041
- }, dateTimeValue);
1187
+ if (format) {
1188
+ value = dayjs(value).format(format);
1189
+ await element.fill(value);
1190
+ }
1191
+ else {
1192
+ const dateTimeValue = await getDateTimeValue({ value, element });
1193
+ await element.evaluateHandle((el, dateTimeValue) => {
1194
+ el.value = ""; // clear input
1195
+ el.value = dateTimeValue;
1196
+ }, dateTimeValue);
1197
+ }
1198
+ if (enter) {
1199
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1200
+ await this.page.keyboard.press("Enter");
1201
+ await this.waitForPageLoad();
1202
+ }
1042
1203
  }
1043
1204
  }
1044
- catch (error) {
1205
+ catch (e) {
1045
1206
  error = e;
1046
1207
  throw e;
1047
1208
  }
@@ -1091,20 +1252,32 @@ class StableBrowser {
1091
1252
  await this.scrollIfNeeded(element, info);
1092
1253
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1093
1254
  await this._highlightElements(element);
1094
- try {
1095
- let currentValue = await element.inputValue();
1096
- if (currentValue) {
1097
- await element.fill("");
1255
+ if (options === null || options === undefined || !options.press) {
1256
+ try {
1257
+ let currentValue = await element.inputValue();
1258
+ if (currentValue) {
1259
+ await element.fill("");
1260
+ }
1261
+ }
1262
+ catch (e) {
1263
+ this.logger.info("unable to clear input value");
1098
1264
  }
1099
1265
  }
1100
- catch (e) {
1101
- this.logger.info("unable to clear input value");
1102
- }
1103
- try {
1104
- await element.click({ timeout: 5000 });
1266
+ if (options === null || options === undefined || options.press) {
1267
+ try {
1268
+ await element.click({ timeout: 5000 });
1269
+ }
1270
+ catch (e) {
1271
+ await element.dispatchEvent("click");
1272
+ }
1105
1273
  }
1106
- catch (e) {
1107
- await element.dispatchEvent("click");
1274
+ else {
1275
+ try {
1276
+ await element.focus();
1277
+ }
1278
+ catch (e) {
1279
+ await element.dispatchEvent("focus");
1280
+ }
1108
1281
  }
1109
1282
  await new Promise((resolve) => setTimeout(resolve, 500));
1110
1283
  const valueSegment = _value.split("&&");
@@ -1192,7 +1365,7 @@ class StableBrowser {
1192
1365
  let element = await this._locate(selectors, info, _params);
1193
1366
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1194
1367
  await this._highlightElements(element);
1195
- await element.fill(value, { timeout: 10000 });
1368
+ await element.fill(value);
1196
1369
  await element.dispatchEvent("change");
1197
1370
  if (enter) {
1198
1371
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -1411,7 +1584,7 @@ class StableBrowser {
1411
1584
  return info;
1412
1585
  }
1413
1586
  catch (e) {
1414
- //await this.closeUnexpectedPopups();
1587
+ await this.closeUnexpectedPopups();
1415
1588
  this.logger.error("verify element contains text failed " + info.log);
1416
1589
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1417
1590
  info.screenshotPath = screenshotPath;
@@ -1459,6 +1632,29 @@ class StableBrowser {
1459
1632
  }
1460
1633
  return dataFile;
1461
1634
  }
1635
+ async waitForUserInput(message, world = null) {
1636
+ if (!message) {
1637
+ message = "# Wait for user input. Press any key to continue";
1638
+ }
1639
+ else {
1640
+ message = "# Wait for user input. " + message;
1641
+ }
1642
+ message += "\n";
1643
+ const value = await new Promise((resolve) => {
1644
+ const rl = readline.createInterface({
1645
+ input: process.stdin,
1646
+ output: process.stdout,
1647
+ });
1648
+ rl.question(message, (answer) => {
1649
+ rl.close();
1650
+ resolve(answer);
1651
+ });
1652
+ });
1653
+ if (value) {
1654
+ this.logger.info(`{{userInput}} was set to: ${value}`);
1655
+ }
1656
+ this.setTestData({ userInput: value }, world);
1657
+ }
1462
1658
  setTestData(testData, world = null) {
1463
1659
  if (!testData) {
1464
1660
  return;
@@ -1486,7 +1682,7 @@ class StableBrowser {
1486
1682
  const data = fs.readFileSync(filePath, "utf8");
1487
1683
  const results = [];
1488
1684
  return new Promise((resolve, reject) => {
1489
- const readableStream = new stream.Readable();
1685
+ const readableStream = new Readable();
1490
1686
  readableStream._read = () => { }; // _read is required but you can noop it
1491
1687
  readableStream.push(data);
1492
1688
  readableStream.push(null);
@@ -1666,30 +1862,29 @@ class StableBrowser {
1666
1862
  ])));
1667
1863
  const { data } = await client.send("Page.captureScreenshot", {
1668
1864
  format: "png",
1669
- clip: {
1670
- x: 0,
1671
- y: 0,
1672
- width: viewportWidth,
1673
- height: viewportHeight,
1674
- scale: 1,
1675
- },
1865
+ // clip: {
1866
+ // x: 0,
1867
+ // y: 0,
1868
+ // width: viewportWidth,
1869
+ // height: viewportHeight,
1870
+ // scale: 1,
1871
+ // },
1676
1872
  });
1677
1873
  if (!screenshotPath) {
1678
1874
  return data;
1679
1875
  }
1680
1876
  let screenshotBuffer = Buffer.from(data, "base64");
1681
- const sharpBuffer = sharp(screenshotBuffer);
1682
- const metadata = await sharpBuffer.metadata();
1683
- //check if you are on retina display and reduce the quality of the image
1684
- if (metadata.width > viewportWidth || metadata.height > viewportHeight) {
1685
- screenshotBuffer = await sharpBuffer
1686
- .resize(viewportWidth, viewportHeight, {
1687
- fit: sharp.fit.inside,
1688
- withoutEnlargement: true,
1689
- })
1690
- .toBuffer();
1691
- }
1692
- fs.writeFileSync(screenshotPath, screenshotBuffer);
1877
+ let image = await Jimp.read(screenshotBuffer);
1878
+ // Get the image dimensions
1879
+ const { width, height } = image.bitmap;
1880
+ // Resize the image to fit within the viewport dimensions without enlarging
1881
+ if (width > viewportWidth || height > viewportHeight) {
1882
+ image = image.resize({ w: viewportWidth, h: viewportHeight }); // Resize the image while maintaining aspect ratio
1883
+ await image.write(screenshotPath);
1884
+ }
1885
+ else {
1886
+ fs.writeFileSync(screenshotPath, screenshotBuffer);
1887
+ }
1693
1888
  await client.detach();
1694
1889
  }
1695
1890
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
@@ -1861,18 +2056,22 @@ class StableBrowser {
1861
2056
  //}
1862
2057
  if ((result && result.data, result.data.status === true)) {
1863
2058
  let codeOrUrlFound = false;
2059
+ let emailCode = null;
2060
+ let emailUrl = null;
1864
2061
  // check if a code is returned
1865
2062
  if (result.data.content && result.data.content.code) {
1866
2063
  let code = result.data.content.code;
1867
2064
  this.setTestData({ emailCode: code }, world);
1868
- this.logger.info("set test data: emailCode=" + code);
2065
+ this.logger.info("set test data: emailCode = " + code);
2066
+ emailCode = code;
1869
2067
  codeOrUrlFound = true;
1870
2068
  }
1871
2069
  // check if a url is returned
1872
2070
  if (result.data.content && result.data.content.url) {
1873
2071
  let url = result.data.content.url;
1874
2072
  this.setTestData({ emailUrl: url }, world);
1875
- this.logger.info("set test data: emailUrl=" + url);
2073
+ this.logger.info("set test data: emailUrl = " + url);
2074
+ emailUrl = url;
1876
2075
  codeOrUrlFound = true;
1877
2076
  }
1878
2077
  if (codeOrUrlFound) {
@@ -2494,13 +2693,13 @@ class StableBrowser {
2494
2693
  }
2495
2694
  catch (e) {
2496
2695
  if (e.label === "networkidle") {
2497
- console.log("waitted for the network to be idle timeout");
2696
+ console.log("waited for the network to be idle timeout");
2498
2697
  }
2499
2698
  else if (e.label === "load") {
2500
- console.log("waitted for the load timeout");
2699
+ console.log("waited for the load timeout");
2501
2700
  }
2502
2701
  else if (e.label === "domcontentloaded") {
2503
- console.log("waitted for the domcontent loaded timeout");
2702
+ console.log("waited for the domcontent loaded timeout");
2504
2703
  }
2505
2704
  console.log(".");
2506
2705
  }
@@ -2535,13 +2734,6 @@ class StableBrowser {
2535
2734
  const info = {};
2536
2735
  try {
2537
2736
  await this.page.close();
2538
- if (this.context && this.context.pages && this.context.pages.length > 0) {
2539
- this.context.pages.pop();
2540
- this.page = this.context.pages[this.context.pages.length - 1];
2541
- this.context.page = this.page;
2542
- let title = await this.page.title();
2543
- console.log("Switched to page " + title);
2544
- }
2545
2737
  }
2546
2738
  catch (e) {
2547
2739
  console.log(".");
@@ -2650,33 +2842,18 @@ class StableBrowser {
2650
2842
  }
2651
2843
  async scrollIfNeeded(element, info) {
2652
2844
  try {
2653
- let didScroll = await element.evaluate((node) => {
2654
- const rect = node.getBoundingClientRect();
2655
- if (rect &&
2656
- rect.top >= 0 &&
2657
- rect.left >= 0 &&
2658
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
2659
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
2660
- return false;
2661
- }
2662
- else {
2663
- node.scrollIntoView({
2664
- behavior: "smooth",
2665
- block: "center",
2666
- inline: "center",
2667
- });
2668
- return true;
2669
- }
2845
+ await element.scrollIntoViewIfNeeded({
2846
+ timeout: 2000,
2670
2847
  });
2671
- if (didScroll) {
2672
- await new Promise((resolve) => setTimeout(resolve, 500));
2673
- if (info) {
2674
- info.box = await element.boundingBox();
2675
- }
2848
+ await new Promise((resolve) => setTimeout(resolve, 500));
2849
+ if (info) {
2850
+ info.box = await element.boundingBox({
2851
+ timeout: 1000,
2852
+ });
2676
2853
  }
2677
2854
  }
2678
2855
  catch (e) {
2679
- console.log("scroll failed");
2856
+ console.log("#-#");
2680
2857
  }
2681
2858
  }
2682
2859
  _reportToWorld(world, properties) {