@lambdatest/smartui-cli 3.0.12 → 4.0.0
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/dist/index.cjs +305 -68
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -16,6 +16,7 @@ var axios = require('axios');
|
|
|
16
16
|
var child_process = require('child_process');
|
|
17
17
|
var spawn = require('cross-spawn');
|
|
18
18
|
var test = require('@playwright/test');
|
|
19
|
+
var sharp = require('sharp');
|
|
19
20
|
|
|
20
21
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
21
22
|
|
|
@@ -29,6 +30,7 @@ var addErrors__default = /*#__PURE__*/_interopDefault(addErrors);
|
|
|
29
30
|
var FormData__default = /*#__PURE__*/_interopDefault(FormData);
|
|
30
31
|
var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
31
32
|
var spawn__default = /*#__PURE__*/_interopDefault(spawn);
|
|
33
|
+
var sharp__default = /*#__PURE__*/_interopDefault(sharp);
|
|
32
34
|
|
|
33
35
|
var __defProp = Object.defineProperty;
|
|
34
36
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
@@ -128,6 +130,18 @@ var constants_default = {
|
|
|
128
130
|
MOBILE_ORIENTATION_LANDSCAPE: "landscape",
|
|
129
131
|
// CI
|
|
130
132
|
GITHUB_API_HOST: "https://api.github.com",
|
|
133
|
+
// log file path
|
|
134
|
+
LOG_FILE_PATH: ".smartui.log",
|
|
135
|
+
// Disallowed file extension
|
|
136
|
+
FILE_EXTENSION_ZIP: ".zip",
|
|
137
|
+
FILE_EXTENSION_GIFS: "gif",
|
|
138
|
+
// Magic Numbers
|
|
139
|
+
MAGIC_NUMBERS: [
|
|
140
|
+
{ ext: "jpg", magic: Buffer.from([255, 216, 255]) },
|
|
141
|
+
{ ext: "jpeg", magic: Buffer.from([255, 216, 255]) },
|
|
142
|
+
{ ext: "png", magic: Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]) },
|
|
143
|
+
{ ext: "gif", magic: Buffer.from([71, 73, 70, 56]) }
|
|
144
|
+
],
|
|
131
145
|
SUPPORTED_MOBILE_DEVICES: {
|
|
132
146
|
"Blackberry KEY2 LE": { os: "android", viewport: { width: 412, height: 618 } },
|
|
133
147
|
"Galaxy A12": { os: "android", viewport: { width: 360, height: 800 } },
|
|
@@ -632,7 +646,15 @@ var validateFigmaDesignConfig = ajv.compile(FigmaDesignConfigSchema);
|
|
|
632
646
|
|
|
633
647
|
// src/lib/server.ts
|
|
634
648
|
var server_default = (ctx) => __async(void 0, null, function* () {
|
|
635
|
-
const server = fastify__default.default({
|
|
649
|
+
const server = fastify__default.default({
|
|
650
|
+
logger: {
|
|
651
|
+
level: "debug",
|
|
652
|
+
stream: { write: (message) => {
|
|
653
|
+
ctx.log.debug(message);
|
|
654
|
+
} }
|
|
655
|
+
},
|
|
656
|
+
bodyLimit: 3e7
|
|
657
|
+
});
|
|
636
658
|
const opts = {};
|
|
637
659
|
const SMARTUI_DOM = fs5.readFileSync(path2__default.default.resolve(__dirname, "dom-serializer.js"), "utf-8");
|
|
638
660
|
server.get("/healthcheck", opts, (_, reply) => {
|
|
@@ -672,7 +694,6 @@ var env_default = () => {
|
|
|
672
694
|
const {
|
|
673
695
|
PROJECT_TOKEN = "",
|
|
674
696
|
SMARTUI_CLIENT_API_URL = "https://api.lambdatest.com/visualui/1.0",
|
|
675
|
-
LT_SDK_LOG_LEVEL,
|
|
676
697
|
LT_SDK_DEBUG,
|
|
677
698
|
SMARTUI_GIT_INFO_FILEPATH,
|
|
678
699
|
HTTP_PROXY,
|
|
@@ -687,7 +708,6 @@ var env_default = () => {
|
|
|
687
708
|
return {
|
|
688
709
|
PROJECT_TOKEN,
|
|
689
710
|
SMARTUI_CLIENT_API_URL,
|
|
690
|
-
LT_SDK_LOG_LEVEL,
|
|
691
711
|
LT_SDK_DEBUG,
|
|
692
712
|
SMARTUI_GIT_INFO_FILEPATH,
|
|
693
713
|
HTTP_PROXY,
|
|
@@ -700,37 +720,37 @@ var env_default = () => {
|
|
|
700
720
|
CURRENT_BRANCH
|
|
701
721
|
};
|
|
702
722
|
};
|
|
703
|
-
var logContext = {
|
|
723
|
+
var logContext = {};
|
|
704
724
|
function updateLogContext(newContext) {
|
|
705
725
|
logContext = __spreadValues(__spreadValues({}, logContext), newContext);
|
|
706
726
|
}
|
|
707
727
|
var logLevel = () => {
|
|
708
728
|
let env = env_default();
|
|
709
|
-
|
|
710
|
-
return debug || env.LT_SDK_LOG_LEVEL || "info";
|
|
729
|
+
return env.LT_SDK_DEBUG === "true" ? "debug" : "info";
|
|
711
730
|
};
|
|
712
731
|
var logger = winston.createLogger({
|
|
713
|
-
level: logLevel(),
|
|
714
732
|
format: winston.format.combine(
|
|
715
733
|
winston.format.timestamp(),
|
|
716
734
|
winston.format.printf((info) => {
|
|
717
735
|
let contextString = Object.values(logContext).join(" | ");
|
|
718
|
-
let message = typeof info.message === "object" ? JSON.stringify(info.message) : info.message;
|
|
736
|
+
let message = typeof info.message === "object" ? JSON.stringify(info.message).trim() : info.message.trim();
|
|
719
737
|
switch (info.level) {
|
|
720
|
-
case "debug":
|
|
721
|
-
message = chalk7__default.default.blue(message);
|
|
722
|
-
break;
|
|
723
738
|
case "warn":
|
|
724
739
|
message = chalk7__default.default.yellow(message);
|
|
725
740
|
break;
|
|
726
|
-
case "error":
|
|
727
|
-
message = chalk7__default.default.red(message);
|
|
728
|
-
break;
|
|
729
741
|
}
|
|
730
742
|
return info.level === "info" ? message : `[${contextString}:${info.level}] ` + message;
|
|
731
743
|
})
|
|
732
744
|
),
|
|
733
|
-
transports: [
|
|
745
|
+
transports: [
|
|
746
|
+
new winston.transports.Console({
|
|
747
|
+
level: logLevel()
|
|
748
|
+
}),
|
|
749
|
+
new winston.transports.File({
|
|
750
|
+
level: "debug",
|
|
751
|
+
filename: constants_default.LOG_FILE_PATH
|
|
752
|
+
})
|
|
753
|
+
]
|
|
734
754
|
});
|
|
735
755
|
var logger_default = logger;
|
|
736
756
|
|
|
@@ -774,7 +794,7 @@ var auth_default = (ctx) => {
|
|
|
774
794
|
};
|
|
775
795
|
|
|
776
796
|
// package.json
|
|
777
|
-
var version = "
|
|
797
|
+
var version = "4.0.0";
|
|
778
798
|
var package_default = {
|
|
779
799
|
name: "@lambdatest/smartui-cli",
|
|
780
800
|
version,
|
|
@@ -814,6 +834,7 @@ var package_default = {
|
|
|
814
834
|
fastify: "^4.24.3",
|
|
815
835
|
"form-data": "^4.0.0",
|
|
816
836
|
listr2: "^7.0.1",
|
|
837
|
+
sharp: "^0.33.4",
|
|
817
838
|
tsup: "^7.2.0",
|
|
818
839
|
which: "^4.0.0",
|
|
819
840
|
winston: "^3.10.0"
|
|
@@ -829,11 +850,11 @@ var httpClient = class {
|
|
|
829
850
|
headers: { "projectToken": PROJECT_TOKEN }
|
|
830
851
|
});
|
|
831
852
|
}
|
|
832
|
-
request(config,
|
|
853
|
+
request(config, log2) {
|
|
833
854
|
return __async(this, null, function* () {
|
|
834
|
-
|
|
855
|
+
log2.debug(`http request: ${config.method} ${config.url}`);
|
|
835
856
|
return this.axiosInstance.request(config).then((resp) => {
|
|
836
|
-
|
|
857
|
+
log2.debug(`http response: ${JSON.stringify({
|
|
837
858
|
status: resp.status,
|
|
838
859
|
headers: resp.headers,
|
|
839
860
|
body: resp.data
|
|
@@ -842,7 +863,7 @@ var httpClient = class {
|
|
|
842
863
|
}).catch((error) => {
|
|
843
864
|
var _a;
|
|
844
865
|
if (error.response) {
|
|
845
|
-
|
|
866
|
+
log2.debug(`http response: ${JSON.stringify({
|
|
846
867
|
status: error.response.status,
|
|
847
868
|
headers: error.response.headers,
|
|
848
869
|
body: error.response.data
|
|
@@ -850,21 +871,21 @@ var httpClient = class {
|
|
|
850
871
|
throw new Error(((_a = error.response.data.error) == null ? void 0 : _a.message) || error.response.data.message);
|
|
851
872
|
}
|
|
852
873
|
if (error.request) {
|
|
853
|
-
|
|
874
|
+
log2.debug(`http request failed: ${error.toJSON()}`);
|
|
854
875
|
throw new Error(error.toJSON().message);
|
|
855
876
|
}
|
|
856
|
-
|
|
877
|
+
log2.debug(`http request failed: ${error.message}`);
|
|
857
878
|
throw new Error(error.message);
|
|
858
879
|
});
|
|
859
880
|
});
|
|
860
881
|
}
|
|
861
|
-
auth(
|
|
882
|
+
auth(log2) {
|
|
862
883
|
return this.request({
|
|
863
884
|
url: "/token/verify",
|
|
864
885
|
method: "GET"
|
|
865
|
-
},
|
|
886
|
+
}, log2);
|
|
866
887
|
}
|
|
867
|
-
createBuild(git, config,
|
|
888
|
+
createBuild(git, config, log2) {
|
|
868
889
|
return this.request({
|
|
869
890
|
url: "/build",
|
|
870
891
|
method: "POST",
|
|
@@ -872,9 +893,9 @@ var httpClient = class {
|
|
|
872
893
|
git,
|
|
873
894
|
config
|
|
874
895
|
}
|
|
875
|
-
},
|
|
896
|
+
}, log2);
|
|
876
897
|
}
|
|
877
|
-
finalizeBuild(buildId, totalSnapshots,
|
|
898
|
+
finalizeBuild(buildId, totalSnapshots, log2) {
|
|
878
899
|
let params = { buildId };
|
|
879
900
|
if (totalSnapshots > -1)
|
|
880
901
|
params.totalSnapshots = totalSnapshots;
|
|
@@ -882,7 +903,7 @@ var httpClient = class {
|
|
|
882
903
|
url: "/build",
|
|
883
904
|
method: "DELETE",
|
|
884
905
|
params
|
|
885
|
-
},
|
|
906
|
+
}, log2);
|
|
886
907
|
}
|
|
887
908
|
uploadSnapshot(ctx, snapshot) {
|
|
888
909
|
return this.request({
|
|
@@ -898,7 +919,7 @@ var httpClient = class {
|
|
|
898
919
|
}
|
|
899
920
|
}, ctx.log);
|
|
900
921
|
}
|
|
901
|
-
uploadScreenshot({ id: buildId, name: buildName, baseline }, ssPath, ssName, browserName, viewport,
|
|
922
|
+
uploadScreenshot({ id: buildId, name: buildName, baseline }, ssPath, ssName, browserName, viewport, log2) {
|
|
902
923
|
browserName = browserName === constants_default.SAFARI ? constants_default.WEBKIT : browserName;
|
|
903
924
|
const file = fs5__default.default.readFileSync(ssPath);
|
|
904
925
|
const form = new FormData__default.default();
|
|
@@ -915,7 +936,7 @@ var httpClient = class {
|
|
|
915
936
|
headers: form.getHeaders(),
|
|
916
937
|
data: form
|
|
917
938
|
}).then(() => {
|
|
918
|
-
|
|
939
|
+
log2.debug(`${ssName} for ${browserName} ${viewport} uploaded successfully`);
|
|
919
940
|
}).catch((error) => {
|
|
920
941
|
if (error.response) {
|
|
921
942
|
throw new Error(error.response.data.error.message);
|
|
@@ -926,7 +947,7 @@ var httpClient = class {
|
|
|
926
947
|
throw new Error(error.message);
|
|
927
948
|
});
|
|
928
949
|
}
|
|
929
|
-
checkUpdate(
|
|
950
|
+
checkUpdate(log2) {
|
|
930
951
|
return this.request({
|
|
931
952
|
url: `/packageinfo`,
|
|
932
953
|
method: "GET",
|
|
@@ -935,9 +956,9 @@ var httpClient = class {
|
|
|
935
956
|
packageName: package_default.name,
|
|
936
957
|
packageVersion: package_default.version
|
|
937
958
|
}
|
|
938
|
-
},
|
|
959
|
+
}, log2);
|
|
939
960
|
}
|
|
940
|
-
getFigmaFilesAndImages(figmaFileToken, figmaToken, queryParams, authToken, depth, markBaseline, buildName,
|
|
961
|
+
getFigmaFilesAndImages(figmaFileToken, figmaToken, queryParams, authToken, depth, markBaseline, buildName, log2) {
|
|
941
962
|
const requestBody = {
|
|
942
963
|
figma_file_token: figmaFileToken,
|
|
943
964
|
figma_token: figmaToken,
|
|
@@ -954,7 +975,34 @@ var httpClient = class {
|
|
|
954
975
|
"Content-Type": "application/json"
|
|
955
976
|
},
|
|
956
977
|
data: JSON.stringify(requestBody)
|
|
957
|
-
},
|
|
978
|
+
}, log2);
|
|
979
|
+
}
|
|
980
|
+
getS3PreSignedURL(ctx) {
|
|
981
|
+
return this.request({
|
|
982
|
+
url: `/loguploadurl`,
|
|
983
|
+
method: "POST",
|
|
984
|
+
headers: { "Content-Type": "application/json" },
|
|
985
|
+
data: {
|
|
986
|
+
buildId: ctx.build.id
|
|
987
|
+
}
|
|
988
|
+
}, ctx.log);
|
|
989
|
+
}
|
|
990
|
+
uploadLogs(ctx, uploadURL) {
|
|
991
|
+
const fileStream = fs5__default.default.createReadStream(constants_default.LOG_FILE_PATH);
|
|
992
|
+
const { size } = fs5__default.default.statSync(constants_default.LOG_FILE_PATH);
|
|
993
|
+
return this.request({
|
|
994
|
+
url: uploadURL,
|
|
995
|
+
method: "PUT",
|
|
996
|
+
headers: {
|
|
997
|
+
"Content-Type": "text/plain",
|
|
998
|
+
"Content-Length": size
|
|
999
|
+
},
|
|
1000
|
+
data: fileStream,
|
|
1001
|
+
maxBodyLength: Infinity,
|
|
1002
|
+
// prevent axios from limiting the body size
|
|
1003
|
+
maxContentLength: Infinity
|
|
1004
|
+
// prevent axios from limiting the content size
|
|
1005
|
+
}, ctx.log);
|
|
958
1006
|
}
|
|
959
1007
|
};
|
|
960
1008
|
var ctx_default = (options) => {
|
|
@@ -964,6 +1012,10 @@ var ctx_default = (options) => {
|
|
|
964
1012
|
let mobileConfig;
|
|
965
1013
|
let config = constants_default.DEFAULT_CONFIG;
|
|
966
1014
|
let port;
|
|
1015
|
+
let resolutionOff;
|
|
1016
|
+
let extensionFiles;
|
|
1017
|
+
let ignoreStripExtension;
|
|
1018
|
+
let ignoreFilePattern;
|
|
967
1019
|
try {
|
|
968
1020
|
if (options.config) {
|
|
969
1021
|
config = JSON.parse(fs5__default.default.readFileSync(options.config, "utf-8"));
|
|
@@ -979,6 +1031,10 @@ var ctx_default = (options) => {
|
|
|
979
1031
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
980
1032
|
throw new Error("Invalid port number. Port number must be an integer between 1 and 65535.");
|
|
981
1033
|
}
|
|
1034
|
+
resolutionOff = options.ignoreResolutions || false;
|
|
1035
|
+
extensionFiles = options.files || ["png", "jpeg", "jpg"];
|
|
1036
|
+
ignoreStripExtension = options.removeExtensions || false;
|
|
1037
|
+
ignoreFilePattern = options.ignoreDir || [];
|
|
982
1038
|
} catch (error) {
|
|
983
1039
|
console.log(`[smartui] Error: ${error.message}`);
|
|
984
1040
|
process.exit();
|
|
@@ -1007,6 +1063,7 @@ var ctx_default = (options) => {
|
|
|
1007
1063
|
enableJavaScript: config.enableJavaScript || false,
|
|
1008
1064
|
allowedHostnames: config.allowedHostnames || []
|
|
1009
1065
|
},
|
|
1066
|
+
uploadFilePath: "",
|
|
1010
1067
|
webStaticConfig: [],
|
|
1011
1068
|
git: {
|
|
1012
1069
|
branch: "",
|
|
@@ -1026,16 +1083,20 @@ var ctx_default = (options) => {
|
|
|
1026
1083
|
parallel: options.parallel ? true : false,
|
|
1027
1084
|
markBaseline: options.markBaseline ? true : false,
|
|
1028
1085
|
buildName: options.buildName || "",
|
|
1029
|
-
port
|
|
1086
|
+
port,
|
|
1087
|
+
ignoreResolutions: resolutionOff,
|
|
1088
|
+
fileExtension: extensionFiles,
|
|
1089
|
+
stripExtension: ignoreStripExtension,
|
|
1090
|
+
ignorePattern: ignoreFilePattern
|
|
1030
1091
|
},
|
|
1031
1092
|
cliVersion: version,
|
|
1032
1093
|
totalSnapshots: -1
|
|
1033
1094
|
};
|
|
1034
1095
|
};
|
|
1035
|
-
function executeCommand(
|
|
1096
|
+
function executeCommand(command5) {
|
|
1036
1097
|
let dst = process.cwd();
|
|
1037
1098
|
try {
|
|
1038
|
-
return child_process.execSync(
|
|
1099
|
+
return child_process.execSync(command5, {
|
|
1039
1100
|
cwd: dst,
|
|
1040
1101
|
stdio: ["ignore"],
|
|
1041
1102
|
encoding: "utf-8"
|
|
@@ -1066,8 +1127,8 @@ var git_default = (ctx) => {
|
|
|
1066
1127
|
} else {
|
|
1067
1128
|
const splitCharacter = "<##>";
|
|
1068
1129
|
const prettyFormat = ["%h", "%H", "%s", "%f", "%b", "%at", "%ct", "%an", "%ae", "%cn", "%ce", "%N", ""];
|
|
1069
|
-
const
|
|
1070
|
-
let res = executeCommand(
|
|
1130
|
+
const command5 = 'git log -1 --pretty=format:"' + prettyFormat.join(splitCharacter) + '" && git rev-parse --abbrev-ref HEAD && git tag --contains HEAD';
|
|
1131
|
+
let res = executeCommand(command5).split(splitCharacter);
|
|
1071
1132
|
var branchAndTags = res[res.length - 1].split("\n").filter((n) => n);
|
|
1072
1133
|
var branch = ctx.env.CURRENT_BRANCH || branchAndTags[0];
|
|
1073
1134
|
branchAndTags.slice(1);
|
|
@@ -1208,9 +1269,9 @@ var finalizeBuild_default = (ctx) => {
|
|
|
1208
1269
|
return {
|
|
1209
1270
|
title: `Finalizing build`,
|
|
1210
1271
|
task: (ctx2, task) => __async(void 0, null, function* () {
|
|
1272
|
+
var _a, _b;
|
|
1211
1273
|
updateLogContext({ task: "finalizeBuild" });
|
|
1212
1274
|
try {
|
|
1213
|
-
yield new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1214
1275
|
yield ctx2.client.finalizeBuild(ctx2.build.id, ctx2.totalSnapshots, ctx2.log);
|
|
1215
1276
|
task.output = chalk7__default.default.gray(`build url: ${ctx2.build.url}`);
|
|
1216
1277
|
task.title = "Finalized build";
|
|
@@ -1219,6 +1280,17 @@ var finalizeBuild_default = (ctx) => {
|
|
|
1219
1280
|
task.output = chalk7__default.default.gray(error.message);
|
|
1220
1281
|
throw new Error("Finalize build failed");
|
|
1221
1282
|
}
|
|
1283
|
+
try {
|
|
1284
|
+
yield (_a = ctx2.browser) == null ? void 0 : _a.close();
|
|
1285
|
+
ctx2.log.debug(`Closed browser`);
|
|
1286
|
+
yield (_b = ctx2.server) == null ? void 0 : _b.close();
|
|
1287
|
+
ctx2.log.debug(`Closed server`);
|
|
1288
|
+
let resp = yield ctx2.client.getS3PreSignedURL(ctx2);
|
|
1289
|
+
yield ctx2.client.uploadLogs(ctx2, resp.data.url);
|
|
1290
|
+
fs5.unlinkSync(constants_default.LOG_FILE_PATH);
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
ctx2.log.debug(error);
|
|
1293
|
+
}
|
|
1222
1294
|
}),
|
|
1223
1295
|
rendererOptions: { persistentOutput: true }
|
|
1224
1296
|
};
|
|
@@ -1403,6 +1475,7 @@ var Queue = class {
|
|
|
1403
1475
|
function processSnapshot(snapshot, ctx) {
|
|
1404
1476
|
return __async(this, null, function* () {
|
|
1405
1477
|
var _a;
|
|
1478
|
+
updateLogContext({ task: "discovery" });
|
|
1406
1479
|
ctx.log.debug(`Processing snapshot ${snapshot.name}`);
|
|
1407
1480
|
let launchOptions = { headless: true };
|
|
1408
1481
|
let contextOptions = {
|
|
@@ -1412,8 +1485,8 @@ function processSnapshot(snapshot, ctx) {
|
|
|
1412
1485
|
if (!((_a = ctx.browser) == null ? void 0 : _a.isConnected())) {
|
|
1413
1486
|
if (ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY)
|
|
1414
1487
|
launchOptions.proxy = { server: ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY };
|
|
1415
|
-
ctx.browser = yield test.
|
|
1416
|
-
ctx.log.debug(`
|
|
1488
|
+
ctx.browser = yield test.firefox.launch(launchOptions);
|
|
1489
|
+
ctx.log.debug(`Firefox launched with options ${JSON.stringify(launchOptions)}`);
|
|
1417
1490
|
}
|
|
1418
1491
|
const context = yield ctx.browser.newContext(contextOptions);
|
|
1419
1492
|
ctx.log.debug(`Browser context created with options ${JSON.stringify(contextOptions)}`);
|
|
@@ -1606,10 +1679,9 @@ function processSnapshot(snapshot, ctx) {
|
|
|
1606
1679
|
|
|
1607
1680
|
// src/commander/exec.ts
|
|
1608
1681
|
var command = new commander.Command();
|
|
1609
|
-
command.name("exec").description("Run test commands around SmartUI").argument("<command...>", "Command supplied for running tests").option("-P, --port <number>", "Port number for the server").action(function(execCommand, _,
|
|
1682
|
+
command.name("exec").description("Run test commands around SmartUI").argument("<command...>", "Command supplied for running tests").option("-P, --port <number>", "Port number for the server").action(function(execCommand, _, command5) {
|
|
1610
1683
|
return __async(this, null, function* () {
|
|
1611
|
-
|
|
1612
|
-
let ctx = ctx_default(command4.optsWithGlobals());
|
|
1684
|
+
let ctx = ctx_default(command5.optsWithGlobals());
|
|
1613
1685
|
if (!which__default.default.sync(execCommand[0], { nothrow: true })) {
|
|
1614
1686
|
ctx.log.error(`Error: Command not found "${execCommand[0]}"`);
|
|
1615
1687
|
return;
|
|
@@ -1642,11 +1714,6 @@ command.name("exec").description("Run test commands around SmartUI").argument("<
|
|
|
1642
1714
|
yield tasks.run(ctx);
|
|
1643
1715
|
} catch (error) {
|
|
1644
1716
|
ctx.log.info("\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/");
|
|
1645
|
-
} finally {
|
|
1646
|
-
yield (_a = ctx.browser) == null ? void 0 : _a.close();
|
|
1647
|
-
ctx.log.debug(`Closed browser`);
|
|
1648
|
-
yield (_b = ctx.server) == null ? void 0 : _b.close();
|
|
1649
|
-
ctx.log.debug(`Closed server`);
|
|
1650
1717
|
}
|
|
1651
1718
|
});
|
|
1652
1719
|
});
|
|
@@ -1832,6 +1899,108 @@ function captureScreenshots(ctx) {
|
|
|
1832
1899
|
return { capturedScreenshots, output };
|
|
1833
1900
|
});
|
|
1834
1901
|
}
|
|
1902
|
+
function getImageDimensions(filePath) {
|
|
1903
|
+
const buffer = fs5__default.default.readFileSync(filePath);
|
|
1904
|
+
let width, height;
|
|
1905
|
+
if (buffer.toString("hex", 0, 2) === "ffd8") {
|
|
1906
|
+
let offset = 2;
|
|
1907
|
+
while (offset < buffer.length) {
|
|
1908
|
+
const marker = buffer.toString("hex", offset, offset + 2);
|
|
1909
|
+
offset += 2;
|
|
1910
|
+
const length = buffer.readUInt16BE(offset);
|
|
1911
|
+
if (marker === "ffc0" || marker === "ffc2") {
|
|
1912
|
+
height = buffer.readUInt16BE(offset + 3);
|
|
1913
|
+
width = buffer.readUInt16BE(offset + 5);
|
|
1914
|
+
return { width, height };
|
|
1915
|
+
}
|
|
1916
|
+
offset += length;
|
|
1917
|
+
}
|
|
1918
|
+
} else if (buffer.toString("hex", 1, 4) === "504e47") {
|
|
1919
|
+
width = buffer.readUInt32BE(16);
|
|
1920
|
+
height = buffer.readUInt32BE(20);
|
|
1921
|
+
return { width, height };
|
|
1922
|
+
}
|
|
1923
|
+
return null;
|
|
1924
|
+
}
|
|
1925
|
+
function isAllowedImage(filePath) {
|
|
1926
|
+
return __async(this, null, function* () {
|
|
1927
|
+
try {
|
|
1928
|
+
const fileBuffer = fs5__default.default.readFileSync(filePath);
|
|
1929
|
+
const isMagicValid = constants_default.MAGIC_NUMBERS.some((magic) => fileBuffer.slice(0, magic.magic.length).equals(magic.magic));
|
|
1930
|
+
const metadata = yield sharp__default.default(filePath).metadata();
|
|
1931
|
+
if (metadata.format === constants_default.FILE_EXTENSION_GIFS) {
|
|
1932
|
+
return false;
|
|
1933
|
+
}
|
|
1934
|
+
if (metadata.width > 0 && metadata.height > 0) {
|
|
1935
|
+
return true;
|
|
1936
|
+
}
|
|
1937
|
+
if (isMagicValid && metadata.format !== constants_default.FILE_EXTENSION_GIFS) {
|
|
1938
|
+
return true;
|
|
1939
|
+
}
|
|
1940
|
+
return false;
|
|
1941
|
+
} catch (error) {
|
|
1942
|
+
return false;
|
|
1943
|
+
}
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
function uploadScreenshots(ctx) {
|
|
1947
|
+
return __async(this, null, function* () {
|
|
1948
|
+
const allowedExtensions = ctx.options.fileExtension.map((ext) => `.${ext.trim().toLowerCase()}`);
|
|
1949
|
+
let noOfScreenshots = 0;
|
|
1950
|
+
function processDirectory(directory, relativePath = "") {
|
|
1951
|
+
return __async(this, null, function* () {
|
|
1952
|
+
const files = fs5__default.default.readdirSync(directory);
|
|
1953
|
+
for (let file of files) {
|
|
1954
|
+
const filePath = path2__default.default.join(directory, file);
|
|
1955
|
+
const stat = fs5__default.default.statSync(filePath);
|
|
1956
|
+
const relativeFilePath = path2__default.default.join(relativePath, file);
|
|
1957
|
+
if (stat.isDirectory() && ctx.options.ignorePattern.includes(relativeFilePath)) {
|
|
1958
|
+
ctx.log.info(`Ignoring Directory ${relativeFilePath}`);
|
|
1959
|
+
continue;
|
|
1960
|
+
}
|
|
1961
|
+
if (stat.isDirectory()) {
|
|
1962
|
+
yield processDirectory(filePath, relativeFilePath);
|
|
1963
|
+
} else {
|
|
1964
|
+
let fileExtension = path2__default.default.extname(file).toLowerCase();
|
|
1965
|
+
if (allowedExtensions.includes(fileExtension)) {
|
|
1966
|
+
const isValid = yield isAllowedImage(filePath);
|
|
1967
|
+
if (!isValid) {
|
|
1968
|
+
ctx.log.info(`File ${filePath} is not a valid ${fileExtension} image or is corrupted. Skipping.`);
|
|
1969
|
+
continue;
|
|
1970
|
+
}
|
|
1971
|
+
let ssId = relativeFilePath;
|
|
1972
|
+
if (ctx.options.stripExtension) {
|
|
1973
|
+
ssId = path2__default.default.join(relativePath, path2__default.default.basename(file, fileExtension));
|
|
1974
|
+
}
|
|
1975
|
+
let viewport = "default";
|
|
1976
|
+
if (!ctx.options.ignoreResolutions) {
|
|
1977
|
+
const dimensions = getImageDimensions(filePath);
|
|
1978
|
+
if (!dimensions) {
|
|
1979
|
+
ctx.log.info(`Unable to determine dimensions for image: ${filePath}`);
|
|
1980
|
+
} else {
|
|
1981
|
+
const width = dimensions.width;
|
|
1982
|
+
const height = dimensions.height;
|
|
1983
|
+
viewport = `${width}x${height}`;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
yield ctx.client.uploadScreenshot(ctx.build, filePath, ssId, "default", viewport, ctx.log);
|
|
1987
|
+
ctx.log.info(`${filePath} : uploaded successfully`);
|
|
1988
|
+
noOfScreenshots++;
|
|
1989
|
+
} else {
|
|
1990
|
+
ctx.log.info(`File ${filePath} has invalid file extension: ${fileExtension}. Skipping`);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
yield processDirectory(ctx.uploadFilePath);
|
|
1997
|
+
if (noOfScreenshots == 0) {
|
|
1998
|
+
ctx.log.info(`No screenshots uploaded.`);
|
|
1999
|
+
} else {
|
|
2000
|
+
ctx.log.info(`${noOfScreenshots} screenshots uploaded successfully.`);
|
|
2001
|
+
}
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
1835
2004
|
var captureScreenshots_default = (ctx) => {
|
|
1836
2005
|
return {
|
|
1837
2006
|
title: "Capturing screenshots",
|
|
@@ -1857,9 +2026,9 @@ var captureScreenshots_default = (ctx) => {
|
|
|
1857
2026
|
|
|
1858
2027
|
// src/commander/capture.ts
|
|
1859
2028
|
var command2 = new commander.Command();
|
|
1860
|
-
command2.name("capture").description("Capture screenshots of static sites").argument("<file>", "Web static config file").option("--parallel", "Capture parallely on all browsers").action(function(file, _,
|
|
2029
|
+
command2.name("capture").description("Capture screenshots of static sites").argument("<file>", "Web static config file").option("--parallel", "Capture parallely on all browsers").action(function(file, _, command5) {
|
|
1861
2030
|
return __async(this, null, function* () {
|
|
1862
|
-
let ctx = ctx_default(
|
|
2031
|
+
let ctx = ctx_default(command5.optsWithGlobals());
|
|
1863
2032
|
if (!fs5__default.default.existsSync(file)) {
|
|
1864
2033
|
console.log(`Error: Web Static Config file ${file} not found.`);
|
|
1865
2034
|
return;
|
|
@@ -1899,6 +2068,71 @@ command2.name("capture").description("Capture screenshots of static sites").argu
|
|
|
1899
2068
|
});
|
|
1900
2069
|
});
|
|
1901
2070
|
var capture_default = command2;
|
|
2071
|
+
var uploadScreenshots_default = (ctx) => {
|
|
2072
|
+
return {
|
|
2073
|
+
title: "Uploading screenshots",
|
|
2074
|
+
task: (ctx2, task) => __async(void 0, null, function* () {
|
|
2075
|
+
try {
|
|
2076
|
+
ctx2.task = task;
|
|
2077
|
+
updateLogContext({ task: "upload" });
|
|
2078
|
+
yield uploadScreenshots(ctx2);
|
|
2079
|
+
task.title = "Screenshots uploaded successfully";
|
|
2080
|
+
} catch (error) {
|
|
2081
|
+
ctx2.log.debug(error);
|
|
2082
|
+
task.output = chalk7__default.default.gray(`${error.message}`);
|
|
2083
|
+
throw new Error("Uploading screenshots failed");
|
|
2084
|
+
}
|
|
2085
|
+
}),
|
|
2086
|
+
rendererOptions: { persistentOutput: true },
|
|
2087
|
+
exitOnError: false
|
|
2088
|
+
};
|
|
2089
|
+
};
|
|
2090
|
+
|
|
2091
|
+
// src/commander/upload.ts
|
|
2092
|
+
var command3 = new commander.Command();
|
|
2093
|
+
command3.name("upload").description("Upload screenshots from given directory").argument("<directory>", "Path of the directory").option("-R, --ignoreResolutions", "Ignore resolution").option("-F, --files <extensions>", "Comma-separated list of allowed file extensions", (val) => {
|
|
2094
|
+
return val.split(",").map((ext) => ext.trim().toLowerCase());
|
|
2095
|
+
}).option("-E, --removeExtensions", "Strips file extensions from snapshot names").option("-i, --ignoreDir <patterns>", "Comma-separated list of directories to ignore", (val) => {
|
|
2096
|
+
return val.split(",").map((pattern) => pattern.trim());
|
|
2097
|
+
}).action(function(directory, _, command5) {
|
|
2098
|
+
return __async(this, null, function* () {
|
|
2099
|
+
let ctx = ctx_default(command5.optsWithGlobals());
|
|
2100
|
+
if (!fs5__default.default.existsSync(directory)) {
|
|
2101
|
+
console.log(`Error: The provided directory ${directory} not found.`);
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
if (path2__default.default.extname(directory).toLowerCase() === constants_default.FILE_EXTENSION_ZIP) {
|
|
2105
|
+
ctx.log.debug(`Error: The provided directory ${directory} is a zip file. Zips are not accepted.`);
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
ctx.uploadFilePath = directory;
|
|
2109
|
+
let tasks = new listr2.Listr(
|
|
2110
|
+
[
|
|
2111
|
+
auth_default(),
|
|
2112
|
+
getGitInfo_default(),
|
|
2113
|
+
createBuild_default(),
|
|
2114
|
+
uploadScreenshots_default(),
|
|
2115
|
+
finalizeBuild_default()
|
|
2116
|
+
],
|
|
2117
|
+
{
|
|
2118
|
+
rendererOptions: {
|
|
2119
|
+
icon: {
|
|
2120
|
+
[listr2.ListrDefaultRendererLogLevels.OUTPUT]: `\u2192`
|
|
2121
|
+
},
|
|
2122
|
+
color: {
|
|
2123
|
+
[listr2.ListrDefaultRendererLogLevels.OUTPUT]: listr2.color.gray
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
);
|
|
2128
|
+
try {
|
|
2129
|
+
yield tasks.run(ctx);
|
|
2130
|
+
} catch (error) {
|
|
2131
|
+
console.log("\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/");
|
|
2132
|
+
}
|
|
2133
|
+
});
|
|
2134
|
+
});
|
|
2135
|
+
var upload_default = command3;
|
|
1902
2136
|
|
|
1903
2137
|
// src/lib/uploadFigmaDesigns.ts
|
|
1904
2138
|
var uploadFigmaDesigns_default = (ctx) => __async(void 0, null, function* () {
|
|
@@ -1950,11 +2184,11 @@ var uploadFigmaDesigns_default2 = (ctx) => {
|
|
|
1950
2184
|
};
|
|
1951
2185
|
|
|
1952
2186
|
// src/commander/uploadFigma.ts
|
|
1953
|
-
var
|
|
1954
|
-
|
|
2187
|
+
var command4 = new commander.Command();
|
|
2188
|
+
command4.name("upload-figma").description("Capture screenshots of static sites").argument("<file>", "figma design config file").option("--markBaseline", "Mark the uploaded images as baseline").option("--buildName <buildName>", "Name of the build").action(function(file, _, command5) {
|
|
1955
2189
|
return __async(this, null, function* () {
|
|
1956
2190
|
var _a, _b;
|
|
1957
|
-
let ctx = ctx_default(
|
|
2191
|
+
let ctx = ctx_default(command5.optsWithGlobals());
|
|
1958
2192
|
if (!fs5__default.default.existsSync(file)) {
|
|
1959
2193
|
console.log(`Error: Figma Config file ${file} not found.`);
|
|
1960
2194
|
return;
|
|
@@ -1992,31 +2226,34 @@ command3.name("upload-figma").description("Capture screenshots of static sites")
|
|
|
1992
2226
|
}
|
|
1993
2227
|
});
|
|
1994
2228
|
});
|
|
1995
|
-
var uploadFigma_default =
|
|
2229
|
+
var uploadFigma_default = command4;
|
|
1996
2230
|
|
|
1997
2231
|
// src/commander/commander.ts
|
|
1998
2232
|
var program = new commander.Command();
|
|
1999
|
-
program.name("smartui").description("CLI to help you run your SmartUI tests on LambdaTest platform").version(`v${version}`).option("-c --config <filepath>", "Config file path").addCommand(exec_default2).addCommand(capture_default).addCommand(configWeb).addCommand(configStatic).addCommand(configFigma).addCommand(uploadFigma_default);
|
|
2233
|
+
program.name("smartui").description("CLI to help you run your SmartUI tests on LambdaTest platform").version(`v${version}`).option("-c --config <filepath>", "Config file path").addCommand(exec_default2).addCommand(capture_default).addCommand(configWeb).addCommand(configStatic).addCommand(upload_default).addCommand(configFigma).addCommand(uploadFigma_default);
|
|
2000
2234
|
var commander_default = program;
|
|
2001
2235
|
(function() {
|
|
2002
2236
|
return __async(this, null, function* () {
|
|
2003
2237
|
let client = new httpClient(env_default());
|
|
2004
|
-
let
|
|
2238
|
+
let log2 = logger_default;
|
|
2005
2239
|
try {
|
|
2006
|
-
|
|
2240
|
+
let { data: { latestVersion, deprecated, additionalDescription, additionalDescriptionLatestVersion } } = yield client.checkUpdate(log2);
|
|
2241
|
+
log2.info(`
|
|
2007
2242
|
LambdaTest SmartUI CLI v${package_default.version}`);
|
|
2008
|
-
|
|
2009
|
-
if (deprecated)
|
|
2010
|
-
|
|
2243
|
+
log2.info(chalk7__default.default.yellow(`${additionalDescription}`));
|
|
2244
|
+
if (deprecated) {
|
|
2245
|
+
log2.warn(`This version is deprecated. A new version ${latestVersion} is available!`);
|
|
2246
|
+
log2.warn(`${additionalDescriptionLatestVersion}
|
|
2011
2247
|
`);
|
|
2012
|
-
else if (package_default.version !== latestVersion)
|
|
2013
|
-
|
|
2248
|
+
} else if (package_default.version !== latestVersion) {
|
|
2249
|
+
log2.info(chalk7__default.default.green(`A new version ${latestVersion} is available!`));
|
|
2250
|
+
log2.info(chalk7__default.default.red(`${additionalDescriptionLatestVersion}
|
|
2014
2251
|
`));
|
|
2015
|
-
else
|
|
2016
|
-
|
|
2252
|
+
} else
|
|
2253
|
+
log2.info(chalk7__default.default.gray("https://www.npmjs.com/package/@lambdatest/smartui-cli\n"));
|
|
2017
2254
|
} catch (error) {
|
|
2018
|
-
|
|
2019
|
-
|
|
2255
|
+
log2.debug(error);
|
|
2256
|
+
log2.info(chalk7__default.default.gray("https://www.npmjs.com/package/@lambdatest/smartui-cli\n"));
|
|
2020
2257
|
}
|
|
2021
2258
|
commander_default.parse();
|
|
2022
2259
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lambdatest/smartui-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/**/*"
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"fastify": "^4.24.3",
|
|
34
34
|
"form-data": "^4.0.0",
|
|
35
35
|
"listr2": "^7.0.1",
|
|
36
|
+
"sharp": "^0.33.4",
|
|
36
37
|
"tsup": "^7.2.0",
|
|
37
38
|
"which": "^4.0.0",
|
|
38
39
|
"winston": "^3.10.0"
|