automation_model 1.0.409-dev → 1.0.409-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,10 +297,10 @@ 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
305
  // handle role/name locators
235
306
  // locator.selector will be something like: textbox[name="Username"i]
@@ -240,24 +311,33 @@ class StableBrowser {
240
311
  const role = match[1];
241
312
  const name = match[3];
242
313
  const flags = match[4];
243
- return scope.getByRole(role, { name }, { exact: flags === "i" });
314
+ locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
244
315
  }
245
316
  }
246
- // handle css locators
247
- if (locator.engine === "css") {
248
- return scope.locator(locator.selector);
249
- }
250
- if ((locator === null || locator === void 0 ? void 0 : locator.engine) && (locator === null || locator === void 0 ? void 0 : locator.score) <= 520) {
251
- let selector = locator.selector.replace(/"/g, '\\"');
252
- if (locator.engine === "internal:att") {
253
- selector = `[${selector}]`;
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}`);
254
329
  }
255
- const newLocator = scope.locator(`${locator.engine}="${selector}"`);
256
- return newLocator;
257
330
  }
258
- throw new Error("unknown locator type");
331
+ if (!locatorReturn) {
332
+ console.error(locator);
333
+ throw new Error("Locator undefined");
334
+ }
335
+ return locatorReturn;
259
336
  }
260
337
  async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
338
+ if (css && css.locator) {
339
+ css = css.locator;
340
+ }
261
341
  let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
262
342
  if (result.elementCount === 0) {
263
343
  return;
@@ -466,6 +546,8 @@ class StableBrowser {
466
546
  if (result.foundElements.length > 0) {
467
547
  let dialogCloseLocator = result.foundElements[0].locator;
468
548
  await dialogCloseLocator.click();
549
+ // wait for the dialog to close
550
+ await dialogCloseLocator.waitFor({ state: "hidden" });
469
551
  return { rerun: true };
470
552
  }
471
553
  }
@@ -474,7 +556,7 @@ class StableBrowser {
474
556
  }
475
557
  async _locate(selectors, info, _params, timeout = 30000) {
476
558
  for (let i = 0; i < 3; i++) {
477
- info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
559
+ info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
478
560
  for (let j = 0; j < selectors.locators.length; j++) {
479
561
  let selector = selectors.locators[j];
480
562
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
@@ -494,9 +576,30 @@ class StableBrowser {
494
576
  //let arrayMode = Array.isArray(selectors);
495
577
  let scope = this.page;
496
578
  if (selectors.iframe_src || selectors.frameLocators) {
579
+ const findFrame = (frame, framescope) => {
580
+ for (let i = 0; i < frame.selectors.length; i++) {
581
+ let frameLocator = frame.selectors[i];
582
+ if (frameLocator.css) {
583
+ framescope = framescope.frameLocator(frameLocator.css);
584
+ if (frameLocator.index) {
585
+ framescope = framescope.nth(frameLocator.index);
586
+ }
587
+ break;
588
+ }
589
+ }
590
+ if (frame.children) {
591
+ return findFrame(frame.children, framescope);
592
+ }
593
+ return framescope;
594
+ };
497
595
  info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
498
596
  while (true) {
499
597
  let frameFound = false;
598
+ if (selectors.nestFrmLoc) {
599
+ scope = findFrame(selectors.nestFrmLoc, scope);
600
+ frameFound = true;
601
+ break;
602
+ }
500
603
  if (selectors.frameLocators) {
501
604
  for (let i = 0; i < selectors.frameLocators.length; i++) {
502
605
  let frameLocator = selectors.frameLocators[i];
@@ -660,6 +763,9 @@ class StableBrowser {
660
763
  async click(selectors, _params, options = {}, world = null) {
661
764
  this._validateSelectors(selectors);
662
765
  const startTime = Date.now();
766
+ if (options && options.context) {
767
+ selectors.locators[0].text = options.context;
768
+ }
663
769
  const info = {};
664
770
  info.log = "***** click on " + selectors.element_name + " *****\n";
665
771
  info.operation = "click";
@@ -673,14 +779,14 @@ class StableBrowser {
673
779
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
674
780
  try {
675
781
  await this._highlightElements(element);
676
- await element.click({ timeout: 5000 });
782
+ await element.click();
677
783
  await new Promise((resolve) => setTimeout(resolve, 1000));
678
784
  }
679
785
  catch (e) {
680
786
  // await this.closeUnexpectedPopups();
681
787
  info.log += "click failed, will try again" + "\n";
682
788
  element = await this._locate(selectors, info, _params);
683
- await element.click({ timeout: 10000, force: true });
789
+ await element.dispatchEvent("click");
684
790
  await new Promise((resolve) => setTimeout(resolve, 1000));
685
791
  }
686
792
  await this.waitForPageLoad();
@@ -733,7 +839,7 @@ class StableBrowser {
733
839
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
734
840
  try {
735
841
  await this._highlightElements(element);
736
- await element.setChecked(checked, { timeout: 5000 });
842
+ await element.setChecked(checked);
737
843
  await new Promise((resolve) => setTimeout(resolve, 1000));
738
844
  }
739
845
  catch (e) {
@@ -797,7 +903,7 @@ class StableBrowser {
797
903
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
798
904
  try {
799
905
  await this._highlightElements(element);
800
- await element.hover({ timeout: 10000 });
906
+ await element.hover();
801
907
  await new Promise((resolve) => setTimeout(resolve, 1000));
802
908
  }
803
909
  catch (e) {
@@ -859,7 +965,7 @@ class StableBrowser {
859
965
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
860
966
  try {
861
967
  await this._highlightElements(element);
862
- await element.selectOption(values, { timeout: 5000 });
968
+ await element.selectOption(values);
863
969
  }
864
970
  catch (e) {
865
971
  //await this.closeUnexpectedPopups();
@@ -1262,7 +1368,7 @@ class StableBrowser {
1262
1368
  let element = await this._locate(selectors, info, _params);
1263
1369
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1264
1370
  await this._highlightElements(element);
1265
- await element.fill(value, { timeout: 10000 });
1371
+ await element.fill(value);
1266
1372
  await element.dispatchEvent("change");
1267
1373
  if (enter) {
1268
1374
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -1481,7 +1587,7 @@ class StableBrowser {
1481
1587
  return info;
1482
1588
  }
1483
1589
  catch (e) {
1484
- //await this.closeUnexpectedPopups();
1590
+ await this.closeUnexpectedPopups();
1485
1591
  this.logger.error("verify element contains text failed " + info.log);
1486
1592
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1487
1593
  info.screenshotPath = screenshotPath;
@@ -1529,6 +1635,29 @@ class StableBrowser {
1529
1635
  }
1530
1636
  return dataFile;
1531
1637
  }
1638
+ async waitForUserInput(message, world = null) {
1639
+ if (!message) {
1640
+ message = "# Wait for user input. Press any key to continue";
1641
+ }
1642
+ else {
1643
+ message = "# Wait for user input. " + message;
1644
+ }
1645
+ message += "\n";
1646
+ const value = await new Promise((resolve) => {
1647
+ const rl = readline.createInterface({
1648
+ input: process.stdin,
1649
+ output: process.stdout,
1650
+ });
1651
+ rl.question(message, (answer) => {
1652
+ rl.close();
1653
+ resolve(answer);
1654
+ });
1655
+ });
1656
+ if (value) {
1657
+ this.logger.info(`{{userInput}} was set to: ${value}`);
1658
+ }
1659
+ this.setTestData({ userInput: value }, world);
1660
+ }
1532
1661
  setTestData(testData, world = null) {
1533
1662
  if (!testData) {
1534
1663
  return;
@@ -1726,40 +1855,32 @@ class StableBrowser {
1726
1855
  document.body.clientWidth,
1727
1856
  document.documentElement.clientWidth,
1728
1857
  ])));
1729
- const viewportHeight = Math.max(...(await this.page.evaluate(() => [
1730
- document.body.scrollHeight,
1731
- document.documentElement.scrollHeight,
1732
- document.body.offsetHeight,
1733
- document.documentElement.offsetHeight,
1734
- document.body.clientHeight,
1735
- document.documentElement.clientHeight,
1736
- ])));
1737
1858
  const { data } = await client.send("Page.captureScreenshot", {
1738
1859
  format: "png",
1739
- clip: {
1740
- x: 0,
1741
- y: 0,
1742
- width: viewportWidth,
1743
- height: viewportHeight,
1744
- scale: 1,
1745
- },
1860
+ // clip: {
1861
+ // x: 0,
1862
+ // y: 0,
1863
+ // width: viewportWidth,
1864
+ // height: viewportHeight,
1865
+ // scale: 1,
1866
+ // },
1746
1867
  });
1747
1868
  if (!screenshotPath) {
1748
1869
  return data;
1749
1870
  }
1750
1871
  let screenshotBuffer = Buffer.from(data, "base64");
1751
- const sharpBuffer = sharp(screenshotBuffer);
1752
- const metadata = await sharpBuffer.metadata();
1753
- //check if you are on retina display and reduce the quality of the image
1754
- if (metadata.width > viewportWidth || metadata.height > viewportHeight) {
1755
- screenshotBuffer = await sharpBuffer
1756
- .resize(viewportWidth, viewportHeight, {
1757
- fit: sharp.fit.inside,
1758
- withoutEnlargement: true,
1759
- })
1760
- .toBuffer();
1761
- }
1762
- fs.writeFileSync(screenshotPath, screenshotBuffer);
1872
+ let image = await Jimp.read(screenshotBuffer);
1873
+ // Get the image dimensions
1874
+ const { width, height } = image.bitmap;
1875
+ const resizeRatio = viewportWidth / width;
1876
+ // Resize the image to fit within the viewport dimensions without enlarging
1877
+ if (width > viewportWidth) {
1878
+ image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
1879
+ await image.write(screenshotPath);
1880
+ }
1881
+ else {
1882
+ fs.writeFileSync(screenshotPath, screenshotBuffer);
1883
+ }
1763
1884
  await client.detach();
1764
1885
  }
1765
1886
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
@@ -2568,13 +2689,13 @@ class StableBrowser {
2568
2689
  }
2569
2690
  catch (e) {
2570
2691
  if (e.label === "networkidle") {
2571
- console.log("waitted for the network to be idle timeout");
2692
+ console.log("waited for the network to be idle timeout");
2572
2693
  }
2573
2694
  else if (e.label === "load") {
2574
- console.log("waitted for the load timeout");
2695
+ console.log("waited for the load timeout");
2575
2696
  }
2576
2697
  else if (e.label === "domcontentloaded") {
2577
- console.log("waitted for the domcontent loaded timeout");
2698
+ console.log("waited for the domcontent loaded timeout");
2578
2699
  }
2579
2700
  console.log(".");
2580
2701
  }
@@ -2717,33 +2838,18 @@ class StableBrowser {
2717
2838
  }
2718
2839
  async scrollIfNeeded(element, info) {
2719
2840
  try {
2720
- let didScroll = await element.evaluate((node) => {
2721
- const rect = node.getBoundingClientRect();
2722
- if (rect &&
2723
- rect.top >= 0 &&
2724
- rect.left >= 0 &&
2725
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
2726
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
2727
- return false;
2728
- }
2729
- else {
2730
- node.scrollIntoView({
2731
- behavior: "smooth",
2732
- block: "center",
2733
- inline: "center",
2734
- });
2735
- return true;
2736
- }
2841
+ await element.scrollIntoViewIfNeeded({
2842
+ timeout: 2000,
2737
2843
  });
2738
- if (didScroll) {
2739
- await new Promise((resolve) => setTimeout(resolve, 500));
2740
- if (info) {
2741
- info.box = await element.boundingBox();
2742
- }
2844
+ await new Promise((resolve) => setTimeout(resolve, 500));
2845
+ if (info) {
2846
+ info.box = await element.boundingBox({
2847
+ timeout: 1000,
2848
+ });
2743
2849
  }
2744
2850
  }
2745
2851
  catch (e) {
2746
- console.log("scroll failed");
2852
+ console.log("#-#");
2747
2853
  }
2748
2854
  }
2749
2855
  _reportToWorld(world, properties) {