Skip to content

Commit

Permalink
Merge pull request #157 from LambdaTest/stage
Browse files Browse the repository at this point in the history
Responsive Dom Comparison
  • Loading branch information
sushobhit-lt authored Oct 22, 2024
2 parents e39f823 + 64e8e5a commit 067d677
Show file tree
Hide file tree
Showing 9 changed files with 535 additions and 74 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lambdatest/smartui-cli",
"version": "4.0.6",
"version": "4.0.7",
"description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
"files": [
"dist/**/*"
Expand Down
2 changes: 1 addition & 1 deletion src/commander/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import createBuild from '../tasks/createBuild.js'
import exec from '../tasks/exec.js'
import processSnapshots from '../tasks/processSnapshot.js'
import finalizeBuild from '../tasks/finalizeBuild.js'
import snapshotQueue from '../lib/processSnapshot.js'
import snapshotQueue from '../lib/snapshotQueue.js'

const command = new Command();

Expand Down
3 changes: 2 additions & 1 deletion src/lib/ctx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ export default (options: Record<string, string>): Context => {
scrollTime: config.scrollTime || constants.DEFAULT_SCROLL_TIME,
allowedHostnames: config.allowedHostnames || [],
basicAuthorization: basicAuthObj,
smartIgnore: config.smartIgnore ?? false
smartIgnore: config.smartIgnore ?? false,
delayedUpload: config.delayedUpload ?? false
},
uploadFilePath: '',
webStaticConfig: [],
Expand Down
119 changes: 50 additions & 69 deletions src/lib/processSnapshot.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Snapshot, Context, ProcessedSnapshot } from "../types.js";
import { scrollToBottomAndBackToTop, getRenderViewports } from "./utils.js"
import { scrollToBottomAndBackToTop, getRenderViewports, getRenderViewportsForOptions } from "./utils.js"
import { chromium, Locator } from "@playwright/test"
import constants from "./constants.js";
import { updateLogContext } from '../lib/logger.js'
Expand All @@ -10,73 +10,7 @@ const ALLOWED_STATUSES = [200, 201];
const REQUEST_TIMEOUT = 10000;
const MIN_VIEWPORT_HEIGHT = 1080;

export default class Queue {
private snapshots: Array<Snapshot> = [];
private processedSnapshots: Array<Record<string, any>> = [];
private processing: boolean = false;
private processingSnapshot: string = '';
private ctx: Context;

constructor(ctx: Context) {
this.ctx = ctx;
}

enqueue(item: Snapshot): void {
this.snapshots.push(item);
if (!this.processing) {
this.processing = true;
this.processNext();
}
}

private async processNext(): Promise<void> {
if (!this.isEmpty()) {
const snapshot = this.snapshots.shift();
try {
this.processingSnapshot = snapshot?.name;
let { processedSnapshot, warnings } = await processSnapshot(snapshot, this.ctx);
await this.ctx.client.uploadSnapshot(this.ctx, processedSnapshot);
this.ctx.totalSnapshots++;
this.processedSnapshots.push({ name: snapshot.name, warnings });
} catch (error: any) {
this.ctx.log.debug(`snapshot failed; ${error}`);
this.processedSnapshots.push({ name: snapshot.name, error: error.message });
}
// Close open browser contexts and pages
if (this.ctx.browser) {
for (let context of this.ctx.browser.contexts()) {
for (let page of context.pages()) {
await page.close();
this.ctx.log.debug(`Closed browser page for snapshot ${snapshot.name}`);
}
await context.close();
this.ctx.log.debug(`Closed browser context for snapshot ${snapshot.name}`);
}
}
this.processNext();
} else {
this.processing = false;
}
}

isProcessing(): boolean {
return this.processing;
}

getProcessingSnapshot(): string {
return this.processingSnapshot;
}

getProcessedSnapshots(): Array<Record<string, any>> {
return this.processedSnapshots;
}

isEmpty(): boolean {
return this.snapshots && this.snapshots.length ? false : true;
}
}

async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record<string, any>> {
export default async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record<string, any>> {
updateLogContext({ task: 'discovery' });
ctx.log.debug(`Processing snapshot ${snapshot.name} ${snapshot.url}`);

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

if (options.web && Object.keys(options.web).length) {
processedOptions.web = {};

// Check and process viewports in web
if (options.web.viewports && options.web.viewports.length > 0) {
processedOptions.web.viewports = options.web.viewports.filter(viewport =>
Array.isArray(viewport) && viewport.length > 0
);
}

// Check and process browsers in web
if (options.web.browsers && options.web.browsers.length > 0) {
processedOptions.web.browsers = options.web.browsers;
}
}

if (options.mobile && Object.keys(options.mobile).length) {
processedOptions.mobile = {};

// Check and process devices in mobile
if (options.mobile.devices && options.mobile.devices.length > 0) {
processedOptions.mobile.devices = options.mobile.devices;
}

// Check if 'fullPage' is provided and is a boolean, otherwise set default to true
if (options.mobile.hasOwnProperty('fullPage') && typeof options.mobile.fullPage === 'boolean') {
processedOptions.mobile.fullPage = options.mobile.fullPage;
} else {
processedOptions.mobile.fullPage = true; // Default value for fullPage
}

// Check if 'orientation' is provided and is valid, otherwise set default to 'portrait'
if (options.mobile.hasOwnProperty('orientation') && (options.mobile.orientation === constants.MOBILE_ORIENTATION_PORTRAIT || options.mobile.orientation === constants.MOBILE_ORIENTATION_LANDSCAPE)) {
processedOptions.mobile.orientation = options.mobile.orientation;
} else {
processedOptions.mobile.orientation = constants.MOBILE_ORIENTATION_PORTRAIT; // Default value for orientation
}
}

if (options.element && Object.keys(options.element).length) {
if (options.element.id) processedOptions.element = '#' + options.element.id;
else if (options.element.class) processedOptions.element = '.' + options.element.class;
Expand Down Expand Up @@ -268,7 +241,14 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
// process for every viewport
let navigated: boolean = false;
let previousDeviceType: string | null = null;
let renderViewports = getRenderViewports(ctx);

let renderViewports;

if((snapshot.options && snapshot.options.web) || (snapshot.options && snapshot.options.mobile)){
renderViewports = getRenderViewportsForOptions(snapshot.options)
} else {
renderViewports = getRenderViewports(ctx);
}

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

Expand Down Expand Up @@ -340,6 +320,7 @@ async function processSnapshot(snapshot: Snapshot, ctx: Context): Promise<Record
});
}
}
ctx.log.debug(`Processed options: ${JSON.stringify(processedOptions)}`);
}

return {
Expand Down
64 changes: 63 additions & 1 deletion src/lib/schemaValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,11 @@ const ConfigSchema = {
errorMessage: "Invalid config; password is mandatory"
},
}
}
},
delayedUpload: {
type: "boolean",
errorMessage: "Invalid config; delayedUpload must be true/false"
},
},
anyOf: [
{ required: ["web"] },
Expand Down Expand Up @@ -289,6 +293,64 @@ const SnapshotSchema: JSONSchemaType<Snapshot> = {
errorMessage: "Invalid snapshot options; selectDOM xpath array must have unique and non-empty items"
},
}
},
web: {
type: "object",
properties: {
browsers: {
type: "array",
items: {
type: "string",
enum: [constants.CHROME, constants.FIREFOX, constants.SAFARI, constants.EDGE],
minLength: 1
},
uniqueItems: true,
errorMessage: `Invalid snapshot options; allowed browsers - ${constants.CHROME}, ${constants.FIREFOX}, ${constants.SAFARI}, ${constants.EDGE}`
},
viewports: {
type: "array",
items: {
type: "array",
items: {
type: "number",
minimum: 1
},
minItems: 1,
maxItems: 2,
errorMessage: "Invalid snapshot options; each viewport array must contain either a single width or a width and height tuple with positive values."
},
uniqueItems: true,
errorMessage: "Invalid snapshot options; viewports must be an array of unique arrays."
}
},
required: ["viewports"],
errorMessage: "Invalid snapshot options; web must include viewports property."
},
mobile: {
type: "object",
properties: {
devices: {
type: "array",
items: {
type: "string",
enum: Object.keys(constants.SUPPORTED_MOBILE_DEVICES),
minLength: 1
},
uniqueItems: true,
errorMessage: "Invalid snapshot options; devices must be an array of unique supported mobile devices."
},
fullPage: {
type: "boolean",
errorMessage: "Invalid snapshot options; fullPage must be a boolean."
},
orientation: {
type: "string",
enum: [constants.MOBILE_ORIENTATION_PORTRAIT, constants.MOBILE_ORIENTATION_LANDSCAPE],
errorMessage: "Invalid snapshot options; orientation must be either 'portrait' or 'landscape'."
}
},
required: ["devices"],
errorMessage: "Invalid snapshot options; mobile must include devices property."
}
},
additionalProperties: false
Expand Down
Loading

0 comments on commit 067d677

Please sign in to comment.