@lambdatest/smartui-cli 4.0.8 → 4.0.10

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 +127 -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]) },
@@ -851,6 +853,8 @@ var env_default = () => {
851
853
  SMARTUI_DO_NOT_USE_CAPTURED_COOKIES,
852
854
  HTTP_PROXY,
853
855
  HTTPS_PROXY,
856
+ SMARTUI_HTTP_PROXY,
857
+ SMARTUI_HTTPS_PROXY,
854
858
  GITHUB_ACTIONS,
855
859
  FIGMA_TOKEN,
856
860
  LT_USERNAME,
@@ -865,6 +869,8 @@ var env_default = () => {
865
869
  SMARTUI_GIT_INFO_FILEPATH,
866
870
  HTTP_PROXY,
867
871
  HTTPS_PROXY,
872
+ SMARTUI_HTTP_PROXY,
873
+ SMARTUI_HTTPS_PROXY,
868
874
  GITHUB_ACTIONS,
869
875
  FIGMA_TOKEN,
870
876
  LT_USERNAME,
@@ -949,7 +955,7 @@ var auth_default = (ctx) => {
949
955
  };
950
956
 
951
957
  // package.json
952
- var version = "4.0.8";
958
+ var version = "4.0.10";
953
959
  var package_default = {
954
960
  name: "@lambdatest/smartui-cli",
955
961
  version,
@@ -959,7 +965,8 @@ var package_default = {
959
965
  ],
960
966
  scripts: {
961
967
  build: "tsup",
962
- release: "pnpm run build && pnpm publish --access public --no-git-checks"
968
+ release: "pnpm run build && pnpm publish --access public --no-git-checks",
969
+ "local-build": "pnpm run build && pnpm pack"
963
970
  },
964
971
  bin: {
965
972
  smartui: "./dist/index.cjs"
@@ -1100,13 +1107,13 @@ var httpClient = class {
1100
1107
  }).then(() => {
1101
1108
  log2.debug(`${ssName} for ${browserName} ${viewport} uploaded successfully`);
1102
1109
  }).catch((error) => {
1103
- if (error.response) {
1110
+ log2.error(`Unable to upload screenshot ${JSON.stringify(error)}`);
1111
+ if (error && error.response && error.response.data && error.response.data.error) {
1104
1112
  throw new Error(error.response.data.error.message);
1105
1113
  }
1106
- if (error.request) {
1107
- throw new Error(error.toJSON().message);
1114
+ if (error) {
1115
+ throw new Error(JSON.stringify(error));
1108
1116
  }
1109
- throw new Error(error.message);
1110
1117
  });
1111
1118
  }
1112
1119
  checkUpdate(log2) {
@@ -1179,6 +1186,7 @@ var ctx_default = (options) => {
1179
1186
  let extensionFiles;
1180
1187
  let ignoreStripExtension;
1181
1188
  let ignoreFilePattern;
1189
+ let parallelObj;
1182
1190
  let fetchResultObj;
1183
1191
  let fetchResultsFileObj;
1184
1192
  try {
@@ -1200,6 +1208,7 @@ var ctx_default = (options) => {
1200
1208
  extensionFiles = options.files || ["png", "jpeg", "jpg"];
1201
1209
  ignoreStripExtension = options.removeExtensions || false;
1202
1210
  ignoreFilePattern = options.ignoreDir || [];
1211
+ parallelObj = options.parallel ? options.parallel === true ? 1 : options.parallel : 1;
1203
1212
  if (options.fetchResults) {
1204
1213
  if (options.fetchResults !== true && !options.fetchResults.endsWith(".json")) {
1205
1214
  console.error("Error: The file extension for --fetch-results must be .json");
@@ -1264,7 +1273,8 @@ var ctx_default = (options) => {
1264
1273
  },
1265
1274
  args: {},
1266
1275
  options: {
1267
- parallel: options.parallel ? true : false,
1276
+ parallel: parallelObj,
1277
+ force: options.force ? true : false,
1268
1278
  markBaseline: options.markBaseline ? true : false,
1269
1279
  buildName: options.buildName || "",
1270
1280
  port,
@@ -1409,8 +1419,10 @@ function scrollToBottomAndBackToTop({
1409
1419
  }
1410
1420
  function launchBrowsers(ctx) {
1411
1421
  return __async(this, null, function* () {
1422
+ var _a;
1412
1423
  let browsers = {};
1413
- let launchOptions = { headless: true };
1424
+ const isHeadless = ((_a = process.env.HEADLESS) == null ? void 0 : _a.toLowerCase()) === "false" ? false : true;
1425
+ let launchOptions = { headless: isHeadless };
1414
1426
  if (ctx.config.web) {
1415
1427
  for (const browser of ctx.config.web.browsers) {
1416
1428
  switch (browser) {
@@ -1748,20 +1760,23 @@ var REQUEST_TIMEOUT = 1e4;
1748
1760
  var MIN_VIEWPORT_HEIGHT = 1080;
1749
1761
  function processSnapshot(snapshot, ctx) {
1750
1762
  return __async(this, null, function* () {
1751
- var _a;
1763
+ var _a, _b;
1752
1764
  updateLogContext({ task: "discovery" });
1753
1765
  ctx.log.debug(`Processing snapshot ${snapshot.name} ${snapshot.url}`);
1766
+ const isHeadless = ((_a = process.env.HEADLESS) == null ? void 0 : _a.toLowerCase()) === "false" ? false : true;
1754
1767
  let launchOptions = {
1755
- headless: true,
1768
+ headless: isHeadless,
1756
1769
  args: constants_default.LAUNCH_ARGS
1757
1770
  };
1758
1771
  let contextOptions = {
1759
1772
  javaScriptEnabled: ctx.config.cliEnableJavaScript,
1760
1773
  userAgent: constants_default.CHROME_USER_AGENT
1761
1774
  };
1762
- if (!((_a = ctx.browser) == null ? void 0 : _a.isConnected())) {
1775
+ if (!((_b = ctx.browser) == null ? void 0 : _b.isConnected())) {
1763
1776
  if (ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY)
1764
1777
  launchOptions.proxy = { server: ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY };
1778
+ if (ctx.env.SMARTUI_HTTP_PROXY || ctx.env.SMARTUI_HTTPS_PROXY)
1779
+ launchOptions.proxy = { server: ctx.env.SMARTUI_HTTP_PROXY || ctx.env.SMARTUI_HTTPS_PROXY };
1765
1780
  ctx.browser = yield test.chromium.launch(launchOptions);
1766
1781
  ctx.log.debug(`Chromium launched with options ${JSON.stringify(launchOptions)}`);
1767
1782
  }
@@ -2426,7 +2441,7 @@ configFigma.name("config:create-figma").description("Create figma designs config
2426
2441
  });
2427
2442
  function captureScreenshotsForConfig(_0, _1, _2, _3, _4) {
2428
2443
  return __async(this, arguments, function* (ctx, browsers, { name, url, waitForTimeout }, browserName, renderViewports) {
2429
- let pageOptions = { waitUntil: process.env.SMARTUI_PAGE_WAIT_UNTIL_EVENT || "load" };
2444
+ let pageOptions = { waitUntil: process.env.SMARTUI_PAGE_WAIT_UNTIL_EVENT || "load", timeout: ctx.config.waitForPageRender || constants_default.DEFAULT_PAGE_LOAD_TIMEOUT };
2430
2445
  let ssId = name.toLowerCase().replace(/\s/g, "_");
2431
2446
  let context;
2432
2447
  let contextOptions = {};
@@ -2639,6 +2654,88 @@ function uploadScreenshots(ctx) {
2639
2654
  }
2640
2655
  });
2641
2656
  }
2657
+ function captureScreenshotsConcurrent(ctx) {
2658
+ return __async(this, null, function* () {
2659
+ delDir("screenshots");
2660
+ let totalSnapshots = ctx.webStaticConfig && ctx.webStaticConfig.length;
2661
+ let browserInstances = ctx.options.parallel || 1;
2662
+ let optimizeBrowserInstances = 0;
2663
+ optimizeBrowserInstances = Math.floor(Math.log2(totalSnapshots));
2664
+ if (optimizeBrowserInstances < 1) {
2665
+ optimizeBrowserInstances = 1;
2666
+ }
2667
+ if (optimizeBrowserInstances > browserInstances) {
2668
+ optimizeBrowserInstances = browserInstances;
2669
+ }
2670
+ if (ctx.options.force && browserInstances > 1) {
2671
+ optimizeBrowserInstances = browserInstances;
2672
+ }
2673
+ let urlsPerInstance = 0;
2674
+ if (optimizeBrowserInstances == 1) {
2675
+ urlsPerInstance = totalSnapshots;
2676
+ } else {
2677
+ urlsPerInstance = Math.ceil(totalSnapshots / optimizeBrowserInstances);
2678
+ }
2679
+ ctx.log.debug(`*** browserInstances requested ${ctx.options.parallel} `);
2680
+ ctx.log.debug(`*** optimizeBrowserInstances ${optimizeBrowserInstances} `);
2681
+ ctx.log.debug(`*** urlsPerInstance ${urlsPerInstance}`);
2682
+ ctx.task.output = `URLs : ${totalSnapshots} || Parallel Browser Instances: ${optimizeBrowserInstances}
2683
+ `;
2684
+ let staticURLChunks = splitURLs(ctx.webStaticConfig, urlsPerInstance);
2685
+ let totalCapturedScreenshots = 0;
2686
+ let output = "";
2687
+ const responses = yield Promise.all(staticURLChunks.map((urlConfig) => __async(this, null, function* () {
2688
+ let { capturedScreenshots, finalOutput } = yield processChunk(ctx, urlConfig);
2689
+ return { capturedScreenshots, finalOutput };
2690
+ })));
2691
+ responses.forEach((response) => {
2692
+ totalCapturedScreenshots += response.capturedScreenshots;
2693
+ output += response.finalOutput;
2694
+ });
2695
+ delDir("screenshots");
2696
+ return { totalCapturedScreenshots, output };
2697
+ });
2698
+ }
2699
+ function splitURLs(arr, chunkSize) {
2700
+ const result = [];
2701
+ for (let i = 0; i < arr.length; i += chunkSize) {
2702
+ result.push(arr.slice(i, i + chunkSize));
2703
+ }
2704
+ return result;
2705
+ }
2706
+ function processChunk(ctx, urlConfig) {
2707
+ return __async(this, null, function* () {
2708
+ let browsers = {};
2709
+ let capturedScreenshots = 0;
2710
+ let finalOutput = "";
2711
+ try {
2712
+ browsers = yield launchBrowsers(ctx);
2713
+ } catch (error) {
2714
+ yield closeBrowsers(browsers);
2715
+ ctx.log.debug(error);
2716
+ throw new Error(`Failed launching browsers ${error}`);
2717
+ }
2718
+ for (let staticConfig of urlConfig) {
2719
+ try {
2720
+ yield captureScreenshotsAsync(ctx, staticConfig, browsers);
2721
+ delDir(`screenshots/${staticConfig.name.toLowerCase().replace(/\s/g, "_")}`);
2722
+ let output = `${chalk6__default.default.gray(staticConfig.name)} ${chalk6__default.default.green("\u2713")}
2723
+ `;
2724
+ ctx.task.output = ctx.task.output ? ctx.task.output + output : output;
2725
+ finalOutput += output;
2726
+ capturedScreenshots++;
2727
+ } catch (error) {
2728
+ ctx.log.debug(`screenshot capture failed for ${JSON.stringify(staticConfig)}; error: ${error}`);
2729
+ let output = `${chalk6__default.default.gray(staticConfig.name)} ${chalk6__default.default.red("\u2717")}
2730
+ `;
2731
+ ctx.task.output += output;
2732
+ finalOutput += output;
2733
+ }
2734
+ }
2735
+ yield closeBrowsers(browsers);
2736
+ return { capturedScreenshots, finalOutput };
2737
+ });
2738
+ }
2642
2739
  var captureScreenshots_default = (ctx) => {
2643
2740
  return {
2644
2741
  title: "Capturing screenshots",
@@ -2649,9 +2746,16 @@ var captureScreenshots_default = (ctx) => {
2649
2746
  startPolling(ctx2, task);
2650
2747
  }
2651
2748
  updateLogContext({ task: "capture" });
2652
- let { capturedScreenshots, output } = yield captureScreenshots(ctx2);
2653
- if (capturedScreenshots != ctx2.webStaticConfig.length) {
2654
- throw new Error(output);
2749
+ if (ctx2.options.parallel) {
2750
+ let { totalCapturedScreenshots, output } = yield captureScreenshotsConcurrent(ctx2);
2751
+ if (totalCapturedScreenshots != ctx2.webStaticConfig.length) {
2752
+ throw new Error(output);
2753
+ }
2754
+ } else {
2755
+ let { capturedScreenshots, output } = yield captureScreenshots(ctx2);
2756
+ if (capturedScreenshots != ctx2.webStaticConfig.length) {
2757
+ throw new Error(output);
2758
+ }
2655
2759
  }
2656
2760
  task.title = "Screenshots captured successfully";
2657
2761
  } catch (error) {
@@ -2667,21 +2771,26 @@ var captureScreenshots_default = (ctx) => {
2667
2771
 
2668
2772
  // src/commander/capture.ts
2669
2773
  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) {
2774
+ 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
2775
  return __async(this, null, function* () {
2672
2776
  let ctx = ctx_default(command5.optsWithGlobals());
2673
2777
  if (!fs5__default.default.existsSync(file)) {
2674
- console.log(`Error: Web Static Config file ${file} not found.`);
2778
+ ctx.log.error(`Web Static Config file ${file} not found.`);
2675
2779
  return;
2676
2780
  }
2677
2781
  try {
2678
2782
  ctx.webStaticConfig = JSON.parse(fs5__default.default.readFileSync(file, "utf8"));
2679
2783
  if (!validateWebStaticConfig(ctx.webStaticConfig))
2680
2784
  throw new Error(validateWebStaticConfig.errors[0].message);
2785
+ if (ctx.webStaticConfig && ctx.webStaticConfig.length === 0) {
2786
+ ctx.log.error(`No URLs found in the specified config file -> ${file}`);
2787
+ return;
2788
+ }
2681
2789
  } catch (error) {
2682
- console.log(`[smartui] Error: Invalid Web Static Config; ${error.message}`);
2790
+ ctx.log.error(`Invalid Web Static Config; ${error.message}`);
2683
2791
  return;
2684
2792
  }
2793
+ ctx.log.debug(ctx.config);
2685
2794
  let tasks = new listr2.Listr(
2686
2795
  [
2687
2796
  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.10",
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
  }