automation_model 1.0.396-dev → 1.0.396-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,26 +261,83 @@ 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
+ }
197
316
  }
198
- throw new Error("unknown locator type");
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");
334
+ }
335
+ return locatorReturn;
199
336
  }
200
337
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
338
+ if (css && css.locator) {
339
+ css = css.locator;
340
+ }
201
341
  let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
202
342
  if (result.elementCount === 0) {
203
343
  return;
@@ -225,6 +365,15 @@ class StableBrowser {
225
365
  return false;
226
366
  }
227
367
  document.isParent = isParent;
368
+ function getRegex(str) {
369
+ const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
370
+ if (!match) {
371
+ return null;
372
+ }
373
+ let [_, pattern, flags] = match;
374
+ return new RegExp(pattern, flags);
375
+ }
376
+ document.getRegex = getRegex;
228
377
  function collectAllShadowDomElements(element, result = []) {
229
378
  // Check and add the element if it has a shadow root
230
379
  if (element.shadowRoot) {
@@ -243,6 +392,10 @@ class StableBrowser {
243
392
  if (!tag) {
244
393
  tag = "*";
245
394
  }
395
+ let regexpSearch = document.getRegex(text);
396
+ if (regexpSearch) {
397
+ regex = true;
398
+ }
246
399
  let elements = Array.from(document.querySelectorAll(tag));
247
400
  let shadowHosts = [];
248
401
  document.collectAllShadowDomElements(document, shadowHosts);
@@ -258,7 +411,9 @@ class StableBrowser {
258
411
  let randomToken = null;
259
412
  const foundElements = [];
260
413
  if (regex) {
261
- let regexpSearch = new RegExp(text, "im");
414
+ if (!regexpSearch) {
415
+ regexpSearch = new RegExp(text, "im");
416
+ }
262
417
  for (let i = 0; i < elements.length; i++) {
263
418
  const element = elements[i];
264
419
  if ((element.innerText && regexpSearch.test(element.innerText)) ||
@@ -406,6 +561,8 @@ class StableBrowser {
406
561
  if (result.foundElements.length > 0) {
407
562
  let dialogCloseLocator = result.foundElements[0].locator;
408
563
  await dialogCloseLocator.click();
564
+ // wait for the dialog to close
565
+ await dialogCloseLocator.waitFor({ state: "hidden" });
409
566
  return { rerun: true };
410
567
  }
411
568
  }
@@ -414,7 +571,7 @@ class StableBrowser {
414
571
  }
415
572
  async _locate(selectors, info, _params, timeout = 30000) {
416
573
  for (let i = 0; i < 3; i++) {
417
- info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
574
+ info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
418
575
  for (let j = 0; j < selectors.locators.length; j++) {
419
576
  let selector = selectors.locators[j];
420
577
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
@@ -434,9 +591,30 @@ class StableBrowser {
434
591
  //let arrayMode = Array.isArray(selectors);
435
592
  let scope = this.page;
436
593
  if (selectors.iframe_src || selectors.frameLocators) {
594
+ const findFrame = (frame, framescope) => {
595
+ for (let i = 0; i < frame.selectors.length; i++) {
596
+ let frameLocator = frame.selectors[i];
597
+ if (frameLocator.css) {
598
+ framescope = framescope.frameLocator(frameLocator.css);
599
+ if (frameLocator.index) {
600
+ framescope = framescope.nth(frameLocator.index);
601
+ }
602
+ break;
603
+ }
604
+ }
605
+ if (frame.children) {
606
+ return findFrame(frame.children, framescope);
607
+ }
608
+ return framescope;
609
+ };
437
610
  info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
438
611
  while (true) {
439
612
  let frameFound = false;
613
+ if (selectors.nestFrmLoc) {
614
+ scope = findFrame(selectors.nestFrmLoc, scope);
615
+ frameFound = true;
616
+ break;
617
+ }
440
618
  if (selectors.frameLocators) {
441
619
  for (let i = 0; i < selectors.frameLocators.length; i++) {
442
620
  let frameLocator = selectors.frameLocators[i];
@@ -600,6 +778,9 @@ class StableBrowser {
600
778
  async click(selectors, _params, options = {}, world = null) {
601
779
  this._validateSelectors(selectors);
602
780
  const startTime = Date.now();
781
+ if (options && options.context) {
782
+ selectors.locators[0].text = options.context;
783
+ }
603
784
  const info = {};
604
785
  info.log = "***** click on " + selectors.element_name + " *****\n";
605
786
  info.operation = "click";
@@ -613,14 +794,14 @@ class StableBrowser {
613
794
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
614
795
  try {
615
796
  await this._highlightElements(element);
616
- await element.click({ timeout: 5000 });
797
+ await element.click();
617
798
  await new Promise((resolve) => setTimeout(resolve, 1000));
618
799
  }
619
800
  catch (e) {
620
801
  // await this.closeUnexpectedPopups();
621
802
  info.log += "click failed, will try again" + "\n";
622
803
  element = await this._locate(selectors, info, _params);
623
- await element.click({ timeout: 10000, force: true });
804
+ await element.dispatchEvent("click");
624
805
  await new Promise((resolve) => setTimeout(resolve, 1000));
625
806
  }
626
807
  await this.waitForPageLoad();
@@ -673,7 +854,7 @@ class StableBrowser {
673
854
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
674
855
  try {
675
856
  await this._highlightElements(element);
676
- await element.setChecked(checked, { timeout: 5000 });
857
+ await element.setChecked(checked);
677
858
  await new Promise((resolve) => setTimeout(resolve, 1000));
678
859
  }
679
860
  catch (e) {
@@ -737,7 +918,7 @@ class StableBrowser {
737
918
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
738
919
  try {
739
920
  await this._highlightElements(element);
740
- await element.hover({ timeout: 10000 });
921
+ await element.hover();
741
922
  await new Promise((resolve) => setTimeout(resolve, 1000));
742
923
  }
743
924
  catch (e) {
@@ -799,7 +980,7 @@ class StableBrowser {
799
980
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
800
981
  try {
801
982
  await this._highlightElements(element);
802
- await element.selectOption(values, { timeout: 5000 });
983
+ await element.selectOption(values);
803
984
  }
804
985
  catch (e) {
805
986
  //await this.closeUnexpectedPopups();
@@ -908,71 +1089,45 @@ class StableBrowser {
908
1089
  });
909
1090
  }
910
1091
  }
911
- async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1092
+ async setInputValue(selectors, value, _params = null, options = {}, world = null) {
1093
+ // set input value for non fillable inputs like date, time, range, color, etc.
912
1094
  this._validateSelectors(selectors);
913
1095
  const startTime = Date.now();
914
- let error = null;
915
- let screenshotId = null;
916
- let screenshotPath = null;
917
1096
  const info = {};
918
- info.log = "";
919
- info.operation = Types.SET_DATE_TIME;
1097
+ info.log = "***** set input value " + selectors.element_name + " *****\n";
1098
+ info.operation = "setInputValue";
920
1099
  info.selectors = selectors;
1100
+ value = this._fixUsingParams(value, _params);
921
1101
  info.value = value;
1102
+ let error = null;
1103
+ let screenshotId = null;
1104
+ let screenshotPath = null;
922
1105
  try {
923
1106
  value = await this._replaceWithLocalData(value, this);
924
1107
  let element = await this._locate(selectors, info, _params);
925
- //insert red border around the element
926
1108
  await this.scrollIfNeeded(element, info);
927
1109
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
928
1110
  await this._highlightElements(element);
929
1111
  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
- }
1112
+ await element.evaluateHandle((el, value) => {
1113
+ el.value = value;
1114
+ }, value);
948
1115
  }
949
1116
  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)));
1117
+ this.logger.error("setInputValue failed, will try again");
1118
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
953
1119
  info.screenshotPath = screenshotPath;
954
1120
  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
- }
1121
+ await element.evaluateHandle((el, value) => {
1122
+ el.value = value;
1123
+ });
973
1124
  }
974
1125
  }
975
- catch (error) {
1126
+ catch (e) {
1127
+ this.logger.error("setInputValue failed " + info.log);
1128
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1129
+ info.screenshotPath = screenshotPath;
1130
+ Object.assign(e, { info: info });
976
1131
  error = e;
977
1132
  throw e;
978
1133
  }
@@ -980,10 +1135,10 @@ class StableBrowser {
980
1135
  const endTime = Date.now();
981
1136
  this._reportToWorld(world, {
982
1137
  element_name: selectors.element_name,
983
- type: Types.SET_DATE_TIME,
984
- screenshotId,
1138
+ type: Types.SET_INPUT,
1139
+ text: `Set input value`,
985
1140
  value: value,
986
- text: `setDateTime input with value: ${value}`,
1141
+ screenshotId,
987
1142
  result: error
988
1143
  ? {
989
1144
  status: "FAILED",
@@ -1000,7 +1155,7 @@ class StableBrowser {
1000
1155
  });
1001
1156
  }
1002
1157
  }
1003
- async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
1158
+ async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1004
1159
  this._validateSelectors(selectors);
1005
1160
  const startTime = Date.now();
1006
1161
  let error = null;
@@ -1012,6 +1167,7 @@ class StableBrowser {
1012
1167
  info.selectors = selectors;
1013
1168
  info.value = value;
1014
1169
  try {
1170
+ value = await this._replaceWithLocalData(value, this);
1015
1171
  let element = await this._locate(selectors, info, _params);
1016
1172
  //insert red border around the element
1017
1173
  await this.scrollIfNeeded(element, info);
@@ -1020,28 +1176,51 @@ class StableBrowser {
1020
1176
  try {
1021
1177
  await element.click();
1022
1178
  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);
1179
+ if (format) {
1180
+ value = dayjs(value).format(format);
1181
+ await element.fill(value);
1182
+ }
1183
+ else {
1184
+ const dateTimeValue = await getDateTimeValue({ value, element });
1185
+ await element.evaluateHandle((el, dateTimeValue) => {
1186
+ el.value = ""; // clear input
1187
+ el.value = dateTimeValue;
1188
+ }, dateTimeValue);
1189
+ }
1190
+ if (enter) {
1191
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1192
+ await this.page.keyboard.press("Enter");
1193
+ await this.waitForPageLoad();
1194
+ }
1028
1195
  }
1029
- catch (error) {
1196
+ catch (err) {
1030
1197
  //await this.closeUnexpectedPopups();
1031
1198
  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)));
1199
+ this.logger.info("Trying again");
1200
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1033
1201
  info.screenshotPath = screenshotPath;
1034
- Object.assign(error, { info: info });
1202
+ Object.assign(err, { info: info });
1035
1203
  await element.click();
1036
1204
  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);
1205
+ if (format) {
1206
+ value = dayjs(value).format(format);
1207
+ await element.fill(value);
1208
+ }
1209
+ else {
1210
+ const dateTimeValue = await getDateTimeValue({ value, element });
1211
+ await element.evaluateHandle((el, dateTimeValue) => {
1212
+ el.value = ""; // clear input
1213
+ el.value = dateTimeValue;
1214
+ }, dateTimeValue);
1215
+ }
1216
+ if (enter) {
1217
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1218
+ await this.page.keyboard.press("Enter");
1219
+ await this.waitForPageLoad();
1220
+ }
1042
1221
  }
1043
1222
  }
1044
- catch (error) {
1223
+ catch (e) {
1045
1224
  error = e;
1046
1225
  throw e;
1047
1226
  }
@@ -1091,20 +1270,32 @@ class StableBrowser {
1091
1270
  await this.scrollIfNeeded(element, info);
1092
1271
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1093
1272
  await this._highlightElements(element);
1094
- try {
1095
- let currentValue = await element.inputValue();
1096
- if (currentValue) {
1097
- await element.fill("");
1273
+ if (options === null || options === undefined || !options.press) {
1274
+ try {
1275
+ let currentValue = await element.inputValue();
1276
+ if (currentValue) {
1277
+ await element.fill("");
1278
+ }
1279
+ }
1280
+ catch (e) {
1281
+ this.logger.info("unable to clear input value");
1098
1282
  }
1099
1283
  }
1100
- catch (e) {
1101
- this.logger.info("unable to clear input value");
1102
- }
1103
- try {
1104
- await element.click({ timeout: 5000 });
1284
+ if (options === null || options === undefined || options.press) {
1285
+ try {
1286
+ await element.click({ timeout: 5000 });
1287
+ }
1288
+ catch (e) {
1289
+ await element.dispatchEvent("click");
1290
+ }
1105
1291
  }
1106
- catch (e) {
1107
- await element.dispatchEvent("click");
1292
+ else {
1293
+ try {
1294
+ await element.focus();
1295
+ }
1296
+ catch (e) {
1297
+ await element.dispatchEvent("focus");
1298
+ }
1108
1299
  }
1109
1300
  await new Promise((resolve) => setTimeout(resolve, 500));
1110
1301
  const valueSegment = _value.split("&&");
@@ -1192,7 +1383,7 @@ class StableBrowser {
1192
1383
  let element = await this._locate(selectors, info, _params);
1193
1384
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1194
1385
  await this._highlightElements(element);
1195
- await element.fill(value, { timeout: 10000 });
1386
+ await element.fill(value);
1196
1387
  await element.dispatchEvent("change");
1197
1388
  if (enter) {
1198
1389
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -1411,7 +1602,7 @@ class StableBrowser {
1411
1602
  return info;
1412
1603
  }
1413
1604
  catch (e) {
1414
- //await this.closeUnexpectedPopups();
1605
+ await this.closeUnexpectedPopups();
1415
1606
  this.logger.error("verify element contains text failed " + info.log);
1416
1607
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1417
1608
  info.screenshotPath = screenshotPath;
@@ -1459,6 +1650,29 @@ class StableBrowser {
1459
1650
  }
1460
1651
  return dataFile;
1461
1652
  }
1653
+ async waitForUserInput(message, world = null) {
1654
+ if (!message) {
1655
+ message = "# Wait for user input. Press any key to continue";
1656
+ }
1657
+ else {
1658
+ message = "# Wait for user input. " + message;
1659
+ }
1660
+ message += "\n";
1661
+ const value = await new Promise((resolve) => {
1662
+ const rl = readline.createInterface({
1663
+ input: process.stdin,
1664
+ output: process.stdout,
1665
+ });
1666
+ rl.question(message, (answer) => {
1667
+ rl.close();
1668
+ resolve(answer);
1669
+ });
1670
+ });
1671
+ if (value) {
1672
+ this.logger.info(`{{userInput}} was set to: ${value}`);
1673
+ }
1674
+ this.setTestData({ userInput: value }, world);
1675
+ }
1462
1676
  setTestData(testData, world = null) {
1463
1677
  if (!testData) {
1464
1678
  return;
@@ -1486,7 +1700,7 @@ class StableBrowser {
1486
1700
  const data = fs.readFileSync(filePath, "utf8");
1487
1701
  const results = [];
1488
1702
  return new Promise((resolve, reject) => {
1489
- const readableStream = new stream.Readable();
1703
+ const readableStream = new Readable();
1490
1704
  readableStream._read = () => { }; // _read is required but you can noop it
1491
1705
  readableStream.push(data);
1492
1706
  readableStream.push(null);
@@ -1646,7 +1860,6 @@ class StableBrowser {
1646
1860
  }
1647
1861
  async takeScreenshot(screenshotPath) {
1648
1862
  const playContext = this.context.playContext;
1649
- const client = await playContext.newCDPSession(this.page);
1650
1863
  // Using CDP to capture the screenshot
1651
1864
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
1652
1865
  document.body.scrollWidth,
@@ -1656,41 +1869,40 @@ class StableBrowser {
1656
1869
  document.body.clientWidth,
1657
1870
  document.documentElement.clientWidth,
1658
1871
  ])));
1659
- const viewportHeight = Math.max(...(await this.page.evaluate(() => [
1660
- document.body.scrollHeight,
1661
- document.documentElement.scrollHeight,
1662
- document.body.offsetHeight,
1663
- document.documentElement.offsetHeight,
1664
- document.body.clientHeight,
1665
- document.documentElement.clientHeight,
1666
- ])));
1667
- const { data } = await client.send("Page.captureScreenshot", {
1668
- format: "png",
1669
- clip: {
1670
- x: 0,
1671
- y: 0,
1672
- width: viewportWidth,
1673
- height: viewportHeight,
1674
- scale: 1,
1675
- },
1676
- });
1677
- if (!screenshotPath) {
1678
- return data;
1679
- }
1680
- 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);
1693
- await client.detach();
1872
+ let screenshotBuffer = null;
1873
+ if (this.context.browserName === "chromium") {
1874
+ const client = await playContext.newCDPSession(this.page);
1875
+ const { data } = await client.send("Page.captureScreenshot", {
1876
+ format: "png",
1877
+ // clip: {
1878
+ // x: 0,
1879
+ // y: 0,
1880
+ // width: viewportWidth,
1881
+ // height: viewportHeight,
1882
+ // scale: 1,
1883
+ // },
1884
+ });
1885
+ await client.detach();
1886
+ if (!screenshotPath) {
1887
+ return data;
1888
+ }
1889
+ screenshotBuffer = Buffer.from(data, "base64");
1890
+ }
1891
+ else {
1892
+ screenshotBuffer = await this.page.screenshot();
1893
+ }
1894
+ let image = await Jimp.read(screenshotBuffer);
1895
+ // Get the image dimensions
1896
+ const { width, height } = image.bitmap;
1897
+ const resizeRatio = viewportWidth / width;
1898
+ // Resize the image to fit within the viewport dimensions without enlarging
1899
+ if (width > viewportWidth) {
1900
+ image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
1901
+ await image.write(screenshotPath);
1902
+ }
1903
+ else {
1904
+ fs.writeFileSync(screenshotPath, screenshotBuffer);
1905
+ }
1694
1906
  }
1695
1907
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1696
1908
  this._validateSelectors(selectors);
@@ -1861,11 +2073,14 @@ class StableBrowser {
1861
2073
  //}
1862
2074
  if ((result && result.data, result.data.status === true)) {
1863
2075
  let codeOrUrlFound = false;
2076
+ let emailCode = null;
2077
+ let emailUrl = null;
1864
2078
  // check if a code is returned
1865
2079
  if (result.data.content && result.data.content.code) {
1866
2080
  let code = result.data.content.code;
1867
2081
  this.setTestData({ emailCode: code }, world);
1868
2082
  this.logger.info("set test data: emailCode = " + code);
2083
+ emailCode = code;
1869
2084
  codeOrUrlFound = true;
1870
2085
  }
1871
2086
  // check if a url is returned
@@ -1873,6 +2088,7 @@ class StableBrowser {
1873
2088
  let url = result.data.content.url;
1874
2089
  this.setTestData({ emailUrl: url }, world);
1875
2090
  this.logger.info("set test data: emailUrl = " + url);
2091
+ emailUrl = url;
1876
2092
  codeOrUrlFound = true;
1877
2093
  }
1878
2094
  if (codeOrUrlFound) {
@@ -2494,13 +2710,13 @@ class StableBrowser {
2494
2710
  }
2495
2711
  catch (e) {
2496
2712
  if (e.label === "networkidle") {
2497
- console.log("waitted for the network to be idle timeout");
2713
+ console.log("waited for the network to be idle timeout");
2498
2714
  }
2499
2715
  else if (e.label === "load") {
2500
- console.log("waitted for the load timeout");
2716
+ console.log("waited for the load timeout");
2501
2717
  }
2502
2718
  else if (e.label === "domcontentloaded") {
2503
- console.log("waitted for the domcontent loaded timeout");
2719
+ console.log("waited for the domcontent loaded timeout");
2504
2720
  }
2505
2721
  console.log(".");
2506
2722
  }
@@ -2535,13 +2751,6 @@ class StableBrowser {
2535
2751
  const info = {};
2536
2752
  try {
2537
2753
  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
2754
  }
2546
2755
  catch (e) {
2547
2756
  console.log(".");
@@ -2650,33 +2859,18 @@ class StableBrowser {
2650
2859
  }
2651
2860
  async scrollIfNeeded(element, info) {
2652
2861
  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
- }
2862
+ await element.scrollIntoViewIfNeeded({
2863
+ timeout: 2000,
2670
2864
  });
2671
- if (didScroll) {
2672
- await new Promise((resolve) => setTimeout(resolve, 500));
2673
- if (info) {
2674
- info.box = await element.boundingBox();
2675
- }
2865
+ await new Promise((resolve) => setTimeout(resolve, 500));
2866
+ if (info) {
2867
+ info.box = await element.boundingBox({
2868
+ timeout: 1000,
2869
+ });
2676
2870
  }
2677
2871
  }
2678
2872
  catch (e) {
2679
- console.log("scroll failed");
2873
+ console.log("#-#");
2680
2874
  }
2681
2875
  }
2682
2876
  _reportToWorld(world, properties) {