test/scripts/screenshots-browser.contract.ts
Rama main · réplica de lectura del repositorio original.
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<void>((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(
'<svg xmlns="http://www.w3.org/2000/svg"><symbol id="save" viewBox="0 0 16 16"><path d="M0 0h16v16H0z"/></symbol></svg>',
{ headers: { "content-type": "image/svg+xml" } },
);
}
return new Response(
'<svg width="24" height="24"><use href="/icons.svg#save"></use></svg>',
{ 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<void>((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(`
<style>
* { box-sizing: border-box; }
body { background: rgb(255, 255, 255); margin: 0; padding: 40px; }
#target { height: 1000px; width: 200px; }
@media (max-height: 844px) { #target { background: rgb(1, 2, 3); } }
@media (min-height: 845px) { #target { background: rgb(250, 1, 1); } }
</style>
<div id="target"></div>
`);
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,
'<select style="background: rgb(255, 0, 0); color: rgb(0, 0, 255)"><option>Chosen value</option></select>',
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,
'<input placeholder="Your name" style="color: rgb(0, 0, 255)">',
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,
'<input type="checkbox" checked><input type="date"><select><option>One</option></select>',
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,
`<style>
#words, button {
text-decoration: underline;
text-decoration-color: rgb(255, 0, 0);
-webkit-text-stroke: 1px rgb(0, 128, 0);
}
</style><p id="words">Words</p><button>Save</button>`,
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,
`<style>
#decorated::before {
background: rgb(255, 0, 0) !important;
border: 2px solid rgb(0, 255, 0) !important;
box-shadow: 0 0 2px rgb(0, 0, 255) !important;
content: "";
}
</style><div id="decorated">Words</div>`,
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,
'<a class="btn" href="/next">Continue</a>',
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,
'<div id="page"><div id="dialog"><div id="target"><button>Save</button><img alt="Square" src="data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"/>"></div></div></div>',
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,
'<svg><rect width="20" height="20"></rect><text x="2" y="12">Map</text></svg>',
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",
);
},
);
});
});