Skip to content

catalog/shoot: optional imageScale for a device-pixel page image, motion-safe (fixes #51) - #75

Merged
jrpool merged 1 commit into
jrpool:mainfrom
wittjeff:feat/image-scale
Aug 19, 2026
Merged

catalog/shoot: optional imageScale for a device-pixel page image, motion-safe (fixes #51)#75
jrpool merged 1 commit into
jrpool:mainfrom
wittjeff:feat/image-scale

Conversation

@wittjeff

Copy link
Copy Markdown
Collaborator

Implements #51 with the revised, motion-safe design (see my reply there answering your question about the motion rule — you were right, and this design removes the coupling entirely).

What it does

When report.imageScale is a number > 1:

  • the catalog's browser context is launched with deviceScaleFactor: imageScale (via a new contextOverrides pass-through in launch()), and
  • after the usual CSS-scale page image is captured as images[0], a second image is captured at scale: 'device' and appended as images[1]imageScale× the pixels of the CSS layout in each dimension.

images[0] keeps its contract: always CSS-pixel scale, exactly as today. The motion rule — which compares its own CSS-scale screenshot against images[0] and fails fast on any dimension mismatch — is therefore unaffected by imageScale. (The originally proposed single-shot design would have made every motion comparison under imageScale > 1 a guaranteed size-mismatch false positive; this is the fix for the problem you spotted.)

boxIDs remain CSS-pixel by definition; consumers map them onto images[1] by multiplying by imageScale. Omitted, 1, or invalid values keep behavior byte-identical to today, including no images[1].

Changes

  • procs/launch.js: launch()/launchOnce() accept contextOverrides — extra Playwright context options spread after the defaults (used here for deviceScaleFactor; the launch-arg --force-device-scale-factor=1 is cleanly overridden by the context option, verified empirically).
  • procs/shoot.js: shoot() accepts scale ('css' default, or 'device'), passed to page.screenshot.
  • procs/catalog.js: validates report.imageScale, applies the context override, and takes the supplemental device-scale shot when applicable.
  • README.md: documents the imageScale job property and the images[1] semantics.

Verification

End-to-end getCatalog runs on a synthetic page (chromium, viewport 1280×800):

imageScale images[0] images[1]
(absent) 1280×800 — (array length 1, as today)
2 1280×800 2560×1600
2.625 1280×800 3360×2100 (exact)

Box IDs are CSS-pixel in all cases, and the closed-<details> expansion from #71 is visible in both images (both shots happen after it).

One note for completeness: when imageScale differs from the emulated device's own DSF, images[0] is a CSS-scale downsample of a higher-DPR render while motion's own shot renders at the test context's DPR — same dimensions, near-identical pixels; pixelmatch's default antialiasing exclusion absorbs the rendering difference. Setting imageScale to the device's deviceScaleFactor (the natural policy suggested in the issue) makes the two rendering pipelines identical.

🤖 Generated with Claude Code

…ion-safe (fixes jrpool#51)

When report.imageScale is a number greater than 1, the catalog browser
context runs at that deviceScaleFactor and the catalog captures a
second page image at device-pixel scale as images[1], with imageScale
times the pixels of the CSS layout in each dimension. Fractional
factors (e.g. a device's native 2.625) capture exactly.

images[0] keeps its contract: it is always captured at CSS-pixel scale,
so the testaro motion rule, which compares its own CSS-scale screenshot
against images[0] and fails fast on any dimension mismatch, is
unaffected by imageScale. (Capturing images[0] itself at device scale
was the originally proposed design; it would have made every motion
comparison under imageScale > 1 a guaranteed size-mismatch false
positive.)

Box IDs remain CSS-pixel by definition (getBoundingClientRect);
consumers map them onto images[1] by multiplying the coordinates by
imageScale. Omitted, 1, or invalid imageScale keeps behavior identical
to before this option existed, including no images[1].

Plumbing: launch() and launchOnce() accept contextOverrides, extra
Playwright context options spread after the defaults; shoot() accepts
scale ('css' or 'device') passed to page.screenshot.

Verified end-to-end on a synthetic page (chromium, 1280x800):
imageScale 2 yields images[0] 1280x800 and images[1] 2560x1600;
imageScale 2.625 yields images[1] 3360x2100 exactly; with no
imageScale, images has one CSS-scale entry as before. Box IDs are
CSS-pixel in all cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@jrpool jrpool left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @wittjeff, for adding this option and making it non-interfering with other functionalities.

@jrpool
jrpool merged commit 4261212 into jrpool:main Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants