automation_model 1.0.407-dev → 1.0.407-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.
@@ -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";
@@ -14,6 +14,8 @@ import objectPath from "object-path";
14
14
  import { decrypt } from "./utils.js";
15
15
  import csv from "csv-parser";
16
16
  import { Readable } from "node:stream";
17
+ import readline from "readline";
18
+ import { getContext } from "./init_browser.js";
17
19
  const Types = {
18
20
  CLICK: "click_element",
19
21
  NAVIGATE: "navigate",
@@ -41,15 +43,19 @@ const Types = {
41
43
  LOAD_DATA: "load_data",
42
44
  SET_INPUT: "set_input",
43
45
  };
46
+ export const apps = {};
44
47
  class StableBrowser {
45
- constructor(browser, page, logger = null, context = null) {
48
+ constructor(browser, page, logger = null, context = null, world = null) {
46
49
  this.browser = browser;
47
50
  this.page = page;
48
51
  this.logger = logger;
49
52
  this.context = context;
53
+ this.world = world;
50
54
  this.project_path = null;
51
55
  this.webLogFile = null;
56
+ this.networkLogger = null;
52
57
  this.configuration = null;
58
+ this.appName = "main";
53
59
  if (!this.logger) {
54
60
  this.logger = console;
55
61
  }
@@ -75,23 +81,34 @@ class StableBrowser {
75
81
  this.logger.error("unable to read ai_config.json");
76
82
  }
77
83
  const logFolder = path.join(this.project_path, "logs", "web");
78
- this.webLogFile = this.getWebLogFile(logFolder);
79
- this.registerConsoleLogListener(page, context, this.webLogFile);
80
- this.registerRequestListener();
84
+ this.world = world;
81
85
  context.pages = [this.page];
82
86
  context.pageLoading = { status: false };
87
+ this.registerEventListeners(this.context);
88
+ }
89
+ registerEventListeners(context) {
90
+ this.registerConsoleLogListener(this.page, context);
91
+ this.registerRequestListener(this.page, context, this.webLogFile);
92
+ if (!context.pageLoading) {
93
+ context.pageLoading = { status: false };
94
+ }
83
95
  context.playContext.on("page", async function (page) {
84
96
  context.pageLoading.status = true;
85
97
  this.page = page;
86
98
  context.page = page;
87
99
  context.pages.push(page);
88
100
  page.on("close", async () => {
89
- if (this.context && this.context.pages && this.context.pages.length > 0) {
101
+ if (this.context && this.context.pages && this.context.pages.length > 1) {
90
102
  this.context.pages.pop();
91
103
  this.page = this.context.pages[this.context.pages.length - 1];
92
104
  this.context.page = this.page;
93
- let title = await this.page.title();
94
- console.log("Switched to page " + title);
105
+ try {
106
+ let title = await this.page.title();
107
+ console.log("Switched to page " + title);
108
+ }
109
+ catch (error) {
110
+ console.error("Error on page close", error);
111
+ }
95
112
  }
96
113
  });
97
114
  try {
@@ -104,6 +121,36 @@ class StableBrowser {
104
121
  context.pageLoading.status = false;
105
122
  }.bind(this));
106
123
  }
124
+ async switchApp(appName) {
125
+ // check if the current app (this.appName) is the same as the new app
126
+ if (this.appName === appName) {
127
+ return;
128
+ }
129
+ let navigate = false;
130
+ if (!apps[appName]) {
131
+ let newContext = await getContext(null, false, this.logger, appName, false, this);
132
+ navigate = true;
133
+ apps[appName] = {
134
+ context: newContext,
135
+ browser: newContext.browser,
136
+ page: newContext.page,
137
+ };
138
+ }
139
+ const tempContext = {};
140
+ this._copyContext(this, tempContext);
141
+ this._copyContext(apps[appName], this);
142
+ apps[this.appName] = tempContext;
143
+ this.appName = appName;
144
+ if (navigate) {
145
+ await this.goto(this.context.environment.baseUrl);
146
+ await this.waitForPageLoad();
147
+ }
148
+ }
149
+ _copyContext(from, to) {
150
+ to.browser = from.browser;
151
+ to.page = from.page;
152
+ to.context = from.context;
153
+ }
107
154
  getWebLogFile(logFolder) {
108
155
  if (!fs.existsSync(logFolder)) {
109
156
  fs.mkdirSync(logFolder, { recursive: true });
@@ -115,37 +162,63 @@ class StableBrowser {
115
162
  const fileName = nextIndex + ".json";
116
163
  return path.join(logFolder, fileName);
117
164
  }
118
- registerConsoleLogListener(page, context, logFile) {
165
+ registerConsoleLogListener(page, context) {
119
166
  if (!this.context.webLogger) {
120
167
  this.context.webLogger = [];
121
168
  }
122
169
  page.on("console", async (msg) => {
123
- this.context.webLogger.push({
170
+ var _a;
171
+ const obj = {
124
172
  type: msg.type(),
125
173
  text: msg.text(),
126
174
  location: msg.location(),
127
175
  time: new Date().toISOString(),
128
- });
129
- await fs.promises.writeFile(logFile, JSON.stringify(this.context.webLogger, null, 2));
176
+ };
177
+ this.context.webLogger.push(obj);
178
+ (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
130
179
  });
131
180
  }
132
- registerRequestListener() {
133
- this.page.on("request", async (data) => {
181
+ registerRequestListener(page, context, logFile) {
182
+ if (!this.context.networkLogger) {
183
+ this.context.networkLogger = [];
184
+ }
185
+ page.on("request", async (data) => {
186
+ var _a;
187
+ const startTime = new Date().getTime();
134
188
  try {
135
- const pageUrl = new URL(this.page.url());
189
+ const pageUrl = new URL(page.url());
136
190
  const requestUrl = new URL(data.url());
137
191
  if (pageUrl.hostname === requestUrl.hostname) {
138
192
  const method = data.method();
139
- if (method === "POST" || method === "GET" || method === "PUT" || method === "DELETE" || method === "PATCH") {
193
+ if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
140
194
  const token = await data.headerValue("Authorization");
141
195
  if (token) {
142
- this.context.authtoken = token;
196
+ context.authtoken = token;
143
197
  }
144
198
  }
145
199
  }
200
+ const response = await data.response();
201
+ const endTime = new Date().getTime();
202
+ const obj = {
203
+ url: data.url(),
204
+ method: data.method(),
205
+ postData: data.postData(),
206
+ error: data.failure() ? data.failure().errorText : null,
207
+ duration: endTime - startTime,
208
+ startTime,
209
+ };
210
+ context.networkLogger.push(obj);
211
+ (_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
146
212
  }
147
213
  catch (error) {
148
214
  console.error("Error in request listener", error);
215
+ context.networkLogger.push({
216
+ error: "not able to listen",
217
+ message: error.message,
218
+ stack: error.stack,
219
+ time: new Date().toISOString(),
220
+ });
221
+ // await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
149
222
  }
150
223
  });
151
224
  }
@@ -215,9 +288,7 @@ class StableBrowser {
215
288
  }
216
289
  _getLocator(locator, scope, _params) {
217
290
  locator = this._fixLocatorUsingParams(locator, _params);
218
- if (locator.type === "pw_selector") {
219
- return scope.locator(locator.selector);
220
- }
291
+ let locatorReturn;
221
292
  if (locator.role) {
222
293
  if (locator.role[1].nameReg) {
223
294
  locator.role[1].name = reg_parser(locator.role[1].nameReg);
@@ -226,20 +297,42 @@ class StableBrowser {
226
297
  // if (locator.role[1].name) {
227
298
  // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
228
299
  // }
229
- return scope.getByRole(locator.role[0], locator.role[1]);
300
+ locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
230
301
  }
231
302
  if (locator.css) {
232
- return scope.locator(locator.css);
303
+ locatorReturn = scope.locator(locator.css);
233
304
  }
234
- if ((locator === null || locator === void 0 ? void 0 : locator.engine) && (locator === null || locator === void 0 ? void 0 : locator.score) <= 520) {
235
- let selector = locator.selector.replace(/"/g, '\\"');
236
- if (locator.engine === "internal:att") {
237
- selector = `[${selector}]`;
305
+ // handle role/name locators
306
+ // locator.selector will be something like: textbox[name="Username"i]
307
+ if (locator.engine === "internal:role") {
308
+ // extract the role, name and the i/s flags using regex
309
+ const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
310
+ if (match) {
311
+ const role = match[1];
312
+ const name = match[3];
313
+ const flags = match[4];
314
+ locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
315
+ }
316
+ }
317
+ if (locator === null || locator === void 0 ? void 0 : locator.engine) {
318
+ if (locator.engine === "css") {
319
+ locatorReturn = scope.locator(locator.selector);
320
+ }
321
+ else {
322
+ let selector = locator.selector;
323
+ if (locator.engine === "internal:attr") {
324
+ if (!selector.startsWith("[")) {
325
+ selector = `[${selector}]`;
326
+ }
327
+ }
328
+ locatorReturn = scope.locator(`${locator.engine}=${selector}`);
238
329
  }
239
- const locator = scope.locator(`${locator.engine}="${selector}"`);
240
- return locator;
241
330
  }
242
- throw new Error("unknown locator type");
331
+ if (!locatorReturn) {
332
+ console.error(locator);
333
+ throw new Error("Locator undefined");
334
+ }
335
+ return locatorReturn;
243
336
  }
244
337
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
245
338
  let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
@@ -450,6 +543,8 @@ class StableBrowser {
450
543
  if (result.foundElements.length > 0) {
451
544
  let dialogCloseLocator = result.foundElements[0].locator;
452
545
  await dialogCloseLocator.click();
546
+ // wait for the dialog to close
547
+ await dialogCloseLocator.waitFor({ state: "hidden" });
453
548
  return { rerun: true };
454
549
  }
455
550
  }
@@ -458,7 +553,7 @@ class StableBrowser {
458
553
  }
459
554
  async _locate(selectors, info, _params, timeout = 30000) {
460
555
  for (let i = 0; i < 3; i++) {
461
- info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
556
+ info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
462
557
  for (let j = 0; j < selectors.locators.length; j++) {
463
558
  let selector = selectors.locators[j];
464
559
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
@@ -478,9 +573,30 @@ class StableBrowser {
478
573
  //let arrayMode = Array.isArray(selectors);
479
574
  let scope = this.page;
480
575
  if (selectors.iframe_src || selectors.frameLocators) {
576
+ const findFrame = (frame, framescope) => {
577
+ for (let i = 0; i < frame.selectors.length; i++) {
578
+ let frameLocator = frame.selectors[i];
579
+ if (frameLocator.css) {
580
+ framescope = framescope.frameLocator(frameLocator.css);
581
+ if (frameLocator.index) {
582
+ framescope = framescope.nth(frameLocator.index);
583
+ }
584
+ break;
585
+ }
586
+ }
587
+ if (frame.children) {
588
+ return findFrame(frame.children, framescope);
589
+ }
590
+ return framescope;
591
+ };
481
592
  info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
482
593
  while (true) {
483
594
  let frameFound = false;
595
+ if (selectors.nestFrmLoc) {
596
+ scope = findFrame(selectors.nestFrmLoc, scope);
597
+ frameFound = true;
598
+ break;
599
+ }
484
600
  if (selectors.frameLocators) {
485
601
  for (let i = 0; i < selectors.frameLocators.length; i++) {
486
602
  let frameLocator = selectors.frameLocators[i];
@@ -644,6 +760,9 @@ class StableBrowser {
644
760
  async click(selectors, _params, options = {}, world = null) {
645
761
  this._validateSelectors(selectors);
646
762
  const startTime = Date.now();
763
+ if (options && options.context) {
764
+ selectors.locators[0].text = options.context;
765
+ }
647
766
  const info = {};
648
767
  info.log = "***** click on " + selectors.element_name + " *****\n";
649
768
  info.operation = "click";
@@ -657,14 +776,14 @@ class StableBrowser {
657
776
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
658
777
  try {
659
778
  await this._highlightElements(element);
660
- await element.click({ timeout: 5000 });
779
+ await element.click();
661
780
  await new Promise((resolve) => setTimeout(resolve, 1000));
662
781
  }
663
782
  catch (e) {
664
783
  // await this.closeUnexpectedPopups();
665
784
  info.log += "click failed, will try again" + "\n";
666
785
  element = await this._locate(selectors, info, _params);
667
- await element.click({ timeout: 10000, force: true });
786
+ await element.dispatchEvent("click");
668
787
  await new Promise((resolve) => setTimeout(resolve, 1000));
669
788
  }
670
789
  await this.waitForPageLoad();
@@ -717,7 +836,7 @@ class StableBrowser {
717
836
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
718
837
  try {
719
838
  await this._highlightElements(element);
720
- await element.setChecked(checked, { timeout: 5000 });
839
+ await element.setChecked(checked);
721
840
  await new Promise((resolve) => setTimeout(resolve, 1000));
722
841
  }
723
842
  catch (e) {
@@ -781,7 +900,7 @@ class StableBrowser {
781
900
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
782
901
  try {
783
902
  await this._highlightElements(element);
784
- await element.hover({ timeout: 10000 });
903
+ await element.hover();
785
904
  await new Promise((resolve) => setTimeout(resolve, 1000));
786
905
  }
787
906
  catch (e) {
@@ -843,7 +962,7 @@ class StableBrowser {
843
962
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
844
963
  try {
845
964
  await this._highlightElements(element);
846
- await element.selectOption(values, { timeout: 5000 });
965
+ await element.selectOption(values);
847
966
  }
848
967
  catch (e) {
849
968
  //await this.closeUnexpectedPopups();
@@ -1059,7 +1178,8 @@ class StableBrowser {
1059
1178
  catch (err) {
1060
1179
  //await this.closeUnexpectedPopups();
1061
1180
  this.logger.error("setting date time input failed " + JSON.stringify(info));
1062
- this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
1181
+ this.logger.info("Trying again");
1182
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1063
1183
  info.screenshotPath = screenshotPath;
1064
1184
  Object.assign(err, { info: info });
1065
1185
  await element.click();
@@ -1245,7 +1365,7 @@ class StableBrowser {
1245
1365
  let element = await this._locate(selectors, info, _params);
1246
1366
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1247
1367
  await this._highlightElements(element);
1248
- await element.fill(value, { timeout: 10000 });
1368
+ await element.fill(value);
1249
1369
  await element.dispatchEvent("change");
1250
1370
  if (enter) {
1251
1371
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -1464,7 +1584,7 @@ class StableBrowser {
1464
1584
  return info;
1465
1585
  }
1466
1586
  catch (e) {
1467
- //await this.closeUnexpectedPopups();
1587
+ await this.closeUnexpectedPopups();
1468
1588
  this.logger.error("verify element contains text failed " + info.log);
1469
1589
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1470
1590
  info.screenshotPath = screenshotPath;
@@ -1512,6 +1632,29 @@ class StableBrowser {
1512
1632
  }
1513
1633
  return dataFile;
1514
1634
  }
1635
+ async waitForUserInput(message, world = null) {
1636
+ if (!message) {
1637
+ message = "# Wait for user input. Press any key to continue";
1638
+ }
1639
+ else {
1640
+ message = "# Wait for user input. " + message;
1641
+ }
1642
+ message += "\n";
1643
+ const value = await new Promise((resolve) => {
1644
+ const rl = readline.createInterface({
1645
+ input: process.stdin,
1646
+ output: process.stdout,
1647
+ });
1648
+ rl.question(message, (answer) => {
1649
+ rl.close();
1650
+ resolve(answer);
1651
+ });
1652
+ });
1653
+ if (value) {
1654
+ this.logger.info(`{{userInput}} was set to: ${value}`);
1655
+ }
1656
+ this.setTestData({ userInput: value }, world);
1657
+ }
1515
1658
  setTestData(testData, world = null) {
1516
1659
  if (!testData) {
1517
1660
  return;
@@ -1719,30 +1862,29 @@ class StableBrowser {
1719
1862
  ])));
1720
1863
  const { data } = await client.send("Page.captureScreenshot", {
1721
1864
  format: "png",
1722
- clip: {
1723
- x: 0,
1724
- y: 0,
1725
- width: viewportWidth,
1726
- height: viewportHeight,
1727
- scale: 1,
1728
- },
1865
+ // clip: {
1866
+ // x: 0,
1867
+ // y: 0,
1868
+ // width: viewportWidth,
1869
+ // height: viewportHeight,
1870
+ // scale: 1,
1871
+ // },
1729
1872
  });
1730
1873
  if (!screenshotPath) {
1731
1874
  return data;
1732
1875
  }
1733
1876
  let screenshotBuffer = Buffer.from(data, "base64");
1734
- const sharpBuffer = sharp(screenshotBuffer);
1735
- const metadata = await sharpBuffer.metadata();
1736
- //check if you are on retina display and reduce the quality of the image
1737
- if (metadata.width > viewportWidth || metadata.height > viewportHeight) {
1738
- screenshotBuffer = await sharpBuffer
1739
- .resize(viewportWidth, viewportHeight, {
1740
- fit: sharp.fit.inside,
1741
- withoutEnlargement: true,
1742
- })
1743
- .toBuffer();
1744
- }
1745
- fs.writeFileSync(screenshotPath, screenshotBuffer);
1877
+ let image = await Jimp.read(screenshotBuffer);
1878
+ // Get the image dimensions
1879
+ const { width, height } = image.bitmap;
1880
+ // Resize the image to fit within the viewport dimensions without enlarging
1881
+ if (width > viewportWidth || height > viewportHeight) {
1882
+ image = image.resize({ w: viewportWidth, h: viewportHeight }); // Resize the image while maintaining aspect ratio
1883
+ await image.write(screenshotPath);
1884
+ }
1885
+ else {
1886
+ fs.writeFileSync(screenshotPath, screenshotBuffer);
1887
+ }
1746
1888
  await client.detach();
1747
1889
  }
1748
1890
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
@@ -2551,13 +2693,13 @@ class StableBrowser {
2551
2693
  }
2552
2694
  catch (e) {
2553
2695
  if (e.label === "networkidle") {
2554
- console.log("waitted for the network to be idle timeout");
2696
+ console.log("waited for the network to be idle timeout");
2555
2697
  }
2556
2698
  else if (e.label === "load") {
2557
- console.log("waitted for the load timeout");
2699
+ console.log("waited for the load timeout");
2558
2700
  }
2559
2701
  else if (e.label === "domcontentloaded") {
2560
- console.log("waitted for the domcontent loaded timeout");
2702
+ console.log("waited for the domcontent loaded timeout");
2561
2703
  }
2562
2704
  console.log(".");
2563
2705
  }
@@ -2700,33 +2842,18 @@ class StableBrowser {
2700
2842
  }
2701
2843
  async scrollIfNeeded(element, info) {
2702
2844
  try {
2703
- let didScroll = await element.evaluate((node) => {
2704
- const rect = node.getBoundingClientRect();
2705
- if (rect &&
2706
- rect.top >= 0 &&
2707
- rect.left >= 0 &&
2708
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
2709
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
2710
- return false;
2711
- }
2712
- else {
2713
- node.scrollIntoView({
2714
- behavior: "smooth",
2715
- block: "center",
2716
- inline: "center",
2717
- });
2718
- return true;
2719
- }
2845
+ await element.scrollIntoViewIfNeeded({
2846
+ timeout: 2000,
2720
2847
  });
2721
- if (didScroll) {
2722
- await new Promise((resolve) => setTimeout(resolve, 500));
2723
- if (info) {
2724
- info.box = await element.boundingBox();
2725
- }
2848
+ await new Promise((resolve) => setTimeout(resolve, 500));
2849
+ if (info) {
2850
+ info.box = await element.boundingBox({
2851
+ timeout: 1000,
2852
+ });
2726
2853
  }
2727
2854
  }
2728
2855
  catch (e) {
2729
- console.log("scroll failed");
2856
+ console.log("#-#");
2730
2857
  }
2731
2858
  }
2732
2859
  _reportToWorld(world, properties) {