automation_model 1.0.397-dev → 1.0.397-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,10 @@ 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";
19
+ import { locate_element } from "./locate_element.js";
16
20
  const Types = {
17
21
  CLICK: "click_element",
18
22
  NAVIGATE: "navigate",
@@ -28,6 +32,7 @@ const Types = {
28
32
  SELECT: "select_combobox",
29
33
  VERIFY_PAGE_PATH: "verify_page_path",
30
34
  TYPE_PRESS: "type_press",
35
+ PRESS: "press_key",
31
36
  HOVER: "hover_element",
32
37
  CHECK: "check_element",
33
38
  UNCHECK: "uncheck_element",
@@ -36,16 +41,22 @@ const Types = {
36
41
  SET_DATE_TIME: "set_date_time",
37
42
  SET_VIEWPORT: "set_viewport",
38
43
  VERIFY_VISUAL: "verify_visual",
44
+ LOAD_DATA: "load_data",
45
+ SET_INPUT: "set_input",
39
46
  };
47
+ export const apps = {};
40
48
  class StableBrowser {
41
- constructor(browser, page, logger = null, context = null) {
49
+ constructor(browser, page, logger = null, context = null, world = null) {
42
50
  this.browser = browser;
43
51
  this.page = page;
44
52
  this.logger = logger;
45
53
  this.context = context;
54
+ this.world = world;
46
55
  this.project_path = null;
47
56
  this.webLogFile = null;
57
+ this.networkLogger = null;
48
58
  this.configuration = null;
59
+ this.appName = "main";
49
60
  if (!this.logger) {
50
61
  this.logger = console;
51
62
  }
@@ -71,19 +82,36 @@ class StableBrowser {
71
82
  this.logger.error("unable to read ai_config.json");
72
83
  }
73
84
  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();
85
+ this.world = world;
77
86
  context.pages = [this.page];
78
87
  context.pageLoading = { status: false };
88
+ this.registerEventListeners(this.context);
89
+ }
90
+ registerEventListeners(context) {
91
+ this.registerConsoleLogListener(this.page, context);
92
+ this.registerRequestListener(this.page, context, this.webLogFile);
93
+ if (!context.pageLoading) {
94
+ context.pageLoading = { status: false };
95
+ }
79
96
  context.playContext.on("page", async function (page) {
80
97
  context.pageLoading.status = true;
81
98
  this.page = page;
82
99
  context.page = page;
83
100
  context.pages.push(page);
84
- this.webLogFile = this.getWebLogFile(logFolder);
85
- this.registerConsoleLogListener(page, context, this.webLogFile);
86
- this.registerRequestListener();
101
+ page.on("close", async () => {
102
+ if (this.context && this.context.pages && this.context.pages.length > 1) {
103
+ this.context.pages.pop();
104
+ this.page = this.context.pages[this.context.pages.length - 1];
105
+ this.context.page = this.page;
106
+ try {
107
+ let title = await this.page.title();
108
+ console.log("Switched to page " + title);
109
+ }
110
+ catch (error) {
111
+ console.error("Error on page close", error);
112
+ }
113
+ }
114
+ });
87
115
  try {
88
116
  await this.waitForPageLoad();
89
117
  console.log("Switch page: " + (await page.title()));
@@ -94,6 +122,36 @@ class StableBrowser {
94
122
  context.pageLoading.status = false;
95
123
  }.bind(this));
96
124
  }
125
+ async switchApp(appName) {
126
+ // check if the current app (this.appName) is the same as the new app
127
+ if (this.appName === appName) {
128
+ return;
129
+ }
130
+ let navigate = false;
131
+ if (!apps[appName]) {
132
+ let newContext = await getContext(null, false, this.logger, appName, false, this);
133
+ navigate = true;
134
+ apps[appName] = {
135
+ context: newContext,
136
+ browser: newContext.browser,
137
+ page: newContext.page,
138
+ };
139
+ }
140
+ const tempContext = {};
141
+ this._copyContext(this, tempContext);
142
+ this._copyContext(apps[appName], this);
143
+ apps[this.appName] = tempContext;
144
+ this.appName = appName;
145
+ if (navigate) {
146
+ await this.goto(this.context.environment.baseUrl);
147
+ await this.waitForPageLoad();
148
+ }
149
+ }
150
+ _copyContext(from, to) {
151
+ to.browser = from.browser;
152
+ to.page = from.page;
153
+ to.context = from.context;
154
+ }
97
155
  getWebLogFile(logFolder) {
98
156
  if (!fs.existsSync(logFolder)) {
99
157
  fs.mkdirSync(logFolder, { recursive: true });
@@ -105,37 +163,65 @@ class StableBrowser {
105
163
  const fileName = nextIndex + ".json";
106
164
  return path.join(logFolder, fileName);
107
165
  }
108
- registerConsoleLogListener(page, context, logFile) {
166
+ registerConsoleLogListener(page, context) {
109
167
  if (!this.context.webLogger) {
110
168
  this.context.webLogger = [];
111
169
  }
112
170
  page.on("console", async (msg) => {
113
- this.context.webLogger.push({
171
+ var _a;
172
+ const obj = {
114
173
  type: msg.type(),
115
174
  text: msg.text(),
116
175
  location: msg.location(),
117
176
  time: new Date().toISOString(),
118
- });
119
- await fs.promises.writeFile(logFile, JSON.stringify(this.context.webLogger, null, 2));
177
+ };
178
+ this.context.webLogger.push(obj);
179
+ if (msg.type() === "error") {
180
+ (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
181
+ }
120
182
  });
121
183
  }
122
- registerRequestListener() {
123
- this.page.on("request", async (data) => {
184
+ registerRequestListener(page, context, logFile) {
185
+ if (!this.context.networkLogger) {
186
+ this.context.networkLogger = [];
187
+ }
188
+ page.on("request", async (data) => {
189
+ var _a;
190
+ const startTime = new Date().getTime();
124
191
  try {
125
- const pageUrl = new URL(this.page.url());
192
+ const pageUrl = new URL(page.url());
126
193
  const requestUrl = new URL(data.url());
127
194
  if (pageUrl.hostname === requestUrl.hostname) {
128
195
  const method = data.method();
129
- if (method === "POST" || method === "GET" || method === "PUT" || method === "DELETE" || method === "PATCH") {
196
+ if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
130
197
  const token = await data.headerValue("Authorization");
131
198
  if (token) {
132
- this.context.authtoken = token;
199
+ context.authtoken = token;
133
200
  }
134
201
  }
135
202
  }
203
+ const response = await data.response();
204
+ const endTime = new Date().getTime();
205
+ const obj = {
206
+ url: data.url(),
207
+ method: data.method(),
208
+ postData: data.postData(),
209
+ error: data.failure() ? data.failure().errorText : null,
210
+ duration: endTime - startTime,
211
+ startTime,
212
+ };
213
+ context.networkLogger.push(obj);
214
+ (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
136
215
  }
137
216
  catch (error) {
138
217
  console.error("Error in request listener", error);
218
+ context.networkLogger.push({
219
+ error: "not able to listen",
220
+ message: error.message,
221
+ stack: error.stack,
222
+ time: new Date().toISOString(),
223
+ });
224
+ // await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
139
225
  }
140
226
  });
141
227
  }
@@ -178,26 +264,83 @@ class StableBrowser {
178
264
  }
179
265
  return text;
180
266
  }
181
- _getLocator(locator, scope, _params) {
182
- if (locator.type === "pw_selector") {
183
- return scope.locator(locator.selector);
267
+ _fixLocatorUsingParams(locator, _params) {
268
+ // check if not null
269
+ if (!locator) {
270
+ return locator;
271
+ }
272
+ // clone the locator
273
+ locator = JSON.parse(JSON.stringify(locator));
274
+ this.scanAndManipulate(locator, _params);
275
+ return locator;
276
+ }
277
+ _isObject(value) {
278
+ return value && typeof value === "object" && value.constructor === Object;
279
+ }
280
+ scanAndManipulate(currentObj, _params) {
281
+ for (const key in currentObj) {
282
+ if (typeof currentObj[key] === "string") {
283
+ // Perform string manipulation
284
+ currentObj[key] = this._fixUsingParams(currentObj[key], _params);
285
+ }
286
+ else if (this._isObject(currentObj[key])) {
287
+ // Recursively scan nested objects
288
+ this.scanAndManipulate(currentObj[key], _params);
289
+ }
184
290
  }
291
+ }
292
+ _getLocator(locator, scope, _params) {
293
+ locator = this._fixLocatorUsingParams(locator, _params);
294
+ let locatorReturn;
185
295
  if (locator.role) {
186
296
  if (locator.role[1].nameReg) {
187
297
  locator.role[1].name = reg_parser(locator.role[1].nameReg);
188
298
  delete locator.role[1].nameReg;
189
299
  }
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]);
300
+ // if (locator.role[1].name) {
301
+ // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
302
+ // }
303
+ locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
194
304
  }
195
305
  if (locator.css) {
196
- return scope.locator(this._fixUsingParams(locator.css, _params));
306
+ locatorReturn = scope.locator(locator.css);
307
+ }
308
+ // handle role/name locators
309
+ // locator.selector will be something like: textbox[name="Username"i]
310
+ if (locator.engine === "internal:role") {
311
+ // extract the role, name and the i/s flags using regex
312
+ const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
313
+ if (match) {
314
+ const role = match[1];
315
+ const name = match[3];
316
+ const flags = match[4];
317
+ locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
318
+ }
319
+ }
320
+ if (locator === null || locator === void 0 ? void 0 : locator.engine) {
321
+ if (locator.engine === "css") {
322
+ locatorReturn = scope.locator(locator.selector);
323
+ }
324
+ else {
325
+ let selector = locator.selector;
326
+ if (locator.engine === "internal:attr") {
327
+ if (!selector.startsWith("[")) {
328
+ selector = `[${selector}]`;
329
+ }
330
+ }
331
+ locatorReturn = scope.locator(`${locator.engine}=${selector}`);
332
+ }
333
+ }
334
+ if (!locatorReturn) {
335
+ console.error(locator);
336
+ throw new Error("Locator undefined");
197
337
  }
198
- throw new Error("unknown locator type");
338
+ return locatorReturn;
199
339
  }
200
340
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
341
+ if (css && css.locator) {
342
+ css = css.locator;
343
+ }
201
344
  let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
202
345
  if (result.elementCount === 0) {
203
346
  return;
@@ -225,6 +368,15 @@ class StableBrowser {
225
368
  return false;
226
369
  }
227
370
  document.isParent = isParent;
371
+ function getRegex(str) {
372
+ const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
373
+ if (!match) {
374
+ return null;
375
+ }
376
+ let [_, pattern, flags] = match;
377
+ return new RegExp(pattern, flags);
378
+ }
379
+ document.getRegex = getRegex;
228
380
  function collectAllShadowDomElements(element, result = []) {
229
381
  // Check and add the element if it has a shadow root
230
382
  if (element.shadowRoot) {
@@ -243,6 +395,10 @@ class StableBrowser {
243
395
  if (!tag) {
244
396
  tag = "*";
245
397
  }
398
+ let regexpSearch = document.getRegex(text);
399
+ if (regexpSearch) {
400
+ regex = true;
401
+ }
246
402
  let elements = Array.from(document.querySelectorAll(tag));
247
403
  let shadowHosts = [];
248
404
  document.collectAllShadowDomElements(document, shadowHosts);
@@ -258,7 +414,9 @@ class StableBrowser {
258
414
  let randomToken = null;
259
415
  const foundElements = [];
260
416
  if (regex) {
261
- let regexpSearch = new RegExp(text, "im");
417
+ if (!regexpSearch) {
418
+ regexpSearch = new RegExp(text, "im");
419
+ }
262
420
  for (let i = 0; i < elements.length; i++) {
263
421
  const element = elements[i];
264
422
  if ((element.innerText && regexpSearch.test(element.innerText)) ||
@@ -272,8 +430,8 @@ class StableBrowser {
272
430
  for (let i = 0; i < elements.length; i++) {
273
431
  const element = elements[i];
274
432
  if (partial) {
275
- if ((element.innerText && element.innerText.trim().includes(text)) ||
276
- (element.value && element.value.includes(text))) {
433
+ if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
434
+ (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
277
435
  foundElements.push(element);
278
436
  }
279
437
  }
@@ -318,6 +476,12 @@ class StableBrowser {
318
476
  }
319
477
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
320
478
  let locatorSearch = selectorHierarchy[index];
479
+ try {
480
+ locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
481
+ }
482
+ catch (e) {
483
+ console.error(e);
484
+ }
321
485
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
322
486
  let locator = null;
323
487
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
@@ -406,6 +570,8 @@ class StableBrowser {
406
570
  if (result.foundElements.length > 0) {
407
571
  let dialogCloseLocator = result.foundElements[0].locator;
408
572
  await dialogCloseLocator.click();
573
+ // wait for the dialog to close
574
+ await dialogCloseLocator.waitFor({ state: "hidden" });
409
575
  return { rerun: true };
410
576
  }
411
577
  }
@@ -414,7 +580,7 @@ class StableBrowser {
414
580
  }
415
581
  async _locate(selectors, info, _params, timeout = 30000) {
416
582
  for (let i = 0; i < 3; i++) {
417
- info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
583
+ info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
418
584
  for (let j = 0; j < selectors.locators.length; j++) {
419
585
  let selector = selectors.locators[j];
420
586
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
@@ -433,10 +599,44 @@ class StableBrowser {
433
599
  let locatorsCount = 0;
434
600
  //let arrayMode = Array.isArray(selectors);
435
601
  let scope = this.page;
602
+ // for the simple click usecase
603
+ if (selectors.frame) {
604
+ scope = selectors.frame;
605
+ }
436
606
  if (selectors.iframe_src || selectors.frameLocators) {
607
+ const findFrame = async (frame, framescope) => {
608
+ for (let i = 0; i < frame.selectors.length; i++) {
609
+ let frameLocator = frame.selectors[i];
610
+ if (frameLocator.css) {
611
+ let testframescope = framescope.frameLocator(frameLocator.css);
612
+ if (frameLocator.index) {
613
+ testframescope = framescope.nth(frameLocator.index);
614
+ }
615
+ try {
616
+ await testframescope.owner().evaluateHandle(() => true, null, {
617
+ timeout: 5000,
618
+ });
619
+ framescope = testframescope;
620
+ break;
621
+ }
622
+ catch (error) {
623
+ console.error("frame not found " + frameLocator.css);
624
+ }
625
+ }
626
+ }
627
+ if (frame.children) {
628
+ return await findFrame(frame.children, framescope);
629
+ }
630
+ return framescope;
631
+ };
437
632
  info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
438
633
  while (true) {
439
634
  let frameFound = false;
635
+ if (selectors.nestFrmLoc) {
636
+ scope = await findFrame(selectors.nestFrmLoc, scope);
637
+ frameFound = true;
638
+ break;
639
+ }
440
640
  if (selectors.frameLocators) {
441
641
  for (let i = 0; i < selectors.frameLocators.length; i++) {
442
642
  let frameLocator = selectors.frameLocators[i];
@@ -597,9 +797,72 @@ class StableBrowser {
597
797
  }
598
798
  return result;
599
799
  }
800
+ async simpleClick(elementDescription, _params, options = {}, world = null) {
801
+ const startTime = Date.now();
802
+ let timeout = 30000;
803
+ if (options && options.timeout) {
804
+ timeout = options.timeout;
805
+ }
806
+ while (true) {
807
+ try {
808
+ const result = await locate_element(this.context, elementDescription, "click");
809
+ if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
810
+ const selectors = {
811
+ frame: result === null || result === void 0 ? void 0 : result.frame,
812
+ locators: [
813
+ {
814
+ css: result === null || result === void 0 ? void 0 : result.css,
815
+ },
816
+ ],
817
+ };
818
+ await this.click(selectors, _params, options, world);
819
+ return;
820
+ }
821
+ }
822
+ catch (e) {
823
+ if (performance.now() - startTime > timeout) {
824
+ throw e;
825
+ }
826
+ }
827
+ await new Promise((resolve) => setTimeout(resolve, 3000));
828
+ }
829
+ }
830
+ async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
831
+ const startTime = Date.now();
832
+ let timeout = 30000;
833
+ if (options && options.timeout) {
834
+ timeout = options.timeout;
835
+ }
836
+ while (true) {
837
+ try {
838
+ const result = await locate_element(this.context, elementDescription, "fill", value);
839
+ if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
840
+ const selectors = {
841
+ frame: result === null || result === void 0 ? void 0 : result.frame,
842
+ locators: [
843
+ {
844
+ css: result === null || result === void 0 ? void 0 : result.css,
845
+ },
846
+ ],
847
+ };
848
+ await this.clickType(selectors, value, false, _params, options, world);
849
+ return;
850
+ }
851
+ }
852
+ catch (e) {
853
+ if (performance.now() - startTime > timeout) {
854
+ throw e;
855
+ }
856
+ }
857
+ await new Promise((resolve) => setTimeout(resolve, 3000));
858
+ }
859
+ }
600
860
  async click(selectors, _params, options = {}, world = null) {
601
861
  this._validateSelectors(selectors);
602
862
  const startTime = Date.now();
863
+ if (options && options.context) {
864
+ selectors.locators[0].text = options.context;
865
+ }
603
866
  const info = {};
604
867
  info.log = "***** click on " + selectors.element_name + " *****\n";
605
868
  info.operation = "click";
@@ -613,14 +876,14 @@ class StableBrowser {
613
876
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
614
877
  try {
615
878
  await this._highlightElements(element);
616
- await element.click({ timeout: 5000 });
879
+ await element.click();
617
880
  await new Promise((resolve) => setTimeout(resolve, 1000));
618
881
  }
619
882
  catch (e) {
620
883
  // await this.closeUnexpectedPopups();
621
884
  info.log += "click failed, will try again" + "\n";
622
885
  element = await this._locate(selectors, info, _params);
623
- await element.click({ timeout: 10000, force: true });
886
+ await element.dispatchEvent("click");
624
887
  await new Promise((resolve) => setTimeout(resolve, 1000));
625
888
  }
626
889
  await this.waitForPageLoad();
@@ -673,7 +936,7 @@ class StableBrowser {
673
936
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
674
937
  try {
675
938
  await this._highlightElements(element);
676
- await element.setChecked(checked, { timeout: 5000 });
939
+ await element.setChecked(checked);
677
940
  await new Promise((resolve) => setTimeout(resolve, 1000));
678
941
  }
679
942
  catch (e) {
@@ -737,7 +1000,7 @@ class StableBrowser {
737
1000
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
738
1001
  try {
739
1002
  await this._highlightElements(element);
740
- await element.hover({ timeout: 10000 });
1003
+ await element.hover();
741
1004
  await new Promise((resolve) => setTimeout(resolve, 1000));
742
1005
  }
743
1006
  catch (e) {
@@ -799,7 +1062,7 @@ class StableBrowser {
799
1062
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
800
1063
  try {
801
1064
  await this._highlightElements(element);
802
- await element.selectOption(values, { timeout: 5000 });
1065
+ await element.selectOption(values);
803
1066
  }
804
1067
  catch (e) {
805
1068
  //await this.closeUnexpectedPopups();
@@ -908,71 +1171,45 @@ class StableBrowser {
908
1171
  });
909
1172
  }
910
1173
  }
911
- async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1174
+ async setInputValue(selectors, value, _params = null, options = {}, world = null) {
1175
+ // set input value for non fillable inputs like date, time, range, color, etc.
912
1176
  this._validateSelectors(selectors);
913
1177
  const startTime = Date.now();
914
- let error = null;
915
- let screenshotId = null;
916
- let screenshotPath = null;
917
1178
  const info = {};
918
- info.log = "";
919
- info.operation = Types.SET_DATE_TIME;
1179
+ info.log = "***** set input value " + selectors.element_name + " *****\n";
1180
+ info.operation = "setInputValue";
920
1181
  info.selectors = selectors;
1182
+ value = this._fixUsingParams(value, _params);
921
1183
  info.value = value;
1184
+ let error = null;
1185
+ let screenshotId = null;
1186
+ let screenshotPath = null;
922
1187
  try {
923
1188
  value = await this._replaceWithLocalData(value, this);
924
1189
  let element = await this._locate(selectors, info, _params);
925
- //insert red border around the element
926
1190
  await this.scrollIfNeeded(element, info);
927
1191
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
928
1192
  await this._highlightElements(element);
929
1193
  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
- }
1194
+ await element.evaluateHandle((el, value) => {
1195
+ el.value = value;
1196
+ }, value);
948
1197
  }
949
1198
  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)));
1199
+ this.logger.error("setInputValue failed, will try again");
1200
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
953
1201
  info.screenshotPath = screenshotPath;
954
1202
  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
- }
1203
+ await element.evaluateHandle((el, value) => {
1204
+ el.value = value;
1205
+ });
973
1206
  }
974
1207
  }
975
- catch (error) {
1208
+ catch (e) {
1209
+ this.logger.error("setInputValue failed " + info.log);
1210
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1211
+ info.screenshotPath = screenshotPath;
1212
+ Object.assign(e, { info: info });
976
1213
  error = e;
977
1214
  throw e;
978
1215
  }
@@ -980,10 +1217,10 @@ class StableBrowser {
980
1217
  const endTime = Date.now();
981
1218
  this._reportToWorld(world, {
982
1219
  element_name: selectors.element_name,
983
- type: Types.SET_DATE_TIME,
984
- screenshotId,
1220
+ type: Types.SET_INPUT,
1221
+ text: `Set input value`,
985
1222
  value: value,
986
- text: `setDateTime input with value: ${value}`,
1223
+ screenshotId,
987
1224
  result: error
988
1225
  ? {
989
1226
  status: "FAILED",
@@ -1000,7 +1237,7 @@ class StableBrowser {
1000
1237
  });
1001
1238
  }
1002
1239
  }
1003
- async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
1240
+ async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1004
1241
  this._validateSelectors(selectors);
1005
1242
  const startTime = Date.now();
1006
1243
  let error = null;
@@ -1012,6 +1249,7 @@ class StableBrowser {
1012
1249
  info.selectors = selectors;
1013
1250
  info.value = value;
1014
1251
  try {
1252
+ value = await this._replaceWithLocalData(value, this);
1015
1253
  let element = await this._locate(selectors, info, _params);
1016
1254
  //insert red border around the element
1017
1255
  await this.scrollIfNeeded(element, info);
@@ -1020,28 +1258,51 @@ class StableBrowser {
1020
1258
  try {
1021
1259
  await element.click();
1022
1260
  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);
1261
+ if (format) {
1262
+ value = dayjs(value).format(format);
1263
+ await element.fill(value);
1264
+ }
1265
+ else {
1266
+ const dateTimeValue = await getDateTimeValue({ value, element });
1267
+ await element.evaluateHandle((el, dateTimeValue) => {
1268
+ el.value = ""; // clear input
1269
+ el.value = dateTimeValue;
1270
+ }, dateTimeValue);
1271
+ }
1272
+ if (enter) {
1273
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1274
+ await this.page.keyboard.press("Enter");
1275
+ await this.waitForPageLoad();
1276
+ }
1028
1277
  }
1029
- catch (error) {
1278
+ catch (err) {
1030
1279
  //await this.closeUnexpectedPopups();
1031
1280
  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)));
1281
+ this.logger.info("Trying again");
1282
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1033
1283
  info.screenshotPath = screenshotPath;
1034
- Object.assign(error, { info: info });
1284
+ Object.assign(err, { info: info });
1035
1285
  await element.click();
1036
1286
  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);
1287
+ if (format) {
1288
+ value = dayjs(value).format(format);
1289
+ await element.fill(value);
1290
+ }
1291
+ else {
1292
+ const dateTimeValue = await getDateTimeValue({ value, element });
1293
+ await element.evaluateHandle((el, dateTimeValue) => {
1294
+ el.value = ""; // clear input
1295
+ el.value = dateTimeValue;
1296
+ }, dateTimeValue);
1297
+ }
1298
+ if (enter) {
1299
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1300
+ await this.page.keyboard.press("Enter");
1301
+ await this.waitForPageLoad();
1302
+ }
1042
1303
  }
1043
1304
  }
1044
- catch (error) {
1305
+ catch (e) {
1045
1306
  error = e;
1046
1307
  throw e;
1047
1308
  }
@@ -1089,22 +1350,33 @@ class StableBrowser {
1089
1350
  let element = await this._locate(selectors, info, _params);
1090
1351
  //insert red border around the element
1091
1352
  await this.scrollIfNeeded(element, info);
1092
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1093
1353
  await this._highlightElements(element);
1094
- try {
1095
- let currentValue = await element.inputValue();
1096
- if (currentValue) {
1097
- await element.fill("");
1354
+ if (options === null || options === undefined || !options.press) {
1355
+ try {
1356
+ let currentValue = await element.inputValue();
1357
+ if (currentValue) {
1358
+ await element.fill("");
1359
+ }
1360
+ }
1361
+ catch (e) {
1362
+ this.logger.info("unable to clear input value");
1098
1363
  }
1099
1364
  }
1100
- catch (e) {
1101
- this.logger.info("unable to clear input value");
1102
- }
1103
- try {
1104
- await element.click({ timeout: 5000 });
1365
+ if (options === null || options === undefined || options.press) {
1366
+ try {
1367
+ await element.click({ timeout: 5000 });
1368
+ }
1369
+ catch (e) {
1370
+ await element.dispatchEvent("click");
1371
+ }
1105
1372
  }
1106
- catch (e) {
1107
- await element.dispatchEvent("click");
1373
+ else {
1374
+ try {
1375
+ await element.focus();
1376
+ }
1377
+ catch (e) {
1378
+ await element.dispatchEvent("focus");
1379
+ }
1108
1380
  }
1109
1381
  await new Promise((resolve) => setTimeout(resolve, 500));
1110
1382
  const valueSegment = _value.split("&&");
@@ -1127,6 +1399,7 @@ class StableBrowser {
1127
1399
  await new Promise((resolve) => setTimeout(resolve, 500));
1128
1400
  }
1129
1401
  }
1402
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1130
1403
  if (enter === true) {
1131
1404
  await new Promise((resolve) => setTimeout(resolve, 2000));
1132
1405
  await this.page.keyboard.press("Enter");
@@ -1192,7 +1465,7 @@ class StableBrowser {
1192
1465
  let element = await this._locate(selectors, info, _params);
1193
1466
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1194
1467
  await this._highlightElements(element);
1195
- await element.fill(value, { timeout: 10000 });
1468
+ await element.fill(value);
1196
1469
  await element.dispatchEvent("change");
1197
1470
  if (enter) {
1198
1471
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -1411,7 +1684,7 @@ class StableBrowser {
1411
1684
  return info;
1412
1685
  }
1413
1686
  catch (e) {
1414
- //await this.closeUnexpectedPopups();
1687
+ await this.closeUnexpectedPopups();
1415
1688
  this.logger.error("verify element contains text failed " + info.log);
1416
1689
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1417
1690
  info.screenshotPath = screenshotPath;
@@ -1459,6 +1732,29 @@ class StableBrowser {
1459
1732
  }
1460
1733
  return dataFile;
1461
1734
  }
1735
+ async waitForUserInput(message, world = null) {
1736
+ if (!message) {
1737
+ message = "# Wait for user input. Press any key to continue";
1738
+ }
1739
+ else {
1740
+ message = "# Wait for user input. " + message;
1741
+ }
1742
+ message += "\n";
1743
+ const value = await new Promise((resolve) => {
1744
+ const rl = readline.createInterface({
1745
+ input: process.stdin,
1746
+ output: process.stdout,
1747
+ });
1748
+ rl.question(message, (answer) => {
1749
+ rl.close();
1750
+ resolve(answer);
1751
+ });
1752
+ });
1753
+ if (value) {
1754
+ this.logger.info(`{{userInput}} was set to: ${value}`);
1755
+ }
1756
+ this.setTestData({ userInput: value }, world);
1757
+ }
1462
1758
  setTestData(testData, world = null) {
1463
1759
  if (!testData) {
1464
1760
  return;
@@ -1486,7 +1782,7 @@ class StableBrowser {
1486
1782
  const data = fs.readFileSync(filePath, "utf8");
1487
1783
  const results = [];
1488
1784
  return new Promise((resolve, reject) => {
1489
- const readableStream = new stream.Readable();
1785
+ const readableStream = new Readable();
1490
1786
  readableStream._read = () => { }; // _read is required but you can noop it
1491
1787
  readableStream.push(data);
1492
1788
  readableStream.push(null);
@@ -1646,7 +1942,6 @@ class StableBrowser {
1646
1942
  }
1647
1943
  async takeScreenshot(screenshotPath) {
1648
1944
  const playContext = this.context.playContext;
1649
- const client = await playContext.newCDPSession(this.page);
1650
1945
  // Using CDP to capture the screenshot
1651
1946
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
1652
1947
  document.body.scrollWidth,
@@ -1656,41 +1951,40 @@ class StableBrowser {
1656
1951
  document.body.clientWidth,
1657
1952
  document.documentElement.clientWidth,
1658
1953
  ])));
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();
1954
+ let screenshotBuffer = null;
1955
+ if (this.context.browserName === "chromium") {
1956
+ const client = await playContext.newCDPSession(this.page);
1957
+ const { data } = await client.send("Page.captureScreenshot", {
1958
+ format: "png",
1959
+ // clip: {
1960
+ // x: 0,
1961
+ // y: 0,
1962
+ // width: viewportWidth,
1963
+ // height: viewportHeight,
1964
+ // scale: 1,
1965
+ // },
1966
+ });
1967
+ await client.detach();
1968
+ if (!screenshotPath) {
1969
+ return data;
1970
+ }
1971
+ screenshotBuffer = Buffer.from(data, "base64");
1972
+ }
1973
+ else {
1974
+ screenshotBuffer = await this.page.screenshot();
1975
+ }
1976
+ let image = await Jimp.read(screenshotBuffer);
1977
+ // Get the image dimensions
1978
+ const { width, height } = image.bitmap;
1979
+ const resizeRatio = viewportWidth / width;
1980
+ // Resize the image to fit within the viewport dimensions without enlarging
1981
+ if (width > viewportWidth) {
1982
+ image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
1983
+ await image.write(screenshotPath);
1984
+ }
1985
+ else {
1986
+ fs.writeFileSync(screenshotPath, screenshotBuffer);
1987
+ }
1694
1988
  }
1695
1989
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1696
1990
  this._validateSelectors(selectors);
@@ -2498,13 +2792,13 @@ class StableBrowser {
2498
2792
  }
2499
2793
  catch (e) {
2500
2794
  if (e.label === "networkidle") {
2501
- console.log("waitted for the network to be idle timeout");
2795
+ console.log("waited for the network to be idle timeout");
2502
2796
  }
2503
2797
  else if (e.label === "load") {
2504
- console.log("waitted for the load timeout");
2798
+ console.log("waited for the load timeout");
2505
2799
  }
2506
2800
  else if (e.label === "domcontentloaded") {
2507
- console.log("waitted for the domcontent loaded timeout");
2801
+ console.log("waited for the domcontent loaded timeout");
2508
2802
  }
2509
2803
  console.log(".");
2510
2804
  }
@@ -2539,13 +2833,6 @@ class StableBrowser {
2539
2833
  const info = {};
2540
2834
  try {
2541
2835
  await this.page.close();
2542
- if (this.context && this.context.pages && this.context.pages.length > 0) {
2543
- this.context.pages.pop();
2544
- this.page = this.context.pages[this.context.pages.length - 1];
2545
- this.context.page = this.page;
2546
- let title = await this.page.title();
2547
- console.log("Switched to page " + title);
2548
- }
2549
2836
  }
2550
2837
  catch (e) {
2551
2838
  console.log(".");
@@ -2654,33 +2941,18 @@ class StableBrowser {
2654
2941
  }
2655
2942
  async scrollIfNeeded(element, info) {
2656
2943
  try {
2657
- let didScroll = await element.evaluate((node) => {
2658
- const rect = node.getBoundingClientRect();
2659
- if (rect &&
2660
- rect.top >= 0 &&
2661
- rect.left >= 0 &&
2662
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
2663
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
2664
- return false;
2665
- }
2666
- else {
2667
- node.scrollIntoView({
2668
- behavior: "smooth",
2669
- block: "center",
2670
- inline: "center",
2671
- });
2672
- return true;
2673
- }
2944
+ await element.scrollIntoViewIfNeeded({
2945
+ timeout: 2000,
2674
2946
  });
2675
- if (didScroll) {
2676
- await new Promise((resolve) => setTimeout(resolve, 500));
2677
- if (info) {
2678
- info.box = await element.boundingBox();
2679
- }
2947
+ await new Promise((resolve) => setTimeout(resolve, 500));
2948
+ if (info) {
2949
+ info.box = await element.boundingBox({
2950
+ timeout: 1000,
2951
+ });
2680
2952
  }
2681
2953
  }
2682
2954
  catch (e) {
2683
- console.log("scroll failed");
2955
+ console.log("#-#");
2684
2956
  }
2685
2957
  }
2686
2958
  _reportToWorld(world, properties) {