CI / check (push) Successful in 32s
CI / tests-imaging-browsers (push) Successful in 1m33s
CI / tests-integration (push) Successful in 2m5s
CI / tests-unit (push) Successful in 2m7s
CI / tests-ui (push) Successful in 2m47s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m44s
Environment-independent contract tests (cache headers + source, CORS, OPTIONS preflight, negative figure/badge tables, concurrency burst) run in both the deploy gate and the cross-browser job. CMS-only /imaging assertions (webp/avif transcode, animated-APNG passthrough, s-maxage edge caching) live in cms-imaging.spec.ts and are skipped behind the edge proxy; the public topology has its own public-routing.spec.ts smoke that fails if the edge ever stops fronting the imager or starts shadowing the CMS endpoints. A committed per-engine visual baseline pins the static render. The cross-browser imaging job is now blocking (IMAGING_EDGE=1), so avatar imaging on chromium/firefox/webkit is a required signal.
58 lines
2.4 KiB
TypeScript
58 lines
2.4 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { avatarPath } from "./imaging-target";
|
|
|
|
/**
|
|
* Cross-browser check that a real browser (chromium/firefox/webkit) can decode
|
|
* the avatar the imager produces and that it actually animates. The endpoint
|
|
* spec covers the HTTP contract; this one covers the client-side decode, where
|
|
* an unsupported image format would surface as a blank/broken avatar.
|
|
*
|
|
* Navigates straight to the image URL (a top-level navigation, which CSP
|
|
* `upgrade-insecure-requests` does not rewrite) rather than embedding it in a
|
|
* page: over a plain-HTTP host the upgrade directive would otherwise send the
|
|
* subresource request to https and break the load, without exercising anything
|
|
* about the image itself.
|
|
*
|
|
* Run across engines via `pnpm test:imaging` (playwright.imaging.config.ts).
|
|
*/
|
|
const ANIMATED = avatarPath({ img_format: "apng", effect: "14" });
|
|
const STATIC = avatarPath({ img_format: "png" });
|
|
|
|
test("the avatar decodes and animates in the browser", async ({ page }) => {
|
|
const response = await page.goto(ANIMATED);
|
|
expect(response?.status()).toBe(200);
|
|
|
|
const image = page.locator("img");
|
|
await expect(image).toBeVisible();
|
|
const decoded = await image.evaluate((element: HTMLImageElement) => ({
|
|
width: element.naturalWidth,
|
|
height: element.naturalHeight,
|
|
}));
|
|
expect(decoded.width).toBeGreaterThan(0);
|
|
expect(decoded.height).toBeGreaterThan(0);
|
|
|
|
// An animated APNG keeps producing new frames; a static/undecodable one
|
|
// yields the same pixels every time. Sampling screenshots over a second is
|
|
// enough to tell them apart without depending on exact frame timing.
|
|
const frames = new Set<string>();
|
|
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
frames.add((await image.screenshot()).toString("base64"));
|
|
await page.waitForTimeout(180);
|
|
}
|
|
expect(frames.size).toBeGreaterThan(1);
|
|
});
|
|
|
|
test("the static render matches its committed visual baseline", async ({
|
|
page,
|
|
}) => {
|
|
// Pins the exact pixels of a non-animated render so a renderer/asset drift
|
|
// (wrong palette, missing part, changed sizing) is caught even when the
|
|
// image still decodes. Baselines are per engine and committed under
|
|
// e2e/imaging/__screenshots__/.
|
|
const response = await page.goto(STATIC);
|
|
expect(response?.status()).toBe(200);
|
|
await expect(page.locator("img")).toHaveScreenshot("static-avatar.png", {
|
|
animations: "disabled",
|
|
});
|
|
});
|