Skip to content

Commit 22ccf3f

Browse files
authored
Test/dark mode brightness assertions (#2097)
## Changes - Replaced pixel-based visual regression tests for dark/light mode with perceptually accurate brightness assertions. - Implemented a `getPageBrightness()` helper that computes **relative luminance** using the ITU-R BT.709/sRGB coefficients (`0.2126*R + 0.7152*G + 0.0722*B`), normalized to the `[0, 1]` range. - Added assertions that light mode brightness is approximately `0.85 ± 0.1` and dark mode is `0.25 ± 0.1`. - Removed all stored screenshot snapshots for dark/light mode across browsers and devices. ## Context Pixel-diff-based screenshot tests are fragile they fail on minor layout shifts, anti-aliasing changes, or browser rendering differences, even when the visual theme (light/dark) is correct. One example is the PR where the monitor around Git's version number is removed, which caused the dark mode tests to fail, even though the new look did not change anything about how dark the dark mode is. Instead, we now verify **perceived brightness** using the standard definition of *relative luminance* from color science (per [Wikipedia](https://en.wikipedia.org/wiki/Relative_luminance)), which aligns with human vision sensitivity (green contributes most, blue least). Reference: #2088 (comment)
2 parents e2eb96f + f3d3240 commit 22ccf3f

11 files changed

+53
-10
lines changed

tests/git-scm.spec.js

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -316,30 +316,73 @@ test('small-and-fast', async ({ page }) => {
316316
});
317317

318318
test('dark mode', async({ page }) => {
319+
// Helper: returns relative luminance in range [0, 1]
320+
const getPageBrightness = async () => {
321+
const screenshot = await page.screenshot({ type: 'png' });
322+
const base64 = screenshot.toString('base64');
323+
324+
return await page.evaluate((b64) => {
325+
return new Promise((resolve) => {
326+
const img = new Image();
327+
img.src = `data:image/png;base64,${b64}`;
328+
img.onload = () => {
329+
const canvas = document.createElement('canvas');
330+
canvas.width = canvas.height = 1;
331+
const ctx = canvas.getContext('2d');
332+
ctx.drawImage(img, 0, 0, 1, 1);
333+
const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
334+
335+
// Calculate brightness, for more details, see
336+
// https://en.wikipedia.org/wiki/Relative_luminance#Relative_luminance_and_%22gamma_encoded%22_colorspaces
337+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
338+
339+
// Normalize to [0, 1]
340+
resolve(luminance / 255);
341+
};
342+
img.onerror = () => resolve(-1); // error indicator
343+
});
344+
}, base64);
345+
};
346+
319347
await page.setViewportSize(devices['iPhone X'].viewport);
348+
await page.goto(url);
320349

321-
await page.goto(`${url}`);
350+
// Ensure consistent test state
322351
await page.evaluate(() => {
323352
document.querySelector('#tagline').innerHTML = '--dark-mode-for-dark-times';
324353
});
354+
325355
const darkModeButton = page.locator('#dark-mode-button');
326356

327-
const o = { maxDiffPixels: 30 };
328-
await expect(page).toHaveScreenshot({ name: 'light-mode.png', ...o });
357+
// 1. Light mode
358+
const lightBrightness = await getPageBrightness();
359+
expect(lightBrightness).toBeCloseTo(0.85, 0.1); // e.g., 0.75–0.95
360+
361+
// 2. Toggle to dark mode
329362
await darkModeButton.click();
330-
await expect(page).toHaveScreenshot({ name: 'dark-mode.png', ...o });
363+
const darkBrightness = await getPageBrightness();
364+
expect(darkBrightness).toBeCloseTo(0.25, 0.1); // e.g., 0.15–0.35
331365

332-
// Now, try again, but this time with system's preference being dark mode
366+
// 3. Verify dark < light
367+
expect(darkBrightness).toBeLessThan(lightBrightness);
333368

369+
// --- Test system preference: prefers-color-scheme: dark ---
334370
await page.emulateMedia({ colorScheme: 'dark' });
335-
await page.evaluate(() => window.localStorage.clear());
336-
await page.evaluate(() => window.sessionStorage.clear());
371+
await page.evaluate(() => {
372+
localStorage.clear();
373+
sessionStorage.clear();
374+
});
337375
await page.reload();
338376
await page.evaluate(() => {
339377
document.querySelector('#tagline').innerHTML = '--dark-mode-for-dark-times';
340378
});
341379

342-
await expect(page).toHaveScreenshot({ name: 'dark-mode.png', ...o });
380+
// Should start in dark mode
381+
const autoDarkBrightness = await getPageBrightness();
382+
expect(autoDarkBrightness).toBeCloseTo(0.25, 0.1);
383+
384+
// Toggle to light
343385
await darkModeButton.click();
344-
await expect(page).toHaveScreenshot({ name: 'light-mode.png', ...o });
345-
})
386+
const autoLightBrightness = await getPageBrightness();
387+
expect(autoLightBrightness).toBeCloseTo(0.85, 0.1);
388+
});
-95.8 KB
Binary file not shown.
-95.8 KB
Binary file not shown.
-119 KB
Binary file not shown.
-118 KB
Binary file not shown.
-118 KB
Binary file not shown.
-126 KB
Binary file not shown.
-126 KB
Binary file not shown.
-165 KB
Binary file not shown.
-154 KB
Binary file not shown.

0 commit comments

Comments
 (0)