import { expect } from "@std/expect"; import { afterAll, beforeAll, describe, it as test } from "@std/testing/bdd"; import type { Browser } from "playwright"; import sharp from "sharp"; import { withStrictBrowserTimeouts } from "#scripts/browser-options.ts"; import { capturePreparedPage } from "#scripts/screenshots/capture.ts"; import { isolateElementCss } from "#scripts/screenshots/checks.ts"; import { waitForScreenshotPage } from "#scripts/screenshots/readiness.ts"; import { launchScreenshotBrowser, layerStyle, withPage, } from "./screenshots-browser-helpers.ts"; describe("screenshot browser contracts", () => { let browser: Browser; beforeAll(async () => { browser = await launchScreenshotBrowser(); }); afterAll(async () => { await browser.close(); }); test("waits for a delayed external SVG before declaring the page ready", async () => { let releaseSprite = () => {}; const spriteReady = new Promise((resolve) => { releaseSprite = resolve; }); const server = Deno.serve( { hostname: "127.0.0.1", onListen: () => {}, port: 0 }, async (request) => { if (new URL(request.url).pathname === "/icons.svg") { await spriteReady; return new Response( '', { headers: { "content-type": "image/svg+xml" } }, ); } return new Response( '', { headers: { "content-type": "text/html" } }, ); }, ); try { if (server.addr.transport !== "tcp") { throw new Error("Browser contract server did not open a TCP port"); } const page = await browser.newPage(); withStrictBrowserTimeouts(page); try { await page.goto(`http://127.0.0.1:${server.addr.port}`, { waitUntil: "domcontentloaded", }); let ready = false; const readiness = (async () => { await waitForScreenshotPage(page); ready = true; })(); await page.evaluate( () => new Promise((resolve) => { requestAnimationFrame(() => requestAnimationFrame(() => requestAnimationFrame(() => resolve()), ), ); }), ); expect(ready).toBe(false); releaseSprite(); await readiness; const icon = await page.locator("svg").screenshot(); const stats = await sharp(icon).flatten({ background: "#fff" }).stats(); expect(stats.channels.slice(0, 3).map(({ min }) => min)).toEqual([ 0, 0, 0, ]); } finally { await page.close(); } } finally { releaseSprite(); await server.shutdown(); await server.finished; } }); test("captures a tall element without changing its responsive viewport", async () => { const page = await browser.newPage({ deviceScaleFactor: 2, viewport: { height: 844, width: 390 }, }); withStrictBrowserTimeouts(page); try { await page.setContent(`
`); const capture = await capturePreparedPage(page, "#target"); const metadata = await sharp(capture.png).metadata(); if (!metadata.width || !metadata.height) { throw new Error("The browser contract PNG has no dimensions"); } const centerPixel = await sharp(capture.png) .extract({ height: 1, left: Math.floor(metadata.width / 2), top: Math.floor(metadata.height / 2), width: 1, }) .removeAlpha() .raw() .toBuffer(); expect(page.viewportSize()).toEqual({ height: 844, width: 390 }); expect( await page .locator("#target") .evaluate((element) => getComputedStyle(element).backgroundColor), ).toBe("rgb(1, 2, 3)"); expect({ height: metadata.height, width: metadata.width }).toEqual({ height: 2064, width: 464, }); expect([...centerPixel]).toEqual([1, 2, 3]); } finally { await page.close(); } }); test("separates select chrome from its lettering", async () => { await withPage( browser, '', async (page) => { expect( await layerStyle( page, "controls", "select", "-webkit-text-fill-color", ), ).toBe("rgba(0, 0, 0, 0)"); expect( await layerStyle(page, "controls", "select", "background-color"), ).toBe("rgb(255, 0, 0)"); expect( await layerStyle(page, "text", "select", "background-color"), ).toBe("rgba(0, 0, 0, 0)"); expect(await layerStyle(page, "text", "select", "color")).toBe( "rgb(0, 0, 255)", ); }, ); }); test("keeps placeholder lettering out of the controls layer", async () => { await withPage( browser, '', async (page) => { expect( await layerStyle(page, "controls", "input", "color", "::placeholder"), ).toBe("rgba(0, 0, 0, 0)"); expect( await layerStyle(page, "text", "input", "color", "::placeholder"), ).not.toBe("rgba(0, 0, 0, 0)"); }, ); }); test("keeps native widget chrome out of the text layer", async () => { await withPage( browser, '', async (page) => { expect( await layerStyle( page, "text", 'input[type="checkbox"]', "visibility", ), ).toBe("hidden"); expect( await layerStyle( page, "controls", 'input[type="checkbox"]', "visibility", ), ).toBe("visible"); for (const selector of ['input[type="date"]', "select"]) { expect(await layerStyle(page, "text", selector, "appearance")).toBe( "none", ); expect( await layerStyle(page, "controls", selector, "appearance"), ).toBe("auto"); } }, ); }); test("keeps text stroke and decoration paint out of non-text layers", async () => { await withPage( browser, `

Words

`, async (page) => { expect( await layerStyle( page, "background", "#words", "text-decoration-color", ), ).toBe("rgba(0, 0, 0, 0)"); expect( await layerStyle( page, "background", "#words", "-webkit-text-stroke-color", ), ).toBe("rgba(0, 0, 0, 0)"); expect( await layerStyle(page, "controls", "button", "text-decoration-color"), ).toBe("rgba(0, 0, 0, 0)"); expect( await layerStyle( page, "controls", "button", "-webkit-text-stroke-color", ), ).toBe("rgba(0, 0, 0, 0)"); expect( await layerStyle(page, "text", "#words", "text-decoration-color"), ).toBe("rgb(255, 0, 0)"); expect( await layerStyle(page, "text", "#words", "-webkit-text-stroke-color"), ).toBe("rgb(0, 128, 0)"); }, ); }); test("removes pseudo-element chrome from the text layer", async () => { await withPage( browser, `
Words
`, async (page) => { expect( await layerStyle( page, "text", "#decorated", "background-color", "::before", ), ).toBe("rgba(0, 0, 0, 0)"); expect( await layerStyle( page, "text", "#decorated", "border-top-color", "::before", ), ).toBe("rgba(0, 0, 0, 0)"); expect( await layerStyle( page, "text", "#decorated", "box-shadow", "::before", ), ).toBe("none"); }, ); }); test("puts btn links in the controls layer", async () => { await withPage( browser, 'Continue', async (page) => { expect(await layerStyle(page, "background", "a", "visibility")).toBe( "hidden", ); expect(await layerStyle(page, "controls", "a", "visibility")).toBe( "visible", ); }, ); }); test("layer masks override ID-based element isolation", async () => { await withPage( browser, '
Square
', async (page) => { await page.addStyleTag({ content: isolateElementCss("#page #dialog #target"), }); expect( await layerStyle(page, "background", "button", "visibility"), ).toBe("hidden"); expect(await layerStyle(page, "text", "img", "visibility")).toBe( "hidden", ); }, ); }); test("keeps SVG text while removing its graphics from the text layer", async () => { await withPage( browser, 'Map', async (page) => { expect(await layerStyle(page, "text", "rect", "visibility")).toBe( "hidden", ); expect(await layerStyle(page, "text", "text", "visibility")).toBe( "visible", ); expect(await layerStyle(page, "background", "text", "visibility")).toBe( "hidden", ); }, ); }); });