Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ renderdiff <left-origin> <right-origin> <route…> [options]
| `--selector <sel>` | Compare the one element this Playwright selector matches, on both sides, instead of the whole page. Applies to every route without its own `@selector`, over the config's per-route `selector`. |
| `--left-selector <sel>`, `--right-selector <sel>` | That side's selector, over `--selector`. One without the other side (or `--selector`) is an error. |
| `--mask <sel>` | Paint this Playwright selector over on both sides, on every route; repeatable. Added to the config's masks and listed in the report as coverage given up. |
| `--hide <sel>` | Hide this CSS selector (not Playwright) on both sides, on every route; repeatable. Added to the config's `hideSelectors`. For chrome that should be absent on both sides; content that drifts belongs in `--mask`. |
| `--mask-media` | Mask every `iframe` on every route, as config `maskMedia: true` does. Video isn't masked: it is frozen by default, so the player's box is still compared. |
| `--no-freeze-video` | Leave videos playing and video.js overlays visible, as config `freezeVideo: false` does. By default every video is paused on frame 0 (its poster if it never loads) ([how it works](https://github.com/tmashara/renderdiff/blob/main/docs/how-it-works.md)); unfrozen, expect unstable pixels or `did not settle`. `--mask-media` doesn't mask them either. |
| `--storage-state <file>` | Start both sides with this Playwright storage state (cookies, localStorage), e.g. signed in. Relative to cwd. Over the config's `storageState`. |
Expand Down
2 changes: 1 addition & 1 deletion skills/renderdiff/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ export default {

A mask takes exactly one of `selector` or `locate: (page) => Locator`; without `urlPath` it
applies to every route, and on a pair it matches either side's path. `--mask <sel>` adds to the
config's masks. Also: `threshold`, `outDir`, `concurrency`, `extraCss`, `before`,
config's masks; `--hide <sel>` (CSS) adds to `hideSelectors`. Also: `threshold`, `outDir`, `concurrency`, `extraCss`, `before`,
`beforeShot`, `storageState`. `hideSelectors`: match the element **exactly** (consent scripts stamp classes on
`<html>`; a loose selector blanks the whole document and blank matches blank), and they hide
rather than remove.
Expand Down
9 changes: 7 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ function parseArgs(argv: string[]): Args {
const s = next().trim();
if (!s) throw new Error("--mask needs a value");
(a.masks ??= []).push(s);
} else if (v === "--hide") {
const s = next().trim();
if (!s) throw new Error("--hide needs a value");
(a.hideSelectors ??= []).push(s);
} else if (v === "--selector" || v === "--left-selector" || v === "--right-selector") {
const s = next();
if (!s) throw new Error(`${v} needs a value`);
Expand Down Expand Up @@ -238,6 +242,7 @@ const HELP = `
--left-selector <sel>, --right-selector <sel>
That side's selector, over --selector.
--mask <sel> Paint this selector over on both sides, every route; repeatable.
--hide <sel> Hide this CSS selector on both sides, every route; repeatable.
--mask-media Mask every iframe on every route.
--no-freeze-video Leave videos playing and video.js overlays visible.
--storage-state <file>
Expand Down Expand Up @@ -270,8 +275,8 @@ const HELP = `

Default widths: 1440, 1152, 896, 704, 375

Per-route masks, hidden selectors, blocked URLs and route lists go in a config file; CLI flags
win over it. See https://github.com/tmashara/renderdiff#readme
Per-route masks, blocked URLs and route lists go in a config file; CLI flags win over it
or add to it. See https://github.com/tmashara/renderdiff#readme
`;

const usage = () => {
Expand Down
11 changes: 9 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,8 @@ export interface CliOverrides {
aria?: boolean;
/** From --mask, appended to the config's masks. */
masks?: string[];
/** From --hide, appended to the config's hideSelectors. */
hideSelectors?: string[];
maskMedia?: boolean;
freezeVideo?: boolean;
}
Expand All @@ -593,7 +595,11 @@ export function resolveConfig(file: RenderdiffConfig, cli: CliOverrides): Resolv
? { left: file.storageState, right: file.storageState }
: file.storageState;

const hideSelectors = [...DEFAULT_HIDE_SELECTORS, ...(file.hideSelectors ?? [])];
const hideSelectors = [
...DEFAULT_HIDE_SELECTORS,
...(file.hideSelectors ?? []),
...(cli.hideSelectors ?? []),
];
const freezeVideo = cli.freezeVideo ?? file.freezeVideo ?? true;

return {
Expand All @@ -607,7 +613,8 @@ export function resolveConfig(file: RenderdiffConfig, cli: CliOverrides): Resolv
hideSelectors,
neutralizeCss:
STILL_CSS +
`${hideSelectors.join(",")}{display:none!important}\n` +
// One rule each, so an invalid selector drops only its own rule.
hideSelectors.map((s) => `${s}{display:none!important}\n`).join("") +
// visibility, not display, so the player keeps its layout.
(freezeVideo
? ".vjs-text-track-display,.vjs-control-bar,.vjs-control-bar *{visibility:hidden!important}\n"
Expand Down
12 changes: 12 additions & 0 deletions tests/unit/cli-args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ describe("flag validation", () => {
);
});

test("--hide needs a value", async () => {
await rejects(["/a", "--left", L, "--right", R, "--dry-run", "--hide"], "--hide needs a value");
await rejects(
["/a", "--left", L, "--right", R, "--dry-run", "--hide="],
"--hide needs a value",
);
await rejects(
["/a", "--left", L, "--right", R, "--dry-run", "--hide", " "],
"--hide needs a value",
);
});

test("--width rejects non-numbers", () =>
rejects(
["/a", "--left", L, "--right", R, "--width", "wide"],
Expand Down
10 changes: 10 additions & 0 deletions tests/visual/guardrails.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,16 @@ describe("selector masks", () => {
assert.deepEqual(parseReport(run.report()!).maskReasons, ["[data-drift] (--mask)"]);
});

test("--hide hides a selector on every route, and a bad one drops only its own rule", async () => {
const run = await capture(["/drifting"], {
extra: ["--no-recheck", "--hide", "text=nope", "--hide", "[data-drift]"],
});
assert.equal(run.code, 0, context(run));
const res = results(run.outDir);
assert.equal(result(res, "/drifting", 1440).diffPixels, 0, context(run));
for (const s of ["text=nope", "[data-drift]"]) assert.ok(res.meta.hideSelectors.includes(s));
});

test("--mask adds to the config's masks", async () => {
const run = await capture(["/drifting"], {
config: path.join(CONFIGS, "mask-selector.mjs"),
Expand Down
Loading