@lambdatest/smartui-cli 2.0.7 → 2.0.8
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 +167 -90
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7,7 +7,7 @@ var listr2 = require('listr2');
|
|
|
7
7
|
var chalk = require('chalk');
|
|
8
8
|
var path2 = require('path');
|
|
9
9
|
var fastify = require('fastify');
|
|
10
|
-
var
|
|
10
|
+
var fs2 = require('fs');
|
|
11
11
|
var test = require('@playwright/test');
|
|
12
12
|
var Ajv = require('ajv');
|
|
13
13
|
var addErrors = require('ajv-errors');
|
|
@@ -23,7 +23,7 @@ var which__default = /*#__PURE__*/_interopDefault(which);
|
|
|
23
23
|
var chalk__default = /*#__PURE__*/_interopDefault(chalk);
|
|
24
24
|
var path2__default = /*#__PURE__*/_interopDefault(path2);
|
|
25
25
|
var fastify__default = /*#__PURE__*/_interopDefault(fastify);
|
|
26
|
-
var
|
|
26
|
+
var fs2__default = /*#__PURE__*/_interopDefault(fs2);
|
|
27
27
|
var Ajv__default = /*#__PURE__*/_interopDefault(Ajv);
|
|
28
28
|
var addErrors__default = /*#__PURE__*/_interopDefault(addErrors);
|
|
29
29
|
var FormData__default = /*#__PURE__*/_interopDefault(FormData);
|
|
@@ -66,19 +66,109 @@ var __async = (__this, __arguments, generator) => {
|
|
|
66
66
|
step((generator = generator.apply(__this, __arguments)).next());
|
|
67
67
|
});
|
|
68
68
|
};
|
|
69
|
+
function delDir(dir) {
|
|
70
|
+
if (fs2__default.default.existsSync(dir)) {
|
|
71
|
+
fs2__default.default.rmSync(dir, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function scrollToBottomAndBackToTop({
|
|
75
|
+
frequency = 100,
|
|
76
|
+
timing = 8,
|
|
77
|
+
remoteWindow = window
|
|
78
|
+
} = {}) {
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
let scrolls = 1;
|
|
81
|
+
let scrollLength = remoteWindow.document.body.scrollHeight / frequency;
|
|
82
|
+
(function scroll() {
|
|
83
|
+
let scrollBy = scrollLength * scrolls;
|
|
84
|
+
remoteWindow.setTimeout(() => {
|
|
85
|
+
remoteWindow.scrollTo(0, scrollBy);
|
|
86
|
+
if (scrolls < frequency) {
|
|
87
|
+
scrolls += 1;
|
|
88
|
+
scroll();
|
|
89
|
+
}
|
|
90
|
+
if (scrolls === frequency) {
|
|
91
|
+
remoteWindow.setTimeout(() => {
|
|
92
|
+
remoteWindow.scrollTo(0, 0);
|
|
93
|
+
resolve();
|
|
94
|
+
}, timing);
|
|
95
|
+
}
|
|
96
|
+
}, timing);
|
|
97
|
+
})();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
var MAX_RESOURCE_SIZE = 5 * 1024 ** 2;
|
|
101
|
+
var ALLOWED_RESOURCES = ["document", "stylesheet", "image", "media", "font", "other"];
|
|
102
|
+
var ALLOWED_STATUSES = [200, 201];
|
|
69
103
|
var MIN_VIEWPORT_HEIGHT = 1080;
|
|
70
104
|
var processSnapshot_default = (snapshot, ctx) => __async(void 0, null, function* () {
|
|
105
|
+
ctx.log.debug(`Processing snapshot ${snapshot.name}`);
|
|
106
|
+
if (!ctx.browser)
|
|
107
|
+
ctx.browser = yield test.chromium.launch({ headless: true });
|
|
108
|
+
const context = yield ctx.browser.newContext();
|
|
109
|
+
const page = yield context.newPage();
|
|
110
|
+
let cache = {};
|
|
111
|
+
yield page.route("**/*", (route, request) => __async(void 0, null, function* () {
|
|
112
|
+
const requestUrl = request.url();
|
|
113
|
+
const snapshotHostname = new URL(snapshot.url).hostname;
|
|
114
|
+
const requestHostname = new URL(requestUrl).hostname;
|
|
115
|
+
try {
|
|
116
|
+
const response = yield page.request.fetch(request);
|
|
117
|
+
const body = yield response.body();
|
|
118
|
+
if (ctx.webConfig.enableJavaScript)
|
|
119
|
+
ALLOWED_RESOURCES.push("script");
|
|
120
|
+
if (!body) {
|
|
121
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
122
|
+
- skipping no response`);
|
|
123
|
+
} else if (!body.length) {
|
|
124
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
125
|
+
- skipping empty response`);
|
|
126
|
+
} else if (requestUrl === snapshot.url) {
|
|
127
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
128
|
+
- skipping root resource`);
|
|
129
|
+
} else if (requestHostname !== snapshotHostname) {
|
|
130
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
131
|
+
- skipping remote resource`);
|
|
132
|
+
} else if (cache[requestUrl]) {
|
|
133
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
134
|
+
- skipping already cached resource`);
|
|
135
|
+
} else if (body.length > MAX_RESOURCE_SIZE) {
|
|
136
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
137
|
+
- skipping resource larger than 5MB`);
|
|
138
|
+
} else if (!ALLOWED_STATUSES.includes(response.status())) {
|
|
139
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
140
|
+
- skipping disallowed status [${response.status()}]`);
|
|
141
|
+
} else if (!ctx.webConfig.enableJavaScript && !ALLOWED_RESOURCES.includes(request.resourceType())) {
|
|
142
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
143
|
+
- skipping disallowed resource type [${request.resourceType()}]`);
|
|
144
|
+
} else {
|
|
145
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
146
|
+
- content-type ${response.headers()["content-type"]}`);
|
|
147
|
+
cache[requestUrl] = {
|
|
148
|
+
body: body.toString("base64"),
|
|
149
|
+
type: response.headers()["content-type"]
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
route.fulfill({
|
|
153
|
+
status: response.status(),
|
|
154
|
+
headers: response.headers(),
|
|
155
|
+
body
|
|
156
|
+
});
|
|
157
|
+
} catch (error) {
|
|
158
|
+
ctx.log.debug(`Handling request ${requestUrl} - aborted`);
|
|
159
|
+
route.abort();
|
|
160
|
+
}
|
|
161
|
+
}));
|
|
71
162
|
let options = snapshot.options;
|
|
72
163
|
let optionWarnings = /* @__PURE__ */ new Set();
|
|
73
164
|
let processedOptions = {};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (options.ignoreDOM && Object.keys(options.ignoreDOM).length !== 0) {
|
|
165
|
+
let selectors2 = [];
|
|
166
|
+
let ignoreOrSelectDOM;
|
|
167
|
+
let ignoreOrSelectBoxes;
|
|
168
|
+
if (options && Object.keys(options).length) {
|
|
169
|
+
ctx.log.debug(`Snapshot options: ${JSON.stringify(options)}`);
|
|
170
|
+
if (options.ignoreDOM && Object.keys(options.ignoreDOM).length || options.selectDOM && Object.keys(options.selectDOM).length) {
|
|
171
|
+
if (options.ignoreDOM && Object.keys(options.ignoreDOM).length) {
|
|
82
172
|
processedOptions.ignoreBoxes = {};
|
|
83
173
|
ignoreOrSelectDOM = "ignoreDOM";
|
|
84
174
|
ignoreOrSelectBoxes = "ignoreBoxes";
|
|
@@ -87,59 +177,72 @@ var processSnapshot_default = (snapshot, ctx) => __async(void 0, null, function*
|
|
|
87
177
|
ignoreOrSelectDOM = "selectDOM";
|
|
88
178
|
ignoreOrSelectBoxes = "selectBoxes";
|
|
89
179
|
}
|
|
90
|
-
let selectors = [];
|
|
91
180
|
for (const [key, value] of Object.entries(options[ignoreOrSelectDOM])) {
|
|
92
181
|
switch (key) {
|
|
93
182
|
case "id":
|
|
94
|
-
|
|
183
|
+
selectors2.push(...value.map((e) => "#" + e));
|
|
95
184
|
break;
|
|
96
185
|
case "class":
|
|
97
|
-
|
|
186
|
+
selectors2.push(...value.map((e) => "." + e));
|
|
98
187
|
break;
|
|
99
188
|
case "xpath":
|
|
100
|
-
|
|
189
|
+
selectors2.push(...value.map((e) => "xpath=" + e));
|
|
101
190
|
break;
|
|
102
191
|
case "cssSelector":
|
|
103
|
-
|
|
192
|
+
selectors2.push(...value);
|
|
104
193
|
break;
|
|
105
194
|
}
|
|
106
195
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
let navigated = false;
|
|
199
|
+
for (const viewport of ctx.webConfig.viewports) {
|
|
200
|
+
yield page.setViewportSize({ width: viewport.width, height: viewport.height || MIN_VIEWPORT_HEIGHT });
|
|
201
|
+
ctx.log.debug(`Page resized to ${viewport.width}x${viewport.height || MIN_VIEWPORT_HEIGHT}`);
|
|
202
|
+
if (!navigated) {
|
|
203
|
+
yield page.goto(snapshot.url);
|
|
204
|
+
navigated = true;
|
|
205
|
+
ctx.log.debug(`Navigated to ${snapshot.url}`);
|
|
206
|
+
}
|
|
207
|
+
if (!viewport.height)
|
|
208
|
+
yield page.evaluate(scrollToBottomAndBackToTop);
|
|
209
|
+
yield page.waitForLoadState("networkidle");
|
|
210
|
+
ctx.log.debug("Network idle 500ms");
|
|
211
|
+
if (selectors2.length) {
|
|
212
|
+
let viewportString = `${viewport.width}${viewport.height ? "x" + viewport.height : ""}`;
|
|
213
|
+
if (!Array.isArray(processedOptions[ignoreOrSelectBoxes][viewportString]))
|
|
214
|
+
processedOptions[ignoreOrSelectBoxes][viewportString] = [];
|
|
215
|
+
let locators = [];
|
|
216
|
+
let boxes = [];
|
|
217
|
+
for (const selector of selectors2) {
|
|
218
|
+
let l = yield page.locator(selector).all();
|
|
219
|
+
if (l.length === 0) {
|
|
220
|
+
optionWarnings.add(`For snapshot ${snapshot.name}, no element found for selector ${selector}`);
|
|
221
|
+
continue;
|
|
132
222
|
}
|
|
133
|
-
|
|
134
|
-
yield page.close();
|
|
223
|
+
locators.push(...l);
|
|
135
224
|
}
|
|
225
|
+
for (const locator of locators) {
|
|
226
|
+
let bb = yield locator.boundingBox();
|
|
227
|
+
if (bb)
|
|
228
|
+
boxes.push({
|
|
229
|
+
left: bb.x,
|
|
230
|
+
top: bb.y,
|
|
231
|
+
right: bb.x + bb.width,
|
|
232
|
+
bottom: bb.y + bb.height
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
processedOptions[ignoreOrSelectBoxes][viewportString].push(...boxes);
|
|
136
236
|
}
|
|
137
237
|
}
|
|
238
|
+
yield page.close();
|
|
239
|
+
yield context.close();
|
|
138
240
|
return {
|
|
139
241
|
processedSnapshot: {
|
|
140
242
|
name: snapshot.name,
|
|
141
243
|
url: snapshot.url,
|
|
142
244
|
dom: Buffer.from(snapshot.dom.html).toString("base64"),
|
|
245
|
+
resources: cache,
|
|
143
246
|
options: processedOptions
|
|
144
247
|
},
|
|
145
248
|
warnings: [...optionWarnings, ...snapshot.dom.warnings]
|
|
@@ -207,6 +310,10 @@ var ConfigSchema = {
|
|
|
207
310
|
minimum: 0,
|
|
208
311
|
maximum: 3e4,
|
|
209
312
|
errorMessage: "Invalid config; waitForTimeout must be > 0 and <= 30000"
|
|
313
|
+
},
|
|
314
|
+
enableJavaScript: {
|
|
315
|
+
type: "boolean",
|
|
316
|
+
errorMessage: "Invalid config; enableJavaScript must be true/false"
|
|
210
317
|
}
|
|
211
318
|
},
|
|
212
319
|
required: ["browsers", "viewports"],
|
|
@@ -337,7 +444,7 @@ var validateSnapshot = ajv.compile(SnapshotSchema);
|
|
|
337
444
|
var server_default = (ctx) => __async(void 0, null, function* () {
|
|
338
445
|
const server = fastify__default.default({ logger: false, bodyLimit: 1e7 });
|
|
339
446
|
const opts = {};
|
|
340
|
-
const SMARTUI_DOM =
|
|
447
|
+
const SMARTUI_DOM = fs2.readFileSync(path2__default.default.resolve(__dirname, "dom-serializer.js"), "utf-8");
|
|
341
448
|
server.get("/healthcheck", opts, (_, reply) => {
|
|
342
449
|
reply.code(200).send({ cliVersion: ctx.cliVersion });
|
|
343
450
|
});
|
|
@@ -474,7 +581,8 @@ var DEFAULT_CONFIG = {
|
|
|
474
581
|
[1366],
|
|
475
582
|
[360]
|
|
476
583
|
],
|
|
477
|
-
waitForTimeout: 1e3
|
|
584
|
+
waitForTimeout: 1e3,
|
|
585
|
+
enableJavaScript: false
|
|
478
586
|
}
|
|
479
587
|
};
|
|
480
588
|
function createConfig(filepath) {
|
|
@@ -484,13 +592,13 @@ function createConfig(filepath) {
|
|
|
484
592
|
console.log("Error: Config file must have .json extension");
|
|
485
593
|
return;
|
|
486
594
|
}
|
|
487
|
-
if (
|
|
595
|
+
if (fs2__default.default.existsSync(filepath)) {
|
|
488
596
|
console.log(`Error: SmartUI Config already exists: ${filepath}`);
|
|
489
597
|
console.log(`To create a new file, please specify the file name like: 'smartui config:create .smartui-config.json'`);
|
|
490
598
|
return;
|
|
491
599
|
}
|
|
492
|
-
|
|
493
|
-
|
|
600
|
+
fs2__default.default.mkdirSync(path2__default.default.dirname(filepath), { recursive: true });
|
|
601
|
+
fs2__default.default.writeFileSync(filepath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n");
|
|
494
602
|
console.log(`Created SmartUI Config: ${filepath}`);
|
|
495
603
|
}
|
|
496
604
|
function createWebStaticConfig(filepath) {
|
|
@@ -500,18 +608,18 @@ function createWebStaticConfig(filepath) {
|
|
|
500
608
|
console.log("Error: Config file must have .json extension");
|
|
501
609
|
return;
|
|
502
610
|
}
|
|
503
|
-
if (
|
|
611
|
+
if (fs2__default.default.existsSync(filepath)) {
|
|
504
612
|
console.log(`Error: web-static config already exists: ${filepath}`);
|
|
505
613
|
console.log(`To create a new file, please specify the file name like: 'smartui config:create-web-static links.json'`);
|
|
506
614
|
return;
|
|
507
615
|
}
|
|
508
|
-
|
|
509
|
-
|
|
616
|
+
fs2__default.default.mkdirSync(path2__default.default.dirname(filepath), { recursive: true });
|
|
617
|
+
fs2__default.default.writeFileSync(filepath, JSON.stringify(DEFAULT_WEB_STATIC_CONFIG, null, 2) + "\n");
|
|
510
618
|
console.log(`Created web-static config: ${filepath}`);
|
|
511
619
|
}
|
|
512
620
|
|
|
513
621
|
// package.json
|
|
514
|
-
var version = "2.0.
|
|
622
|
+
var version = "2.0.8";
|
|
515
623
|
var package_default = {
|
|
516
624
|
name: "@lambdatest/smartui-cli",
|
|
517
625
|
version,
|
|
@@ -559,39 +667,6 @@ var package_default = {
|
|
|
559
667
|
typescript: "^5.3.2"
|
|
560
668
|
}
|
|
561
669
|
};
|
|
562
|
-
function delDir(dir) {
|
|
563
|
-
if (fs__default.default.existsSync(dir)) {
|
|
564
|
-
fs__default.default.rmSync(dir, { recursive: true });
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
function scrollToBottomAndBackToTop({
|
|
568
|
-
frequency = 100,
|
|
569
|
-
timing = 8,
|
|
570
|
-
remoteWindow = window
|
|
571
|
-
} = {}) {
|
|
572
|
-
return new Promise((resolve) => {
|
|
573
|
-
let scrolls = 1;
|
|
574
|
-
let scrollLength = remoteWindow.document.body.scrollHeight / frequency;
|
|
575
|
-
(function scroll() {
|
|
576
|
-
let scrollBy = scrollLength * scrolls;
|
|
577
|
-
remoteWindow.setTimeout(() => {
|
|
578
|
-
remoteWindow.scrollTo(0, scrollBy);
|
|
579
|
-
if (scrolls < frequency) {
|
|
580
|
-
scrolls += 1;
|
|
581
|
-
scroll();
|
|
582
|
-
}
|
|
583
|
-
if (scrolls === frequency) {
|
|
584
|
-
remoteWindow.setTimeout(() => {
|
|
585
|
-
remoteWindow.scrollTo(0, 0);
|
|
586
|
-
resolve();
|
|
587
|
-
}, timing);
|
|
588
|
-
}
|
|
589
|
-
}, timing);
|
|
590
|
-
})();
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// src/lib/httpClient.ts
|
|
595
670
|
var httpClient = class {
|
|
596
671
|
constructor({ SMARTUI_CLIENT_API_URL, PROJECT_TOKEN }) {
|
|
597
672
|
this.axiosInstance = axios__default.default.create({
|
|
@@ -601,7 +676,7 @@ var httpClient = class {
|
|
|
601
676
|
}
|
|
602
677
|
request(config, log) {
|
|
603
678
|
return __async(this, null, function* () {
|
|
604
|
-
log.debug(`http request: ${
|
|
679
|
+
log.debug(`http request: ${config.method} ${config.url}`);
|
|
605
680
|
return this.axiosInstance.request(config).then((resp) => {
|
|
606
681
|
log.debug(`http response: ${JSON.stringify({
|
|
607
682
|
status: resp.status,
|
|
@@ -679,7 +754,7 @@ var httpClient = class {
|
|
|
679
754
|
}, log);
|
|
680
755
|
}
|
|
681
756
|
uploadScreenshot({ id: buildId, name: buildName, baseline }, ssPath, ssName, browserName, viewport, completed) {
|
|
682
|
-
const file =
|
|
757
|
+
const file = fs2__default.default.readFileSync(ssPath);
|
|
683
758
|
const form = new FormData__default.default();
|
|
684
759
|
form.append("screenshots", file, { filename: `${ssName}.png`, contentType: "image/png" });
|
|
685
760
|
form.append("browser", browserName);
|
|
@@ -725,7 +800,7 @@ var ctx_default = (options) => {
|
|
|
725
800
|
let config = DEFAULT_CONFIG;
|
|
726
801
|
try {
|
|
727
802
|
if (options.config) {
|
|
728
|
-
config = JSON.parse(
|
|
803
|
+
config = JSON.parse(fs2__default.default.readFileSync(options.config, "utf-8"));
|
|
729
804
|
if (config.web.resolutions) {
|
|
730
805
|
config.web.viewports = config.web.resolutions;
|
|
731
806
|
delete config.web.resolutions;
|
|
@@ -747,7 +822,8 @@ var ctx_default = (options) => {
|
|
|
747
822
|
browsers: config.web.browsers,
|
|
748
823
|
viewports,
|
|
749
824
|
waitForPageRender: config.web.waitForPageRender || 0,
|
|
750
|
-
waitForTimeout: config.web.waitForTimeout || 0
|
|
825
|
+
waitForTimeout: config.web.waitForTimeout || 0,
|
|
826
|
+
enableJavaScript: config.web.enableJavaScript || false
|
|
751
827
|
},
|
|
752
828
|
webStaticConfig: [],
|
|
753
829
|
git: {
|
|
@@ -889,6 +965,7 @@ var finalizeBuild_default = (ctx) => {
|
|
|
889
965
|
return {
|
|
890
966
|
title: `Finalizing build`,
|
|
891
967
|
task: (ctx2, task) => __async(void 0, null, function* () {
|
|
968
|
+
updateLogContext({ task: "finalizeBuild" });
|
|
892
969
|
try {
|
|
893
970
|
yield new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
894
971
|
yield ctx2.client.finalizeBuild(ctx2.build.id, ctx2.totalSnapshots, ctx2.log);
|
|
@@ -1052,12 +1129,12 @@ var command2 = new commander.Command();
|
|
|
1052
1129
|
command2.name("capture").description("Capture screenshots of static sites").argument("<file>", "Web static config file").action(function(file, _, command3) {
|
|
1053
1130
|
return __async(this, null, function* () {
|
|
1054
1131
|
let ctx = ctx_default(command3.optsWithGlobals());
|
|
1055
|
-
if (!
|
|
1132
|
+
if (!fs2__default.default.existsSync(file)) {
|
|
1056
1133
|
console.log(`Error: Web Static Config file ${file} not found.`);
|
|
1057
1134
|
return;
|
|
1058
1135
|
}
|
|
1059
1136
|
try {
|
|
1060
|
-
ctx.webStaticConfig = JSON.parse(
|
|
1137
|
+
ctx.webStaticConfig = JSON.parse(fs2__default.default.readFileSync(file, "utf8"));
|
|
1061
1138
|
if (!validateWebStaticConfig(ctx.webStaticConfig))
|
|
1062
1139
|
throw new Error(validateWebStaticConfig.errors[0].message);
|
|
1063
1140
|
} catch (error) {
|