mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-31 01:42:27 +00:00
The "video modal mobile regression" visual test was failing in CI with a significant diff ratio, likely due to slow rendering or font loading in the CI environment (which uses serial mode and different hardware). This commit increases the `waitForTimeout` from 100ms to 1000ms in `tests/visual/kitchen-sink.spec.ts` for both the theme tests and the video modal test. This extra time allows the layout and fonts to stabilize before the screenshot is taken, reducing the likelihood of false positives. Verified locally that tests still pass. Co-authored-by: PR0M3TH3AN <[email protected]>
213 lines
6.4 KiB
TypeScript
213 lines
6.4 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import pixelmatch from "pixelmatch";
|
|
import { PNG } from "pngjs";
|
|
import {
|
|
getBaseline,
|
|
saveBaselines,
|
|
setBaseline,
|
|
type BaselineKey,
|
|
type KitchenSinkTheme
|
|
} from "./baseline-store";
|
|
|
|
const THEMES: { name: KitchenSinkTheme; value?: string }[] = [
|
|
{ name: "default" },
|
|
{ name: "light", value: "light" },
|
|
{ name: "contrast", value: "contrast" }
|
|
];
|
|
|
|
const VIDEO_MODAL_BASELINE_KEY = "video-modal-mobile" as BaselineKey;
|
|
|
|
const MAX_DIFF_RATIO = 0.001;
|
|
const UPDATE_BASELINES = process.env.UPDATE_VISUAL_BASELINES === "1";
|
|
|
|
test.describe.configure({ mode: "serial" });
|
|
|
|
test.use({
|
|
viewport: { width: 1280, height: 720 }
|
|
});
|
|
|
|
test.afterAll(async () => {
|
|
if (UPDATE_BASELINES) {
|
|
saveBaselines();
|
|
}
|
|
});
|
|
|
|
test.describe("design system kitchen sink", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.emulateMedia({ reducedMotion: "reduce" });
|
|
});
|
|
|
|
for (const theme of THEMES) {
|
|
test(`renders ${theme.name} theme without regressions`, async ({
|
|
page
|
|
}, testInfo) => {
|
|
await page.goto("/docs/kitchen-sink.html", { waitUntil: "networkidle" });
|
|
|
|
await page.addStyleTag({
|
|
content:
|
|
"*, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; animation-delay: 0s !important; caret-color: transparent !important; } ::-webkit-scrollbar { display: none; } body { -ms-overflow-style: none; scrollbar-width: none; width: 1280px; max-width: 1280px; overflow-x: hidden; margin: 0; }"
|
|
});
|
|
|
|
await page.evaluate((value) => {
|
|
const root = document.documentElement;
|
|
const storageKey = "bitvid-docs-theme";
|
|
|
|
if (!value) {
|
|
root.removeAttribute("data-theme");
|
|
window.localStorage.removeItem(storageKey);
|
|
} else {
|
|
root.setAttribute("data-theme", value);
|
|
window.localStorage.setItem(storageKey, value);
|
|
}
|
|
}, theme.value ?? null);
|
|
|
|
await page.waitForTimeout(1000);
|
|
|
|
const screenshotBuffer = await page.screenshot({
|
|
type: "png",
|
|
fullPage: true,
|
|
animations: "disabled"
|
|
});
|
|
|
|
if (UPDATE_BASELINES) {
|
|
setBaseline(theme.name, screenshotBuffer);
|
|
await testInfo.attach(`${theme.name}-actual.png`, {
|
|
body: screenshotBuffer,
|
|
contentType: "image/png"
|
|
});
|
|
return;
|
|
}
|
|
|
|
const baselineBuffer = getBaseline(theme.name);
|
|
const actual = PNG.sync.read(screenshotBuffer);
|
|
const expected = PNG.sync.read(baselineBuffer);
|
|
|
|
expect(actual.width).toBe(expected.width);
|
|
expect(actual.height).toBe(expected.height);
|
|
|
|
const diff = new PNG({ width: actual.width, height: actual.height });
|
|
const diffPixelCount = pixelmatch(
|
|
actual.data,
|
|
expected.data,
|
|
diff.data,
|
|
actual.width,
|
|
actual.height,
|
|
{ threshold: 0.2 }
|
|
);
|
|
|
|
const diffRatio = diffPixelCount / (actual.width * actual.height);
|
|
|
|
if (diffPixelCount > 0) {
|
|
await testInfo.attach(`${theme.name}-actual.png`, {
|
|
body: screenshotBuffer,
|
|
contentType: "image/png"
|
|
});
|
|
await testInfo.attach(`${theme.name}-baseline.png`, {
|
|
body: baselineBuffer,
|
|
contentType: "image/png"
|
|
});
|
|
await testInfo.attach(`${theme.name}-diff.png`, {
|
|
body: PNG.sync.write(diff),
|
|
contentType: "image/png"
|
|
});
|
|
}
|
|
|
|
expect(diffRatio).toBeLessThanOrEqual(MAX_DIFF_RATIO);
|
|
});
|
|
}
|
|
});
|
|
|
|
test.describe("video modal mobile regression", () => {
|
|
test.use({ viewport: { width: 390, height: 844 } });
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.emulateMedia({ reducedMotion: "reduce" });
|
|
});
|
|
|
|
test("renders video modal without regressions", async ({ page }, testInfo) => {
|
|
await page.goto("/components/video-modal.html", {
|
|
waitUntil: "networkidle"
|
|
});
|
|
|
|
await page.addStyleTag({
|
|
content:
|
|
"*, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; animation-delay: 0s !important; caret-color: transparent !important; } ::-webkit-scrollbar { display: none; } body { -ms-overflow-style: none; scrollbar-width: none; width: 390px; max-width: 390px; overflow-x: hidden; margin: 0; }"
|
|
});
|
|
|
|
await page.evaluate(() => {
|
|
const modal = document.getElementById("playerModal");
|
|
if (!modal) {
|
|
throw new Error("Missing player modal root element");
|
|
}
|
|
|
|
modal.classList.remove("hidden");
|
|
modal.removeAttribute("hidden");
|
|
|
|
modal.setAttribute("data-ds", "new");
|
|
|
|
const html = document.documentElement;
|
|
if (html) {
|
|
html.setAttribute("data-ds", "new");
|
|
}
|
|
if (document.body) {
|
|
document.body.setAttribute("data-ds", "new");
|
|
}
|
|
|
|
window.scrollTo(0, 0);
|
|
});
|
|
|
|
await page.waitForTimeout(1000);
|
|
|
|
const screenshotBuffer = await page.screenshot({
|
|
type: "png",
|
|
fullPage: true,
|
|
animations: "disabled"
|
|
});
|
|
|
|
if (UPDATE_BASELINES) {
|
|
setBaseline(VIDEO_MODAL_BASELINE_KEY, screenshotBuffer);
|
|
await testInfo.attach(`${VIDEO_MODAL_BASELINE_KEY}-actual.png`, {
|
|
body: screenshotBuffer,
|
|
contentType: "image/png"
|
|
});
|
|
return;
|
|
}
|
|
|
|
const baselineBuffer = getBaseline(VIDEO_MODAL_BASELINE_KEY);
|
|
const actual = PNG.sync.read(screenshotBuffer);
|
|
const expected = PNG.sync.read(baselineBuffer);
|
|
|
|
expect(actual.width).toBe(expected.width);
|
|
expect(actual.height).toBe(expected.height);
|
|
|
|
const diff = new PNG({ width: actual.width, height: actual.height });
|
|
const diffPixelCount = pixelmatch(
|
|
actual.data,
|
|
expected.data,
|
|
diff.data,
|
|
actual.width,
|
|
actual.height,
|
|
{ threshold: 0.2 }
|
|
);
|
|
|
|
const diffRatio = diffPixelCount / (actual.width * actual.height);
|
|
|
|
if (diffPixelCount > 0) {
|
|
await testInfo.attach(`${VIDEO_MODAL_BASELINE_KEY}-actual.png`, {
|
|
body: screenshotBuffer,
|
|
contentType: "image/png"
|
|
});
|
|
await testInfo.attach(`${VIDEO_MODAL_BASELINE_KEY}-baseline.png`, {
|
|
body: baselineBuffer,
|
|
contentType: "image/png"
|
|
});
|
|
await testInfo.attach(`${VIDEO_MODAL_BASELINE_KEY}-diff.png`, {
|
|
body: PNG.sync.write(diff),
|
|
contentType: "image/png"
|
|
});
|
|
}
|
|
|
|
expect(diffRatio).toBeLessThanOrEqual(MAX_DIFF_RATIO);
|
|
});
|
|
});
|