@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.
- package/README.md +68 -0
- package/dist/index.cjs +121 -18
- 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
|
+
⋅
|
|
8
|
+
<a href="https://www.lambdatest.com/support/docs/?utm_source=github&utm_medium=repo&utm_campaign=playwright-sample" target="_bank">Docs</a>
|
|
9
|
+
⋅
|
|
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
|
+
⋅
|
|
12
|
+
<a href="https://www.lambdatest.com/newsletter/?utm_source=github&utm_medium=repo&utm_campaign=playwright-sample" target="_bank">Newsletter</a>
|
|
13
|
+
⋅
|
|
14
|
+
<a href="https://www.lambdatest.com/certifications/?utm_source=github&utm_medium=repo&utm_campaign=playwright-sample" target="_bank">Certifications</a>
|
|
15
|
+
⋅
|
|
16
|
+
<a href="https://www.youtube.com/c/LambdaTest" target="_bank">YouTube</a>
|
|
17
|
+
</p>
|
|
18
|
+
 
|
|
19
|
+
 
|
|
20
|
+
 
|
|
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.
|
|
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
|
-
|
|
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
|
|
1107
|
-
throw new Error(
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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 (!((
|
|
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
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
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", "
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
}
|