Last active
April 26, 2025 08:33
-
-
Save evagoras/4b6376af5e661ec98e8df4b196019940 to your computer and use it in GitHub Desktop.
Keep Images Honest: Zero-Dependency Aspect Ratio Testing with Playwright
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Locator } from '@playwright/test'; | |
export interface AspectRatioResult { | |
withinTolerance: boolean; | |
deviationPct: number; | |
naturalRatio: number; | |
renderedRatio: number; | |
} | |
/** | |
* General image-related utilities for Playwright tests. | |
*/ | |
export class ImageUtils { | |
/** | |
* Checks whether an image’s rendered aspect ratio matches its natural aspect ratio | |
* within a given tolerance (in percent). | |
* | |
* @param image Playwright Locator for the <img> element | |
* @param tolerancePct Allowed deviation in percent (e.g. 1 = 1%) | |
* @returns Object describing whether it’s within tolerance, plus metrics | |
*/ | |
public static async checkAspectRatio( | |
image: Locator, | |
tolerancePct: number = 1 | |
): Promise<AspectRatioResult> { | |
// Bundle all DOM reads into a single evaluate() round-trip | |
const data = await image.evaluate((img: HTMLImageElement) => { | |
// 1) Ensure the image is loaded | |
if (!img.complete || img.naturalWidth === 0 || img.naturalHeight === 0) { | |
return null; | |
} | |
// 2) Get on-screen size (includes CSS transforms & fractional pixels) | |
const { width: rectW, height: rectH } = img.getBoundingClientRect(); | |
const style = window.getComputedStyle(img); | |
// 3) Subtract padding & border to get the true content box | |
const padX = | |
parseFloat(style.paddingLeft || '0') + | |
parseFloat(style.paddingRight || '0'); | |
const padY = | |
parseFloat(style.paddingTop || '0') + | |
parseFloat(style.paddingBottom || '0'); | |
const borderX = | |
parseFloat(style.borderLeftWidth || '0') + | |
parseFloat(style.borderRightWidth || '0'); | |
const borderY = | |
parseFloat(style.borderTopWidth || '0') + | |
parseFloat(style.borderBottomWidth || '0'); | |
return { | |
naturalW: img.naturalWidth, | |
naturalH: img.naturalHeight, | |
renderedW: rectW - padX - borderX, | |
renderedH: rectH - padY - borderY | |
}; | |
}); | |
if (!data) { | |
throw new Error('Image not loaded or has zero natural dimensions'); | |
} | |
const { naturalW, naturalH, renderedW, renderedH } = data; | |
const naturalRatio = naturalW / naturalH; | |
const renderedRatio = renderedW / renderedH; | |
// 4) Compute deviation percentage | |
const deviationPct = Math.abs(renderedRatio / naturalRatio - 1) * 100; | |
const withinTolerance = deviationPct <= tolerancePct; | |
// build and return an explicit AspectRatioResult | |
const result: AspectRatioResult = { | |
withinTolerance, | |
deviationPct, | |
naturalRatio, | |
renderedRatio, | |
}; | |
if (!withinTolerance) { | |
console.warn( | |
`Aspect-ratio mismatch: rendered=${renderedRatio.toFixed(4)}, ` + | |
`natural=${naturalRatio.toFixed(4)} → ` + | |
`deviation=${deviationPct.toFixed(2)}% ` + | |
`(tolerance=${tolerancePct}%)` | |
); | |
} | |
return result; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { test, expect } from '@playwright/test'; | |
import { AspectRatioResult, ImageUtils } from '../utils/ImageUtils'; | |
test('product thumbnail maintains its aspect ratio', async ({ page }) => { | |
await page.goto('https://your-app.local/products/42'); | |
const thumbnail = page.locator('.product-thumbnail img'); | |
const { | |
withinTolerance, | |
deviationPct, | |
naturalRatio, | |
renderedRatio | |
}: AspectRatioResult = await ImageUtils.checkAspectRatio(thumbnail, 0.5); | |
expect(withinTolerance).toBeTruthy(); | |
expect(deviationPct).toBeLessThanOrEqual(0.5); | |
console.log( | |
`Natural ratio: ${naturalRatio.toFixed(4)}, ` + | |
`Rendered ratio: ${renderedRatio.toFixed(4)}, ` + | |
`Deviation: ${deviationPct.toFixed(2)}%` | |
); | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://evagoras.com/2025/04/26/keep-images-honest-zero-dependency-aspect-ratio-testing-with-playwright/