@lambdatest/smartui-cli 4.0.8 → 4.0.9

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.
Files changed (3) hide show
  1. package/README.md +68 -0
  2. package/dist/index.cjs +121 -18
  3. package/package.json +3 -2
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # SmartUI-CLI
2
+
3
+ <img height="400" src="https://user-images.githubusercontent.com/126776938/232535511-8d51cf1b-1a33-48fc-825c-b13e7a9ec388.png">
4
+
5
+ <p align="center">
6
+ <a href="https://www.lambdatest.com/blog/?utm_source=github&utm_medium=repo&utm_campaign=playwright-sample" target="_bank">Blog</a>
7
+ &nbsp; &#8901; &nbsp;
8
+ <a href="https://www.lambdatest.com/support/docs/?utm_source=github&utm_medium=repo&utm_campaign=playwright-sample" target="_bank">Docs</a>
9
+ &nbsp; &#8901; &nbsp;
10
+ <a href="https://www.lambdatest.com/learning-hub/?utm_source=github&utm_medium=repo&utm_campaign=playwright-sample" target="_bank">Learning Hub</a>
11
+ &nbsp; &#8901; &nbsp;
12
+ <a href="https://www.lambdatest.com/newsletter/?utm_source=github&utm_medium=repo&utm_campaign=playwright-sample" target="_bank">Newsletter</a>
13
+ &nbsp; &#8901; &nbsp;
14
+ <a href="https://www.lambdatest.com/certifications/?utm_source=github&utm_medium=repo&utm_campaign=playwright-sample" target="_bank">Certifications</a>
15
+ &nbsp; &#8901; &nbsp;
16
+ <a href="https://www.youtube.com/c/LambdaTest" target="_bank">YouTube</a>
17
+ </p>
18
+ &emsp;
19
+ &emsp;
20
+ &emsp;
21
+
22
+
23
+
24
+ [<img height="58" width="200" src="https://user-images.githubusercontent.com/70570645/171866795-52c11b49-0728-4229-b073-4b704209ddde.png">](https://accounts.lambdatest.com/register?utm_source=github&utm_medium=repo&utm_campaign=playwright-sample)
25
+
26
+
27
+ The **SmartUI-CLI** allows you to capture visual snapshots of your web applications, upload images, and run visual regression tests using [LambdaTest's SmartUI](https://www.lambdatest.com/visual-regression-testing) platform directly from the command line.
28
+
29
+ - [Installation](#installation)
30
+ - [Commands](#commands)
31
+ - [Documentation](#documentation)
32
+ - [Issues](#issues)
33
+
34
+ ## Installation
35
+
36
+ ```sh-session
37
+ $ npm install smartui-cli
38
+ ```
39
+
40
+ **Note:**
41
+ If you face any problems executing tests with SmartUI-CLI `versions >= v4.x.x`, upgrade your Node.js version to `v20.3` or above.
42
+
43
+ ## Commands
44
+ - `npx smartui exec` - Capture DOM assets for visual testing across multiple browsers and resolutions.
45
+ - `npx smartui capture` - Bulk capture static URLs for visual testing.
46
+ - `npx smartui upload` - Upload custom images or screenshots for visual comparison.
47
+ - `npx smartui upload-figma` - Upload Figma design images for visual comparison.
48
+ - `npx smartui config` - Creates configuration file according to the usecase.
49
+
50
+ ### Documentation
51
+
52
+ In addition to its core functionalities, the SmartUI CLI leverages LambdaTest's cloud infrastructure for robust, scalable visual regression testing across various browsers and devices.
53
+
54
+ - [SmartUI Selenium SDK](https://www.lambdatest.com/support/docs/smartui-selenium-java-sdk) - A complete SDK to capture DOM assets for visual tests.
55
+ - [LambdaTest Documentation](https://www.lambdatest.com/support/docs/) - Official LambdaTest documentation for SmartUI and other integrations.
56
+ - [Bulk capturing static URLs with SmartUI](https://www.lambdatest.com/support/docs/smartui-cli/) - Documentation for capturing satatic urls in bulk with SmartUI
57
+ - [Bring your own screenshots](https://www.lambdatest.com/support/docs/smartui-cli-upload/) - Documentation for capturing satatic urls in bulk
58
+ - [Figma CLI](https://www.lambdatest.com/support/docs/smartui-cli-figma/) - Documentation for uploading figma components to SmartUI
59
+
60
+ ### Issues
61
+
62
+ If you encounter problems with SmartUI-CLI, [add an issue on GitHub](https://github.com/LambdaTest/smartui-cli/issues/new).
63
+
64
+ For other support issues, reach out via [LambdaTest Support](https://www.lambdatest.com/support).
65
+
66
+ ------
67
+
68
+ [Know more](https://www.lambdatest.com/visual-regression-testing) about SmartUI and it's AI enabled comparison engines.
package/dist/index.cjs CHANGED
@@ -199,6 +199,8 @@ var constants_default = {
199
199
  FILE_EXTENSION_GIFS: "gif",
200
200
  // Default scrollTime
201
201
  DEFAULT_SCROLL_TIME: 8,
202
+ // Default page load time
203
+ DEFAULT_PAGE_LOAD_TIMEOUT: 18e4,
202
204
  // Magic Numbers
203
205
  MAGIC_NUMBERS: [
204
206
  { ext: "jpg", magic: Buffer.from([255, 216, 255]) },
@@ -949,7 +951,7 @@ var auth_default = (ctx) => {
949
951
  };
950
952
 
951
953
  // package.json
952
- var version = "4.0.8";
954
+ var version = "4.0.9";
953
955
  var package_default = {
954
956
  name: "@lambdatest/smartui-cli",
955
957
  version,
@@ -959,7 +961,8 @@ var package_default = {
959
961
  ],
960
962
  scripts: {
961
963
  build: "tsup",
962
- release: "pnpm run build && pnpm publish --access public --no-git-checks"
964
+ release: "pnpm run build && pnpm publish --access public --no-git-checks",
965
+ "local-build": "pnpm run build && pnpm pack"
963
966
  },
964
967
  bin: {
965
968
  smartui: "./dist/index.cjs"
@@ -1100,13 +1103,13 @@ var httpClient = class {
1100
1103
  }).then(() => {
1101
1104
  log2.debug(`${ssName} for ${browserName} ${viewport} uploaded successfully`);
1102
1105
  }).catch((error) => {
1103
- if (error.response) {
1106
+ log2.error(`Unable to upload screenshot ${JSON.stringify(error)}`);
1107
+ if (error && error.response && error.response.data && error.response.data.error) {
1104
1108
  throw new Error(error.response.data.error.message);
1105
1109
  }
1106
- if (error.request) {
1107
- throw new Error(error.toJSON().message);
1110
+ if (error) {
1111
+ throw new Error(JSON.stringify(error));
1108
1112
  }
1109
- throw new Error(error.message);
1110
1113
  });
1111
1114
  }
1112
1115
  checkUpdate(log2) {
@@ -1179,6 +1182,7 @@ var ctx_default = (options) => {
1179
1182
  let extensionFiles;
1180
1183
  let ignoreStripExtension;
1181
1184
  let ignoreFilePattern;
1185
+ let parallelObj;
1182
1186
  let fetchResultObj;
1183
1187
  let fetchResultsFileObj;
1184
1188
  try {
@@ -1200,6 +1204,7 @@ var ctx_default = (options) => {
1200
1204
  extensionFiles = options.files || ["png", "jpeg", "jpg"];
1201
1205
  ignoreStripExtension = options.removeExtensions || false;
1202
1206
  ignoreFilePattern = options.ignoreDir || [];
1207
+ parallelObj = options.parallel ? options.parallel === true ? 1 : options.parallel : 1;
1203
1208
  if (options.fetchResults) {
1204
1209
  if (options.fetchResults !== true && !options.fetchResults.endsWith(".json")) {
1205
1210
  console.error("Error: The file extension for --fetch-results must be .json");
@@ -1264,7 +1269,8 @@ var ctx_default = (options) => {
1264
1269
  },
1265
1270
  args: {},
1266
1271
  options: {
1267
- parallel: options.parallel ? true : false,
1272
+ parallel: parallelObj,
1273
+ force: options.force ? true : false,
1268
1274
  markBaseline: options.markBaseline ? true : false,
1269
1275
  buildName: options.buildName || "",
1270
1276
  port,
@@ -1409,8 +1415,10 @@ function scrollToBottomAndBackToTop({
1409
1415
  }
1410
1416
  function launchBrowsers(ctx) {
1411
1417
  return __async(this, null, function* () {
1418
+ var _a;
1412
1419
  let browsers = {};
1413
- let launchOptions = { headless: true };
1420
+ const isHeadless = ((_a = process.env.HEADLESS) == null ? void 0 : _a.toLowerCase()) === "false" ? false : true;
1421
+ let launchOptions = { headless: isHeadless };
1414
1422
  if (ctx.config.web) {
1415
1423
  for (const browser of ctx.config.web.browsers) {
1416
1424
  switch (browser) {
@@ -1748,18 +1756,19 @@ var REQUEST_TIMEOUT = 1e4;
1748
1756
  var MIN_VIEWPORT_HEIGHT = 1080;
1749
1757
  function processSnapshot(snapshot, ctx) {
1750
1758
  return __async(this, null, function* () {
1751
- var _a;
1759
+ var _a, _b;
1752
1760
  updateLogContext({ task: "discovery" });
1753
1761
  ctx.log.debug(`Processing snapshot ${snapshot.name} ${snapshot.url}`);
1762
+ const isHeadless = ((_a = process.env.HEADLESS) == null ? void 0 : _a.toLowerCase()) === "false" ? false : true;
1754
1763
  let launchOptions = {
1755
- headless: true,
1764
+ headless: isHeadless,
1756
1765
  args: constants_default.LAUNCH_ARGS
1757
1766
  };
1758
1767
  let contextOptions = {
1759
1768
  javaScriptEnabled: ctx.config.cliEnableJavaScript,
1760
1769
  userAgent: constants_default.CHROME_USER_AGENT
1761
1770
  };
1762
- if (!((_a = ctx.browser) == null ? void 0 : _a.isConnected())) {
1771
+ if (!((_b = ctx.browser) == null ? void 0 : _b.isConnected())) {
1763
1772
  if (ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY)
1764
1773
  launchOptions.proxy = { server: ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY };
1765
1774
  ctx.browser = yield test.chromium.launch(launchOptions);
@@ -2426,7 +2435,7 @@ configFigma.name("config:create-figma").description("Create figma designs config
2426
2435
  });
2427
2436
  function captureScreenshotsForConfig(_0, _1, _2, _3, _4) {
2428
2437
  return __async(this, arguments, function* (ctx, browsers, { name, url, waitForTimeout }, browserName, renderViewports) {
2429
- let pageOptions = { waitUntil: process.env.SMARTUI_PAGE_WAIT_UNTIL_EVENT || "load" };
2438
+ let pageOptions = { waitUntil: process.env.SMARTUI_PAGE_WAIT_UNTIL_EVENT || "load", timeout: ctx.config.waitForPageRender || constants_default.DEFAULT_PAGE_LOAD_TIMEOUT };
2430
2439
  let ssId = name.toLowerCase().replace(/\s/g, "_");
2431
2440
  let context;
2432
2441
  let contextOptions = {};
@@ -2639,6 +2648,88 @@ function uploadScreenshots(ctx) {
2639
2648
  }
2640
2649
  });
2641
2650
  }
2651
+ function captureScreenshotsConcurrent(ctx) {
2652
+ return __async(this, null, function* () {
2653
+ delDir("screenshots");
2654
+ let totalSnapshots = ctx.webStaticConfig && ctx.webStaticConfig.length;
2655
+ let browserInstances = ctx.options.parallel || 1;
2656
+ let optimizeBrowserInstances = 0;
2657
+ optimizeBrowserInstances = Math.floor(Math.log2(totalSnapshots));
2658
+ if (optimizeBrowserInstances < 1) {
2659
+ optimizeBrowserInstances = 1;
2660
+ }
2661
+ if (optimizeBrowserInstances > browserInstances) {
2662
+ optimizeBrowserInstances = browserInstances;
2663
+ }
2664
+ if (ctx.options.force && browserInstances > 1) {
2665
+ optimizeBrowserInstances = browserInstances;
2666
+ }
2667
+ let urlsPerInstance = 0;
2668
+ if (optimizeBrowserInstances == 1) {
2669
+ urlsPerInstance = totalSnapshots;
2670
+ } else {
2671
+ urlsPerInstance = Math.ceil(totalSnapshots / optimizeBrowserInstances);
2672
+ }
2673
+ ctx.log.debug(`*** browserInstances requested ${ctx.options.parallel} `);
2674
+ ctx.log.debug(`*** optimizeBrowserInstances ${optimizeBrowserInstances} `);
2675
+ ctx.log.debug(`*** urlsPerInstance ${urlsPerInstance}`);
2676
+ ctx.task.output = `URLs : ${totalSnapshots} || Parallel Browser Instances: ${optimizeBrowserInstances}
2677
+ `;
2678
+ let staticURLChunks = splitURLs(ctx.webStaticConfig, urlsPerInstance);
2679
+ let totalCapturedScreenshots = 0;
2680
+ let output = "";
2681
+ const responses = yield Promise.all(staticURLChunks.map((urlConfig) => __async(this, null, function* () {
2682
+ let { capturedScreenshots, finalOutput } = yield processChunk(ctx, urlConfig);
2683
+ return { capturedScreenshots, finalOutput };
2684
+ })));
2685
+ responses.forEach((response) => {
2686
+ totalCapturedScreenshots += response.capturedScreenshots;
2687
+ output += response.finalOutput;
2688
+ });
2689
+ delDir("screenshots");
2690
+ return { totalCapturedScreenshots, output };
2691
+ });
2692
+ }
2693
+ function splitURLs(arr, chunkSize) {
2694
+ const result = [];
2695
+ for (let i = 0; i < arr.length; i += chunkSize) {
2696
+ result.push(arr.slice(i, i + chunkSize));
2697
+ }
2698
+ return result;
2699
+ }
2700
+ function processChunk(ctx, urlConfig) {
2701
+ return __async(this, null, function* () {
2702
+ let browsers = {};
2703
+ let capturedScreenshots = 0;
2704
+ let finalOutput = "";
2705
+ try {
2706
+ browsers = yield launchBrowsers(ctx);
2707
+ } catch (error) {
2708
+ yield closeBrowsers(browsers);
2709
+ ctx.log.debug(error);
2710
+ throw new Error(`Failed launching browsers ${error}`);
2711
+ }
2712
+ for (let staticConfig of urlConfig) {
2713
+ try {
2714
+ yield captureScreenshotsAsync(ctx, staticConfig, browsers);
2715
+ delDir(`screenshots/${staticConfig.name.toLowerCase().replace(/\s/g, "_")}`);
2716
+ let output = `${chalk6__default.default.gray(staticConfig.name)} ${chalk6__default.default.green("\u2713")}
2717
+ `;
2718
+ ctx.task.output = ctx.task.output ? ctx.task.output + output : output;
2719
+ finalOutput += output;
2720
+ capturedScreenshots++;
2721
+ } catch (error) {
2722
+ ctx.log.debug(`screenshot capture failed for ${JSON.stringify(staticConfig)}; error: ${error}`);
2723
+ let output = `${chalk6__default.default.gray(staticConfig.name)} ${chalk6__default.default.red("\u2717")}
2724
+ `;
2725
+ ctx.task.output += output;
2726
+ finalOutput += output;
2727
+ }
2728
+ }
2729
+ yield closeBrowsers(browsers);
2730
+ return { capturedScreenshots, finalOutput };
2731
+ });
2732
+ }
2642
2733
  var captureScreenshots_default = (ctx) => {
2643
2734
  return {
2644
2735
  title: "Capturing screenshots",
@@ -2649,9 +2740,16 @@ var captureScreenshots_default = (ctx) => {
2649
2740
  startPolling(ctx2, task);
2650
2741
  }
2651
2742
  updateLogContext({ task: "capture" });
2652
- let { capturedScreenshots, output } = yield captureScreenshots(ctx2);
2653
- if (capturedScreenshots != ctx2.webStaticConfig.length) {
2654
- throw new Error(output);
2743
+ if (ctx2.options.parallel) {
2744
+ let { totalCapturedScreenshots, output } = yield captureScreenshotsConcurrent(ctx2);
2745
+ if (totalCapturedScreenshots != ctx2.webStaticConfig.length) {
2746
+ throw new Error(output);
2747
+ }
2748
+ } else {
2749
+ let { capturedScreenshots, output } = yield captureScreenshots(ctx2);
2750
+ if (capturedScreenshots != ctx2.webStaticConfig.length) {
2751
+ throw new Error(output);
2752
+ }
2655
2753
  }
2656
2754
  task.title = "Screenshots captured successfully";
2657
2755
  } catch (error) {
@@ -2667,21 +2765,26 @@ var captureScreenshots_default = (ctx) => {
2667
2765
 
2668
2766
  // src/commander/capture.ts
2669
2767
  var command2 = new commander.Command();
2670
- command2.name("capture").description("Capture screenshots of static sites").argument("<file>", "Web static config file").option("--parallel", "Capture parallely on all browsers").option("--fetch-results [filename]", "Fetch results and optionally specify an output file, e.g., <filename>.json").action(function(file, _, command5) {
2768
+ command2.name("capture").description("Capture screenshots of static sites").argument("<file>", "Web static config file").option("-C, --parallel [number]", "Specify the number of instances per browser", parseInt).option("-F, --force", "forcefully apply the specified parallel instances per browser").option("--fetch-results [filename]", "Fetch results and optionally specify an output file, e.g., <filename>.json").action(function(file, _, command5) {
2671
2769
  return __async(this, null, function* () {
2672
2770
  let ctx = ctx_default(command5.optsWithGlobals());
2673
2771
  if (!fs5__default.default.existsSync(file)) {
2674
- console.log(`Error: Web Static Config file ${file} not found.`);
2772
+ ctx.log.error(`Web Static Config file ${file} not found.`);
2675
2773
  return;
2676
2774
  }
2677
2775
  try {
2678
2776
  ctx.webStaticConfig = JSON.parse(fs5__default.default.readFileSync(file, "utf8"));
2679
2777
  if (!validateWebStaticConfig(ctx.webStaticConfig))
2680
2778
  throw new Error(validateWebStaticConfig.errors[0].message);
2779
+ if (ctx.webStaticConfig && ctx.webStaticConfig.length === 0) {
2780
+ ctx.log.error(`No URLs found in the specified config file -> ${file}`);
2781
+ return;
2782
+ }
2681
2783
  } catch (error) {
2682
- console.log(`[smartui] Error: Invalid Web Static Config; ${error.message}`);
2784
+ ctx.log.error(`Invalid Web Static Config; ${error.message}`);
2683
2785
  return;
2684
2786
  }
2787
+ ctx.log.debug(ctx.config);
2685
2788
  let tasks = new listr2.Listr(
2686
2789
  [
2687
2790
  auth_default(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdatest/smartui-cli",
3
- "version": "4.0.8",
3
+ "version": "4.0.9",
4
4
  "description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
5
5
  "files": [
6
6
  "dist/**/*"
@@ -43,6 +43,7 @@
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsup",
46
- "release": "pnpm run build && pnpm publish --access public --no-git-checks"
46
+ "release": "pnpm run build && pnpm publish --access public --no-git-checks",
47
+ "local-build": "pnpm run build && pnpm pack"
47
48
  }
48
49
  }