Skip to content
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.1.29",
"version": "4.1.30",
"description": "A command line interface (CLI) to run SmartUI tests on LambdaTest",
"files": [
"dist/**/*"
Expand Down
54 changes: 53 additions & 1 deletion src/lib/processSnapshot.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Snapshot, Context, DiscoveryErrors } from "../types.js";
import { scrollToBottomAndBackToTop, getRenderViewports, getRenderViewportsForOptions } from "./utils.js"
import { scrollToBottomAndBackToTop, getRenderViewports, getRenderViewportsForOptions, validateCoordinates } from "./utils.js"
import { chromium, Locator } from "@playwright/test"
import constants from "./constants.js";
import { updateLogContext } from '../lib/logger.js'
Expand Down Expand Up @@ -126,6 +126,9 @@ export async function prepareSnapshot(snapshot: Snapshot, ctx: Context): Promise
case 'cssSelector':
selectors.push(...value);
break;
case 'coordinates':
selectors.push(...value.map(e => `coordinates=${e}`));
break;
}
}
}
Expand Down Expand Up @@ -500,6 +503,9 @@ export default async function processSnapshot(snapshot: Snapshot, ctx: Context):
case 'cssSelector':
selectors.push(...value);
break;
case 'coordinates':
selectors.push(...value.map(e => `coordinates=${e}`));
break;
}
}
}
Expand Down Expand Up @@ -663,14 +669,58 @@ export default async function processSnapshot(snapshot: Snapshot, ctx: Context):
if (!Array.isArray(processedOptions[ignoreOrSelectBoxes][viewportString])) processedOptions[ignoreOrSelectBoxes][viewportString] = []

for (const selector of selectors) {
if (selector.startsWith('coordinates=')) {
const coordString = selector.replace('coordinates=', '');
let pageHeight = height;
if (viewport.height) {
pageHeight = viewport.height;
}
const validation = validateCoordinates(
coordString,
pageHeight,
viewport.width,
snapshot.name
);

if (!validation.valid) {
optionWarnings.add(validation.error!);
continue;
}

if(renderViewports.length > 1){
optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, coordinates may not be accurate for multiple viewports`);
}


const coordinateElement = {
type: 'coordinates',
...validation.coords
};
locators.push(coordinateElement as any);
continue;
}

let l = await page.locator(selector).all()
if (l.length === 0) {
optionWarnings.add(`for snapshot ${snapshot.name} viewport ${viewportString}, no element found for selector ${selector}`);
continue;
}
locators.push(...l);
}

for (const locator of locators) {
if (locator && typeof locator === 'object' && locator.hasOwnProperty('type') && (locator as any).type === 'coordinates') {
const coordLocator = locator as any;
const { top, bottom, left, right } = coordLocator;
processedOptions[ignoreOrSelectBoxes][viewportString].push({
left: left,
top: top,
right: right,
bottom: bottom
});
continue;
}

let bb = await locator.boundingBox();
if (bb) {
// Calculate top and bottom from the bounding box properties
Expand Down Expand Up @@ -738,3 +788,5 @@ export default async function processSnapshot(snapshot: Snapshot, ctx: Context):
discoveryErrors: discoveryErrors
}
}


12 changes: 12 additions & 0 deletions src/lib/schemaValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,12 @@ const SnapshotSchema: JSONSchemaType<Snapshot> = {
uniqueItems: true,
errorMessage: "Invalid snapshot options; ignoreDOM xpath array must have unique and non-empty items"
},
coordinates: {
type: "array",
items: { type: "string", minLength: 1 },
uniqueItems: true,
errorMessage: "Invalid snapshot options; ignoreDOM coordinates array must have unique and non-empty items"
}
}
},
selectDOM: {
Expand Down Expand Up @@ -424,6 +430,12 @@ const SnapshotSchema: JSONSchemaType<Snapshot> = {
uniqueItems: true,
errorMessage: "Invalid snapshot options; selectDOM xpath array must have unique and non-empty items"
},
coordinates: {
type: "array",
items: { type: "string", minLength: 1 },
uniqueItems: true,
errorMessage: "Invalid snapshot options; selectDOM coordinates array must have unique and non-empty items"
}
}
},
ignoreType: {
Expand Down
66 changes: 66 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,4 +665,70 @@ function formatPdfsForOutput(pdfGroups: Record<string, any[]>): any[] {
function getPageNumber(screenshotName: string): string {
const parts = screenshotName.split('#');
return parts.length > 1 ? parts[1] : '1';
}

export function validateCoordinates(
coordString: string,
pageHeight: number,
pageWidth: number,
snapshotName: string
): { valid: boolean, error?: string, coords?: { top: number, bottom: number, left: number, right: number } } {

const coords = coordString.split(',').map(Number);

if (coords.length !== 4) {
return {
valid: false,
error: `for snapshot ${snapshotName}, invalid coordinates format: ${coordString}. Expected: top,bottom,left,right`
};
}

const [top, bottom, left, right] = coords;

if (coords.some(isNaN)) {
return {
valid: false,
error: `for snapshot ${snapshotName}, invalid coordinate values: ${coordString}. All values must be numbers`
};
}

if (top < 0 || left < 0 || bottom < 0 || right < 0) {
return {
valid: false,
error: `for snapshot ${snapshotName}, invalid coordinate bounds: ${coordString}. top,left,bottom,right must be >= 0`
};
}

if (top >= bottom) {
return {
valid: false,
error: `for snapshot ${snapshotName}, invalid coordinate bounds: ${coordString}. top must be < bottom`
};
}

if (left >= right) {
return {
valid: false,
error: `for snapshot ${snapshotName}, invalid coordinate bounds: ${coordString}. left must be < right`
};
}

if (bottom > pageHeight) {
return {
valid: false,
error: `for snapshot ${snapshotName}, coordinates exceed viewport bounds: ${coordString}. bottom (${bottom}) exceeds viewport height (${pageHeight})`
};
}

if (right > pageWidth) {
return {
valid: false,
error: `for snapshot ${snapshotName}, coordinates exceed viewport bounds: ${coordString}. right (${right}) exceeds viewport width (${pageWidth})`
};
}

return {
valid: true,
coords: { top, bottom, left, right }
};
}
6 changes: 4 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,15 @@ export interface Snapshot {
id?: Array<string>,
class?: Array<string>,
cssSelector?: Array<string>,
xpath?: Array<string>
xpath?: Array<string>,
coordinates?: Array<string>
},
selectDOM?: {
id?: Array<string>,
class?: Array<string>,
cssSelector?: Array<string>,
xpath?: Array<string>
xpath?: Array<string>,
coordinates?: Array<string>
},
element?: {
id?: string,
Expand Down