@lambdatest/smartui-cli 3.0.11 → 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 +754 -423
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
var commander = require('commander');
|
|
5
5
|
var which = require('which');
|
|
6
6
|
var listr2 = require('listr2');
|
|
7
|
-
var
|
|
7
|
+
var chalk7 = require('chalk');
|
|
8
8
|
var path2 = require('path');
|
|
9
9
|
var fastify = require('fastify');
|
|
10
10
|
var fs5 = require('fs');
|
|
11
|
-
var test = require('@playwright/test');
|
|
12
11
|
var Ajv = require('ajv');
|
|
13
12
|
var addErrors = require('ajv-errors');
|
|
14
13
|
var winston = require('winston');
|
|
@@ -16,11 +15,13 @@ var FormData = require('form-data');
|
|
|
16
15
|
var axios = require('axios');
|
|
17
16
|
var child_process = require('child_process');
|
|
18
17
|
var spawn = require('cross-spawn');
|
|
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
|
|
|
22
23
|
var which__default = /*#__PURE__*/_interopDefault(which);
|
|
23
|
-
var
|
|
24
|
+
var chalk7__default = /*#__PURE__*/_interopDefault(chalk7);
|
|
24
25
|
var path2__default = /*#__PURE__*/_interopDefault(path2);
|
|
25
26
|
var fastify__default = /*#__PURE__*/_interopDefault(fastify);
|
|
26
27
|
var fs5__default = /*#__PURE__*/_interopDefault(fs5);
|
|
@@ -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 } },
|
|
@@ -315,327 +329,7 @@ var constants_default = {
|
|
|
315
329
|
}
|
|
316
330
|
};
|
|
317
331
|
|
|
318
|
-
// src/lib/
|
|
319
|
-
function delDir(dir) {
|
|
320
|
-
if (fs5__default.default.existsSync(dir)) {
|
|
321
|
-
fs5__default.default.rmSync(dir, { recursive: true });
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
function scrollToBottomAndBackToTop({
|
|
325
|
-
frequency = 100,
|
|
326
|
-
timing = 8,
|
|
327
|
-
remoteWindow = window
|
|
328
|
-
} = {}) {
|
|
329
|
-
return new Promise((resolve) => {
|
|
330
|
-
let scrolls = 1;
|
|
331
|
-
let scrollLength = remoteWindow.document.body.scrollHeight / frequency;
|
|
332
|
-
(function scroll() {
|
|
333
|
-
let scrollBy = scrollLength * scrolls;
|
|
334
|
-
remoteWindow.setTimeout(() => {
|
|
335
|
-
remoteWindow.scrollTo(0, scrollBy);
|
|
336
|
-
if (scrolls < frequency) {
|
|
337
|
-
scrolls += 1;
|
|
338
|
-
scroll();
|
|
339
|
-
}
|
|
340
|
-
if (scrolls === frequency) {
|
|
341
|
-
remoteWindow.setTimeout(() => {
|
|
342
|
-
remoteWindow.scrollTo(0, 0);
|
|
343
|
-
resolve();
|
|
344
|
-
}, timing);
|
|
345
|
-
}
|
|
346
|
-
}, timing);
|
|
347
|
-
})();
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
function launchBrowsers(ctx) {
|
|
351
|
-
return __async(this, null, function* () {
|
|
352
|
-
let browsers = {};
|
|
353
|
-
let launchOptions = { headless: true };
|
|
354
|
-
if (ctx.config.web) {
|
|
355
|
-
for (const browser of ctx.config.web.browsers) {
|
|
356
|
-
switch (browser) {
|
|
357
|
-
case constants_default.CHROME:
|
|
358
|
-
browsers[constants_default.CHROME] = yield test.chromium.launch(launchOptions);
|
|
359
|
-
break;
|
|
360
|
-
case constants_default.SAFARI:
|
|
361
|
-
browsers[constants_default.SAFARI] = yield test.webkit.launch(launchOptions);
|
|
362
|
-
break;
|
|
363
|
-
case constants_default.FIREFOX:
|
|
364
|
-
browsers[constants_default.FIREFOX] = yield test.firefox.launch(launchOptions);
|
|
365
|
-
break;
|
|
366
|
-
case constants_default.EDGE:
|
|
367
|
-
browsers[constants_default.EDGE] = yield test.chromium.launch(__spreadValues({ channel: constants_default.EDGE_CHANNEL }, launchOptions));
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
if (ctx.config.mobile) {
|
|
373
|
-
for (const device of ctx.config.mobile.devices) {
|
|
374
|
-
if (constants_default.SUPPORTED_MOBILE_DEVICES[device].os === "android" && !browsers[constants_default.CHROME])
|
|
375
|
-
browsers[constants_default.CHROME] = yield test.chromium.launch(launchOptions);
|
|
376
|
-
else if (constants_default.SUPPORTED_MOBILE_DEVICES[device].os === "ios" && !browsers[constants_default.SAFARI])
|
|
377
|
-
browsers[constants_default.SAFARI] = yield test.webkit.launch(launchOptions);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
return browsers;
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
function closeBrowsers(browsers) {
|
|
384
|
-
return __async(this, null, function* () {
|
|
385
|
-
var _a;
|
|
386
|
-
for (const browserName of Object.keys(browsers))
|
|
387
|
-
yield (_a = browsers[browserName]) == null ? void 0 : _a.close();
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
function getWebRenderViewports(ctx) {
|
|
391
|
-
let webRenderViewports = [];
|
|
392
|
-
if (ctx.config.web) {
|
|
393
|
-
for (const viewport of ctx.config.web.viewports) {
|
|
394
|
-
webRenderViewports.push({
|
|
395
|
-
viewport,
|
|
396
|
-
viewportString: `${viewport.width}${viewport.height ? "x" + viewport.height : ""}`,
|
|
397
|
-
fullPage: viewport.height ? false : true,
|
|
398
|
-
device: false
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
return webRenderViewports;
|
|
403
|
-
}
|
|
404
|
-
function getMobileRenderViewports(ctx) {
|
|
405
|
-
var _a;
|
|
406
|
-
let mobileRenderViewports = {};
|
|
407
|
-
mobileRenderViewports[constants_default.MOBILE_OS_IOS] = [];
|
|
408
|
-
mobileRenderViewports[constants_default.MOBILE_OS_ANDROID] = [];
|
|
409
|
-
if (ctx.config.mobile) {
|
|
410
|
-
for (const device of ctx.config.mobile.devices) {
|
|
411
|
-
let os = constants_default.SUPPORTED_MOBILE_DEVICES[device].os;
|
|
412
|
-
let { width, height } = constants_default.SUPPORTED_MOBILE_DEVICES[device].viewport;
|
|
413
|
-
let portrait = ctx.config.mobile.orientation === constants_default.MOBILE_ORIENTATION_PORTRAIT ? true : false;
|
|
414
|
-
(_a = mobileRenderViewports[os]) == null ? void 0 : _a.push({
|
|
415
|
-
viewport: { width: portrait ? width : height, height: portrait ? height : width },
|
|
416
|
-
viewportString: `${device} (${ctx.config.mobile.orientation})`,
|
|
417
|
-
fullPage: ctx.config.mobile.fullPage,
|
|
418
|
-
device: true,
|
|
419
|
-
os
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return mobileRenderViewports;
|
|
424
|
-
}
|
|
425
|
-
function getRenderViewports(ctx) {
|
|
426
|
-
let mobileRenderViewports = getMobileRenderViewports(ctx);
|
|
427
|
-
return [
|
|
428
|
-
...getWebRenderViewports(ctx),
|
|
429
|
-
...mobileRenderViewports[constants_default.MOBILE_OS_IOS],
|
|
430
|
-
...mobileRenderViewports[constants_default.MOBILE_OS_ANDROID]
|
|
431
|
-
];
|
|
432
|
-
}
|
|
433
|
-
var MAX_RESOURCE_SIZE = 15 * 1024 ** 2;
|
|
434
|
-
var ALLOWED_RESOURCES = ["document", "stylesheet", "image", "media", "font", "other"];
|
|
435
|
-
var ALLOWED_STATUSES = [200, 201];
|
|
436
|
-
var REQUEST_TIMEOUT = 1e4;
|
|
437
|
-
var MIN_VIEWPORT_HEIGHT = 1080;
|
|
438
|
-
var processSnapshot_default = (snapshot, ctx) => __async(void 0, null, function* () {
|
|
439
|
-
var _a;
|
|
440
|
-
ctx.log.debug(`Processing snapshot ${snapshot.name}`);
|
|
441
|
-
let launchOptions = { headless: true };
|
|
442
|
-
let contextOptions = {
|
|
443
|
-
javaScriptEnabled: ctx.config.enableJavaScript,
|
|
444
|
-
userAgent: constants_default.CHROME_USER_AGENT
|
|
445
|
-
};
|
|
446
|
-
if (!((_a = ctx.browser) == null ? void 0 : _a.isConnected())) {
|
|
447
|
-
if (ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY)
|
|
448
|
-
launchOptions.proxy = { server: ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY };
|
|
449
|
-
ctx.browser = yield test.chromium.launch(launchOptions);
|
|
450
|
-
ctx.log.debug(`Chromium launched with options ${JSON.stringify(launchOptions)}`);
|
|
451
|
-
}
|
|
452
|
-
const context = yield ctx.browser.newContext(contextOptions);
|
|
453
|
-
ctx.log.debug(`Browser context created with options ${JSON.stringify(contextOptions)}`);
|
|
454
|
-
const page = yield context.newPage();
|
|
455
|
-
let cache = {};
|
|
456
|
-
yield page.route("**/*", (route, request) => __async(void 0, null, function* () {
|
|
457
|
-
const requestUrl = request.url();
|
|
458
|
-
const requestHostname = new URL(requestUrl).hostname;
|
|
459
|
-
try {
|
|
460
|
-
if (/\.(mp3|mp4|wav|ogg|webm)$/i.test(request.url())) {
|
|
461
|
-
throw new Error("resource type mp3/mp4/wav/ogg/webm");
|
|
462
|
-
}
|
|
463
|
-
ctx.config.allowedHostnames.push(new URL(snapshot.url).hostname);
|
|
464
|
-
if (ctx.config.enableJavaScript)
|
|
465
|
-
ALLOWED_RESOURCES.push("script");
|
|
466
|
-
const response = yield page.request.fetch(request, { timeout: REQUEST_TIMEOUT });
|
|
467
|
-
const body = yield response.body();
|
|
468
|
-
if (!body) {
|
|
469
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
470
|
-
- skipping no response`);
|
|
471
|
-
} else if (!body.length) {
|
|
472
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
473
|
-
- skipping empty response`);
|
|
474
|
-
} else if (requestUrl === snapshot.url) {
|
|
475
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
476
|
-
- skipping root resource`);
|
|
477
|
-
} else if (!ctx.config.allowedHostnames.includes(requestHostname)) {
|
|
478
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
479
|
-
- skipping remote resource`);
|
|
480
|
-
} else if (cache[requestUrl]) {
|
|
481
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
482
|
-
- skipping already cached resource`);
|
|
483
|
-
} else if (body.length > MAX_RESOURCE_SIZE) {
|
|
484
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
485
|
-
- skipping resource larger than 15MB`);
|
|
486
|
-
} else if (!ALLOWED_STATUSES.includes(response.status())) {
|
|
487
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
488
|
-
- skipping disallowed status [${response.status()}]`);
|
|
489
|
-
} else if (!ALLOWED_RESOURCES.includes(request.resourceType())) {
|
|
490
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
491
|
-
- skipping disallowed resource type [${request.resourceType()}]`);
|
|
492
|
-
} else {
|
|
493
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
494
|
-
- content-type ${response.headers()["content-type"]}`);
|
|
495
|
-
cache[requestUrl] = {
|
|
496
|
-
body: body.toString("base64"),
|
|
497
|
-
type: response.headers()["content-type"]
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
route.fulfill({
|
|
501
|
-
status: response.status(),
|
|
502
|
-
headers: response.headers(),
|
|
503
|
-
body
|
|
504
|
-
});
|
|
505
|
-
} catch (error) {
|
|
506
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
507
|
-
- aborted due to ${error.message}`);
|
|
508
|
-
route.abort();
|
|
509
|
-
}
|
|
510
|
-
}));
|
|
511
|
-
let options = snapshot.options;
|
|
512
|
-
let optionWarnings = /* @__PURE__ */ new Set();
|
|
513
|
-
let processedOptions = {};
|
|
514
|
-
let selectors = [];
|
|
515
|
-
let ignoreOrSelectDOM;
|
|
516
|
-
let ignoreOrSelectBoxes;
|
|
517
|
-
if (options && Object.keys(options).length) {
|
|
518
|
-
ctx.log.debug(`Snapshot options: ${JSON.stringify(options)}`);
|
|
519
|
-
const isNotAllEmpty = (obj) => {
|
|
520
|
-
var _a2;
|
|
521
|
-
for (let key in obj)
|
|
522
|
-
if ((_a2 = obj[key]) == null ? void 0 : _a2.length)
|
|
523
|
-
return true;
|
|
524
|
-
return false;
|
|
525
|
-
};
|
|
526
|
-
if (options.element && Object.keys(options.element).length) {
|
|
527
|
-
if (options.element.id)
|
|
528
|
-
processedOptions.element = "#" + options.element.id;
|
|
529
|
-
else if (options.element.class)
|
|
530
|
-
processedOptions.element = "." + options.element.class;
|
|
531
|
-
else if (options.element.cssSelector)
|
|
532
|
-
processedOptions.element = options.element.cssSelector;
|
|
533
|
-
else if (options.element.xpath)
|
|
534
|
-
processedOptions.element = "xpath=" + options.element.xpath;
|
|
535
|
-
} else if (options.ignoreDOM && Object.keys(options.ignoreDOM).length && isNotAllEmpty(options.ignoreDOM)) {
|
|
536
|
-
processedOptions.ignoreBoxes = {};
|
|
537
|
-
ignoreOrSelectDOM = "ignoreDOM";
|
|
538
|
-
ignoreOrSelectBoxes = "ignoreBoxes";
|
|
539
|
-
} else if (options.selectDOM && Object.keys(options.selectDOM).length && isNotAllEmpty(options.selectDOM)) {
|
|
540
|
-
processedOptions.selectBoxes = {};
|
|
541
|
-
ignoreOrSelectDOM = "selectDOM";
|
|
542
|
-
ignoreOrSelectBoxes = "selectBoxes";
|
|
543
|
-
}
|
|
544
|
-
if (ignoreOrSelectDOM) {
|
|
545
|
-
for (const [key, value] of Object.entries(options[ignoreOrSelectDOM])) {
|
|
546
|
-
switch (key) {
|
|
547
|
-
case "id":
|
|
548
|
-
selectors.push(...value.map((e) => "#" + e));
|
|
549
|
-
break;
|
|
550
|
-
case "class":
|
|
551
|
-
selectors.push(...value.map((e) => "." + e));
|
|
552
|
-
break;
|
|
553
|
-
case "xpath":
|
|
554
|
-
selectors.push(...value.map((e) => "xpath=" + e));
|
|
555
|
-
break;
|
|
556
|
-
case "cssSelector":
|
|
557
|
-
selectors.push(...value);
|
|
558
|
-
break;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
let navigated = false;
|
|
564
|
-
let renderViewports = getRenderViewports(ctx);
|
|
565
|
-
for (const { viewport, viewportString, fullPage } of renderViewports) {
|
|
566
|
-
yield page.setViewportSize({ width: viewport.width, height: viewport.height || MIN_VIEWPORT_HEIGHT });
|
|
567
|
-
ctx.log.debug(`Page resized to ${viewport.width}x${viewport.height || MIN_VIEWPORT_HEIGHT}`);
|
|
568
|
-
if (!navigated) {
|
|
569
|
-
try {
|
|
570
|
-
yield page.goto(snapshot.url, { waitUntil: "domcontentloaded" });
|
|
571
|
-
yield new Promise((r) => setTimeout(r, 1250));
|
|
572
|
-
if (ctx.config.waitForTimeout)
|
|
573
|
-
yield page.waitForTimeout(ctx.config.waitForTimeout);
|
|
574
|
-
navigated = true;
|
|
575
|
-
ctx.log.debug(`Navigated to ${snapshot.url}`);
|
|
576
|
-
} catch (error) {
|
|
577
|
-
ctx.log.debug(`Navigation to discovery page failed; ${error}`);
|
|
578
|
-
throw new Error(error.message);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
if (ctx.config.enableJavaScript && fullPage)
|
|
582
|
-
yield page.evaluate(scrollToBottomAndBackToTop);
|
|
583
|
-
try {
|
|
584
|
-
yield page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
585
|
-
ctx.log.debug("Network idle 500ms");
|
|
586
|
-
} catch (error) {
|
|
587
|
-
ctx.log.debug(`Network idle failed due to ${error}`);
|
|
588
|
-
}
|
|
589
|
-
if (processedOptions.element) {
|
|
590
|
-
let l = yield page.locator(processedOptions.element).all();
|
|
591
|
-
if (l.length === 0) {
|
|
592
|
-
throw new Error(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${processedOptions.element}`);
|
|
593
|
-
} else if (l.length > 1) {
|
|
594
|
-
throw new Error(`for snapshot ${snapshot.name} viewport ${viewportString}, multiple elements found for selector ${processedOptions.element}`);
|
|
595
|
-
}
|
|
596
|
-
} else if (selectors.length) {
|
|
597
|
-
let locators = [];
|
|
598
|
-
if (!Array.isArray(processedOptions[ignoreOrSelectBoxes][viewportString]))
|
|
599
|
-
processedOptions[ignoreOrSelectBoxes][viewportString] = [];
|
|
600
|
-
for (const selector of selectors) {
|
|
601
|
-
let l = yield page.locator(selector).all();
|
|
602
|
-
if (l.length === 0) {
|
|
603
|
-
optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${selector}`);
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
locators.push(...l);
|
|
607
|
-
}
|
|
608
|
-
for (const locator of locators) {
|
|
609
|
-
let bb = yield locator.boundingBox();
|
|
610
|
-
if (bb)
|
|
611
|
-
processedOptions[ignoreOrSelectBoxes][viewportString].push({
|
|
612
|
-
left: bb.x,
|
|
613
|
-
top: bb.y,
|
|
614
|
-
right: bb.x + bb.width,
|
|
615
|
-
bottom: bb.y + bb.height
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
if (snapshot.dom.resources.length) {
|
|
621
|
-
for (let resource of snapshot.dom.resources) {
|
|
622
|
-
cache[resource.url] = {
|
|
623
|
-
body: resource.content,
|
|
624
|
-
type: resource.mimetype
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
return {
|
|
629
|
-
processedSnapshot: {
|
|
630
|
-
name: snapshot.name,
|
|
631
|
-
url: snapshot.url,
|
|
632
|
-
dom: Buffer.from(snapshot.dom.html).toString("base64"),
|
|
633
|
-
resources: cache,
|
|
634
|
-
options: processedOptions
|
|
635
|
-
},
|
|
636
|
-
warnings: [...optionWarnings, ...snapshot.dom.warnings]
|
|
637
|
-
};
|
|
638
|
-
});
|
|
332
|
+
// src/lib/schemaValidation.ts
|
|
639
333
|
var ajv = new Ajv__default.default({ allErrors: true });
|
|
640
334
|
ajv.addFormat("web-url", {
|
|
641
335
|
type: "string",
|
|
@@ -952,7 +646,15 @@ var validateFigmaDesignConfig = ajv.compile(FigmaDesignConfigSchema);
|
|
|
952
646
|
|
|
953
647
|
// src/lib/server.ts
|
|
954
648
|
var server_default = (ctx) => __async(void 0, null, function* () {
|
|
955
|
-
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
|
+
});
|
|
956
658
|
const opts = {};
|
|
957
659
|
const SMARTUI_DOM = fs5.readFileSync(path2__default.default.resolve(__dirname, "dom-serializer.js"), "utf-8");
|
|
958
660
|
server.get("/healthcheck", opts, (_, reply) => {
|
|
@@ -962,32 +664,22 @@ var server_default = (ctx) => __async(void 0, null, function* () {
|
|
|
962
664
|
reply.code(200).send({ data: { dom: SMARTUI_DOM } });
|
|
963
665
|
});
|
|
964
666
|
server.post("/snapshot", opts, (request, reply) => __async(void 0, null, function* () {
|
|
667
|
+
var _a;
|
|
965
668
|
let replyCode;
|
|
966
669
|
let replyBody;
|
|
967
670
|
try {
|
|
968
671
|
let { snapshot, testType } = request.body;
|
|
969
672
|
if (!validateSnapshot(snapshot))
|
|
970
673
|
throw new Error(validateSnapshot.errors[0].message);
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
ctx.totalSnapshots++;
|
|
674
|
+
ctx.testType = testType;
|
|
675
|
+
(_a = ctx.snapshotQueue) == null ? void 0 : _a.enqueue(snapshot);
|
|
974
676
|
replyCode = 200;
|
|
975
|
-
replyBody = { data: { message: "success", warnings } };
|
|
677
|
+
replyBody = { data: { message: "success", warnings: [] } };
|
|
976
678
|
} catch (error) {
|
|
977
679
|
ctx.log.debug(`snapshot failed; ${error}`);
|
|
978
680
|
replyCode = 500;
|
|
979
681
|
replyBody = { error: { message: error.message } };
|
|
980
682
|
}
|
|
981
|
-
if (ctx.browser) {
|
|
982
|
-
for (let context of ctx.browser.contexts()) {
|
|
983
|
-
for (let page of context.pages()) {
|
|
984
|
-
yield page.close();
|
|
985
|
-
ctx.log.debug(`Closed browser page`);
|
|
986
|
-
}
|
|
987
|
-
yield context.close();
|
|
988
|
-
ctx.log.debug(`Closed browser context`);
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
683
|
return reply.code(replyCode).send(replyBody);
|
|
992
684
|
}));
|
|
993
685
|
yield server.listen({ port: ctx.options.port });
|
|
@@ -1002,7 +694,6 @@ var env_default = () => {
|
|
|
1002
694
|
const {
|
|
1003
695
|
PROJECT_TOKEN = "",
|
|
1004
696
|
SMARTUI_CLIENT_API_URL = "https://api.lambdatest.com/visualui/1.0",
|
|
1005
|
-
LT_SDK_LOG_LEVEL,
|
|
1006
697
|
LT_SDK_DEBUG,
|
|
1007
698
|
SMARTUI_GIT_INFO_FILEPATH,
|
|
1008
699
|
HTTP_PROXY,
|
|
@@ -1017,7 +708,6 @@ var env_default = () => {
|
|
|
1017
708
|
return {
|
|
1018
709
|
PROJECT_TOKEN,
|
|
1019
710
|
SMARTUI_CLIENT_API_URL,
|
|
1020
|
-
LT_SDK_LOG_LEVEL,
|
|
1021
711
|
LT_SDK_DEBUG,
|
|
1022
712
|
SMARTUI_GIT_INFO_FILEPATH,
|
|
1023
713
|
HTTP_PROXY,
|
|
@@ -1030,37 +720,37 @@ var env_default = () => {
|
|
|
1030
720
|
CURRENT_BRANCH
|
|
1031
721
|
};
|
|
1032
722
|
};
|
|
1033
|
-
var logContext = {
|
|
723
|
+
var logContext = {};
|
|
1034
724
|
function updateLogContext(newContext) {
|
|
1035
725
|
logContext = __spreadValues(__spreadValues({}, logContext), newContext);
|
|
1036
726
|
}
|
|
1037
727
|
var logLevel = () => {
|
|
1038
728
|
let env = env_default();
|
|
1039
|
-
|
|
1040
|
-
return debug || env.LT_SDK_LOG_LEVEL || "info";
|
|
729
|
+
return env.LT_SDK_DEBUG === "true" ? "debug" : "info";
|
|
1041
730
|
};
|
|
1042
731
|
var logger = winston.createLogger({
|
|
1043
|
-
level: logLevel(),
|
|
1044
732
|
format: winston.format.combine(
|
|
1045
733
|
winston.format.timestamp(),
|
|
1046
734
|
winston.format.printf((info) => {
|
|
1047
735
|
let contextString = Object.values(logContext).join(" | ");
|
|
1048
|
-
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();
|
|
1049
737
|
switch (info.level) {
|
|
1050
|
-
case "debug":
|
|
1051
|
-
message = chalk8__default.default.blue(message);
|
|
1052
|
-
break;
|
|
1053
738
|
case "warn":
|
|
1054
|
-
message =
|
|
1055
|
-
break;
|
|
1056
|
-
case "error":
|
|
1057
|
-
message = chalk8__default.default.red(message);
|
|
739
|
+
message = chalk7__default.default.yellow(message);
|
|
1058
740
|
break;
|
|
1059
741
|
}
|
|
1060
742
|
return info.level === "info" ? message : `[${contextString}:${info.level}] ` + message;
|
|
1061
743
|
})
|
|
1062
744
|
),
|
|
1063
|
-
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
|
+
]
|
|
1064
754
|
});
|
|
1065
755
|
var logger_default = logger;
|
|
1066
756
|
|
|
@@ -1073,11 +763,11 @@ var startServer_default = (ctx) => {
|
|
|
1073
763
|
updateLogContext({ task: "startServer" });
|
|
1074
764
|
try {
|
|
1075
765
|
ctx2.server = yield server_default(ctx2);
|
|
1076
|
-
task.output =
|
|
766
|
+
task.output = chalk7__default.default.gray(`listening on port ${(_a = ctx2.server.addresses()[0]) == null ? void 0 : _a.port}`);
|
|
1077
767
|
task.title = "SmartUI started";
|
|
1078
768
|
} catch (error) {
|
|
1079
769
|
ctx2.log.debug(error);
|
|
1080
|
-
task.output =
|
|
770
|
+
task.output = chalk7__default.default.gray(error.message);
|
|
1081
771
|
throw new Error("SmartUI server setup failed");
|
|
1082
772
|
}
|
|
1083
773
|
}),
|
|
@@ -1091,11 +781,11 @@ var auth_default = (ctx) => {
|
|
|
1091
781
|
updateLogContext({ task: "auth" });
|
|
1092
782
|
try {
|
|
1093
783
|
yield ctx2.client.auth(ctx2.log);
|
|
1094
|
-
task.output =
|
|
784
|
+
task.output = chalk7__default.default.gray(`using project token '******#${ctx2.env.PROJECT_TOKEN.split("#").pop()}'`);
|
|
1095
785
|
task.title = "Authenticated with SmartUI";
|
|
1096
786
|
} catch (error) {
|
|
1097
787
|
ctx2.log.debug(error);
|
|
1098
|
-
task.output =
|
|
788
|
+
task.output = chalk7__default.default.gray(error.message);
|
|
1099
789
|
throw new Error("Authentication failed");
|
|
1100
790
|
}
|
|
1101
791
|
}),
|
|
@@ -1104,7 +794,7 @@ var auth_default = (ctx) => {
|
|
|
1104
794
|
};
|
|
1105
795
|
|
|
1106
796
|
// package.json
|
|
1107
|
-
var version = "
|
|
797
|
+
var version = "4.0.0";
|
|
1108
798
|
var package_default = {
|
|
1109
799
|
name: "@lambdatest/smartui-cli",
|
|
1110
800
|
version,
|
|
@@ -1144,6 +834,7 @@ var package_default = {
|
|
|
1144
834
|
fastify: "^4.24.3",
|
|
1145
835
|
"form-data": "^4.0.0",
|
|
1146
836
|
listr2: "^7.0.1",
|
|
837
|
+
sharp: "^0.33.4",
|
|
1147
838
|
tsup: "^7.2.0",
|
|
1148
839
|
which: "^4.0.0",
|
|
1149
840
|
winston: "^3.10.0"
|
|
@@ -1159,11 +850,11 @@ var httpClient = class {
|
|
|
1159
850
|
headers: { "projectToken": PROJECT_TOKEN }
|
|
1160
851
|
});
|
|
1161
852
|
}
|
|
1162
|
-
request(config,
|
|
853
|
+
request(config, log2) {
|
|
1163
854
|
return __async(this, null, function* () {
|
|
1164
|
-
|
|
855
|
+
log2.debug(`http request: ${config.method} ${config.url}`);
|
|
1165
856
|
return this.axiosInstance.request(config).then((resp) => {
|
|
1166
|
-
|
|
857
|
+
log2.debug(`http response: ${JSON.stringify({
|
|
1167
858
|
status: resp.status,
|
|
1168
859
|
headers: resp.headers,
|
|
1169
860
|
body: resp.data
|
|
@@ -1172,7 +863,7 @@ var httpClient = class {
|
|
|
1172
863
|
}).catch((error) => {
|
|
1173
864
|
var _a;
|
|
1174
865
|
if (error.response) {
|
|
1175
|
-
|
|
866
|
+
log2.debug(`http response: ${JSON.stringify({
|
|
1176
867
|
status: error.response.status,
|
|
1177
868
|
headers: error.response.headers,
|
|
1178
869
|
body: error.response.data
|
|
@@ -1180,21 +871,21 @@ var httpClient = class {
|
|
|
1180
871
|
throw new Error(((_a = error.response.data.error) == null ? void 0 : _a.message) || error.response.data.message);
|
|
1181
872
|
}
|
|
1182
873
|
if (error.request) {
|
|
1183
|
-
|
|
874
|
+
log2.debug(`http request failed: ${error.toJSON()}`);
|
|
1184
875
|
throw new Error(error.toJSON().message);
|
|
1185
876
|
}
|
|
1186
|
-
|
|
877
|
+
log2.debug(`http request failed: ${error.message}`);
|
|
1187
878
|
throw new Error(error.message);
|
|
1188
879
|
});
|
|
1189
880
|
});
|
|
1190
881
|
}
|
|
1191
|
-
auth(
|
|
882
|
+
auth(log2) {
|
|
1192
883
|
return this.request({
|
|
1193
884
|
url: "/token/verify",
|
|
1194
885
|
method: "GET"
|
|
1195
|
-
},
|
|
886
|
+
}, log2);
|
|
1196
887
|
}
|
|
1197
|
-
createBuild(git, config,
|
|
888
|
+
createBuild(git, config, log2) {
|
|
1198
889
|
return this.request({
|
|
1199
890
|
url: "/build",
|
|
1200
891
|
method: "POST",
|
|
@@ -1202,9 +893,9 @@ var httpClient = class {
|
|
|
1202
893
|
git,
|
|
1203
894
|
config
|
|
1204
895
|
}
|
|
1205
|
-
},
|
|
896
|
+
}, log2);
|
|
1206
897
|
}
|
|
1207
|
-
finalizeBuild(buildId, totalSnapshots,
|
|
898
|
+
finalizeBuild(buildId, totalSnapshots, log2) {
|
|
1208
899
|
let params = { buildId };
|
|
1209
900
|
if (totalSnapshots > -1)
|
|
1210
901
|
params.totalSnapshots = totalSnapshots;
|
|
@@ -1212,23 +903,23 @@ var httpClient = class {
|
|
|
1212
903
|
url: "/build",
|
|
1213
904
|
method: "DELETE",
|
|
1214
905
|
params
|
|
1215
|
-
},
|
|
906
|
+
}, log2);
|
|
1216
907
|
}
|
|
1217
|
-
uploadSnapshot(
|
|
908
|
+
uploadSnapshot(ctx, snapshot) {
|
|
1218
909
|
return this.request({
|
|
1219
|
-
url: `/builds/${
|
|
910
|
+
url: `/builds/${ctx.build.id}/snapshot`,
|
|
1220
911
|
method: "POST",
|
|
1221
912
|
headers: { "Content-Type": "application/json" },
|
|
1222
913
|
data: {
|
|
1223
914
|
snapshot,
|
|
1224
915
|
test: {
|
|
1225
|
-
type: testType,
|
|
916
|
+
type: ctx.testType,
|
|
1226
917
|
source: "cli"
|
|
1227
918
|
}
|
|
1228
919
|
}
|
|
1229
|
-
}, log);
|
|
920
|
+
}, ctx.log);
|
|
1230
921
|
}
|
|
1231
|
-
uploadScreenshot({ id: buildId, name: buildName, baseline }, ssPath, ssName, browserName, viewport,
|
|
922
|
+
uploadScreenshot({ id: buildId, name: buildName, baseline }, ssPath, ssName, browserName, viewport, log2) {
|
|
1232
923
|
browserName = browserName === constants_default.SAFARI ? constants_default.WEBKIT : browserName;
|
|
1233
924
|
const file = fs5__default.default.readFileSync(ssPath);
|
|
1234
925
|
const form = new FormData__default.default();
|
|
@@ -1245,7 +936,7 @@ var httpClient = class {
|
|
|
1245
936
|
headers: form.getHeaders(),
|
|
1246
937
|
data: form
|
|
1247
938
|
}).then(() => {
|
|
1248
|
-
|
|
939
|
+
log2.debug(`${ssName} for ${browserName} ${viewport} uploaded successfully`);
|
|
1249
940
|
}).catch((error) => {
|
|
1250
941
|
if (error.response) {
|
|
1251
942
|
throw new Error(error.response.data.error.message);
|
|
@@ -1256,7 +947,7 @@ var httpClient = class {
|
|
|
1256
947
|
throw new Error(error.message);
|
|
1257
948
|
});
|
|
1258
949
|
}
|
|
1259
|
-
checkUpdate(
|
|
950
|
+
checkUpdate(log2) {
|
|
1260
951
|
return this.request({
|
|
1261
952
|
url: `/packageinfo`,
|
|
1262
953
|
method: "GET",
|
|
@@ -1265,9 +956,9 @@ var httpClient = class {
|
|
|
1265
956
|
packageName: package_default.name,
|
|
1266
957
|
packageVersion: package_default.version
|
|
1267
958
|
}
|
|
1268
|
-
},
|
|
959
|
+
}, log2);
|
|
1269
960
|
}
|
|
1270
|
-
getFigmaFilesAndImages(figmaFileToken, figmaToken, queryParams, authToken, depth, markBaseline, buildName,
|
|
961
|
+
getFigmaFilesAndImages(figmaFileToken, figmaToken, queryParams, authToken, depth, markBaseline, buildName, log2) {
|
|
1271
962
|
const requestBody = {
|
|
1272
963
|
figma_file_token: figmaFileToken,
|
|
1273
964
|
figma_token: figmaToken,
|
|
@@ -1284,7 +975,34 @@ var httpClient = class {
|
|
|
1284
975
|
"Content-Type": "application/json"
|
|
1285
976
|
},
|
|
1286
977
|
data: JSON.stringify(requestBody)
|
|
1287
|
-
},
|
|
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);
|
|
1288
1006
|
}
|
|
1289
1007
|
};
|
|
1290
1008
|
var ctx_default = (options) => {
|
|
@@ -1294,6 +1012,10 @@ var ctx_default = (options) => {
|
|
|
1294
1012
|
let mobileConfig;
|
|
1295
1013
|
let config = constants_default.DEFAULT_CONFIG;
|
|
1296
1014
|
let port;
|
|
1015
|
+
let resolutionOff;
|
|
1016
|
+
let extensionFiles;
|
|
1017
|
+
let ignoreStripExtension;
|
|
1018
|
+
let ignoreFilePattern;
|
|
1297
1019
|
try {
|
|
1298
1020
|
if (options.config) {
|
|
1299
1021
|
config = JSON.parse(fs5__default.default.readFileSync(options.config, "utf-8"));
|
|
@@ -1309,6 +1031,10 @@ var ctx_default = (options) => {
|
|
|
1309
1031
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
1310
1032
|
throw new Error("Invalid port number. Port number must be an integer between 1 and 65535.");
|
|
1311
1033
|
}
|
|
1034
|
+
resolutionOff = options.ignoreResolutions || false;
|
|
1035
|
+
extensionFiles = options.files || ["png", "jpeg", "jpg"];
|
|
1036
|
+
ignoreStripExtension = options.removeExtensions || false;
|
|
1037
|
+
ignoreFilePattern = options.ignoreDir || [];
|
|
1312
1038
|
} catch (error) {
|
|
1313
1039
|
console.log(`[smartui] Error: ${error.message}`);
|
|
1314
1040
|
process.exit();
|
|
@@ -1337,6 +1063,7 @@ var ctx_default = (options) => {
|
|
|
1337
1063
|
enableJavaScript: config.enableJavaScript || false,
|
|
1338
1064
|
allowedHostnames: config.allowedHostnames || []
|
|
1339
1065
|
},
|
|
1066
|
+
uploadFilePath: "",
|
|
1340
1067
|
webStaticConfig: [],
|
|
1341
1068
|
git: {
|
|
1342
1069
|
branch: "",
|
|
@@ -1356,16 +1083,20 @@ var ctx_default = (options) => {
|
|
|
1356
1083
|
parallel: options.parallel ? true : false,
|
|
1357
1084
|
markBaseline: options.markBaseline ? true : false,
|
|
1358
1085
|
buildName: options.buildName || "",
|
|
1359
|
-
port
|
|
1086
|
+
port,
|
|
1087
|
+
ignoreResolutions: resolutionOff,
|
|
1088
|
+
fileExtension: extensionFiles,
|
|
1089
|
+
stripExtension: ignoreStripExtension,
|
|
1090
|
+
ignorePattern: ignoreFilePattern
|
|
1360
1091
|
},
|
|
1361
1092
|
cliVersion: version,
|
|
1362
1093
|
totalSnapshots: -1
|
|
1363
1094
|
};
|
|
1364
1095
|
};
|
|
1365
|
-
function executeCommand(
|
|
1096
|
+
function executeCommand(command5) {
|
|
1366
1097
|
let dst = process.cwd();
|
|
1367
1098
|
try {
|
|
1368
|
-
return child_process.execSync(
|
|
1099
|
+
return child_process.execSync(command5, {
|
|
1369
1100
|
cwd: dst,
|
|
1370
1101
|
stdio: ["ignore"],
|
|
1371
1102
|
encoding: "utf-8"
|
|
@@ -1396,8 +1127,8 @@ var git_default = (ctx) => {
|
|
|
1396
1127
|
} else {
|
|
1397
1128
|
const splitCharacter = "<##>";
|
|
1398
1129
|
const prettyFormat = ["%h", "%H", "%s", "%f", "%b", "%at", "%ct", "%an", "%ae", "%cn", "%ce", "%N", ""];
|
|
1399
|
-
const
|
|
1400
|
-
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);
|
|
1401
1132
|
var branchAndTags = res[res.length - 1].split("\n").filter((n) => n);
|
|
1402
1133
|
var branch = ctx.env.CURRENT_BRANCH || branchAndTags[0];
|
|
1403
1134
|
branchAndTags.slice(1);
|
|
@@ -1423,11 +1154,11 @@ var getGitInfo_default = (ctx) => {
|
|
|
1423
1154
|
}
|
|
1424
1155
|
try {
|
|
1425
1156
|
ctx2.git = git_default(ctx2);
|
|
1426
|
-
task.output =
|
|
1157
|
+
task.output = chalk7__default.default.gray(`branch: ${ctx2.git.branch}, commit: ${ctx2.git.commitId}, author: ${ctx2.git.commitAuthor}`);
|
|
1427
1158
|
task.title = "Fetched git information";
|
|
1428
1159
|
} catch (error) {
|
|
1429
1160
|
ctx2.log.debug(error);
|
|
1430
|
-
task.output =
|
|
1161
|
+
task.output = chalk7__default.default.gray(`${error.message}`);
|
|
1431
1162
|
throw new Error("Error fetching git repo details");
|
|
1432
1163
|
}
|
|
1433
1164
|
}),
|
|
@@ -1447,11 +1178,11 @@ var createBuild_default = (ctx) => {
|
|
|
1447
1178
|
url: resp.data.buildURL,
|
|
1448
1179
|
baseline: resp.data.baseline
|
|
1449
1180
|
};
|
|
1450
|
-
task.output =
|
|
1181
|
+
task.output = chalk7__default.default.gray(`build id: ${resp.data.buildId}`);
|
|
1451
1182
|
task.title = "SmartUI build created";
|
|
1452
1183
|
} catch (error) {
|
|
1453
1184
|
ctx2.log.debug(error);
|
|
1454
|
-
task.output =
|
|
1185
|
+
task.output = chalk7__default.default.gray(error.message);
|
|
1455
1186
|
throw new Error("SmartUI build creation failed");
|
|
1456
1187
|
}
|
|
1457
1188
|
}),
|
|
@@ -1470,13 +1201,13 @@ var exec_default = (ctx) => {
|
|
|
1470
1201
|
let totalOutput = "";
|
|
1471
1202
|
const output = listr2.createWritable((chunk) => {
|
|
1472
1203
|
totalOutput += chunk;
|
|
1473
|
-
task.output =
|
|
1204
|
+
task.output = chalk7__default.default.gray(totalOutput);
|
|
1474
1205
|
});
|
|
1475
1206
|
(_b = childProcess.stdout) == null ? void 0 : _b.pipe(output);
|
|
1476
1207
|
(_c = childProcess.stderr) == null ? void 0 : _c.pipe(output);
|
|
1477
1208
|
childProcess.on("error", (error) => {
|
|
1478
1209
|
var _a3;
|
|
1479
|
-
task.output =
|
|
1210
|
+
task.output = chalk7__default.default.gray(`error: ${error.message}`);
|
|
1480
1211
|
throw new Error(`Execution of '${(_a3 = ctx2.args.execCommand) == null ? void 0 : _a3.join(" ")}' failed`);
|
|
1481
1212
|
});
|
|
1482
1213
|
childProcess.on("close", (code, signal) => __async(void 0, null, function* () {
|
|
@@ -1494,37 +1225,469 @@ var exec_default = (ctx) => {
|
|
|
1494
1225
|
exitOnError: false
|
|
1495
1226
|
};
|
|
1496
1227
|
};
|
|
1228
|
+
var processSnapshot_default = (ctx) => {
|
|
1229
|
+
return {
|
|
1230
|
+
title: `Processing snapshots`,
|
|
1231
|
+
task: (ctx2, task) => __async(void 0, null, function* () {
|
|
1232
|
+
var _a;
|
|
1233
|
+
try {
|
|
1234
|
+
yield new Promise((resolve) => {
|
|
1235
|
+
let output2 = "";
|
|
1236
|
+
const intervalId = setInterval(() => {
|
|
1237
|
+
var _a2, _b, _c;
|
|
1238
|
+
if (((_a2 = ctx2.snapshotQueue) == null ? void 0 : _a2.isEmpty()) && !((_b = ctx2.snapshotQueue) == null ? void 0 : _b.isProcessing())) {
|
|
1239
|
+
clearInterval(intervalId);
|
|
1240
|
+
resolve();
|
|
1241
|
+
} else {
|
|
1242
|
+
task.title = `Processing snapshot ${(_c = ctx2.snapshotQueue) == null ? void 0 : _c.getProcessingSnapshot()}`;
|
|
1243
|
+
}
|
|
1244
|
+
}, 500);
|
|
1245
|
+
});
|
|
1246
|
+
let output = "";
|
|
1247
|
+
for (let snapshot of (_a = ctx2.snapshotQueue) == null ? void 0 : _a.getProcessedSnapshots()) {
|
|
1248
|
+
if (snapshot.error)
|
|
1249
|
+
output += `${chalk7__default.default.red("\u2717")} ${chalk7__default.default.gray(`${snapshot.name}
|
|
1250
|
+
[error] ${snapshot.error}`)}
|
|
1251
|
+
`;
|
|
1252
|
+
else
|
|
1253
|
+
output += `${chalk7__default.default.green("\u2713")} ${chalk7__default.default.gray(snapshot.name)}
|
|
1254
|
+
${snapshot.warnings.length ? chalk7__default.default.gray(`[warning] ${snapshot.warnings.join("\n[warning] ")}
|
|
1255
|
+
`) : ""}`;
|
|
1256
|
+
}
|
|
1257
|
+
task.output = output;
|
|
1258
|
+
task.title = "Processed snapshots";
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
ctx2.log.debug(error);
|
|
1261
|
+
task.output = chalk7__default.default.gray(error.message);
|
|
1262
|
+
throw new Error("Processing of snapshots failed");
|
|
1263
|
+
}
|
|
1264
|
+
}),
|
|
1265
|
+
rendererOptions: { persistentOutput: true }
|
|
1266
|
+
};
|
|
1267
|
+
};
|
|
1497
1268
|
var finalizeBuild_default = (ctx) => {
|
|
1498
1269
|
return {
|
|
1499
1270
|
title: `Finalizing build`,
|
|
1500
1271
|
task: (ctx2, task) => __async(void 0, null, function* () {
|
|
1272
|
+
var _a, _b;
|
|
1501
1273
|
updateLogContext({ task: "finalizeBuild" });
|
|
1502
1274
|
try {
|
|
1503
|
-
yield new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1504
1275
|
yield ctx2.client.finalizeBuild(ctx2.build.id, ctx2.totalSnapshots, ctx2.log);
|
|
1505
|
-
task.output =
|
|
1276
|
+
task.output = chalk7__default.default.gray(`build url: ${ctx2.build.url}`);
|
|
1506
1277
|
task.title = "Finalized build";
|
|
1507
1278
|
} catch (error) {
|
|
1508
1279
|
ctx2.log.debug(error);
|
|
1509
|
-
task.output =
|
|
1280
|
+
task.output = chalk7__default.default.gray(error.message);
|
|
1510
1281
|
throw new Error("Finalize build failed");
|
|
1511
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
|
+
}
|
|
1512
1294
|
}),
|
|
1513
1295
|
rendererOptions: { persistentOutput: true }
|
|
1514
1296
|
};
|
|
1515
1297
|
};
|
|
1298
|
+
function delDir(dir) {
|
|
1299
|
+
if (fs5__default.default.existsSync(dir)) {
|
|
1300
|
+
fs5__default.default.rmSync(dir, { recursive: true });
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
function scrollToBottomAndBackToTop({
|
|
1304
|
+
frequency = 100,
|
|
1305
|
+
timing = 8,
|
|
1306
|
+
remoteWindow = window
|
|
1307
|
+
} = {}) {
|
|
1308
|
+
return new Promise((resolve) => {
|
|
1309
|
+
let scrolls = 1;
|
|
1310
|
+
let scrollLength = remoteWindow.document.body.scrollHeight / frequency;
|
|
1311
|
+
(function scroll() {
|
|
1312
|
+
let scrollBy = scrollLength * scrolls;
|
|
1313
|
+
remoteWindow.setTimeout(() => {
|
|
1314
|
+
remoteWindow.scrollTo(0, scrollBy);
|
|
1315
|
+
if (scrolls < frequency) {
|
|
1316
|
+
scrolls += 1;
|
|
1317
|
+
scroll();
|
|
1318
|
+
}
|
|
1319
|
+
if (scrolls === frequency) {
|
|
1320
|
+
remoteWindow.setTimeout(() => {
|
|
1321
|
+
remoteWindow.scrollTo(0, 0);
|
|
1322
|
+
resolve();
|
|
1323
|
+
}, timing);
|
|
1324
|
+
}
|
|
1325
|
+
}, timing);
|
|
1326
|
+
})();
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
function launchBrowsers(ctx) {
|
|
1330
|
+
return __async(this, null, function* () {
|
|
1331
|
+
let browsers = {};
|
|
1332
|
+
let launchOptions = { headless: true };
|
|
1333
|
+
if (ctx.config.web) {
|
|
1334
|
+
for (const browser of ctx.config.web.browsers) {
|
|
1335
|
+
switch (browser) {
|
|
1336
|
+
case constants_default.CHROME:
|
|
1337
|
+
browsers[constants_default.CHROME] = yield test.chromium.launch(launchOptions);
|
|
1338
|
+
break;
|
|
1339
|
+
case constants_default.SAFARI:
|
|
1340
|
+
browsers[constants_default.SAFARI] = yield test.webkit.launch(launchOptions);
|
|
1341
|
+
break;
|
|
1342
|
+
case constants_default.FIREFOX:
|
|
1343
|
+
browsers[constants_default.FIREFOX] = yield test.firefox.launch(launchOptions);
|
|
1344
|
+
break;
|
|
1345
|
+
case constants_default.EDGE:
|
|
1346
|
+
browsers[constants_default.EDGE] = yield test.chromium.launch(__spreadValues({ channel: constants_default.EDGE_CHANNEL }, launchOptions));
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
if (ctx.config.mobile) {
|
|
1352
|
+
for (const device of ctx.config.mobile.devices) {
|
|
1353
|
+
if (constants_default.SUPPORTED_MOBILE_DEVICES[device].os === "android" && !browsers[constants_default.CHROME])
|
|
1354
|
+
browsers[constants_default.CHROME] = yield test.chromium.launch(launchOptions);
|
|
1355
|
+
else if (constants_default.SUPPORTED_MOBILE_DEVICES[device].os === "ios" && !browsers[constants_default.SAFARI])
|
|
1356
|
+
browsers[constants_default.SAFARI] = yield test.webkit.launch(launchOptions);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
return browsers;
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
function closeBrowsers(browsers) {
|
|
1363
|
+
return __async(this, null, function* () {
|
|
1364
|
+
var _a;
|
|
1365
|
+
for (const browserName of Object.keys(browsers))
|
|
1366
|
+
yield (_a = browsers[browserName]) == null ? void 0 : _a.close();
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
function getWebRenderViewports(ctx) {
|
|
1370
|
+
let webRenderViewports = [];
|
|
1371
|
+
if (ctx.config.web) {
|
|
1372
|
+
for (const viewport of ctx.config.web.viewports) {
|
|
1373
|
+
webRenderViewports.push({
|
|
1374
|
+
viewport,
|
|
1375
|
+
viewportString: `${viewport.width}${viewport.height ? "x" + viewport.height : ""}`,
|
|
1376
|
+
fullPage: viewport.height ? false : true,
|
|
1377
|
+
device: false
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
return webRenderViewports;
|
|
1382
|
+
}
|
|
1383
|
+
function getMobileRenderViewports(ctx) {
|
|
1384
|
+
var _a;
|
|
1385
|
+
let mobileRenderViewports = {};
|
|
1386
|
+
mobileRenderViewports[constants_default.MOBILE_OS_IOS] = [];
|
|
1387
|
+
mobileRenderViewports[constants_default.MOBILE_OS_ANDROID] = [];
|
|
1388
|
+
if (ctx.config.mobile) {
|
|
1389
|
+
for (const device of ctx.config.mobile.devices) {
|
|
1390
|
+
let os = constants_default.SUPPORTED_MOBILE_DEVICES[device].os;
|
|
1391
|
+
let { width, height } = constants_default.SUPPORTED_MOBILE_DEVICES[device].viewport;
|
|
1392
|
+
let portrait = ctx.config.mobile.orientation === constants_default.MOBILE_ORIENTATION_PORTRAIT ? true : false;
|
|
1393
|
+
(_a = mobileRenderViewports[os]) == null ? void 0 : _a.push({
|
|
1394
|
+
viewport: { width: portrait ? width : height, height: portrait ? height : width },
|
|
1395
|
+
viewportString: `${device} (${ctx.config.mobile.orientation})`,
|
|
1396
|
+
fullPage: ctx.config.mobile.fullPage,
|
|
1397
|
+
device: true,
|
|
1398
|
+
os
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
return mobileRenderViewports;
|
|
1403
|
+
}
|
|
1404
|
+
function getRenderViewports(ctx) {
|
|
1405
|
+
let mobileRenderViewports = getMobileRenderViewports(ctx);
|
|
1406
|
+
return [
|
|
1407
|
+
...getWebRenderViewports(ctx),
|
|
1408
|
+
...mobileRenderViewports[constants_default.MOBILE_OS_IOS],
|
|
1409
|
+
...mobileRenderViewports[constants_default.MOBILE_OS_ANDROID]
|
|
1410
|
+
];
|
|
1411
|
+
}
|
|
1412
|
+
var MAX_RESOURCE_SIZE = 15 * 1024 ** 2;
|
|
1413
|
+
var ALLOWED_RESOURCES = ["document", "stylesheet", "image", "media", "font", "other"];
|
|
1414
|
+
var ALLOWED_STATUSES = [200, 201];
|
|
1415
|
+
var REQUEST_TIMEOUT = 1e4;
|
|
1416
|
+
var MIN_VIEWPORT_HEIGHT = 1080;
|
|
1417
|
+
var Queue = class {
|
|
1418
|
+
constructor(ctx) {
|
|
1419
|
+
this.snapshots = [];
|
|
1420
|
+
this.processedSnapshots = [];
|
|
1421
|
+
this.processing = false;
|
|
1422
|
+
this.processingSnapshot = "";
|
|
1423
|
+
this.ctx = ctx;
|
|
1424
|
+
}
|
|
1425
|
+
enqueue(item) {
|
|
1426
|
+
this.snapshots.push(item);
|
|
1427
|
+
if (!this.processing) {
|
|
1428
|
+
this.processing = true;
|
|
1429
|
+
this.processNext();
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
processNext() {
|
|
1433
|
+
return __async(this, null, function* () {
|
|
1434
|
+
if (!this.isEmpty()) {
|
|
1435
|
+
const snapshot = this.snapshots.shift();
|
|
1436
|
+
try {
|
|
1437
|
+
this.processingSnapshot = snapshot == null ? void 0 : snapshot.name;
|
|
1438
|
+
let { processedSnapshot, warnings } = yield processSnapshot(snapshot, this.ctx);
|
|
1439
|
+
yield this.ctx.client.uploadSnapshot(this.ctx, processedSnapshot);
|
|
1440
|
+
this.ctx.totalSnapshots++;
|
|
1441
|
+
this.processedSnapshots.push({ name: snapshot.name, warnings });
|
|
1442
|
+
} catch (error) {
|
|
1443
|
+
this.ctx.log.debug(`snapshot failed; ${error}`);
|
|
1444
|
+
this.processedSnapshots.push({ name: snapshot.name, error: error.message });
|
|
1445
|
+
}
|
|
1446
|
+
if (this.ctx.browser) {
|
|
1447
|
+
for (let context of this.ctx.browser.contexts()) {
|
|
1448
|
+
for (let page of context.pages()) {
|
|
1449
|
+
yield page.close();
|
|
1450
|
+
this.ctx.log.debug(`Closed browser page for snapshot ${snapshot.name}`);
|
|
1451
|
+
}
|
|
1452
|
+
yield context.close();
|
|
1453
|
+
this.ctx.log.debug(`Closed browser context for snapshot ${snapshot.name}`);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
this.processNext();
|
|
1457
|
+
} else {
|
|
1458
|
+
this.processing = false;
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
isProcessing() {
|
|
1463
|
+
return this.processing;
|
|
1464
|
+
}
|
|
1465
|
+
getProcessingSnapshot() {
|
|
1466
|
+
return this.processingSnapshot;
|
|
1467
|
+
}
|
|
1468
|
+
getProcessedSnapshots() {
|
|
1469
|
+
return this.processedSnapshots;
|
|
1470
|
+
}
|
|
1471
|
+
isEmpty() {
|
|
1472
|
+
return this.snapshots.length ? false : true;
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
function processSnapshot(snapshot, ctx) {
|
|
1476
|
+
return __async(this, null, function* () {
|
|
1477
|
+
var _a;
|
|
1478
|
+
updateLogContext({ task: "discovery" });
|
|
1479
|
+
ctx.log.debug(`Processing snapshot ${snapshot.name}`);
|
|
1480
|
+
let launchOptions = { headless: true };
|
|
1481
|
+
let contextOptions = {
|
|
1482
|
+
javaScriptEnabled: ctx.config.enableJavaScript,
|
|
1483
|
+
userAgent: constants_default.CHROME_USER_AGENT
|
|
1484
|
+
};
|
|
1485
|
+
if (!((_a = ctx.browser) == null ? void 0 : _a.isConnected())) {
|
|
1486
|
+
if (ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY)
|
|
1487
|
+
launchOptions.proxy = { server: ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY };
|
|
1488
|
+
ctx.browser = yield test.firefox.launch(launchOptions);
|
|
1489
|
+
ctx.log.debug(`Firefox launched with options ${JSON.stringify(launchOptions)}`);
|
|
1490
|
+
}
|
|
1491
|
+
const context = yield ctx.browser.newContext(contextOptions);
|
|
1492
|
+
ctx.log.debug(`Browser context created with options ${JSON.stringify(contextOptions)}`);
|
|
1493
|
+
const page = yield context.newPage();
|
|
1494
|
+
let cache = {};
|
|
1495
|
+
yield page.route("**/*", (route, request) => __async(this, null, function* () {
|
|
1496
|
+
const requestUrl = request.url();
|
|
1497
|
+
const requestHostname = new URL(requestUrl).hostname;
|
|
1498
|
+
try {
|
|
1499
|
+
if (/\.(mp3|mp4|wav|ogg|webm)$/i.test(request.url())) {
|
|
1500
|
+
throw new Error("resource type mp3/mp4/wav/ogg/webm");
|
|
1501
|
+
}
|
|
1502
|
+
ctx.config.allowedHostnames.push(new URL(snapshot.url).hostname);
|
|
1503
|
+
if (ctx.config.enableJavaScript)
|
|
1504
|
+
ALLOWED_RESOURCES.push("script");
|
|
1505
|
+
const response = yield page.request.fetch(request, { timeout: REQUEST_TIMEOUT });
|
|
1506
|
+
const body = yield response.body();
|
|
1507
|
+
if (!body) {
|
|
1508
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1509
|
+
- skipping no response`);
|
|
1510
|
+
} else if (!body.length) {
|
|
1511
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1512
|
+
- skipping empty response`);
|
|
1513
|
+
} else if (requestUrl === snapshot.url) {
|
|
1514
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1515
|
+
- skipping root resource`);
|
|
1516
|
+
} else if (!ctx.config.allowedHostnames.includes(requestHostname)) {
|
|
1517
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1518
|
+
- skipping remote resource`);
|
|
1519
|
+
} else if (cache[requestUrl]) {
|
|
1520
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1521
|
+
- skipping already cached resource`);
|
|
1522
|
+
} else if (body.length > MAX_RESOURCE_SIZE) {
|
|
1523
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1524
|
+
- skipping resource larger than 15MB`);
|
|
1525
|
+
} else if (!ALLOWED_STATUSES.includes(response.status())) {
|
|
1526
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1527
|
+
- skipping disallowed status [${response.status()}]`);
|
|
1528
|
+
} else if (!ALLOWED_RESOURCES.includes(request.resourceType())) {
|
|
1529
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1530
|
+
- skipping disallowed resource type [${request.resourceType()}]`);
|
|
1531
|
+
} else {
|
|
1532
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1533
|
+
- content-type ${response.headers()["content-type"]}`);
|
|
1534
|
+
cache[requestUrl] = {
|
|
1535
|
+
body: body.toString("base64"),
|
|
1536
|
+
type: response.headers()["content-type"]
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
route.fulfill({
|
|
1540
|
+
status: response.status(),
|
|
1541
|
+
headers: response.headers(),
|
|
1542
|
+
body
|
|
1543
|
+
});
|
|
1544
|
+
} catch (error) {
|
|
1545
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1546
|
+
- aborted due to ${error.message}`);
|
|
1547
|
+
route.abort();
|
|
1548
|
+
}
|
|
1549
|
+
}));
|
|
1550
|
+
let options = snapshot.options;
|
|
1551
|
+
let optionWarnings = /* @__PURE__ */ new Set();
|
|
1552
|
+
let processedOptions = {};
|
|
1553
|
+
let selectors = [];
|
|
1554
|
+
let ignoreOrSelectDOM;
|
|
1555
|
+
let ignoreOrSelectBoxes;
|
|
1556
|
+
if (options && Object.keys(options).length) {
|
|
1557
|
+
ctx.log.debug(`Snapshot options: ${JSON.stringify(options)}`);
|
|
1558
|
+
const isNotAllEmpty = (obj) => {
|
|
1559
|
+
var _a2;
|
|
1560
|
+
for (let key in obj)
|
|
1561
|
+
if ((_a2 = obj[key]) == null ? void 0 : _a2.length)
|
|
1562
|
+
return true;
|
|
1563
|
+
return false;
|
|
1564
|
+
};
|
|
1565
|
+
if (options.element && Object.keys(options.element).length) {
|
|
1566
|
+
if (options.element.id)
|
|
1567
|
+
processedOptions.element = "#" + options.element.id;
|
|
1568
|
+
else if (options.element.class)
|
|
1569
|
+
processedOptions.element = "." + options.element.class;
|
|
1570
|
+
else if (options.element.cssSelector)
|
|
1571
|
+
processedOptions.element = options.element.cssSelector;
|
|
1572
|
+
else if (options.element.xpath)
|
|
1573
|
+
processedOptions.element = "xpath=" + options.element.xpath;
|
|
1574
|
+
} else if (options.ignoreDOM && Object.keys(options.ignoreDOM).length && isNotAllEmpty(options.ignoreDOM)) {
|
|
1575
|
+
processedOptions.ignoreBoxes = {};
|
|
1576
|
+
ignoreOrSelectDOM = "ignoreDOM";
|
|
1577
|
+
ignoreOrSelectBoxes = "ignoreBoxes";
|
|
1578
|
+
} else if (options.selectDOM && Object.keys(options.selectDOM).length && isNotAllEmpty(options.selectDOM)) {
|
|
1579
|
+
processedOptions.selectBoxes = {};
|
|
1580
|
+
ignoreOrSelectDOM = "selectDOM";
|
|
1581
|
+
ignoreOrSelectBoxes = "selectBoxes";
|
|
1582
|
+
}
|
|
1583
|
+
if (ignoreOrSelectDOM) {
|
|
1584
|
+
for (const [key, value] of Object.entries(options[ignoreOrSelectDOM])) {
|
|
1585
|
+
switch (key) {
|
|
1586
|
+
case "id":
|
|
1587
|
+
selectors.push(...value.map((e) => "#" + e));
|
|
1588
|
+
break;
|
|
1589
|
+
case "class":
|
|
1590
|
+
selectors.push(...value.map((e) => "." + e));
|
|
1591
|
+
break;
|
|
1592
|
+
case "xpath":
|
|
1593
|
+
selectors.push(...value.map((e) => "xpath=" + e));
|
|
1594
|
+
break;
|
|
1595
|
+
case "cssSelector":
|
|
1596
|
+
selectors.push(...value);
|
|
1597
|
+
break;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
let navigated = false;
|
|
1603
|
+
let renderViewports = getRenderViewports(ctx);
|
|
1604
|
+
for (const { viewport, viewportString, fullPage } of renderViewports) {
|
|
1605
|
+
yield page.setViewportSize({ width: viewport.width, height: viewport.height || MIN_VIEWPORT_HEIGHT });
|
|
1606
|
+
ctx.log.debug(`Page resized to ${viewport.width}x${viewport.height || MIN_VIEWPORT_HEIGHT}`);
|
|
1607
|
+
if (!navigated) {
|
|
1608
|
+
try {
|
|
1609
|
+
yield page.goto(snapshot.url, { waitUntil: "domcontentloaded" });
|
|
1610
|
+
yield new Promise((r) => setTimeout(r, 1250));
|
|
1611
|
+
if (ctx.config.waitForTimeout)
|
|
1612
|
+
yield page.waitForTimeout(ctx.config.waitForTimeout);
|
|
1613
|
+
navigated = true;
|
|
1614
|
+
ctx.log.debug(`Navigated to ${snapshot.url}`);
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
ctx.log.debug(`Navigation to discovery page failed; ${error}`);
|
|
1617
|
+
throw new Error(error.message);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
if (ctx.config.enableJavaScript && fullPage)
|
|
1621
|
+
yield page.evaluate(scrollToBottomAndBackToTop);
|
|
1622
|
+
try {
|
|
1623
|
+
yield page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
1624
|
+
ctx.log.debug("Network idle 500ms");
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
ctx.log.debug(`Network idle failed due to ${error}`);
|
|
1627
|
+
}
|
|
1628
|
+
if (processedOptions.element) {
|
|
1629
|
+
let l = yield page.locator(processedOptions.element).all();
|
|
1630
|
+
if (l.length === 0) {
|
|
1631
|
+
throw new Error(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${processedOptions.element}`);
|
|
1632
|
+
} else if (l.length > 1) {
|
|
1633
|
+
throw new Error(`for snapshot ${snapshot.name} viewport ${viewportString}, multiple elements found for selector ${processedOptions.element}`);
|
|
1634
|
+
}
|
|
1635
|
+
} else if (selectors.length) {
|
|
1636
|
+
let locators = [];
|
|
1637
|
+
if (!Array.isArray(processedOptions[ignoreOrSelectBoxes][viewportString]))
|
|
1638
|
+
processedOptions[ignoreOrSelectBoxes][viewportString] = [];
|
|
1639
|
+
for (const selector of selectors) {
|
|
1640
|
+
let l = yield page.locator(selector).all();
|
|
1641
|
+
if (l.length === 0) {
|
|
1642
|
+
optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${selector}`);
|
|
1643
|
+
continue;
|
|
1644
|
+
}
|
|
1645
|
+
locators.push(...l);
|
|
1646
|
+
}
|
|
1647
|
+
for (const locator of locators) {
|
|
1648
|
+
let bb = yield locator.boundingBox();
|
|
1649
|
+
if (bb)
|
|
1650
|
+
processedOptions[ignoreOrSelectBoxes][viewportString].push({
|
|
1651
|
+
left: bb.x,
|
|
1652
|
+
top: bb.y,
|
|
1653
|
+
right: bb.x + bb.width,
|
|
1654
|
+
bottom: bb.y + bb.height
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
if (snapshot.dom.resources.length) {
|
|
1660
|
+
for (let resource of snapshot.dom.resources) {
|
|
1661
|
+
cache[resource.url] = {
|
|
1662
|
+
body: resource.content,
|
|
1663
|
+
type: resource.mimetype
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
return {
|
|
1668
|
+
processedSnapshot: {
|
|
1669
|
+
name: snapshot.name,
|
|
1670
|
+
url: snapshot.url,
|
|
1671
|
+
dom: Buffer.from(snapshot.dom.html).toString("base64"),
|
|
1672
|
+
resources: cache,
|
|
1673
|
+
options: processedOptions
|
|
1674
|
+
},
|
|
1675
|
+
warnings: [...optionWarnings, ...snapshot.dom.warnings]
|
|
1676
|
+
};
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1516
1679
|
|
|
1517
1680
|
// src/commander/exec.ts
|
|
1518
1681
|
var command = new commander.Command();
|
|
1519
|
-
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) {
|
|
1520
1683
|
return __async(this, null, function* () {
|
|
1521
|
-
|
|
1522
|
-
let ctx = ctx_default(command4.optsWithGlobals());
|
|
1684
|
+
let ctx = ctx_default(command5.optsWithGlobals());
|
|
1523
1685
|
if (!which__default.default.sync(execCommand[0], { nothrow: true })) {
|
|
1524
1686
|
ctx.log.error(`Error: Command not found "${execCommand[0]}"`);
|
|
1525
1687
|
return;
|
|
1526
1688
|
}
|
|
1527
1689
|
ctx.args.execCommand = execCommand;
|
|
1690
|
+
ctx.snapshotQueue = new Queue(ctx);
|
|
1528
1691
|
ctx.totalSnapshots = 0;
|
|
1529
1692
|
let tasks = new listr2.Listr(
|
|
1530
1693
|
[
|
|
@@ -1533,6 +1696,7 @@ command.name("exec").description("Run test commands around SmartUI").argument("<
|
|
|
1533
1696
|
getGitInfo_default(),
|
|
1534
1697
|
createBuild_default(),
|
|
1535
1698
|
exec_default(ctx),
|
|
1699
|
+
processSnapshot_default(),
|
|
1536
1700
|
finalizeBuild_default()
|
|
1537
1701
|
],
|
|
1538
1702
|
{
|
|
@@ -1550,9 +1714,6 @@ command.name("exec").description("Run test commands around SmartUI").argument("<
|
|
|
1550
1714
|
yield tasks.run(ctx);
|
|
1551
1715
|
} catch (error) {
|
|
1552
1716
|
ctx.log.info("\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/");
|
|
1553
|
-
} finally {
|
|
1554
|
-
yield (_a = ctx.server) == null ? void 0 : _a.close();
|
|
1555
|
-
yield (_b = ctx.browser) == null ? void 0 : _b.close();
|
|
1556
1717
|
}
|
|
1557
1718
|
});
|
|
1558
1719
|
});
|
|
@@ -1722,13 +1883,13 @@ function captureScreenshots(ctx) {
|
|
|
1722
1883
|
else
|
|
1723
1884
|
yield captureScreenshotsSync(ctx, staticConfig, browsers);
|
|
1724
1885
|
delDir(`screenshots/${staticConfig.name.toLowerCase().replace(/\s/g, "_")}`);
|
|
1725
|
-
output += `${
|
|
1886
|
+
output += `${chalk7__default.default.gray(staticConfig.name)} ${chalk7__default.default.green("\u2713")}
|
|
1726
1887
|
`;
|
|
1727
1888
|
ctx.task.output = output;
|
|
1728
1889
|
capturedScreenshots++;
|
|
1729
1890
|
} catch (error) {
|
|
1730
1891
|
ctx.log.debug(`screenshot capture failed for ${JSON.stringify(staticConfig)}; error: ${error}`);
|
|
1731
|
-
output += `${
|
|
1892
|
+
output += `${chalk7__default.default.gray(staticConfig.name)} ${chalk7__default.default.red("\u2717")}
|
|
1732
1893
|
`;
|
|
1733
1894
|
ctx.task.output = output;
|
|
1734
1895
|
}
|
|
@@ -1738,6 +1899,108 @@ function captureScreenshots(ctx) {
|
|
|
1738
1899
|
return { capturedScreenshots, output };
|
|
1739
1900
|
});
|
|
1740
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
|
+
}
|
|
1741
2004
|
var captureScreenshots_default = (ctx) => {
|
|
1742
2005
|
return {
|
|
1743
2006
|
title: "Capturing screenshots",
|
|
@@ -1752,7 +2015,7 @@ var captureScreenshots_default = (ctx) => {
|
|
|
1752
2015
|
task.title = "Screenshots captured successfully";
|
|
1753
2016
|
} catch (error) {
|
|
1754
2017
|
ctx2.log.debug(error);
|
|
1755
|
-
task.output =
|
|
2018
|
+
task.output = chalk7__default.default.gray(`${error.message}`);
|
|
1756
2019
|
throw new Error("Capturing screenshots failed");
|
|
1757
2020
|
}
|
|
1758
2021
|
}),
|
|
@@ -1763,9 +2026,9 @@ var captureScreenshots_default = (ctx) => {
|
|
|
1763
2026
|
|
|
1764
2027
|
// src/commander/capture.ts
|
|
1765
2028
|
var command2 = new commander.Command();
|
|
1766
|
-
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) {
|
|
1767
2030
|
return __async(this, null, function* () {
|
|
1768
|
-
let ctx = ctx_default(
|
|
2031
|
+
let ctx = ctx_default(command5.optsWithGlobals());
|
|
1769
2032
|
if (!fs5__default.default.existsSync(file)) {
|
|
1770
2033
|
console.log(`Error: Web Static Config file ${file} not found.`);
|
|
1771
2034
|
return;
|
|
@@ -1805,6 +2068,71 @@ command2.name("capture").description("Capture screenshots of static sites").argu
|
|
|
1805
2068
|
});
|
|
1806
2069
|
});
|
|
1807
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;
|
|
1808
2136
|
|
|
1809
2137
|
// src/lib/uploadFigmaDesigns.ts
|
|
1810
2138
|
var uploadFigmaDesigns_default = (ctx) => __async(void 0, null, function* () {
|
|
@@ -1846,7 +2174,7 @@ var uploadFigmaDesigns_default2 = (ctx) => {
|
|
|
1846
2174
|
ctx2.log.debug(`Figma designs processed: ${results}`);
|
|
1847
2175
|
} catch (error) {
|
|
1848
2176
|
ctx2.log.debug(error);
|
|
1849
|
-
task.output =
|
|
2177
|
+
task.output = chalk7__default.default.gray(`${error.message}`);
|
|
1850
2178
|
throw new Error("Uploading Figma designs failed");
|
|
1851
2179
|
}
|
|
1852
2180
|
}),
|
|
@@ -1856,11 +2184,11 @@ var uploadFigmaDesigns_default2 = (ctx) => {
|
|
|
1856
2184
|
};
|
|
1857
2185
|
|
|
1858
2186
|
// src/commander/uploadFigma.ts
|
|
1859
|
-
var
|
|
1860
|
-
|
|
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) {
|
|
1861
2189
|
return __async(this, null, function* () {
|
|
1862
2190
|
var _a, _b;
|
|
1863
|
-
let ctx = ctx_default(
|
|
2191
|
+
let ctx = ctx_default(command5.optsWithGlobals());
|
|
1864
2192
|
if (!fs5__default.default.existsSync(file)) {
|
|
1865
2193
|
console.log(`Error: Figma Config file ${file} not found.`);
|
|
1866
2194
|
return;
|
|
@@ -1898,31 +2226,34 @@ command3.name("upload-figma").description("Capture screenshots of static sites")
|
|
|
1898
2226
|
}
|
|
1899
2227
|
});
|
|
1900
2228
|
});
|
|
1901
|
-
var uploadFigma_default =
|
|
2229
|
+
var uploadFigma_default = command4;
|
|
1902
2230
|
|
|
1903
2231
|
// src/commander/commander.ts
|
|
1904
2232
|
var program = new commander.Command();
|
|
1905
|
-
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);
|
|
1906
2234
|
var commander_default = program;
|
|
1907
2235
|
(function() {
|
|
1908
2236
|
return __async(this, null, function* () {
|
|
1909
2237
|
let client = new httpClient(env_default());
|
|
1910
|
-
let
|
|
2238
|
+
let log2 = logger_default;
|
|
1911
2239
|
try {
|
|
1912
|
-
|
|
2240
|
+
let { data: { latestVersion, deprecated, additionalDescription, additionalDescriptionLatestVersion } } = yield client.checkUpdate(log2);
|
|
2241
|
+
log2.info(`
|
|
1913
2242
|
LambdaTest SmartUI CLI v${package_default.version}`);
|
|
1914
|
-
|
|
1915
|
-
if (deprecated)
|
|
1916
|
-
|
|
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}
|
|
1917
2247
|
`);
|
|
1918
|
-
else if (package_default.version !== latestVersion)
|
|
1919
|
-
|
|
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}
|
|
1920
2251
|
`));
|
|
1921
|
-
else
|
|
1922
|
-
|
|
2252
|
+
} else
|
|
2253
|
+
log2.info(chalk7__default.default.gray("https://www.npmjs.com/package/@lambdatest/smartui-cli\n"));
|
|
1923
2254
|
} catch (error) {
|
|
1924
|
-
|
|
1925
|
-
|
|
2255
|
+
log2.debug(error);
|
|
2256
|
+
log2.info(chalk7__default.default.gray("https://www.npmjs.com/package/@lambdatest/smartui-cli\n"));
|
|
1926
2257
|
}
|
|
1927
2258
|
commander_default.parse();
|
|
1928
2259
|
});
|