Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/cli/src/capture/captureCompositionFrame.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,11 @@ describe("captureRegionCrop", () => {
const buffer = await captureRegionCrop(page, region, 3);

expect(setViewport).toHaveBeenNthCalledWith(1, { ...original, deviceScaleFactor: 3 });
expect(screenshot).toHaveBeenCalledWith({ clip: region, type: "png" });
expect(screenshot).toHaveBeenCalledWith({
clip: region,
type: "png",
omitBackground: true,
});
expect(setViewport).toHaveBeenNthCalledWith(2, original);
expect(buffer).toBeInstanceOf(Buffer);
expect(Array.from(buffer)).toEqual([1, 2, 3]);
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/capture/captureCompositionFrame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ export interface CropCapturePage {
height: number;
deviceScaleFactor?: number;
}): Promise<void>;
screenshot(options: { clip: CropRegion; type: "png" }): Promise<Uint8Array>;
screenshot(options: { clip: CropRegion; type: "png"; omitBackground: true }): Promise<Uint8Array>;
}

/**
Expand All @@ -440,7 +440,7 @@ export async function captureRegionCrop(
const original = page.viewport();
if (original) await page.setViewport({ ...original, deviceScaleFactor: scale });
try {
const shot = await page.screenshot({ clip: region, type: "png" });
const shot = await page.screenshot({ clip: region, type: "png", omitBackground: true });
return Buffer.isBuffer(shot) ? shot : Buffer.from(shot);
} finally {
if (original) await page.setViewport(original);
Expand Down
12 changes: 11 additions & 1 deletion packages/cli/src/commands/keyframes.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
import { existsSync, mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { beforeAll, describe, expect, it } from "vitest";
import { ensureDOMParser } from "../utils/dom.js";
import { collectShotSelectors, resolveScope, surfaceComposition } from "./keyframes.js";
import { ensureShotOutputDir } from "./motionShot.js";

beforeAll(() => ensureDOMParser());

Expand All @@ -26,6 +27,15 @@ describe("keyframes direct composition scope", () => {
});
});

describe("keyframes shot output", () => {
it("creates a missing parent directory before writing --shot", () => {
const projectDir = mkdtempSync(join(tmpdir(), "hf-keyframes-shot-dir-"));
const outputDir = join(projectDir, "nested", "proofs");
ensureShotOutputDir(join(outputDir, "shot.png"));
expect(existsSync(outputDir)).toBe(true);
});
});

describe("keyframes multi-stroke traces", () => {
it("composites ≥2 position strokes on one element into a single trace", () => {
const html = wrap(`
Expand Down
8 changes: 7 additions & 1 deletion packages/cli/src/commands/motionShot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
// exactly what it's editing. All geometry + SVG live in ./motionShotLayout.ts
// (pure, tested); this file only drives the browser and SAMPLES.

import { writeFileSync } from "node:fs";
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";
import {
buildOnionSvg,
ghostAlphas,
Expand All @@ -25,6 +26,10 @@ export interface ShotRequest {
selector: string;
}

export function ensureShotOutputDir(outPath: string): void {
mkdirSync(dirname(outPath), { recursive: true });
}

/** Returned by the in-browser selector resolver: which animated selectors a
* `--selector SCOPE` actually resolves to (scope itself, or its descendants),
* plus diagnostic context when nothing under the scope animates. */
Expand Down Expand Up @@ -611,6 +616,7 @@ export async function captureMotionPathShot(
outPath: string,
opts: ShotOptions = {},
): Promise<string> {
ensureShotOutputDir(outPath);
let requests = requestsIn;
const samples = Math.max(1, Math.min(60, opts.samples ?? 9));
const layout = opts.layout ?? "path";
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/commands/snapshot.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import { computeSnapshotTimes, parseZoomScale, tailFrameTime } from "./snapshot.js";
import { readFileSync } from "node:fs";

// --zoom's crop-region math (selector bbox + padding + clamp, exact region
// form, no-match error) is owned by and tested in
Expand All @@ -21,6 +22,15 @@ describe("tailFrameTime", () => {
});
});

describe("transparent snapshot capture", () => {
it("asks Chrome to retain the alpha channel in review PNGs", () => {
const source = readFileSync(new URL("./snapshot.ts", import.meta.url), "utf8");
expect(source).toContain(
'page.screenshot({ path: framePath, type: "png", omitBackground: true })',
);
});
});

describe("computeSnapshotTimes (FINDING [7]: tail is always captured)", () => {
it("default frames: last point is the readable tail, never exact duration", () => {
const { times, appendedTail } = computeSnapshotTimes(8, { frames: 5 });
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ async function captureSnapshots(
);
writeFileSync(framePath, buffer);
} else {
await page.screenshot({ path: framePath, type: "png" });
await page.screenshot({ path: framePath, type: "png", omitBackground: true });
}
const rel = relative(projectDir, framePath);
savedPaths.push(rel.startsWith("..") || isAbsolute(rel) ? framePath : rel);
Expand Down
Loading