automation_model 1.0.400-dev → 1.0.400-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.
@@ -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",
@@ -37,16 +40,22 @@ const Types = {
37
40
  SET_DATE_TIME: "set_date_time",
38
41
  SET_VIEWPORT: "set_viewport",
39
42
  VERIFY_VISUAL: "verify_visual",
43
+ LOAD_DATA: "load_data",
44
+ SET_INPUT: "set_input",
40
45
  };
46
+ export const apps = {};
41
47
  class StableBrowser {
42
- constructor(browser, page, logger = null, context = null) {
48
+ constructor(browser, page, logger = null, context = null, world = null) {
43
49
  this.browser = browser;
44
50
  this.page = page;
45
51
  this.logger = logger;
46
52
  this.context = context;
53
+ this.world = world;
47
54
  this.project_path = null;
48
55
  this.webLogFile = null;
56
+ this.networkLogger = null;
49
57
  this.configuration = null;
58
+ this.appName = "main";
50
59
  if (!this.logger) {
51
60
  this.logger = console;
52
61
  }
@@ -72,19 +81,31 @@ class StableBrowser {
72
81
  this.logger.error("unable to read ai_config.json");
73
82
  }
74
83
  const logFolder = path.join(this.project_path, "logs", "web");
75
- this.webLogFile = this.getWebLogFile(logFolder);
76
- this.registerConsoleLogListener(page, context, this.webLogFile);
77
- this.registerRequestListener();
78
- context.pages = [this.page];
79
- context.pageLoading = { status: false };
84
+ this.world = world;
85
+ this.registerEventListeners(context);
86
+ }
87
+ registerEventListeners(context) {
88
+ this.registerConsoleLogListener(this.page, context);
89
+ this.registerRequestListener(this.page, context, this.webLogFile);
80
90
  context.playContext.on("page", async function (page) {
81
91
  context.pageLoading.status = true;
82
92
  this.page = page;
83
93
  context.page = page;
84
94
  context.pages.push(page);
85
- this.webLogFile = this.getWebLogFile(logFolder);
86
- this.registerConsoleLogListener(page, context, this.webLogFile);
87
- this.registerRequestListener();
95
+ page.on("close", async () => {
96
+ if (this.context && this.context.pages && this.context.pages.length > 1) {
97
+ this.context.pages.pop();
98
+ this.page = this.context.pages[this.context.pages.length - 1];
99
+ this.context.page = this.page;
100
+ try {
101
+ let title = await this.page.title();
102
+ console.log("Switched to page " + title);
103
+ }
104
+ catch (error) {
105
+ console.error("Error on page close", error);
106
+ }
107
+ }
108
+ });
88
109
  try {
89
110
  await this.waitForPageLoad();
90
111
  console.log("Switch page: " + (await page.title()));
@@ -95,6 +116,36 @@ class StableBrowser {
95
116
  context.pageLoading.status = false;
96
117
  }.bind(this));
97
118
  }
119
+ async switchApp(appName) {
120
+ // check if the current app (this.appName) is the same as the new app
121
+ if (this.appName === appName) {
122
+ return;
123
+ }
124
+ let navigate = false;
125
+ if (!apps[appName]) {
126
+ let newContext = await getContext(null, false, this.logger, appName, false, this);
127
+ navigate = true;
128
+ apps[appName] = {
129
+ context: newContext,
130
+ browser: newContext.browser,
131
+ page: newContext.page,
132
+ };
133
+ }
134
+ const tempContext = {};
135
+ this._copyContext(this, tempContext);
136
+ this._copyContext(apps[appName], this);
137
+ apps[this.appName] = tempContext;
138
+ this.appName = appName;
139
+ if (navigate) {
140
+ await this.goto(this.context.environment.baseUrl);
141
+ await this.waitForPageLoad();
142
+ }
143
+ }
144
+ _copyContext(from, to) {
145
+ to.browser = from.browser;
146
+ to.page = from.page;
147
+ to.context = from.context;
148
+ }
98
149
  getWebLogFile(logFolder) {
99
150
  if (!fs.existsSync(logFolder)) {
100
151
  fs.mkdirSync(logFolder, { recursive: true });
@@ -106,37 +157,63 @@ class StableBrowser {
106
157
  const fileName = nextIndex + ".json";
107
158
  return path.join(logFolder, fileName);
108
159
  }
109
- registerConsoleLogListener(page, context, logFile) {
160
+ registerConsoleLogListener(page, context) {
110
161
  if (!this.context.webLogger) {
111
162
  this.context.webLogger = [];
112
163
  }
113
164
  page.on("console", async (msg) => {
114
- this.context.webLogger.push({
165
+ var _a;
166
+ const obj = {
115
167
  type: msg.type(),
116
168
  text: msg.text(),
117
169
  location: msg.location(),
118
170
  time: new Date().toISOString(),
119
- });
120
- await fs.promises.writeFile(logFile, JSON.stringify(this.context.webLogger, null, 2));
171
+ };
172
+ this.context.webLogger.push(obj);
173
+ (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
121
174
  });
122
175
  }
123
- registerRequestListener() {
124
- this.page.on("request", async (data) => {
176
+ registerRequestListener(page, context, logFile) {
177
+ if (!this.context.networkLogger) {
178
+ this.context.networkLogger = [];
179
+ }
180
+ page.on("request", async (data) => {
181
+ var _a;
182
+ const startTime = new Date().getTime();
125
183
  try {
126
- const pageUrl = new URL(this.page.url());
184
+ const pageUrl = new URL(page.url());
127
185
  const requestUrl = new URL(data.url());
128
186
  if (pageUrl.hostname === requestUrl.hostname) {
129
187
  const method = data.method();
130
- if (method === "POST" || method === "GET" || method === "PUT" || method === "DELETE" || method === "PATCH") {
188
+ if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
131
189
  const token = await data.headerValue("Authorization");
132
190
  if (token) {
133
- this.context.authtoken = token;
191
+ context.authtoken = token;
134
192
  }
135
193
  }
136
194
  }
195
+ const response = await data.response();
196
+ const endTime = new Date().getTime();
197
+ const obj = {
198
+ url: data.url(),
199
+ method: data.method(),
200
+ postData: data.postData(),
201
+ error: data.failure() ? data.failure().errorText : null,
202
+ duration: endTime - startTime,
203
+ startTime,
204
+ };
205
+ context.networkLogger.push(obj);
206
+ (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
137
207
  }
138
208
  catch (error) {
139
209
  console.error("Error in request listener", error);
210
+ context.networkLogger.push({
211
+ error: "not able to listen",
212
+ message: error.message,
213
+ stack: error.stack,
214
+ time: new Date().toISOString(),
215
+ });
216
+ // await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
140
217
  }
141
218
  });
142
219
  }
@@ -179,24 +256,78 @@ class StableBrowser {
179
256
  }
180
257
  return text;
181
258
  }
182
- _getLocator(locator, scope, _params) {
183
- if (locator.type === "pw_selector") {
184
- return scope.locator(locator.selector);
259
+ _fixLocatorUsingParams(locator, _params) {
260
+ // check if not null
261
+ if (!locator) {
262
+ return locator;
263
+ }
264
+ // clone the locator
265
+ locator = JSON.parse(JSON.stringify(locator));
266
+ this.scanAndManipulate(locator, _params);
267
+ return locator;
268
+ }
269
+ _isObject(value) {
270
+ return value && typeof value === "object" && value.constructor === Object;
271
+ }
272
+ scanAndManipulate(currentObj, _params) {
273
+ for (const key in currentObj) {
274
+ if (typeof currentObj[key] === "string") {
275
+ // Perform string manipulation
276
+ currentObj[key] = this._fixUsingParams(currentObj[key], _params);
277
+ }
278
+ else if (this._isObject(currentObj[key])) {
279
+ // Recursively scan nested objects
280
+ this.scanAndManipulate(currentObj[key], _params);
281
+ }
185
282
  }
283
+ }
284
+ _getLocator(locator, scope, _params) {
285
+ locator = this._fixLocatorUsingParams(locator, _params);
286
+ let locatorReturn;
186
287
  if (locator.role) {
187
288
  if (locator.role[1].nameReg) {
188
289
  locator.role[1].name = reg_parser(locator.role[1].nameReg);
189
290
  delete locator.role[1].nameReg;
190
291
  }
191
- if (locator.role[1].name) {
192
- locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
193
- }
194
- return scope.getByRole(locator.role[0], locator.role[1]);
292
+ // if (locator.role[1].name) {
293
+ // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
294
+ // }
295
+ locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
195
296
  }
196
297
  if (locator.css) {
197
- return scope.locator(this._fixUsingParams(locator.css, _params));
298
+ locatorReturn = scope.locator(locator.css);
299
+ }
300
+ // handle role/name locators
301
+ // locator.selector will be something like: textbox[name="Username"i]
302
+ if (locator.engine === "internal:role") {
303
+ // extract the role, name and the i/s flags using regex
304
+ const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
305
+ if (match) {
306
+ const role = match[1];
307
+ const name = match[3];
308
+ const flags = match[4];
309
+ locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
310
+ }
311
+ }
312
+ if (locator === null || locator === void 0 ? void 0 : locator.engine) {
313
+ if (locator.engine === "css") {
314
+ locatorReturn = scope.locator(locator.selector);
315
+ }
316
+ else {
317
+ let selector = locator.selector;
318
+ if (locator.engine === "internal:attr") {
319
+ if (!selector.startsWith("[")) {
320
+ selector = `[${selector}]`;
321
+ }
322
+ }
323
+ locatorReturn = scope.locator(`${locator.engine}=${selector}`);
324
+ }
198
325
  }
199
- throw new Error("unknown locator type");
326
+ if (!locatorReturn) {
327
+ console.error(locator);
328
+ throw new Error("Locator undefined");
329
+ }
330
+ return locatorReturn;
200
331
  }
201
332
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
202
333
  let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
@@ -407,6 +538,8 @@ class StableBrowser {
407
538
  if (result.foundElements.length > 0) {
408
539
  let dialogCloseLocator = result.foundElements[0].locator;
409
540
  await dialogCloseLocator.click();
541
+ // wait for the dialog to close
542
+ await dialogCloseLocator.waitFor({ state: "hidden" });
410
543
  return { rerun: true };
411
544
  }
412
545
  }
@@ -415,7 +548,7 @@ class StableBrowser {
415
548
  }
416
549
  async _locate(selectors, info, _params, timeout = 30000) {
417
550
  for (let i = 0; i < 3; i++) {
418
- info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
551
+ info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
419
552
  for (let j = 0; j < selectors.locators.length; j++) {
420
553
  let selector = selectors.locators[j];
421
554
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
@@ -435,9 +568,27 @@ class StableBrowser {
435
568
  //let arrayMode = Array.isArray(selectors);
436
569
  let scope = this.page;
437
570
  if (selectors.iframe_src || selectors.frameLocators) {
571
+ const findFrame = (frame, framescope) => {
572
+ for (let i = 0; i < frame.selectors.length; i++) {
573
+ let frameLocator = frame.selectors[i];
574
+ if (frameLocator.css) {
575
+ framescope = framescope.frameLocator(frameLocator.css);
576
+ break;
577
+ }
578
+ }
579
+ if (frame.children) {
580
+ return findFrame(frame.children, framescope);
581
+ }
582
+ return framescope;
583
+ };
438
584
  info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
439
585
  while (true) {
440
586
  let frameFound = false;
587
+ if (selectors.nestFrmLoc) {
588
+ scope = findFrame(selectors.nestFrmLoc, scope);
589
+ frameFound = true;
590
+ break;
591
+ }
441
592
  if (selectors.frameLocators) {
442
593
  for (let i = 0; i < selectors.frameLocators.length; i++) {
443
594
  let frameLocator = selectors.frameLocators[i];
@@ -601,6 +752,9 @@ class StableBrowser {
601
752
  async click(selectors, _params, options = {}, world = null) {
602
753
  this._validateSelectors(selectors);
603
754
  const startTime = Date.now();
755
+ if (options && options.context) {
756
+ selectors.locators[0].text = options.context;
757
+ }
604
758
  const info = {};
605
759
  info.log = "***** click on " + selectors.element_name + " *****\n";
606
760
  info.operation = "click";
@@ -614,14 +768,14 @@ class StableBrowser {
614
768
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
615
769
  try {
616
770
  await this._highlightElements(element);
617
- await element.click({ timeout: 5000 });
771
+ await element.click();
618
772
  await new Promise((resolve) => setTimeout(resolve, 1000));
619
773
  }
620
774
  catch (e) {
621
775
  // await this.closeUnexpectedPopups();
622
776
  info.log += "click failed, will try again" + "\n";
623
777
  element = await this._locate(selectors, info, _params);
624
- await element.click({ timeout: 10000, force: true });
778
+ await element.dispatchEvent("click");
625
779
  await new Promise((resolve) => setTimeout(resolve, 1000));
626
780
  }
627
781
  await this.waitForPageLoad();
@@ -674,7 +828,7 @@ class StableBrowser {
674
828
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
675
829
  try {
676
830
  await this._highlightElements(element);
677
- await element.setChecked(checked, { timeout: 5000 });
831
+ await element.setChecked(checked);
678
832
  await new Promise((resolve) => setTimeout(resolve, 1000));
679
833
  }
680
834
  catch (e) {
@@ -738,7 +892,7 @@ class StableBrowser {
738
892
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
739
893
  try {
740
894
  await this._highlightElements(element);
741
- await element.hover({ timeout: 10000 });
895
+ await element.hover();
742
896
  await new Promise((resolve) => setTimeout(resolve, 1000));
743
897
  }
744
898
  catch (e) {
@@ -800,7 +954,7 @@ class StableBrowser {
800
954
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
801
955
  try {
802
956
  await this._highlightElements(element);
803
- await element.selectOption(values, { timeout: 5000 });
957
+ await element.selectOption(values);
804
958
  }
805
959
  catch (e) {
806
960
  //await this.closeUnexpectedPopups();
@@ -909,71 +1063,45 @@ class StableBrowser {
909
1063
  });
910
1064
  }
911
1065
  }
912
- async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1066
+ async setInputValue(selectors, value, _params = null, options = {}, world = null) {
1067
+ // set input value for non fillable inputs like date, time, range, color, etc.
913
1068
  this._validateSelectors(selectors);
914
1069
  const startTime = Date.now();
915
- let error = null;
916
- let screenshotId = null;
917
- let screenshotPath = null;
918
1070
  const info = {};
919
- info.log = "";
920
- info.operation = Types.SET_DATE_TIME;
1071
+ info.log = "***** set input value " + selectors.element_name + " *****\n";
1072
+ info.operation = "setInputValue";
921
1073
  info.selectors = selectors;
1074
+ value = this._fixUsingParams(value, _params);
922
1075
  info.value = value;
1076
+ let error = null;
1077
+ let screenshotId = null;
1078
+ let screenshotPath = null;
923
1079
  try {
924
1080
  value = await this._replaceWithLocalData(value, this);
925
1081
  let element = await this._locate(selectors, info, _params);
926
- //insert red border around the element
927
1082
  await this.scrollIfNeeded(element, info);
928
1083
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
929
1084
  await this._highlightElements(element);
930
1085
  try {
931
- await element.click();
932
- await new Promise((resolve) => setTimeout(resolve, 500));
933
- if (format) {
934
- value = dayjs(value).format(format);
935
- await element.fill(value);
936
- }
937
- else {
938
- const dateTimeValue = await getDateTimeValue({ value, element });
939
- await element.evaluateHandle((el, dateTimeValue) => {
940
- el.value = ""; // clear input
941
- el.value = dateTimeValue;
942
- }, dateTimeValue);
943
- }
944
- if (enter) {
945
- await new Promise((resolve) => setTimeout(resolve, 2000));
946
- await this.page.keyboard.press("Enter");
947
- await this.waitForPageLoad();
948
- }
1086
+ await element.evaluateHandle((el, value) => {
1087
+ el.value = value;
1088
+ }, value);
949
1089
  }
950
1090
  catch (error) {
951
- //await this.closeUnexpectedPopups();
952
- this.logger.error("setting date time input failed " + JSON.stringify(info));
953
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1091
+ this.logger.error("setInputValue failed, will try again");
1092
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
954
1093
  info.screenshotPath = screenshotPath;
955
1094
  Object.assign(error, { info: info });
956
- await element.click();
957
- await new Promise((resolve) => setTimeout(resolve, 500));
958
- if (format) {
959
- value = dayjs(value).format(format);
960
- await element.fill(value);
961
- }
962
- else {
963
- const dateTimeValue = await getDateTimeValue({ value, element });
964
- await element.evaluateHandle((el, dateTimeValue) => {
965
- el.value = ""; // clear input
966
- el.value = dateTimeValue;
967
- }, dateTimeValue);
968
- }
969
- if (enter) {
970
- await new Promise((resolve) => setTimeout(resolve, 2000));
971
- await this.page.keyboard.press("Enter");
972
- await this.waitForPageLoad();
973
- }
1095
+ await element.evaluateHandle((el, value) => {
1096
+ el.value = value;
1097
+ });
974
1098
  }
975
1099
  }
976
- catch (error) {
1100
+ catch (e) {
1101
+ this.logger.error("setInputValue failed " + info.log);
1102
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1103
+ info.screenshotPath = screenshotPath;
1104
+ Object.assign(e, { info: info });
977
1105
  error = e;
978
1106
  throw e;
979
1107
  }
@@ -981,10 +1109,10 @@ class StableBrowser {
981
1109
  const endTime = Date.now();
982
1110
  this._reportToWorld(world, {
983
1111
  element_name: selectors.element_name,
984
- type: Types.SET_DATE_TIME,
985
- screenshotId,
1112
+ type: Types.SET_INPUT,
1113
+ text: `Set input value`,
986
1114
  value: value,
987
- text: `setDateTime input with value: ${value}`,
1115
+ screenshotId,
988
1116
  result: error
989
1117
  ? {
990
1118
  status: "FAILED",
@@ -1001,7 +1129,7 @@ class StableBrowser {
1001
1129
  });
1002
1130
  }
1003
1131
  }
1004
- async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
1132
+ async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1005
1133
  this._validateSelectors(selectors);
1006
1134
  const startTime = Date.now();
1007
1135
  let error = null;
@@ -1013,6 +1141,7 @@ class StableBrowser {
1013
1141
  info.selectors = selectors;
1014
1142
  info.value = value;
1015
1143
  try {
1144
+ value = await this._replaceWithLocalData(value, this);
1016
1145
  let element = await this._locate(selectors, info, _params);
1017
1146
  //insert red border around the element
1018
1147
  await this.scrollIfNeeded(element, info);
@@ -1021,28 +1150,51 @@ class StableBrowser {
1021
1150
  try {
1022
1151
  await element.click();
1023
1152
  await new Promise((resolve) => setTimeout(resolve, 500));
1024
- const dateTimeValue = await getDateTimeValue({ value, element });
1025
- await element.evaluateHandle((el, dateTimeValue) => {
1026
- el.value = ""; // clear input
1027
- el.value = dateTimeValue;
1028
- }, dateTimeValue);
1153
+ if (format) {
1154
+ value = dayjs(value).format(format);
1155
+ await element.fill(value);
1156
+ }
1157
+ else {
1158
+ const dateTimeValue = await getDateTimeValue({ value, element });
1159
+ await element.evaluateHandle((el, dateTimeValue) => {
1160
+ el.value = ""; // clear input
1161
+ el.value = dateTimeValue;
1162
+ }, dateTimeValue);
1163
+ }
1164
+ if (enter) {
1165
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1166
+ await this.page.keyboard.press("Enter");
1167
+ await this.waitForPageLoad();
1168
+ }
1029
1169
  }
1030
- catch (error) {
1170
+ catch (err) {
1031
1171
  //await this.closeUnexpectedPopups();
1032
1172
  this.logger.error("setting date time input failed " + JSON.stringify(info));
1033
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1173
+ this.logger.info("Trying again");
1174
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1034
1175
  info.screenshotPath = screenshotPath;
1035
- Object.assign(error, { info: info });
1176
+ Object.assign(err, { info: info });
1036
1177
  await element.click();
1037
1178
  await new Promise((resolve) => setTimeout(resolve, 500));
1038
- const dateTimeValue = await getDateTimeValue({ value, element });
1039
- await element.evaluateHandle((el, dateTimeValue) => {
1040
- el.value = ""; // clear input
1041
- el.value = dateTimeValue;
1042
- }, 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
+ }
1043
1195
  }
1044
1196
  }
1045
- catch (error) {
1197
+ catch (e) {
1046
1198
  error = e;
1047
1199
  throw e;
1048
1200
  }
@@ -1205,7 +1357,7 @@ class StableBrowser {
1205
1357
  let element = await this._locate(selectors, info, _params);
1206
1358
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1207
1359
  await this._highlightElements(element);
1208
- await element.fill(value, { timeout: 10000 });
1360
+ await element.fill(value);
1209
1361
  await element.dispatchEvent("change");
1210
1362
  if (enter) {
1211
1363
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -1424,7 +1576,7 @@ class StableBrowser {
1424
1576
  return info;
1425
1577
  }
1426
1578
  catch (e) {
1427
- //await this.closeUnexpectedPopups();
1579
+ await this.closeUnexpectedPopups();
1428
1580
  this.logger.error("verify element contains text failed " + info.log);
1429
1581
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1430
1582
  info.screenshotPath = screenshotPath;
@@ -1472,6 +1624,29 @@ class StableBrowser {
1472
1624
  }
1473
1625
  return dataFile;
1474
1626
  }
1627
+ async waitForUserInput(message, world = null) {
1628
+ if (!message) {
1629
+ message = "# Wait for user input. Press any key to continue";
1630
+ }
1631
+ else {
1632
+ message = "# Wait for user input. " + message;
1633
+ }
1634
+ message += "\n";
1635
+ const value = await new Promise((resolve) => {
1636
+ const rl = readline.createInterface({
1637
+ input: process.stdin,
1638
+ output: process.stdout,
1639
+ });
1640
+ rl.question(message, (answer) => {
1641
+ rl.close();
1642
+ resolve(answer);
1643
+ });
1644
+ });
1645
+ if (value) {
1646
+ this.logger.info(`{{userInput}} was set to: ${value}`);
1647
+ }
1648
+ this.setTestData({ userInput: value }, world);
1649
+ }
1475
1650
  setTestData(testData, world = null) {
1476
1651
  if (!testData) {
1477
1652
  return;
@@ -1499,7 +1674,7 @@ class StableBrowser {
1499
1674
  const data = fs.readFileSync(filePath, "utf8");
1500
1675
  const results = [];
1501
1676
  return new Promise((resolve, reject) => {
1502
- const readableStream = new stream.Readable();
1677
+ const readableStream = new Readable();
1503
1678
  readableStream._read = () => { }; // _read is required but you can noop it
1504
1679
  readableStream.push(data);
1505
1680
  readableStream.push(null);
@@ -1679,13 +1854,13 @@ class StableBrowser {
1679
1854
  ])));
1680
1855
  const { data } = await client.send("Page.captureScreenshot", {
1681
1856
  format: "png",
1682
- clip: {
1683
- x: 0,
1684
- y: 0,
1685
- width: viewportWidth,
1686
- height: viewportHeight,
1687
- scale: 1,
1688
- },
1857
+ // clip: {
1858
+ // x: 0,
1859
+ // y: 0,
1860
+ // width: viewportWidth,
1861
+ // height: viewportHeight,
1862
+ // scale: 1,
1863
+ // },
1689
1864
  });
1690
1865
  if (!screenshotPath) {
1691
1866
  return data;
@@ -2511,13 +2686,13 @@ class StableBrowser {
2511
2686
  }
2512
2687
  catch (e) {
2513
2688
  if (e.label === "networkidle") {
2514
- console.log("waitted for the network to be idle timeout");
2689
+ console.log("waited for the network to be idle timeout");
2515
2690
  }
2516
2691
  else if (e.label === "load") {
2517
- console.log("waitted for the load timeout");
2692
+ console.log("waited for the load timeout");
2518
2693
  }
2519
2694
  else if (e.label === "domcontentloaded") {
2520
- console.log("waitted for the domcontent loaded timeout");
2695
+ console.log("waited for the domcontent loaded timeout");
2521
2696
  }
2522
2697
  console.log(".");
2523
2698
  }
@@ -2552,13 +2727,6 @@ class StableBrowser {
2552
2727
  const info = {};
2553
2728
  try {
2554
2729
  await this.page.close();
2555
- if (this.context && this.context.pages && this.context.pages.length > 0) {
2556
- this.context.pages.pop();
2557
- this.page = this.context.pages[this.context.pages.length - 1];
2558
- this.context.page = this.page;
2559
- let title = await this.page.title();
2560
- console.log("Switched to page " + title);
2561
- }
2562
2730
  }
2563
2731
  catch (e) {
2564
2732
  console.log(".");
@@ -2667,33 +2835,18 @@ class StableBrowser {
2667
2835
  }
2668
2836
  async scrollIfNeeded(element, info) {
2669
2837
  try {
2670
- let didScroll = await element.evaluate((node) => {
2671
- const rect = node.getBoundingClientRect();
2672
- if (rect &&
2673
- rect.top >= 0 &&
2674
- rect.left >= 0 &&
2675
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
2676
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
2677
- return false;
2678
- }
2679
- else {
2680
- node.scrollIntoView({
2681
- behavior: "smooth",
2682
- block: "center",
2683
- inline: "center",
2684
- });
2685
- return true;
2686
- }
2838
+ await element.scrollIntoViewIfNeeded({
2839
+ timeout: 2000,
2687
2840
  });
2688
- if (didScroll) {
2689
- await new Promise((resolve) => setTimeout(resolve, 500));
2690
- if (info) {
2691
- info.box = await element.boundingBox();
2692
- }
2841
+ await new Promise((resolve) => setTimeout(resolve, 500));
2842
+ if (info) {
2843
+ info.box = await element.boundingBox({
2844
+ timeout: 1000,
2845
+ });
2693
2846
  }
2694
2847
  }
2695
2848
  catch (e) {
2696
- console.log("scroll failed");
2849
+ console.log("#-#");
2697
2850
  }
2698
2851
  }
2699
2852
  _reportToWorld(world, properties) {