fix(cli): check no longer calls a redrawn WebGL canvas a dead stretch (#6749)

* fix(cli): check no longer calls a redrawn WebGL canvas a dead stretch

* fix(cli): keep the seek comment under the line limit
This commit is contained in:
Miguel Ángel
2026-10-29 14:34:07 +00:00
committed by GitHub
parent a3ff1c4902
commit 558097ed70
5 changed files with 42 additions and 7 deletions
+14
View File
@@ -2841,6 +2841,20 @@ describe("check pipeline", () => {
expect(checkExitCode(strict.report)).toBe(0);
});
it("seeks each craft frame with WebGL draws on, so a redrawn canvas reads as moving", async () => {
const driver = craftDriver();
await runScenario(driver);
const painted = new Set(
vi
.mocked(driver.seekGeometry)
.mock.calls.flatMap(([time, seek]) => (seek?.draws ? [time] : [])),
);
const sampled = vi.mocked(driver.collectCraftFrame!).mock.calls.map(([time]) => time);
expect(sampled.length).toBeGreaterThan(0);
expect(sampled.every((time) => painted.has(time))).toBe(true);
});
it("re-measures a short line between dense samples before judging it", async () => {
const driver = craftDriver();
const { report } = await runScenario(driver);
@@ -3,7 +3,7 @@ import puppeteer, { type Browser, type Page } from "puppeteer-core";
import { createRequire } from "node:module";
import { resolve } from "node:path";
import { measuredInMotion, readContrastMotion } from "./contrastMotion.js";
import { collectSeekClock } from "./checkBrowser.js";
import { collectSeekClock, createPageDriver } from "./checkBrowser.js";
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH;
@@ -119,6 +119,24 @@ describe.runIf(executablePath)("collectSeekClock in Chromium", () => {
await page.close();
});
it("skips a seek's WebGL draws unless the caller asks to paint them", async () => {
page = await browser.newPage();
await page.setContent(`<script>
window.draws = 0;
window.WebGLRenderingContext = function () {};
window.WebGLRenderingContext.prototype.drawArrays = () => { window.draws += 1; };
window.__hf = { seek: () => new window.WebGLRenderingContext().drawArrays() };
</script>`);
const driver = createPageDriver(page);
const drawn = () => page.evaluate(() => Reflect.get(window, "draws"));
await driver.seekGeometry(1);
expect(await drawn()).toBe(0);
await driver.seekGeometry(2, { draws: true });
expect(await drawn()).toBe(1);
await page.close();
});
it("reports nothing on a page with no animations", async () => {
page = await browser.newPage();
await page.setContent("<body><h1>Hello</h1></body>");
+5 -2
View File
@@ -969,9 +969,12 @@ export function createPageDriver(
screenshotFollows ? SCREENSHOT_SEEK_OPTIONS : AUDIT_SEEK_OPTIONS,
);
},
seekGeometry: async (time) => {
seekGeometry: async (time, { draws = false } = {}) => {
clock.at(time);
await seekCompositionTimeline(page, time, DENSE_GEOMETRY_SEEK_OPTIONS);
const options = draws
? { ...DENSE_GEOMETRY_SEEK_OPTIONS, skipWebglDraws: false }
: DENSE_GEOMETRY_SEEK_OPTIONS;
await seekCompositionTimeline(page, time, options);
},
collectLayout: (time, tolerance, layout) => collectLayout(page, time, tolerance, layout),
collectOverlap: (time) => collectOverlap(page, time),
+2 -2
View File
@@ -759,7 +759,7 @@ async function collectMotionOverlapSamples(
const craftTimes = buildCraftSampleTimes(grid.duration).filter((time) => time < grid.duration);
for (const [index, time] of craftTimes.entries()) {
progress(`craft frame at ${time}s`);
await driver.seekGeometry(time);
await driver.seekGeometry(time, { draws: true });
collected.craftFrames.push(...(await collectCraftFrame(driver, time)));
progress();
report("craft", index + 1, craftTimes.length);
@@ -1583,7 +1583,7 @@ export async function runAuditGrid(
noTimeline || sweepFindings.length > 0 || wholeCompositionKeepsMoving(plan.assertions),
},
async (time) => {
await driver.seekGeometry(time);
await driver.seekGeometry(time, { draws: true });
if (!driver.collectCraftFrame) throw new Error("craft sampling unavailable");
return driver.collectCraftFrame(time);
},
+2 -2
View File
@@ -262,8 +262,8 @@ export interface CheckAuditDriver {
getCanvas(): Promise<Canvas>;
findAmbiguousSelectors(selectors: string[]): Promise<AnchoredLayoutIssue[]>;
seek(time: number, screenshotFollows?: boolean): Promise<void>;
/** Settle-free seek for the geometry-only dense content_overlap pass; only collectOverlap consumes it, and getBoundingClientRect is valid synchronously after setTime. */
seekGeometry(time: number): Promise<void>;
/** Settle-free seek for the dense passes; WebGL draws are skipped unless `draws` (a pass reading canvas pixels). */
seekGeometry(time: number, options?: { draws?: boolean }): Promise<void>;
collectLayout(
time: number,
tolerance: number,