@lambdatest/smartui-cli 3.0.10 → 3.0.12
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 +470 -369
- package/package.json +1 -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,12 @@ 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
19
|
|
|
20
20
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
21
21
|
|
|
22
22
|
var which__default = /*#__PURE__*/_interopDefault(which);
|
|
23
|
-
var
|
|
23
|
+
var chalk7__default = /*#__PURE__*/_interopDefault(chalk7);
|
|
24
24
|
var path2__default = /*#__PURE__*/_interopDefault(path2);
|
|
25
25
|
var fastify__default = /*#__PURE__*/_interopDefault(fastify);
|
|
26
26
|
var fs5__default = /*#__PURE__*/_interopDefault(fs5);
|
|
@@ -315,326 +315,7 @@ var constants_default = {
|
|
|
315
315
|
}
|
|
316
316
|
};
|
|
317
317
|
|
|
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
|
-
ctx.log.debug(`Processing snapshot ${snapshot.name}`);
|
|
440
|
-
let launchOptions = { headless: true };
|
|
441
|
-
let contextOptions = {
|
|
442
|
-
javaScriptEnabled: ctx.config.enableJavaScript,
|
|
443
|
-
userAgent: constants_default.CHROME_USER_AGENT
|
|
444
|
-
};
|
|
445
|
-
if (!ctx.browser) {
|
|
446
|
-
if (ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY)
|
|
447
|
-
launchOptions.proxy = { server: ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY };
|
|
448
|
-
ctx.browser = yield test.chromium.launch(launchOptions);
|
|
449
|
-
ctx.log.debug(`Chromium launched with options ${JSON.stringify(launchOptions)}`);
|
|
450
|
-
}
|
|
451
|
-
const context = yield ctx.browser.newContext(contextOptions);
|
|
452
|
-
ctx.log.debug(`Browser context created with options ${JSON.stringify(contextOptions)}`);
|
|
453
|
-
const page = yield context.newPage();
|
|
454
|
-
let cache = {};
|
|
455
|
-
yield page.route("**/*", (route, request) => __async(void 0, null, function* () {
|
|
456
|
-
const requestUrl = request.url();
|
|
457
|
-
const requestHostname = new URL(requestUrl).hostname;
|
|
458
|
-
try {
|
|
459
|
-
if (/\.(mp3|mp4|wav|ogg|webm)$/i.test(request.url())) {
|
|
460
|
-
throw new Error("resource type mp3/mp4/wav/ogg/webm");
|
|
461
|
-
}
|
|
462
|
-
ctx.config.allowedHostnames.push(new URL(snapshot.url).hostname);
|
|
463
|
-
if (ctx.config.enableJavaScript)
|
|
464
|
-
ALLOWED_RESOURCES.push("script");
|
|
465
|
-
const response = yield page.request.fetch(request, { timeout: REQUEST_TIMEOUT });
|
|
466
|
-
const body = yield response.body();
|
|
467
|
-
if (!body) {
|
|
468
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
469
|
-
- skipping no response`);
|
|
470
|
-
} else if (!body.length) {
|
|
471
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
472
|
-
- skipping empty response`);
|
|
473
|
-
} else if (requestUrl === snapshot.url) {
|
|
474
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
475
|
-
- skipping root resource`);
|
|
476
|
-
} else if (!ctx.config.allowedHostnames.includes(requestHostname)) {
|
|
477
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
478
|
-
- skipping remote resource`);
|
|
479
|
-
} else if (cache[requestUrl]) {
|
|
480
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
481
|
-
- skipping already cached resource`);
|
|
482
|
-
} else if (body.length > MAX_RESOURCE_SIZE) {
|
|
483
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
484
|
-
- skipping resource larger than 15MB`);
|
|
485
|
-
} else if (!ALLOWED_STATUSES.includes(response.status())) {
|
|
486
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
487
|
-
- skipping disallowed status [${response.status()}]`);
|
|
488
|
-
} else if (!ALLOWED_RESOURCES.includes(request.resourceType())) {
|
|
489
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
490
|
-
- skipping disallowed resource type [${request.resourceType()}]`);
|
|
491
|
-
} else {
|
|
492
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
493
|
-
- content-type ${response.headers()["content-type"]}`);
|
|
494
|
-
cache[requestUrl] = {
|
|
495
|
-
body: body.toString("base64"),
|
|
496
|
-
type: response.headers()["content-type"]
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
route.fulfill({
|
|
500
|
-
status: response.status(),
|
|
501
|
-
headers: response.headers(),
|
|
502
|
-
body
|
|
503
|
-
});
|
|
504
|
-
} catch (error) {
|
|
505
|
-
ctx.log.debug(`Handling request ${requestUrl}
|
|
506
|
-
- aborted due to ${error.message}`);
|
|
507
|
-
route.abort();
|
|
508
|
-
}
|
|
509
|
-
}));
|
|
510
|
-
let options = snapshot.options;
|
|
511
|
-
let optionWarnings = /* @__PURE__ */ new Set();
|
|
512
|
-
let processedOptions = {};
|
|
513
|
-
let selectors = [];
|
|
514
|
-
let ignoreOrSelectDOM;
|
|
515
|
-
let ignoreOrSelectBoxes;
|
|
516
|
-
if (options && Object.keys(options).length) {
|
|
517
|
-
ctx.log.debug(`Snapshot options: ${JSON.stringify(options)}`);
|
|
518
|
-
const isNotAllEmpty = (obj) => {
|
|
519
|
-
var _a;
|
|
520
|
-
for (let key in obj)
|
|
521
|
-
if ((_a = obj[key]) == null ? void 0 : _a.length)
|
|
522
|
-
return true;
|
|
523
|
-
return false;
|
|
524
|
-
};
|
|
525
|
-
if (options.element && Object.keys(options.element).length) {
|
|
526
|
-
if (options.element.id)
|
|
527
|
-
processedOptions.element = "#" + options.element.id;
|
|
528
|
-
else if (options.element.class)
|
|
529
|
-
processedOptions.element = "." + options.element.class;
|
|
530
|
-
else if (options.element.cssSelector)
|
|
531
|
-
processedOptions.element = options.element.cssSelector;
|
|
532
|
-
else if (options.element.xpath)
|
|
533
|
-
processedOptions.element = "xpath=" + options.element.xpath;
|
|
534
|
-
} else if (options.ignoreDOM && Object.keys(options.ignoreDOM).length && isNotAllEmpty(options.ignoreDOM)) {
|
|
535
|
-
processedOptions.ignoreBoxes = {};
|
|
536
|
-
ignoreOrSelectDOM = "ignoreDOM";
|
|
537
|
-
ignoreOrSelectBoxes = "ignoreBoxes";
|
|
538
|
-
} else if (options.selectDOM && Object.keys(options.selectDOM).length && isNotAllEmpty(options.selectDOM)) {
|
|
539
|
-
processedOptions.selectBoxes = {};
|
|
540
|
-
ignoreOrSelectDOM = "selectDOM";
|
|
541
|
-
ignoreOrSelectBoxes = "selectBoxes";
|
|
542
|
-
}
|
|
543
|
-
if (ignoreOrSelectDOM) {
|
|
544
|
-
for (const [key, value] of Object.entries(options[ignoreOrSelectDOM])) {
|
|
545
|
-
switch (key) {
|
|
546
|
-
case "id":
|
|
547
|
-
selectors.push(...value.map((e) => "#" + e));
|
|
548
|
-
break;
|
|
549
|
-
case "class":
|
|
550
|
-
selectors.push(...value.map((e) => "." + e));
|
|
551
|
-
break;
|
|
552
|
-
case "xpath":
|
|
553
|
-
selectors.push(...value.map((e) => "xpath=" + e));
|
|
554
|
-
break;
|
|
555
|
-
case "cssSelector":
|
|
556
|
-
selectors.push(...value);
|
|
557
|
-
break;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
let navigated = false;
|
|
563
|
-
let renderViewports = getRenderViewports(ctx);
|
|
564
|
-
for (const { viewport, viewportString, fullPage } of renderViewports) {
|
|
565
|
-
yield page.setViewportSize({ width: viewport.width, height: viewport.height || MIN_VIEWPORT_HEIGHT });
|
|
566
|
-
ctx.log.debug(`Page resized to ${viewport.width}x${viewport.height || MIN_VIEWPORT_HEIGHT}`);
|
|
567
|
-
if (!navigated) {
|
|
568
|
-
try {
|
|
569
|
-
yield page.goto(snapshot.url, { waitUntil: "domcontentloaded" });
|
|
570
|
-
yield new Promise((r) => setTimeout(r, 1250));
|
|
571
|
-
if (ctx.config.waitForTimeout)
|
|
572
|
-
yield page.waitForTimeout(ctx.config.waitForTimeout);
|
|
573
|
-
navigated = true;
|
|
574
|
-
ctx.log.debug(`Navigated to ${snapshot.url}`);
|
|
575
|
-
} catch (error) {
|
|
576
|
-
ctx.log.debug(`Navigation to discovery page failed; ${error}`);
|
|
577
|
-
throw new Error(error.message);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
if (ctx.config.enableJavaScript && fullPage)
|
|
581
|
-
yield page.evaluate(scrollToBottomAndBackToTop);
|
|
582
|
-
try {
|
|
583
|
-
yield page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
584
|
-
ctx.log.debug("Network idle 500ms");
|
|
585
|
-
} catch (error) {
|
|
586
|
-
ctx.log.debug(`Network idle failed due to ${error}`);
|
|
587
|
-
}
|
|
588
|
-
if (processedOptions.element) {
|
|
589
|
-
let l = yield page.locator(processedOptions.element).all();
|
|
590
|
-
if (l.length === 0) {
|
|
591
|
-
throw new Error(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${processedOptions.element}`);
|
|
592
|
-
} else if (l.length > 1) {
|
|
593
|
-
throw new Error(`for snapshot ${snapshot.name} viewport ${viewportString}, multiple elements found for selector ${processedOptions.element}`);
|
|
594
|
-
}
|
|
595
|
-
} else if (selectors.length) {
|
|
596
|
-
let locators = [];
|
|
597
|
-
if (!Array.isArray(processedOptions[ignoreOrSelectBoxes][viewportString]))
|
|
598
|
-
processedOptions[ignoreOrSelectBoxes][viewportString] = [];
|
|
599
|
-
for (const selector of selectors) {
|
|
600
|
-
let l = yield page.locator(selector).all();
|
|
601
|
-
if (l.length === 0) {
|
|
602
|
-
optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${selector}`);
|
|
603
|
-
continue;
|
|
604
|
-
}
|
|
605
|
-
locators.push(...l);
|
|
606
|
-
}
|
|
607
|
-
for (const locator of locators) {
|
|
608
|
-
let bb = yield locator.boundingBox();
|
|
609
|
-
if (bb)
|
|
610
|
-
processedOptions[ignoreOrSelectBoxes][viewportString].push({
|
|
611
|
-
left: bb.x,
|
|
612
|
-
top: bb.y,
|
|
613
|
-
right: bb.x + bb.width,
|
|
614
|
-
bottom: bb.y + bb.height
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
if (snapshot.dom.resources.length) {
|
|
620
|
-
for (let resource of snapshot.dom.resources) {
|
|
621
|
-
cache[resource.url] = {
|
|
622
|
-
body: resource.content,
|
|
623
|
-
type: resource.mimetype
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
return {
|
|
628
|
-
processedSnapshot: {
|
|
629
|
-
name: snapshot.name,
|
|
630
|
-
url: snapshot.url,
|
|
631
|
-
dom: Buffer.from(snapshot.dom.html).toString("base64"),
|
|
632
|
-
resources: cache,
|
|
633
|
-
options: processedOptions
|
|
634
|
-
},
|
|
635
|
-
warnings: [...optionWarnings, ...snapshot.dom.warnings]
|
|
636
|
-
};
|
|
637
|
-
});
|
|
318
|
+
// src/lib/schemaValidation.ts
|
|
638
319
|
var ajv = new Ajv__default.default({ allErrors: true });
|
|
639
320
|
ajv.addFormat("web-url", {
|
|
640
321
|
type: "string",
|
|
@@ -961,35 +642,25 @@ var server_default = (ctx) => __async(void 0, null, function* () {
|
|
|
961
642
|
reply.code(200).send({ data: { dom: SMARTUI_DOM } });
|
|
962
643
|
});
|
|
963
644
|
server.post("/snapshot", opts, (request, reply) => __async(void 0, null, function* () {
|
|
645
|
+
var _a;
|
|
964
646
|
let replyCode;
|
|
965
647
|
let replyBody;
|
|
966
648
|
try {
|
|
967
649
|
let { snapshot, testType } = request.body;
|
|
968
650
|
if (!validateSnapshot(snapshot))
|
|
969
651
|
throw new Error(validateSnapshot.errors[0].message);
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
ctx.totalSnapshots++;
|
|
652
|
+
ctx.testType = testType;
|
|
653
|
+
(_a = ctx.snapshotQueue) == null ? void 0 : _a.enqueue(snapshot);
|
|
973
654
|
replyCode = 200;
|
|
974
|
-
replyBody = { data: { message: "success", warnings } };
|
|
655
|
+
replyBody = { data: { message: "success", warnings: [] } };
|
|
975
656
|
} catch (error) {
|
|
976
657
|
ctx.log.debug(`snapshot failed; ${error}`);
|
|
977
658
|
replyCode = 500;
|
|
978
659
|
replyBody = { error: { message: error.message } };
|
|
979
660
|
}
|
|
980
|
-
if (ctx.browser) {
|
|
981
|
-
for (let context of ctx.browser.contexts()) {
|
|
982
|
-
for (let page of context.pages()) {
|
|
983
|
-
yield page.close();
|
|
984
|
-
ctx.log.debug(`Closed browser page`);
|
|
985
|
-
}
|
|
986
|
-
yield context.close();
|
|
987
|
-
ctx.log.debug(`Closed browser context`);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
661
|
return reply.code(replyCode).send(replyBody);
|
|
991
662
|
}));
|
|
992
|
-
yield server.listen({ port:
|
|
663
|
+
yield server.listen({ port: ctx.options.port });
|
|
993
664
|
let { port } = server.addresses()[0];
|
|
994
665
|
process.env.SMARTUI_SERVER_ADDRESS = `http://localhost:${port}`;
|
|
995
666
|
process.env.CYPRESS_SMARTUI_SERVER_ADDRESS = `http://localhost:${port}`;
|
|
@@ -1047,13 +718,13 @@ var logger = winston.createLogger({
|
|
|
1047
718
|
let message = typeof info.message === "object" ? JSON.stringify(info.message) : info.message;
|
|
1048
719
|
switch (info.level) {
|
|
1049
720
|
case "debug":
|
|
1050
|
-
message =
|
|
721
|
+
message = chalk7__default.default.blue(message);
|
|
1051
722
|
break;
|
|
1052
723
|
case "warn":
|
|
1053
|
-
message =
|
|
724
|
+
message = chalk7__default.default.yellow(message);
|
|
1054
725
|
break;
|
|
1055
726
|
case "error":
|
|
1056
|
-
message =
|
|
727
|
+
message = chalk7__default.default.red(message);
|
|
1057
728
|
break;
|
|
1058
729
|
}
|
|
1059
730
|
return info.level === "info" ? message : `[${contextString}:${info.level}] ` + message;
|
|
@@ -1072,11 +743,11 @@ var startServer_default = (ctx) => {
|
|
|
1072
743
|
updateLogContext({ task: "startServer" });
|
|
1073
744
|
try {
|
|
1074
745
|
ctx2.server = yield server_default(ctx2);
|
|
1075
|
-
task.output =
|
|
746
|
+
task.output = chalk7__default.default.gray(`listening on port ${(_a = ctx2.server.addresses()[0]) == null ? void 0 : _a.port}`);
|
|
1076
747
|
task.title = "SmartUI started";
|
|
1077
748
|
} catch (error) {
|
|
1078
749
|
ctx2.log.debug(error);
|
|
1079
|
-
task.output =
|
|
750
|
+
task.output = chalk7__default.default.gray(error.message);
|
|
1080
751
|
throw new Error("SmartUI server setup failed");
|
|
1081
752
|
}
|
|
1082
753
|
}),
|
|
@@ -1090,11 +761,11 @@ var auth_default = (ctx) => {
|
|
|
1090
761
|
updateLogContext({ task: "auth" });
|
|
1091
762
|
try {
|
|
1092
763
|
yield ctx2.client.auth(ctx2.log);
|
|
1093
|
-
task.output =
|
|
764
|
+
task.output = chalk7__default.default.gray(`using project token '******#${ctx2.env.PROJECT_TOKEN.split("#").pop()}'`);
|
|
1094
765
|
task.title = "Authenticated with SmartUI";
|
|
1095
766
|
} catch (error) {
|
|
1096
767
|
ctx2.log.debug(error);
|
|
1097
|
-
task.output =
|
|
768
|
+
task.output = chalk7__default.default.gray(error.message);
|
|
1098
769
|
throw new Error("Authentication failed");
|
|
1099
770
|
}
|
|
1100
771
|
}),
|
|
@@ -1103,7 +774,7 @@ var auth_default = (ctx) => {
|
|
|
1103
774
|
};
|
|
1104
775
|
|
|
1105
776
|
// package.json
|
|
1106
|
-
var version = "3.0.
|
|
777
|
+
var version = "3.0.12";
|
|
1107
778
|
var package_default = {
|
|
1108
779
|
name: "@lambdatest/smartui-cli",
|
|
1109
780
|
version,
|
|
@@ -1213,19 +884,19 @@ var httpClient = class {
|
|
|
1213
884
|
params
|
|
1214
885
|
}, log);
|
|
1215
886
|
}
|
|
1216
|
-
uploadSnapshot(
|
|
887
|
+
uploadSnapshot(ctx, snapshot) {
|
|
1217
888
|
return this.request({
|
|
1218
|
-
url: `/builds/${
|
|
889
|
+
url: `/builds/${ctx.build.id}/snapshot`,
|
|
1219
890
|
method: "POST",
|
|
1220
891
|
headers: { "Content-Type": "application/json" },
|
|
1221
892
|
data: {
|
|
1222
893
|
snapshot,
|
|
1223
894
|
test: {
|
|
1224
|
-
type: testType,
|
|
895
|
+
type: ctx.testType,
|
|
1225
896
|
source: "cli"
|
|
1226
897
|
}
|
|
1227
898
|
}
|
|
1228
|
-
}, log);
|
|
899
|
+
}, ctx.log);
|
|
1229
900
|
}
|
|
1230
901
|
uploadScreenshot({ id: buildId, name: buildName, baseline }, ssPath, ssName, browserName, viewport, log) {
|
|
1231
902
|
browserName = browserName === constants_default.SAFARI ? constants_default.WEBKIT : browserName;
|
|
@@ -1292,6 +963,7 @@ var ctx_default = (options) => {
|
|
|
1292
963
|
let webConfig;
|
|
1293
964
|
let mobileConfig;
|
|
1294
965
|
let config = constants_default.DEFAULT_CONFIG;
|
|
966
|
+
let port;
|
|
1295
967
|
try {
|
|
1296
968
|
if (options.config) {
|
|
1297
969
|
config = JSON.parse(fs5__default.default.readFileSync(options.config, "utf-8"));
|
|
@@ -1303,6 +975,10 @@ var ctx_default = (options) => {
|
|
|
1303
975
|
throw new Error(validateConfig.errors[0].message);
|
|
1304
976
|
}
|
|
1305
977
|
}
|
|
978
|
+
port = parseInt(options.port || "49152", 10);
|
|
979
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
980
|
+
throw new Error("Invalid port number. Port number must be an integer between 1 and 65535.");
|
|
981
|
+
}
|
|
1306
982
|
} catch (error) {
|
|
1307
983
|
console.log(`[smartui] Error: ${error.message}`);
|
|
1308
984
|
process.exit();
|
|
@@ -1349,7 +1025,8 @@ var ctx_default = (options) => {
|
|
|
1349
1025
|
options: {
|
|
1350
1026
|
parallel: options.parallel ? true : false,
|
|
1351
1027
|
markBaseline: options.markBaseline ? true : false,
|
|
1352
|
-
buildName: options.buildName || ""
|
|
1028
|
+
buildName: options.buildName || "",
|
|
1029
|
+
port
|
|
1353
1030
|
},
|
|
1354
1031
|
cliVersion: version,
|
|
1355
1032
|
totalSnapshots: -1
|
|
@@ -1416,11 +1093,11 @@ var getGitInfo_default = (ctx) => {
|
|
|
1416
1093
|
}
|
|
1417
1094
|
try {
|
|
1418
1095
|
ctx2.git = git_default(ctx2);
|
|
1419
|
-
task.output =
|
|
1096
|
+
task.output = chalk7__default.default.gray(`branch: ${ctx2.git.branch}, commit: ${ctx2.git.commitId}, author: ${ctx2.git.commitAuthor}`);
|
|
1420
1097
|
task.title = "Fetched git information";
|
|
1421
1098
|
} catch (error) {
|
|
1422
1099
|
ctx2.log.debug(error);
|
|
1423
|
-
task.output =
|
|
1100
|
+
task.output = chalk7__default.default.gray(`${error.message}`);
|
|
1424
1101
|
throw new Error("Error fetching git repo details");
|
|
1425
1102
|
}
|
|
1426
1103
|
}),
|
|
@@ -1440,11 +1117,11 @@ var createBuild_default = (ctx) => {
|
|
|
1440
1117
|
url: resp.data.buildURL,
|
|
1441
1118
|
baseline: resp.data.baseline
|
|
1442
1119
|
};
|
|
1443
|
-
task.output =
|
|
1120
|
+
task.output = chalk7__default.default.gray(`build id: ${resp.data.buildId}`);
|
|
1444
1121
|
task.title = "SmartUI build created";
|
|
1445
1122
|
} catch (error) {
|
|
1446
1123
|
ctx2.log.debug(error);
|
|
1447
|
-
task.output =
|
|
1124
|
+
task.output = chalk7__default.default.gray(error.message);
|
|
1448
1125
|
throw new Error("SmartUI build creation failed");
|
|
1449
1126
|
}
|
|
1450
1127
|
}),
|
|
@@ -1463,13 +1140,13 @@ var exec_default = (ctx) => {
|
|
|
1463
1140
|
let totalOutput = "";
|
|
1464
1141
|
const output = listr2.createWritable((chunk) => {
|
|
1465
1142
|
totalOutput += chunk;
|
|
1466
|
-
task.output =
|
|
1143
|
+
task.output = chalk7__default.default.gray(totalOutput);
|
|
1467
1144
|
});
|
|
1468
1145
|
(_b = childProcess.stdout) == null ? void 0 : _b.pipe(output);
|
|
1469
1146
|
(_c = childProcess.stderr) == null ? void 0 : _c.pipe(output);
|
|
1470
1147
|
childProcess.on("error", (error) => {
|
|
1471
1148
|
var _a3;
|
|
1472
|
-
task.output =
|
|
1149
|
+
task.output = chalk7__default.default.gray(`error: ${error.message}`);
|
|
1473
1150
|
throw new Error(`Execution of '${(_a3 = ctx2.args.execCommand) == null ? void 0 : _a3.join(" ")}' failed`);
|
|
1474
1151
|
});
|
|
1475
1152
|
childProcess.on("close", (code, signal) => __async(void 0, null, function* () {
|
|
@@ -1487,6 +1164,46 @@ var exec_default = (ctx) => {
|
|
|
1487
1164
|
exitOnError: false
|
|
1488
1165
|
};
|
|
1489
1166
|
};
|
|
1167
|
+
var processSnapshot_default = (ctx) => {
|
|
1168
|
+
return {
|
|
1169
|
+
title: `Processing snapshots`,
|
|
1170
|
+
task: (ctx2, task) => __async(void 0, null, function* () {
|
|
1171
|
+
var _a;
|
|
1172
|
+
try {
|
|
1173
|
+
yield new Promise((resolve) => {
|
|
1174
|
+
let output2 = "";
|
|
1175
|
+
const intervalId = setInterval(() => {
|
|
1176
|
+
var _a2, _b, _c;
|
|
1177
|
+
if (((_a2 = ctx2.snapshotQueue) == null ? void 0 : _a2.isEmpty()) && !((_b = ctx2.snapshotQueue) == null ? void 0 : _b.isProcessing())) {
|
|
1178
|
+
clearInterval(intervalId);
|
|
1179
|
+
resolve();
|
|
1180
|
+
} else {
|
|
1181
|
+
task.title = `Processing snapshot ${(_c = ctx2.snapshotQueue) == null ? void 0 : _c.getProcessingSnapshot()}`;
|
|
1182
|
+
}
|
|
1183
|
+
}, 500);
|
|
1184
|
+
});
|
|
1185
|
+
let output = "";
|
|
1186
|
+
for (let snapshot of (_a = ctx2.snapshotQueue) == null ? void 0 : _a.getProcessedSnapshots()) {
|
|
1187
|
+
if (snapshot.error)
|
|
1188
|
+
output += `${chalk7__default.default.red("\u2717")} ${chalk7__default.default.gray(`${snapshot.name}
|
|
1189
|
+
[error] ${snapshot.error}`)}
|
|
1190
|
+
`;
|
|
1191
|
+
else
|
|
1192
|
+
output += `${chalk7__default.default.green("\u2713")} ${chalk7__default.default.gray(snapshot.name)}
|
|
1193
|
+
${snapshot.warnings.length ? chalk7__default.default.gray(`[warning] ${snapshot.warnings.join("\n[warning] ")}
|
|
1194
|
+
`) : ""}`;
|
|
1195
|
+
}
|
|
1196
|
+
task.output = output;
|
|
1197
|
+
task.title = "Processed snapshots";
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
ctx2.log.debug(error);
|
|
1200
|
+
task.output = chalk7__default.default.gray(error.message);
|
|
1201
|
+
throw new Error("Processing of snapshots failed");
|
|
1202
|
+
}
|
|
1203
|
+
}),
|
|
1204
|
+
rendererOptions: { persistentOutput: true }
|
|
1205
|
+
};
|
|
1206
|
+
};
|
|
1490
1207
|
var finalizeBuild_default = (ctx) => {
|
|
1491
1208
|
return {
|
|
1492
1209
|
title: `Finalizing build`,
|
|
@@ -1495,21 +1212,401 @@ var finalizeBuild_default = (ctx) => {
|
|
|
1495
1212
|
try {
|
|
1496
1213
|
yield new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1497
1214
|
yield ctx2.client.finalizeBuild(ctx2.build.id, ctx2.totalSnapshots, ctx2.log);
|
|
1498
|
-
task.output =
|
|
1215
|
+
task.output = chalk7__default.default.gray(`build url: ${ctx2.build.url}`);
|
|
1499
1216
|
task.title = "Finalized build";
|
|
1500
1217
|
} catch (error) {
|
|
1501
1218
|
ctx2.log.debug(error);
|
|
1502
|
-
task.output =
|
|
1219
|
+
task.output = chalk7__default.default.gray(error.message);
|
|
1503
1220
|
throw new Error("Finalize build failed");
|
|
1504
1221
|
}
|
|
1505
1222
|
}),
|
|
1506
1223
|
rendererOptions: { persistentOutput: true }
|
|
1507
1224
|
};
|
|
1508
1225
|
};
|
|
1226
|
+
function delDir(dir) {
|
|
1227
|
+
if (fs5__default.default.existsSync(dir)) {
|
|
1228
|
+
fs5__default.default.rmSync(dir, { recursive: true });
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
function scrollToBottomAndBackToTop({
|
|
1232
|
+
frequency = 100,
|
|
1233
|
+
timing = 8,
|
|
1234
|
+
remoteWindow = window
|
|
1235
|
+
} = {}) {
|
|
1236
|
+
return new Promise((resolve) => {
|
|
1237
|
+
let scrolls = 1;
|
|
1238
|
+
let scrollLength = remoteWindow.document.body.scrollHeight / frequency;
|
|
1239
|
+
(function scroll() {
|
|
1240
|
+
let scrollBy = scrollLength * scrolls;
|
|
1241
|
+
remoteWindow.setTimeout(() => {
|
|
1242
|
+
remoteWindow.scrollTo(0, scrollBy);
|
|
1243
|
+
if (scrolls < frequency) {
|
|
1244
|
+
scrolls += 1;
|
|
1245
|
+
scroll();
|
|
1246
|
+
}
|
|
1247
|
+
if (scrolls === frequency) {
|
|
1248
|
+
remoteWindow.setTimeout(() => {
|
|
1249
|
+
remoteWindow.scrollTo(0, 0);
|
|
1250
|
+
resolve();
|
|
1251
|
+
}, timing);
|
|
1252
|
+
}
|
|
1253
|
+
}, timing);
|
|
1254
|
+
})();
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
function launchBrowsers(ctx) {
|
|
1258
|
+
return __async(this, null, function* () {
|
|
1259
|
+
let browsers = {};
|
|
1260
|
+
let launchOptions = { headless: true };
|
|
1261
|
+
if (ctx.config.web) {
|
|
1262
|
+
for (const browser of ctx.config.web.browsers) {
|
|
1263
|
+
switch (browser) {
|
|
1264
|
+
case constants_default.CHROME:
|
|
1265
|
+
browsers[constants_default.CHROME] = yield test.chromium.launch(launchOptions);
|
|
1266
|
+
break;
|
|
1267
|
+
case constants_default.SAFARI:
|
|
1268
|
+
browsers[constants_default.SAFARI] = yield test.webkit.launch(launchOptions);
|
|
1269
|
+
break;
|
|
1270
|
+
case constants_default.FIREFOX:
|
|
1271
|
+
browsers[constants_default.FIREFOX] = yield test.firefox.launch(launchOptions);
|
|
1272
|
+
break;
|
|
1273
|
+
case constants_default.EDGE:
|
|
1274
|
+
browsers[constants_default.EDGE] = yield test.chromium.launch(__spreadValues({ channel: constants_default.EDGE_CHANNEL }, launchOptions));
|
|
1275
|
+
break;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
if (ctx.config.mobile) {
|
|
1280
|
+
for (const device of ctx.config.mobile.devices) {
|
|
1281
|
+
if (constants_default.SUPPORTED_MOBILE_DEVICES[device].os === "android" && !browsers[constants_default.CHROME])
|
|
1282
|
+
browsers[constants_default.CHROME] = yield test.chromium.launch(launchOptions);
|
|
1283
|
+
else if (constants_default.SUPPORTED_MOBILE_DEVICES[device].os === "ios" && !browsers[constants_default.SAFARI])
|
|
1284
|
+
browsers[constants_default.SAFARI] = yield test.webkit.launch(launchOptions);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
return browsers;
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
function closeBrowsers(browsers) {
|
|
1291
|
+
return __async(this, null, function* () {
|
|
1292
|
+
var _a;
|
|
1293
|
+
for (const browserName of Object.keys(browsers))
|
|
1294
|
+
yield (_a = browsers[browserName]) == null ? void 0 : _a.close();
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
function getWebRenderViewports(ctx) {
|
|
1298
|
+
let webRenderViewports = [];
|
|
1299
|
+
if (ctx.config.web) {
|
|
1300
|
+
for (const viewport of ctx.config.web.viewports) {
|
|
1301
|
+
webRenderViewports.push({
|
|
1302
|
+
viewport,
|
|
1303
|
+
viewportString: `${viewport.width}${viewport.height ? "x" + viewport.height : ""}`,
|
|
1304
|
+
fullPage: viewport.height ? false : true,
|
|
1305
|
+
device: false
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
return webRenderViewports;
|
|
1310
|
+
}
|
|
1311
|
+
function getMobileRenderViewports(ctx) {
|
|
1312
|
+
var _a;
|
|
1313
|
+
let mobileRenderViewports = {};
|
|
1314
|
+
mobileRenderViewports[constants_default.MOBILE_OS_IOS] = [];
|
|
1315
|
+
mobileRenderViewports[constants_default.MOBILE_OS_ANDROID] = [];
|
|
1316
|
+
if (ctx.config.mobile) {
|
|
1317
|
+
for (const device of ctx.config.mobile.devices) {
|
|
1318
|
+
let os = constants_default.SUPPORTED_MOBILE_DEVICES[device].os;
|
|
1319
|
+
let { width, height } = constants_default.SUPPORTED_MOBILE_DEVICES[device].viewport;
|
|
1320
|
+
let portrait = ctx.config.mobile.orientation === constants_default.MOBILE_ORIENTATION_PORTRAIT ? true : false;
|
|
1321
|
+
(_a = mobileRenderViewports[os]) == null ? void 0 : _a.push({
|
|
1322
|
+
viewport: { width: portrait ? width : height, height: portrait ? height : width },
|
|
1323
|
+
viewportString: `${device} (${ctx.config.mobile.orientation})`,
|
|
1324
|
+
fullPage: ctx.config.mobile.fullPage,
|
|
1325
|
+
device: true,
|
|
1326
|
+
os
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
return mobileRenderViewports;
|
|
1331
|
+
}
|
|
1332
|
+
function getRenderViewports(ctx) {
|
|
1333
|
+
let mobileRenderViewports = getMobileRenderViewports(ctx);
|
|
1334
|
+
return [
|
|
1335
|
+
...getWebRenderViewports(ctx),
|
|
1336
|
+
...mobileRenderViewports[constants_default.MOBILE_OS_IOS],
|
|
1337
|
+
...mobileRenderViewports[constants_default.MOBILE_OS_ANDROID]
|
|
1338
|
+
];
|
|
1339
|
+
}
|
|
1340
|
+
var MAX_RESOURCE_SIZE = 15 * 1024 ** 2;
|
|
1341
|
+
var ALLOWED_RESOURCES = ["document", "stylesheet", "image", "media", "font", "other"];
|
|
1342
|
+
var ALLOWED_STATUSES = [200, 201];
|
|
1343
|
+
var REQUEST_TIMEOUT = 1e4;
|
|
1344
|
+
var MIN_VIEWPORT_HEIGHT = 1080;
|
|
1345
|
+
var Queue = class {
|
|
1346
|
+
constructor(ctx) {
|
|
1347
|
+
this.snapshots = [];
|
|
1348
|
+
this.processedSnapshots = [];
|
|
1349
|
+
this.processing = false;
|
|
1350
|
+
this.processingSnapshot = "";
|
|
1351
|
+
this.ctx = ctx;
|
|
1352
|
+
}
|
|
1353
|
+
enqueue(item) {
|
|
1354
|
+
this.snapshots.push(item);
|
|
1355
|
+
if (!this.processing) {
|
|
1356
|
+
this.processing = true;
|
|
1357
|
+
this.processNext();
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
processNext() {
|
|
1361
|
+
return __async(this, null, function* () {
|
|
1362
|
+
if (!this.isEmpty()) {
|
|
1363
|
+
const snapshot = this.snapshots.shift();
|
|
1364
|
+
try {
|
|
1365
|
+
this.processingSnapshot = snapshot == null ? void 0 : snapshot.name;
|
|
1366
|
+
let { processedSnapshot, warnings } = yield processSnapshot(snapshot, this.ctx);
|
|
1367
|
+
yield this.ctx.client.uploadSnapshot(this.ctx, processedSnapshot);
|
|
1368
|
+
this.ctx.totalSnapshots++;
|
|
1369
|
+
this.processedSnapshots.push({ name: snapshot.name, warnings });
|
|
1370
|
+
} catch (error) {
|
|
1371
|
+
this.ctx.log.debug(`snapshot failed; ${error}`);
|
|
1372
|
+
this.processedSnapshots.push({ name: snapshot.name, error: error.message });
|
|
1373
|
+
}
|
|
1374
|
+
if (this.ctx.browser) {
|
|
1375
|
+
for (let context of this.ctx.browser.contexts()) {
|
|
1376
|
+
for (let page of context.pages()) {
|
|
1377
|
+
yield page.close();
|
|
1378
|
+
this.ctx.log.debug(`Closed browser page for snapshot ${snapshot.name}`);
|
|
1379
|
+
}
|
|
1380
|
+
yield context.close();
|
|
1381
|
+
this.ctx.log.debug(`Closed browser context for snapshot ${snapshot.name}`);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
this.processNext();
|
|
1385
|
+
} else {
|
|
1386
|
+
this.processing = false;
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
isProcessing() {
|
|
1391
|
+
return this.processing;
|
|
1392
|
+
}
|
|
1393
|
+
getProcessingSnapshot() {
|
|
1394
|
+
return this.processingSnapshot;
|
|
1395
|
+
}
|
|
1396
|
+
getProcessedSnapshots() {
|
|
1397
|
+
return this.processedSnapshots;
|
|
1398
|
+
}
|
|
1399
|
+
isEmpty() {
|
|
1400
|
+
return this.snapshots.length ? false : true;
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
function processSnapshot(snapshot, ctx) {
|
|
1404
|
+
return __async(this, null, function* () {
|
|
1405
|
+
var _a;
|
|
1406
|
+
ctx.log.debug(`Processing snapshot ${snapshot.name}`);
|
|
1407
|
+
let launchOptions = { headless: true };
|
|
1408
|
+
let contextOptions = {
|
|
1409
|
+
javaScriptEnabled: ctx.config.enableJavaScript,
|
|
1410
|
+
userAgent: constants_default.CHROME_USER_AGENT
|
|
1411
|
+
};
|
|
1412
|
+
if (!((_a = ctx.browser) == null ? void 0 : _a.isConnected())) {
|
|
1413
|
+
if (ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY)
|
|
1414
|
+
launchOptions.proxy = { server: ctx.env.HTTP_PROXY || ctx.env.HTTPS_PROXY };
|
|
1415
|
+
ctx.browser = yield test.chromium.launch(launchOptions);
|
|
1416
|
+
ctx.log.debug(`Chromium launched with options ${JSON.stringify(launchOptions)}`);
|
|
1417
|
+
}
|
|
1418
|
+
const context = yield ctx.browser.newContext(contextOptions);
|
|
1419
|
+
ctx.log.debug(`Browser context created with options ${JSON.stringify(contextOptions)}`);
|
|
1420
|
+
const page = yield context.newPage();
|
|
1421
|
+
let cache = {};
|
|
1422
|
+
yield page.route("**/*", (route, request) => __async(this, null, function* () {
|
|
1423
|
+
const requestUrl = request.url();
|
|
1424
|
+
const requestHostname = new URL(requestUrl).hostname;
|
|
1425
|
+
try {
|
|
1426
|
+
if (/\.(mp3|mp4|wav|ogg|webm)$/i.test(request.url())) {
|
|
1427
|
+
throw new Error("resource type mp3/mp4/wav/ogg/webm");
|
|
1428
|
+
}
|
|
1429
|
+
ctx.config.allowedHostnames.push(new URL(snapshot.url).hostname);
|
|
1430
|
+
if (ctx.config.enableJavaScript)
|
|
1431
|
+
ALLOWED_RESOURCES.push("script");
|
|
1432
|
+
const response = yield page.request.fetch(request, { timeout: REQUEST_TIMEOUT });
|
|
1433
|
+
const body = yield response.body();
|
|
1434
|
+
if (!body) {
|
|
1435
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1436
|
+
- skipping no response`);
|
|
1437
|
+
} else if (!body.length) {
|
|
1438
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1439
|
+
- skipping empty response`);
|
|
1440
|
+
} else if (requestUrl === snapshot.url) {
|
|
1441
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1442
|
+
- skipping root resource`);
|
|
1443
|
+
} else if (!ctx.config.allowedHostnames.includes(requestHostname)) {
|
|
1444
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1445
|
+
- skipping remote resource`);
|
|
1446
|
+
} else if (cache[requestUrl]) {
|
|
1447
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1448
|
+
- skipping already cached resource`);
|
|
1449
|
+
} else if (body.length > MAX_RESOURCE_SIZE) {
|
|
1450
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1451
|
+
- skipping resource larger than 15MB`);
|
|
1452
|
+
} else if (!ALLOWED_STATUSES.includes(response.status())) {
|
|
1453
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1454
|
+
- skipping disallowed status [${response.status()}]`);
|
|
1455
|
+
} else if (!ALLOWED_RESOURCES.includes(request.resourceType())) {
|
|
1456
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1457
|
+
- skipping disallowed resource type [${request.resourceType()}]`);
|
|
1458
|
+
} else {
|
|
1459
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1460
|
+
- content-type ${response.headers()["content-type"]}`);
|
|
1461
|
+
cache[requestUrl] = {
|
|
1462
|
+
body: body.toString("base64"),
|
|
1463
|
+
type: response.headers()["content-type"]
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
route.fulfill({
|
|
1467
|
+
status: response.status(),
|
|
1468
|
+
headers: response.headers(),
|
|
1469
|
+
body
|
|
1470
|
+
});
|
|
1471
|
+
} catch (error) {
|
|
1472
|
+
ctx.log.debug(`Handling request ${requestUrl}
|
|
1473
|
+
- aborted due to ${error.message}`);
|
|
1474
|
+
route.abort();
|
|
1475
|
+
}
|
|
1476
|
+
}));
|
|
1477
|
+
let options = snapshot.options;
|
|
1478
|
+
let optionWarnings = /* @__PURE__ */ new Set();
|
|
1479
|
+
let processedOptions = {};
|
|
1480
|
+
let selectors = [];
|
|
1481
|
+
let ignoreOrSelectDOM;
|
|
1482
|
+
let ignoreOrSelectBoxes;
|
|
1483
|
+
if (options && Object.keys(options).length) {
|
|
1484
|
+
ctx.log.debug(`Snapshot options: ${JSON.stringify(options)}`);
|
|
1485
|
+
const isNotAllEmpty = (obj) => {
|
|
1486
|
+
var _a2;
|
|
1487
|
+
for (let key in obj)
|
|
1488
|
+
if ((_a2 = obj[key]) == null ? void 0 : _a2.length)
|
|
1489
|
+
return true;
|
|
1490
|
+
return false;
|
|
1491
|
+
};
|
|
1492
|
+
if (options.element && Object.keys(options.element).length) {
|
|
1493
|
+
if (options.element.id)
|
|
1494
|
+
processedOptions.element = "#" + options.element.id;
|
|
1495
|
+
else if (options.element.class)
|
|
1496
|
+
processedOptions.element = "." + options.element.class;
|
|
1497
|
+
else if (options.element.cssSelector)
|
|
1498
|
+
processedOptions.element = options.element.cssSelector;
|
|
1499
|
+
else if (options.element.xpath)
|
|
1500
|
+
processedOptions.element = "xpath=" + options.element.xpath;
|
|
1501
|
+
} else if (options.ignoreDOM && Object.keys(options.ignoreDOM).length && isNotAllEmpty(options.ignoreDOM)) {
|
|
1502
|
+
processedOptions.ignoreBoxes = {};
|
|
1503
|
+
ignoreOrSelectDOM = "ignoreDOM";
|
|
1504
|
+
ignoreOrSelectBoxes = "ignoreBoxes";
|
|
1505
|
+
} else if (options.selectDOM && Object.keys(options.selectDOM).length && isNotAllEmpty(options.selectDOM)) {
|
|
1506
|
+
processedOptions.selectBoxes = {};
|
|
1507
|
+
ignoreOrSelectDOM = "selectDOM";
|
|
1508
|
+
ignoreOrSelectBoxes = "selectBoxes";
|
|
1509
|
+
}
|
|
1510
|
+
if (ignoreOrSelectDOM) {
|
|
1511
|
+
for (const [key, value] of Object.entries(options[ignoreOrSelectDOM])) {
|
|
1512
|
+
switch (key) {
|
|
1513
|
+
case "id":
|
|
1514
|
+
selectors.push(...value.map((e) => "#" + e));
|
|
1515
|
+
break;
|
|
1516
|
+
case "class":
|
|
1517
|
+
selectors.push(...value.map((e) => "." + e));
|
|
1518
|
+
break;
|
|
1519
|
+
case "xpath":
|
|
1520
|
+
selectors.push(...value.map((e) => "xpath=" + e));
|
|
1521
|
+
break;
|
|
1522
|
+
case "cssSelector":
|
|
1523
|
+
selectors.push(...value);
|
|
1524
|
+
break;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
let navigated = false;
|
|
1530
|
+
let renderViewports = getRenderViewports(ctx);
|
|
1531
|
+
for (const { viewport, viewportString, fullPage } of renderViewports) {
|
|
1532
|
+
yield page.setViewportSize({ width: viewport.width, height: viewport.height || MIN_VIEWPORT_HEIGHT });
|
|
1533
|
+
ctx.log.debug(`Page resized to ${viewport.width}x${viewport.height || MIN_VIEWPORT_HEIGHT}`);
|
|
1534
|
+
if (!navigated) {
|
|
1535
|
+
try {
|
|
1536
|
+
yield page.goto(snapshot.url, { waitUntil: "domcontentloaded" });
|
|
1537
|
+
yield new Promise((r) => setTimeout(r, 1250));
|
|
1538
|
+
if (ctx.config.waitForTimeout)
|
|
1539
|
+
yield page.waitForTimeout(ctx.config.waitForTimeout);
|
|
1540
|
+
navigated = true;
|
|
1541
|
+
ctx.log.debug(`Navigated to ${snapshot.url}`);
|
|
1542
|
+
} catch (error) {
|
|
1543
|
+
ctx.log.debug(`Navigation to discovery page failed; ${error}`);
|
|
1544
|
+
throw new Error(error.message);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
if (ctx.config.enableJavaScript && fullPage)
|
|
1548
|
+
yield page.evaluate(scrollToBottomAndBackToTop);
|
|
1549
|
+
try {
|
|
1550
|
+
yield page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
1551
|
+
ctx.log.debug("Network idle 500ms");
|
|
1552
|
+
} catch (error) {
|
|
1553
|
+
ctx.log.debug(`Network idle failed due to ${error}`);
|
|
1554
|
+
}
|
|
1555
|
+
if (processedOptions.element) {
|
|
1556
|
+
let l = yield page.locator(processedOptions.element).all();
|
|
1557
|
+
if (l.length === 0) {
|
|
1558
|
+
throw new Error(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${processedOptions.element}`);
|
|
1559
|
+
} else if (l.length > 1) {
|
|
1560
|
+
throw new Error(`for snapshot ${snapshot.name} viewport ${viewportString}, multiple elements found for selector ${processedOptions.element}`);
|
|
1561
|
+
}
|
|
1562
|
+
} else if (selectors.length) {
|
|
1563
|
+
let locators = [];
|
|
1564
|
+
if (!Array.isArray(processedOptions[ignoreOrSelectBoxes][viewportString]))
|
|
1565
|
+
processedOptions[ignoreOrSelectBoxes][viewportString] = [];
|
|
1566
|
+
for (const selector of selectors) {
|
|
1567
|
+
let l = yield page.locator(selector).all();
|
|
1568
|
+
if (l.length === 0) {
|
|
1569
|
+
optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${selector}`);
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
locators.push(...l);
|
|
1573
|
+
}
|
|
1574
|
+
for (const locator of locators) {
|
|
1575
|
+
let bb = yield locator.boundingBox();
|
|
1576
|
+
if (bb)
|
|
1577
|
+
processedOptions[ignoreOrSelectBoxes][viewportString].push({
|
|
1578
|
+
left: bb.x,
|
|
1579
|
+
top: bb.y,
|
|
1580
|
+
right: bb.x + bb.width,
|
|
1581
|
+
bottom: bb.y + bb.height
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
if (snapshot.dom.resources.length) {
|
|
1587
|
+
for (let resource of snapshot.dom.resources) {
|
|
1588
|
+
cache[resource.url] = {
|
|
1589
|
+
body: resource.content,
|
|
1590
|
+
type: resource.mimetype
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
return {
|
|
1595
|
+
processedSnapshot: {
|
|
1596
|
+
name: snapshot.name,
|
|
1597
|
+
url: snapshot.url,
|
|
1598
|
+
dom: Buffer.from(snapshot.dom.html).toString("base64"),
|
|
1599
|
+
resources: cache,
|
|
1600
|
+
options: processedOptions
|
|
1601
|
+
},
|
|
1602
|
+
warnings: [...optionWarnings, ...snapshot.dom.warnings]
|
|
1603
|
+
};
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1509
1606
|
|
|
1510
1607
|
// src/commander/exec.ts
|
|
1511
1608
|
var command = new commander.Command();
|
|
1512
|
-
command.name("exec").description("Run test commands around SmartUI").argument("<command...>", "Command supplied for running tests").action(function(execCommand, _, command4) {
|
|
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, _, command4) {
|
|
1513
1610
|
return __async(this, null, function* () {
|
|
1514
1611
|
var _a, _b;
|
|
1515
1612
|
let ctx = ctx_default(command4.optsWithGlobals());
|
|
@@ -1518,6 +1615,7 @@ command.name("exec").description("Run test commands around SmartUI").argument("<
|
|
|
1518
1615
|
return;
|
|
1519
1616
|
}
|
|
1520
1617
|
ctx.args.execCommand = execCommand;
|
|
1618
|
+
ctx.snapshotQueue = new Queue(ctx);
|
|
1521
1619
|
ctx.totalSnapshots = 0;
|
|
1522
1620
|
let tasks = new listr2.Listr(
|
|
1523
1621
|
[
|
|
@@ -1526,6 +1624,7 @@ command.name("exec").description("Run test commands around SmartUI").argument("<
|
|
|
1526
1624
|
getGitInfo_default(),
|
|
1527
1625
|
createBuild_default(),
|
|
1528
1626
|
exec_default(ctx),
|
|
1627
|
+
processSnapshot_default(),
|
|
1529
1628
|
finalizeBuild_default()
|
|
1530
1629
|
],
|
|
1531
1630
|
{
|
|
@@ -1544,8 +1643,10 @@ command.name("exec").description("Run test commands around SmartUI").argument("<
|
|
|
1544
1643
|
} catch (error) {
|
|
1545
1644
|
ctx.log.info("\nRefer docs: https://www.lambdatest.com/support/docs/smart-visual-regression-testing/");
|
|
1546
1645
|
} finally {
|
|
1547
|
-
yield (_a = ctx.
|
|
1548
|
-
|
|
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`);
|
|
1549
1650
|
}
|
|
1550
1651
|
});
|
|
1551
1652
|
});
|
|
@@ -1715,13 +1816,13 @@ function captureScreenshots(ctx) {
|
|
|
1715
1816
|
else
|
|
1716
1817
|
yield captureScreenshotsSync(ctx, staticConfig, browsers);
|
|
1717
1818
|
delDir(`screenshots/${staticConfig.name.toLowerCase().replace(/\s/g, "_")}`);
|
|
1718
|
-
output += `${
|
|
1819
|
+
output += `${chalk7__default.default.gray(staticConfig.name)} ${chalk7__default.default.green("\u2713")}
|
|
1719
1820
|
`;
|
|
1720
1821
|
ctx.task.output = output;
|
|
1721
1822
|
capturedScreenshots++;
|
|
1722
1823
|
} catch (error) {
|
|
1723
1824
|
ctx.log.debug(`screenshot capture failed for ${JSON.stringify(staticConfig)}; error: ${error}`);
|
|
1724
|
-
output += `${
|
|
1825
|
+
output += `${chalk7__default.default.gray(staticConfig.name)} ${chalk7__default.default.red("\u2717")}
|
|
1725
1826
|
`;
|
|
1726
1827
|
ctx.task.output = output;
|
|
1727
1828
|
}
|
|
@@ -1745,7 +1846,7 @@ var captureScreenshots_default = (ctx) => {
|
|
|
1745
1846
|
task.title = "Screenshots captured successfully";
|
|
1746
1847
|
} catch (error) {
|
|
1747
1848
|
ctx2.log.debug(error);
|
|
1748
|
-
task.output =
|
|
1849
|
+
task.output = chalk7__default.default.gray(`${error.message}`);
|
|
1749
1850
|
throw new Error("Capturing screenshots failed");
|
|
1750
1851
|
}
|
|
1751
1852
|
}),
|
|
@@ -1839,7 +1940,7 @@ var uploadFigmaDesigns_default2 = (ctx) => {
|
|
|
1839
1940
|
ctx2.log.debug(`Figma designs processed: ${results}`);
|
|
1840
1941
|
} catch (error) {
|
|
1841
1942
|
ctx2.log.debug(error);
|
|
1842
|
-
task.output =
|
|
1943
|
+
task.output = chalk7__default.default.gray(`${error.message}`);
|
|
1843
1944
|
throw new Error("Uploading Figma designs failed");
|
|
1844
1945
|
}
|
|
1845
1946
|
}),
|
|
@@ -1909,13 +2010,13 @@ LambdaTest SmartUI CLI v${package_default.version}`);
|
|
|
1909
2010
|
log.warn(`This version is deprecated. A new version ${latestVersion} is available!
|
|
1910
2011
|
`);
|
|
1911
2012
|
else if (package_default.version !== latestVersion)
|
|
1912
|
-
log.info(
|
|
2013
|
+
log.info(chalk7__default.default.gray(`A new version ${latestVersion} is available!
|
|
1913
2014
|
`));
|
|
1914
2015
|
else
|
|
1915
|
-
log.info(
|
|
2016
|
+
log.info(chalk7__default.default.gray("https://www.npmjs.com/package/@lambdatest/smartui-cli\n"));
|
|
1916
2017
|
} catch (error) {
|
|
1917
2018
|
log.debug(error);
|
|
1918
|
-
log.info(
|
|
2019
|
+
log.info(chalk7__default.default.gray("https://www.npmjs.com/package/@lambdatest/smartui-cli\n"));
|
|
1919
2020
|
}
|
|
1920
2021
|
commander_default.parse();
|
|
1921
2022
|
});
|