Skip to content

Commit 067d677

Browse files
authored
Merge pull request #157 from LambdaTest/stage
Responsive Dom Comparison
2 parents e39f823 + 64e8e5a commit 067d677

File tree

9 files changed

+535
-74
lines changed

9 files changed

+535
-74
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lambdatest/smartui-cli",
3-
"version": "4.0.6",
3+
"version": "4.0.7",
44
"description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
55
"files": [
66
"dist/**/*"

src/commander/exec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import createBuild from '../tasks/createBuild.js'
1010
import exec from '../tasks/exec.js'
1111
import processSnapshots from '../tasks/processSnapshot.js'
1212
import finalizeBuild from '../tasks/finalizeBuild.js'
13-
import snapshotQueue from '../lib/processSnapshot.js'
13+
import snapshotQueue from '../lib/snapshotQueue.js'
1414

1515
const command = new Command();
1616

src/lib/ctx.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ export default (options: Record<string, string>): Context => {
7575
scrollTime: config.scrollTime || constants.DEFAULT_SCROLL_TIME,
7676
allowedHostnames: config.allowedHostnames || [],
7777
basicAuthorization: basicAuthObj,
78-
smartIgnore: config.smartIgnore ?? false
78+
smartIgnore: config.smartIgnore ?? false,
79+
delayedUpload: config.delayedUpload ?? false
7980
},
8081
uploadFilePath: '',
8182
webStaticConfig: [],

src/lib/processSnapshot.ts

Lines changed: 50 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Snapshot, Context, ProcessedSnapshot } from "../types.js";
2-
import { scrollToBottomAndBackToTop, getRenderViewports } from "./utils.js"
2+
import { scrollToBottomAndBackToTop, getRenderViewports, getRenderViewportsForOptions } from "./utils.js"
33
import { chromium, Locator } from "@playwright/test"
44
import constants from "./constants.js";
55
import { updateLogContext } from '../lib/logger.js'
@@ -10,73 +10,7 @@ const ALLOWED_STATUSES = [200, 201];
1010
const REQUEST_TIMEOUT = 10000;
1111
const MIN_VIEWPORT_HEIGHT = 1080;
1212

13-
export default class Queue {
14-
private snapshots: Array<Snapshot> = [];
15-
private processedSnapshots: Array<Record<string, any>> = [];
16-
private processing: boolean = false;
17-
private processingSnapshot: string = '';
18-
private ctx: Context;
19-
20-
constructor(ctx: Context) {
21-
this.ctx = ctx;
22-
}
23-
24-
enqueue(item: Snapshot): void {
25-
this.snapshots.push(item);
26-
if (!this.processing) {
27-
this.processing = true;
28-
this.processNext();
29-
}
30-
}
31-
32-
private async processNext(): Promise<void> {
33-
if (!this.isEmpty()) {
34-
const snapshot = this.snapshots.shift();
35-
try {
36-
this.processingSnapshot = snapshot?.name;
37-
let { processedSnapshot, warnings } = await processSnapshot(snapshot, this.ctx);
38-
await this.ctx.client.uploadSnapshot(this.ctx, processedSnapshot);
39-
this.ctx.totalSnapshots++;
40-
this.processedSnapshots.push({ name: snapshot.name, warnings });
41-
} catch (error: any) {
42-
this.ctx.log.debug(`snapshot failed; ${error}`);
43-
this.processedSnapshots.push({ name: snapshot.name, error: error.message });
44-
}
45-
// Close open browser contexts and pages
46-
if (this.ctx.browser) {
47-
for (let context of this.ctx.browser.contexts()) {
48-
for (let page of context.pages()) {
49-
await page.close();
50-
this.ctx.log.debug(`Closed browser page for snapshot ${snapshot.name}`);
51-
}
52-
await context.close();
53-
this.ctx.log.debug(`Closed browser context for snapshot ${snapshot.name}`);
54-
}
55-
}
56-
this.processNext();
57-
} else {
58-
this.processing = false;
59-
}
60-
}
61-
62-
isProcessing(): boolean {
63-
return this.processing;
64-
}
65-
66-
getProcessingSnapshot(): string {
67-
return this.processingSnapshot;
68-
}
69-
70-
getProcessedSnapshots(): Array<Record<string, any>> {
71-
return this.processedSnapshots;
72-
}
73-
74-
isEmpty(): boolean {
75-
return this.snapshots && this.snapshots.length ? false : true;
76-
}
77-
}
78-
79-
async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record<string, any>> {
13+
export default async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record<string, any>> {
8014
updateLogContext({ task: 'discovery' });
8115
ctx.log.debug(`Processing snapshot ${snapshot.name} ${snapshot.url}`);
8216

@@ -231,6 +165,45 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
231165
return false;
232166
}
233167

168+
if (options.web && Object.keys(options.web).length) {
169+
processedOptions.web = {};
170+
171+
// Check and process viewports in web
172+
if (options.web.viewports && options.web.viewports.length > 0) {
173+
processedOptions.web.viewports = options.web.viewports.filter(viewport =>
174+
Array.isArray(viewport) && viewport.length > 0
175+
);
176+
}
177+
178+
// Check and process browsers in web
179+
if (options.web.browsers && options.web.browsers.length > 0) {
180+
processedOptions.web.browsers = options.web.browsers;
181+
}
182+
}
183+
184+
if (options.mobile && Object.keys(options.mobile).length) {
185+
processedOptions.mobile = {};
186+
187+
// Check and process devices in mobile
188+
if (options.mobile.devices && options.mobile.devices.length > 0) {
189+
processedOptions.mobile.devices = options.mobile.devices;
190+
}
191+
192+
// Check if 'fullPage' is provided and is a boolean, otherwise set default to true
193+
if (options.mobile.hasOwnProperty('fullPage') && typeof options.mobile.fullPage === 'boolean') {
194+
processedOptions.mobile.fullPage = options.mobile.fullPage;
195+
} else {
196+
processedOptions.mobile.fullPage = true; // Default value for fullPage
197+
}
198+
199+
// Check if 'orientation' is provided and is valid, otherwise set default to 'portrait'
200+
if (options.mobile.hasOwnProperty('orientation') && (options.mobile.orientation === constants.MOBILE_ORIENTATION_PORTRAIT || options.mobile.orientation === constants.MOBILE_ORIENTATION_LANDSCAPE)) {
201+
processedOptions.mobile.orientation = options.mobile.orientation;
202+
} else {
203+
processedOptions.mobile.orientation = constants.MOBILE_ORIENTATION_PORTRAIT; // Default value for orientation
204+
}
205+
}
206+
234207
if (options.element && Object.keys(options.element).length) {
235208
if (options.element.id) processedOptions.element = '#' + options.element.id;
236209
else if (options.element.class) processedOptions.element = '.' + options.element.class;
@@ -268,7 +241,14 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
268241
// process for every viewport
269242
let navigated: boolean = false;
270243
let previousDeviceType: string | null = null;
271-
let renderViewports = getRenderViewports(ctx);
244+
245+
let renderViewports;
246+
247+
if((snapshot.options && snapshot.options.web) || (snapshot.options && snapshot.options.mobile)){
248+
renderViewports = getRenderViewportsForOptions(snapshot.options)
249+
} else {
250+
renderViewports = getRenderViewports(ctx);
251+
}
272252

273253
for (const { viewport, viewportString, fullPage, device } of renderViewports) {
274254

@@ -340,6 +320,7 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
340320
});
341321
}
342322
}
323+
ctx.log.debug(`Processed options: ${JSON.stringify(processedOptions)}`);
343324
}
344325

345326
return {

src/lib/schemaValidation.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,11 @@ const ConfigSchema = {
150150
errorMessage: "Invalid config; password is mandatory"
151151
},
152152
}
153-
}
153+
},
154+
delayedUpload: {
155+
type: "boolean",
156+
errorMessage: "Invalid config; delayedUpload must be true/false"
157+
},
154158
},
155159
anyOf: [
156160
{ required: ["web"] },
@@ -289,6 +293,64 @@ const SnapshotSchema: JSONSchemaType<Snapshot> = {
289293
errorMessage: "Invalid snapshot options; selectDOM xpath array must have unique and non-empty items"
290294
},
291295
}
296+
},
297+
web: {
298+
type: "object",
299+
properties: {
300+
browsers: {
301+
type: "array",
302+
items: {
303+
type: "string",
304+
enum: [constants.CHROME, constants.FIREFOX, constants.SAFARI, constants.EDGE],
305+
minLength: 1
306+
},
307+
uniqueItems: true,
308+
errorMessage: `Invalid snapshot options; allowed browsers - ${constants.CHROME}, ${constants.FIREFOX}, ${constants.SAFARI}, ${constants.EDGE}`
309+
},
310+
viewports: {
311+
type: "array",
312+
items: {
313+
type: "array",
314+
items: {
315+
type: "number",
316+
minimum: 1
317+
},
318+
minItems: 1,
319+
maxItems: 2,
320+
errorMessage: "Invalid snapshot options; each viewport array must contain either a single width or a width and height tuple with positive values."
321+
},
322+
uniqueItems: true,
323+
errorMessage: "Invalid snapshot options; viewports must be an array of unique arrays."
324+
}
325+
},
326+
required: ["viewports"],
327+
errorMessage: "Invalid snapshot options; web must include viewports property."
328+
},
329+
mobile: {
330+
type: "object",
331+
properties: {
332+
devices: {
333+
type: "array",
334+
items: {
335+
type: "string",
336+
enum: Object.keys(constants.SUPPORTED_MOBILE_DEVICES),
337+
minLength: 1
338+
},
339+
uniqueItems: true,
340+
errorMessage: "Invalid snapshot options; devices must be an array of unique supported mobile devices."
341+
},
342+
fullPage: {
343+
type: "boolean",
344+
errorMessage: "Invalid snapshot options; fullPage must be a boolean."
345+
},
346+
orientation: {
347+
type: "string",
348+
enum: [constants.MOBILE_ORIENTATION_PORTRAIT, constants.MOBILE_ORIENTATION_LANDSCAPE],
349+
errorMessage: "Invalid snapshot options; orientation must be either 'portrait' or 'landscape'."
350+
}
351+
},
352+
required: ["devices"],
353+
errorMessage: "Invalid snapshot options; mobile must include devices property."
292354
}
293355
},
294356
additionalProperties: false

0 commit comments

Comments
 (0)