automation_model 1.0.402-dev → 1.0.402-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,22 +81,33 @@ 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(this.context);
86
+ }
87
+ registerEventListeners(context) {
88
+ this.registerConsoleLogListener(this.page, context);
89
+ this.registerRequestListener(this.page, context, this.webLogFile);
90
+ if (!context.pageLoading) {
91
+ context.pageLoading = { status: false };
92
+ }
80
93
  context.playContext.on("page", async function (page) {
81
94
  context.pageLoading.status = true;
82
95
  this.page = page;
83
96
  context.page = page;
84
97
  context.pages.push(page);
85
- this.webLogFile = this.getWebLogFile(logFolder);
86
- this.registerConsoleLogListener(page, context, this.webLogFile);
87
- this.registerRequestListener();
88
- page.on("close", () => {
89
- context.pages = context.pages.filter((p) => p !== page);
90
- this.page = context.pages[context.pages.length - 1]; // assuming the last page is the active page
98
+ page.on("close", async () => {
99
+ if (this.context && this.context.pages && this.context.pages.length > 1) {
100
+ this.context.pages.pop();
101
+ this.page = this.context.pages[this.context.pages.length - 1];
102
+ this.context.page = this.page;
103
+ try {
104
+ let title = await this.page.title();
105
+ console.log("Switched to page " + title);
106
+ }
107
+ catch (error) {
108
+ console.error("Error on page close", error);
109
+ }
110
+ }
91
111
  });
92
112
  try {
93
113
  await this.waitForPageLoad();
@@ -99,6 +119,36 @@ class StableBrowser {
99
119
  context.pageLoading.status = false;
100
120
  }.bind(this));
101
121
  }
122
+ async switchApp(appName) {
123
+ // check if the current app (this.appName) is the same as the new app
124
+ if (this.appName === appName) {
125
+ return;
126
+ }
127
+ let navigate = false;
128
+ if (!apps[appName]) {
129
+ let newContext = await getContext(null, false, this.logger, appName, false, this);
130
+ navigate = true;
131
+ apps[appName] = {
132
+ context: newContext,
133
+ browser: newContext.browser,
134
+ page: newContext.page,
135
+ };
136
+ }
137
+ const tempContext = {};
138
+ this._copyContext(this, tempContext);
139
+ this._copyContext(apps[appName], this);
140
+ apps[this.appName] = tempContext;
141
+ this.appName = appName;
142
+ if (navigate) {
143
+ await this.goto(this.context.environment.baseUrl);
144
+ await this.waitForPageLoad();
145
+ }
146
+ }
147
+ _copyContext(from, to) {
148
+ to.browser = from.browser;
149
+ to.page = from.page;
150
+ to.context = from.context;
151
+ }
102
152
  getWebLogFile(logFolder) {
103
153
  if (!fs.existsSync(logFolder)) {
104
154
  fs.mkdirSync(logFolder, { recursive: true });
@@ -110,37 +160,63 @@ class StableBrowser {
110
160
  const fileName = nextIndex + ".json";
111
161
  return path.join(logFolder, fileName);
112
162
  }
113
- registerConsoleLogListener(page, context, logFile) {
163
+ registerConsoleLogListener(page, context) {
114
164
  if (!this.context.webLogger) {
115
165
  this.context.webLogger = [];
116
166
  }
117
167
  page.on("console", async (msg) => {
118
- this.context.webLogger.push({
168
+ var _a;
169
+ const obj = {
119
170
  type: msg.type(),
120
171
  text: msg.text(),
121
172
  location: msg.location(),
122
173
  time: new Date().toISOString(),
123
- });
124
- await fs.promises.writeFile(logFile, JSON.stringify(this.context.webLogger, null, 2));
174
+ };
175
+ this.context.webLogger.push(obj);
176
+ (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
125
177
  });
126
178
  }
127
- registerRequestListener() {
128
- this.page.on("request", async (data) => {
179
+ registerRequestListener(page, context, logFile) {
180
+ if (!this.context.networkLogger) {
181
+ this.context.networkLogger = [];
182
+ }
183
+ page.on("request", async (data) => {
184
+ var _a;
185
+ const startTime = new Date().getTime();
129
186
  try {
130
- const pageUrl = new URL(this.page.url());
187
+ const pageUrl = new URL(page.url());
131
188
  const requestUrl = new URL(data.url());
132
189
  if (pageUrl.hostname === requestUrl.hostname) {
133
190
  const method = data.method();
134
- if (method === "POST" || method === "GET" || method === "PUT" || method === "DELETE" || method === "PATCH") {
191
+ if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
135
192
  const token = await data.headerValue("Authorization");
136
193
  if (token) {
137
- this.context.authtoken = token;
194
+ context.authtoken = token;
138
195
  }
139
196
  }
140
197
  }
198
+ const response = await data.response();
199
+ const endTime = new Date().getTime();
200
+ const obj = {
201
+ url: data.url(),
202
+ method: data.method(),
203
+ postData: data.postData(),
204
+ error: data.failure() ? data.failure().errorText : null,
205
+ duration: endTime - startTime,
206
+ startTime,
207
+ };
208
+ context.networkLogger.push(obj);
209
+ (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
141
210
  }
142
211
  catch (error) {
143
212
  console.error("Error in request listener", error);
213
+ context.networkLogger.push({
214
+ error: "not able to listen",
215
+ message: error.message,
216
+ stack: error.stack,
217
+ time: new Date().toISOString(),
218
+ });
219
+ // await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
144
220
  }
145
221
  });
146
222
  }
@@ -183,32 +259,78 @@ class StableBrowser {
183
259
  }
184
260
  return text;
185
261
  }
186
- _getLocator(locator, scope, _params) {
187
- if (locator.type === "pw_selector") {
188
- return scope.locator(locator.selector);
262
+ _fixLocatorUsingParams(locator, _params) {
263
+ // check if not null
264
+ if (!locator) {
265
+ return locator;
189
266
  }
267
+ // clone the locator
268
+ locator = JSON.parse(JSON.stringify(locator));
269
+ this.scanAndManipulate(locator, _params);
270
+ return locator;
271
+ }
272
+ _isObject(value) {
273
+ return value && typeof value === "object" && value.constructor === Object;
274
+ }
275
+ scanAndManipulate(currentObj, _params) {
276
+ for (const key in currentObj) {
277
+ if (typeof currentObj[key] === "string") {
278
+ // Perform string manipulation
279
+ currentObj[key] = this._fixUsingParams(currentObj[key], _params);
280
+ }
281
+ else if (this._isObject(currentObj[key])) {
282
+ // Recursively scan nested objects
283
+ this.scanAndManipulate(currentObj[key], _params);
284
+ }
285
+ }
286
+ }
287
+ _getLocator(locator, scope, _params) {
288
+ locator = this._fixLocatorUsingParams(locator, _params);
289
+ let locatorReturn;
190
290
  if (locator.role) {
191
291
  if (locator.role[1].nameReg) {
192
292
  locator.role[1].name = reg_parser(locator.role[1].nameReg);
193
293
  delete locator.role[1].nameReg;
194
294
  }
195
- if (locator.role[1].name) {
196
- locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
197
- }
198
- return scope.getByRole(locator.role[0], locator.role[1]);
295
+ // if (locator.role[1].name) {
296
+ // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
297
+ // }
298
+ locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
199
299
  }
200
300
  if (locator.css) {
201
- return scope.locator(this._fixUsingParams(locator.css, _params));
301
+ locatorReturn = scope.locator(locator.css);
202
302
  }
203
- if ((locator === null || locator === void 0 ? void 0 : locator.engine) && (locator === null || locator === void 0 ? void 0 : locator.score) <= 520) {
204
- let selector = locator.selector.replace(/"/g, '\\"');
205
- if (locator.engine === "internal:att") {
206
- selector = `[${selector}]`;
303
+ // handle role/name locators
304
+ // locator.selector will be something like: textbox[name="Username"i]
305
+ if (locator.engine === "internal:role") {
306
+ // extract the role, name and the i/s flags using regex
307
+ const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
308
+ if (match) {
309
+ const role = match[1];
310
+ const name = match[3];
311
+ const flags = match[4];
312
+ locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
313
+ }
314
+ }
315
+ if (locator === null || locator === void 0 ? void 0 : locator.engine) {
316
+ if (locator.engine === "css") {
317
+ locatorReturn = scope.locator(locator.selector);
318
+ }
319
+ else {
320
+ let selector = locator.selector;
321
+ if (locator.engine === "internal:attr") {
322
+ if (!selector.startsWith("[")) {
323
+ selector = `[${selector}]`;
324
+ }
325
+ }
326
+ locatorReturn = scope.locator(`${locator.engine}=${selector}`);
207
327
  }
208
- const locator = scope.locator(`${locator.engine}="${selector}"`);
209
- return locator;
210
328
  }
211
- throw new Error("unknown locator type");
329
+ if (!locatorReturn) {
330
+ console.error(locator);
331
+ throw new Error("Locator undefined");
332
+ }
333
+ return locatorReturn;
212
334
  }
213
335
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
214
336
  let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
@@ -419,6 +541,8 @@ class StableBrowser {
419
541
  if (result.foundElements.length > 0) {
420
542
  let dialogCloseLocator = result.foundElements[0].locator;
421
543
  await dialogCloseLocator.click();
544
+ // wait for the dialog to close
545
+ await dialogCloseLocator.waitFor({ state: "hidden" });
422
546
  return { rerun: true };
423
547
  }
424
548
  }
@@ -427,7 +551,7 @@ class StableBrowser {
427
551
  }
428
552
  async _locate(selectors, info, _params, timeout = 30000) {
429
553
  for (let i = 0; i < 3; i++) {
430
- info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
554
+ info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
431
555
  for (let j = 0; j < selectors.locators.length; j++) {
432
556
  let selector = selectors.locators[j];
433
557
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
@@ -447,9 +571,27 @@ class StableBrowser {
447
571
  //let arrayMode = Array.isArray(selectors);
448
572
  let scope = this.page;
449
573
  if (selectors.iframe_src || selectors.frameLocators) {
574
+ const findFrame = (frame, framescope) => {
575
+ for (let i = 0; i < frame.selectors.length; i++) {
576
+ let frameLocator = frame.selectors[i];
577
+ if (frameLocator.css) {
578
+ framescope = framescope.frameLocator(frameLocator.css);
579
+ break;
580
+ }
581
+ }
582
+ if (frame.children) {
583
+ return findFrame(frame.children, framescope);
584
+ }
585
+ return framescope;
586
+ };
450
587
  info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
451
588
  while (true) {
452
589
  let frameFound = false;
590
+ if (selectors.nestFrmLoc) {
591
+ scope = findFrame(selectors.nestFrmLoc, scope);
592
+ frameFound = true;
593
+ break;
594
+ }
453
595
  if (selectors.frameLocators) {
454
596
  for (let i = 0; i < selectors.frameLocators.length; i++) {
455
597
  let frameLocator = selectors.frameLocators[i];
@@ -613,6 +755,9 @@ class StableBrowser {
613
755
  async click(selectors, _params, options = {}, world = null) {
614
756
  this._validateSelectors(selectors);
615
757
  const startTime = Date.now();
758
+ if (options && options.context) {
759
+ selectors.locators[0].text = options.context;
760
+ }
616
761
  const info = {};
617
762
  info.log = "***** click on " + selectors.element_name + " *****\n";
618
763
  info.operation = "click";
@@ -626,14 +771,14 @@ class StableBrowser {
626
771
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
627
772
  try {
628
773
  await this._highlightElements(element);
629
- await element.click({ timeout: 5000 });
774
+ await element.click();
630
775
  await new Promise((resolve) => setTimeout(resolve, 1000));
631
776
  }
632
777
  catch (e) {
633
778
  // await this.closeUnexpectedPopups();
634
779
  info.log += "click failed, will try again" + "\n";
635
780
  element = await this._locate(selectors, info, _params);
636
- await element.click({ timeout: 10000, force: true });
781
+ await element.dispatchEvent("click");
637
782
  await new Promise((resolve) => setTimeout(resolve, 1000));
638
783
  }
639
784
  await this.waitForPageLoad();
@@ -686,7 +831,7 @@ class StableBrowser {
686
831
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
687
832
  try {
688
833
  await this._highlightElements(element);
689
- await element.setChecked(checked, { timeout: 5000 });
834
+ await element.setChecked(checked);
690
835
  await new Promise((resolve) => setTimeout(resolve, 1000));
691
836
  }
692
837
  catch (e) {
@@ -750,7 +895,7 @@ class StableBrowser {
750
895
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
751
896
  try {
752
897
  await this._highlightElements(element);
753
- await element.hover({ timeout: 10000 });
898
+ await element.hover();
754
899
  await new Promise((resolve) => setTimeout(resolve, 1000));
755
900
  }
756
901
  catch (e) {
@@ -812,7 +957,7 @@ class StableBrowser {
812
957
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
813
958
  try {
814
959
  await this._highlightElements(element);
815
- await element.selectOption(values, { timeout: 5000 });
960
+ await element.selectOption(values);
816
961
  }
817
962
  catch (e) {
818
963
  //await this.closeUnexpectedPopups();
@@ -921,71 +1066,45 @@ class StableBrowser {
921
1066
  });
922
1067
  }
923
1068
  }
924
- async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1069
+ async setInputValue(selectors, value, _params = null, options = {}, world = null) {
1070
+ // set input value for non fillable inputs like date, time, range, color, etc.
925
1071
  this._validateSelectors(selectors);
926
1072
  const startTime = Date.now();
927
- let error = null;
928
- let screenshotId = null;
929
- let screenshotPath = null;
930
1073
  const info = {};
931
- info.log = "";
932
- info.operation = Types.SET_DATE_TIME;
1074
+ info.log = "***** set input value " + selectors.element_name + " *****\n";
1075
+ info.operation = "setInputValue";
933
1076
  info.selectors = selectors;
1077
+ value = this._fixUsingParams(value, _params);
934
1078
  info.value = value;
1079
+ let error = null;
1080
+ let screenshotId = null;
1081
+ let screenshotPath = null;
935
1082
  try {
936
1083
  value = await this._replaceWithLocalData(value, this);
937
1084
  let element = await this._locate(selectors, info, _params);
938
- //insert red border around the element
939
1085
  await this.scrollIfNeeded(element, info);
940
1086
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
941
1087
  await this._highlightElements(element);
942
1088
  try {
943
- await element.click();
944
- await new Promise((resolve) => setTimeout(resolve, 500));
945
- if (format) {
946
- value = dayjs(value).format(format);
947
- await element.fill(value);
948
- }
949
- else {
950
- const dateTimeValue = await getDateTimeValue({ value, element });
951
- await element.evaluateHandle((el, dateTimeValue) => {
952
- el.value = ""; // clear input
953
- el.value = dateTimeValue;
954
- }, dateTimeValue);
955
- }
956
- if (enter) {
957
- await new Promise((resolve) => setTimeout(resolve, 2000));
958
- await this.page.keyboard.press("Enter");
959
- await this.waitForPageLoad();
960
- }
1089
+ await element.evaluateHandle((el, value) => {
1090
+ el.value = value;
1091
+ }, value);
961
1092
  }
962
1093
  catch (error) {
963
- //await this.closeUnexpectedPopups();
964
- this.logger.error("setting date time input failed " + JSON.stringify(info));
965
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1094
+ this.logger.error("setInputValue failed, will try again");
1095
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
966
1096
  info.screenshotPath = screenshotPath;
967
1097
  Object.assign(error, { info: info });
968
- await element.click();
969
- await new Promise((resolve) => setTimeout(resolve, 500));
970
- if (format) {
971
- value = dayjs(value).format(format);
972
- await element.fill(value);
973
- }
974
- else {
975
- const dateTimeValue = await getDateTimeValue({ value, element });
976
- await element.evaluateHandle((el, dateTimeValue) => {
977
- el.value = ""; // clear input
978
- el.value = dateTimeValue;
979
- }, dateTimeValue);
980
- }
981
- if (enter) {
982
- await new Promise((resolve) => setTimeout(resolve, 2000));
983
- await this.page.keyboard.press("Enter");
984
- await this.waitForPageLoad();
985
- }
1098
+ await element.evaluateHandle((el, value) => {
1099
+ el.value = value;
1100
+ });
986
1101
  }
987
1102
  }
988
- catch (error) {
1103
+ catch (e) {
1104
+ this.logger.error("setInputValue failed " + info.log);
1105
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1106
+ info.screenshotPath = screenshotPath;
1107
+ Object.assign(e, { info: info });
989
1108
  error = e;
990
1109
  throw e;
991
1110
  }
@@ -993,10 +1112,10 @@ class StableBrowser {
993
1112
  const endTime = Date.now();
994
1113
  this._reportToWorld(world, {
995
1114
  element_name: selectors.element_name,
996
- type: Types.SET_DATE_TIME,
997
- screenshotId,
1115
+ type: Types.SET_INPUT,
1116
+ text: `Set input value`,
998
1117
  value: value,
999
- text: `setDateTime input with value: ${value}`,
1118
+ screenshotId,
1000
1119
  result: error
1001
1120
  ? {
1002
1121
  status: "FAILED",
@@ -1013,7 +1132,7 @@ class StableBrowser {
1013
1132
  });
1014
1133
  }
1015
1134
  }
1016
- async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
1135
+ async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1017
1136
  this._validateSelectors(selectors);
1018
1137
  const startTime = Date.now();
1019
1138
  let error = null;
@@ -1025,6 +1144,7 @@ class StableBrowser {
1025
1144
  info.selectors = selectors;
1026
1145
  info.value = value;
1027
1146
  try {
1147
+ value = await this._replaceWithLocalData(value, this);
1028
1148
  let element = await this._locate(selectors, info, _params);
1029
1149
  //insert red border around the element
1030
1150
  await this.scrollIfNeeded(element, info);
@@ -1033,28 +1153,51 @@ class StableBrowser {
1033
1153
  try {
1034
1154
  await element.click();
1035
1155
  await new Promise((resolve) => setTimeout(resolve, 500));
1036
- const dateTimeValue = await getDateTimeValue({ value, element });
1037
- await element.evaluateHandle((el, dateTimeValue) => {
1038
- el.value = ""; // clear input
1039
- el.value = dateTimeValue;
1040
- }, dateTimeValue);
1156
+ if (format) {
1157
+ value = dayjs(value).format(format);
1158
+ await element.fill(value);
1159
+ }
1160
+ else {
1161
+ const dateTimeValue = await getDateTimeValue({ value, element });
1162
+ await element.evaluateHandle((el, dateTimeValue) => {
1163
+ el.value = ""; // clear input
1164
+ el.value = dateTimeValue;
1165
+ }, dateTimeValue);
1166
+ }
1167
+ if (enter) {
1168
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1169
+ await this.page.keyboard.press("Enter");
1170
+ await this.waitForPageLoad();
1171
+ }
1041
1172
  }
1042
- catch (error) {
1173
+ catch (err) {
1043
1174
  //await this.closeUnexpectedPopups();
1044
1175
  this.logger.error("setting date time input failed " + JSON.stringify(info));
1045
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1176
+ this.logger.info("Trying again");
1177
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1046
1178
  info.screenshotPath = screenshotPath;
1047
- Object.assign(error, { info: info });
1179
+ Object.assign(err, { info: info });
1048
1180
  await element.click();
1049
1181
  await new Promise((resolve) => setTimeout(resolve, 500));
1050
- const dateTimeValue = await getDateTimeValue({ value, element });
1051
- await element.evaluateHandle((el, dateTimeValue) => {
1052
- el.value = ""; // clear input
1053
- el.value = dateTimeValue;
1054
- }, dateTimeValue);
1182
+ if (format) {
1183
+ value = dayjs(value).format(format);
1184
+ await element.fill(value);
1185
+ }
1186
+ else {
1187
+ const dateTimeValue = await getDateTimeValue({ value, element });
1188
+ await element.evaluateHandle((el, dateTimeValue) => {
1189
+ el.value = ""; // clear input
1190
+ el.value = dateTimeValue;
1191
+ }, dateTimeValue);
1192
+ }
1193
+ if (enter) {
1194
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1195
+ await this.page.keyboard.press("Enter");
1196
+ await this.waitForPageLoad();
1197
+ }
1055
1198
  }
1056
1199
  }
1057
- catch (error) {
1200
+ catch (e) {
1058
1201
  error = e;
1059
1202
  throw e;
1060
1203
  }
@@ -1217,7 +1360,7 @@ class StableBrowser {
1217
1360
  let element = await this._locate(selectors, info, _params);
1218
1361
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1219
1362
  await this._highlightElements(element);
1220
- await element.fill(value, { timeout: 10000 });
1363
+ await element.fill(value);
1221
1364
  await element.dispatchEvent("change");
1222
1365
  if (enter) {
1223
1366
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -1436,7 +1579,7 @@ class StableBrowser {
1436
1579
  return info;
1437
1580
  }
1438
1581
  catch (e) {
1439
- //await this.closeUnexpectedPopups();
1582
+ await this.closeUnexpectedPopups();
1440
1583
  this.logger.error("verify element contains text failed " + info.log);
1441
1584
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1442
1585
  info.screenshotPath = screenshotPath;
@@ -1484,6 +1627,29 @@ class StableBrowser {
1484
1627
  }
1485
1628
  return dataFile;
1486
1629
  }
1630
+ async waitForUserInput(message, world = null) {
1631
+ if (!message) {
1632
+ message = "# Wait for user input. Press any key to continue";
1633
+ }
1634
+ else {
1635
+ message = "# Wait for user input. " + message;
1636
+ }
1637
+ message += "\n";
1638
+ const value = await new Promise((resolve) => {
1639
+ const rl = readline.createInterface({
1640
+ input: process.stdin,
1641
+ output: process.stdout,
1642
+ });
1643
+ rl.question(message, (answer) => {
1644
+ rl.close();
1645
+ resolve(answer);
1646
+ });
1647
+ });
1648
+ if (value) {
1649
+ this.logger.info(`{{userInput}} was set to: ${value}`);
1650
+ }
1651
+ this.setTestData({ userInput: value }, world);
1652
+ }
1487
1653
  setTestData(testData, world = null) {
1488
1654
  if (!testData) {
1489
1655
  return;
@@ -1511,7 +1677,7 @@ class StableBrowser {
1511
1677
  const data = fs.readFileSync(filePath, "utf8");
1512
1678
  const results = [];
1513
1679
  return new Promise((resolve, reject) => {
1514
- const readableStream = new stream.Readable();
1680
+ const readableStream = new Readable();
1515
1681
  readableStream._read = () => { }; // _read is required but you can noop it
1516
1682
  readableStream.push(data);
1517
1683
  readableStream.push(null);
@@ -1691,13 +1857,13 @@ class StableBrowser {
1691
1857
  ])));
1692
1858
  const { data } = await client.send("Page.captureScreenshot", {
1693
1859
  format: "png",
1694
- clip: {
1695
- x: 0,
1696
- y: 0,
1697
- width: viewportWidth,
1698
- height: viewportHeight,
1699
- scale: 1,
1700
- },
1860
+ // clip: {
1861
+ // x: 0,
1862
+ // y: 0,
1863
+ // width: viewportWidth,
1864
+ // height: viewportHeight,
1865
+ // scale: 1,
1866
+ // },
1701
1867
  });
1702
1868
  if (!screenshotPath) {
1703
1869
  return data;
@@ -2523,13 +2689,13 @@ class StableBrowser {
2523
2689
  }
2524
2690
  catch (e) {
2525
2691
  if (e.label === "networkidle") {
2526
- console.log("waitted for the network to be idle timeout");
2692
+ console.log("waited for the network to be idle timeout");
2527
2693
  }
2528
2694
  else if (e.label === "load") {
2529
- console.log("waitted for the load timeout");
2695
+ console.log("waited for the load timeout");
2530
2696
  }
2531
2697
  else if (e.label === "domcontentloaded") {
2532
- console.log("waitted for the domcontent loaded timeout");
2698
+ console.log("waited for the domcontent loaded timeout");
2533
2699
  }
2534
2700
  console.log(".");
2535
2701
  }
@@ -2564,13 +2730,6 @@ class StableBrowser {
2564
2730
  const info = {};
2565
2731
  try {
2566
2732
  await this.page.close();
2567
- if (this.context && this.context.pages && this.context.pages.length > 0) {
2568
- this.context.pages.pop();
2569
- this.page = this.context.pages[this.context.pages.length - 1];
2570
- this.context.page = this.page;
2571
- let title = await this.page.title();
2572
- console.log("Switched to page " + title);
2573
- }
2574
2733
  }
2575
2734
  catch (e) {
2576
2735
  console.log(".");
@@ -2679,33 +2838,18 @@ class StableBrowser {
2679
2838
  }
2680
2839
  async scrollIfNeeded(element, info) {
2681
2840
  try {
2682
- let didScroll = await element.evaluate((node) => {
2683
- const rect = node.getBoundingClientRect();
2684
- if (rect &&
2685
- rect.top >= 0 &&
2686
- rect.left >= 0 &&
2687
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
2688
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
2689
- return false;
2690
- }
2691
- else {
2692
- node.scrollIntoView({
2693
- behavior: "smooth",
2694
- block: "center",
2695
- inline: "center",
2696
- });
2697
- return true;
2698
- }
2841
+ await element.scrollIntoViewIfNeeded({
2842
+ timeout: 2000,
2699
2843
  });
2700
- if (didScroll) {
2701
- await new Promise((resolve) => setTimeout(resolve, 500));
2702
- if (info) {
2703
- info.box = await element.boundingBox();
2704
- }
2844
+ await new Promise((resolve) => setTimeout(resolve, 500));
2845
+ if (info) {
2846
+ info.box = await element.boundingBox({
2847
+ timeout: 1000,
2848
+ });
2705
2849
  }
2706
2850
  }
2707
2851
  catch (e) {
2708
- console.log("scroll failed");
2852
+ console.log("#-#");
2709
2853
  }
2710
2854
  }
2711
2855
  _reportToWorld(world, properties) {