Repository navigation
Releases: TRazDev/Mugshot
Release list
3.4.2
Fixed
- A Kotlin Multiplatform module using Compose Multiplatform resources works with the configuration
cache. Storing it failed withQuerying the mapped value of ... before task ':copyAndroidMainComposeResourcesToAndroidAssets' has completed is not supported, because
Mugshot recorded that plugin's generated assets directory as an input before the directory
existed. recordMugshotworks with the configuration cache in a Kotlin Multiplatform module that has
@Mugshotpreviews. The snapshot directory is nowsrc/androidHostTest/snapshotsby name,
where goldens already were, rather than resolved from sources that include the generated test.
Changed
prepareMugshot<Variant>Resourcesruns on every build instead of being cached, as a generated
asset directory's path cannot be part of a cache key. It only writes a list of paths; when that
list is unchanged, the screenshot tests stay up to date or come from the build cache as before.
3.4.1
Fixed
mugshot-annotationsandmugshot-preview-runtimeare published. 3.4.0 reached Maven Central
without them, so a build applying the 3.4.0 plugin fails withCould not find uk.co.fractalmotion.mugshot:mugshot-annotations:3.4.0. Use 3.4.1 instead; nothing else changed.
3.4.0
Added
@Mugshotpreviews in Kotlin Multiplatform modules. The plugin used to skip a multiplatform
module entirely, andmugshot-annotationsandmugshot-preview-runtimewere JVM-only, so a
preview incommonMaincould be neither annotated nor generated. Both modules are now
multiplatform and are added tocommonMain; a preview declared there is found like one in
androidMain. Apply the plugin alongsidecom.android.kotlin.multiplatform.library.- A
commonMainpreview that reads Compose Multiplatform resources withstringResourcenow
renders. Mugshot hands that resource system an AndroidContextbefore each render; projects
that do not use it are unaffected.
Changed
mugshot-preview-runtimeexposes Compose Multiplatform's runtime as anapidependency rather
thancompileOnly, which Kotlin/Native, JS and Wasm do not support. On Android it resolves to
the androidx Compose runtime a Compose project already has.
Fixed
- Applying the plugin no longer risks
NoClassDefFoundErroronKspExtension. Mugshot declares
KSPcompileOnly, so a direct reference to that class compiled and then failed to load against
the consuming build's classloader. - A module with both a hand-written and a generated snapshot test keeps all its goldens in one
directory. They could previously be split across the source set's directory and a generated one;
if yours were, move them into the source set's directory or re-record that module.
3.3.0
Changed
- Snapshots render a third of the device's resolution rather than rendering it full size and
shrinking the bitmap afterwards. Screen dimensions, dpi and density scale together, so every dp
is unchanged and the layout is identical -- there are simply fewer pixels in it. Measured over
7176 images, verification runs 27% faster and the goldens take 45% less disk, and text comes out
sharper because nothing is resampled after rendering.
uk.co.fractalmotion.mugshot.downscalechanges it;1is the device's own resolution and the
most detail available, and values below that are rejected. Reach for1in a module shipping
density-qualified drawables, since the qualifier resolves from the scaled density and can select
a different asset than the real device would. Every golden changes, so re-record. - Pixel 10 replaces Nexus 5 as the default device, for generated preview tests as well as a
hand-written rule. - A module that generates preview tests gets
mugshotTest<Variant>, which runs the generated test
and nothing else.verifyMugshotused to depend on the module's whole unit test suite, so it ran
every test the module had, and layoutlib shared a JVM with all of them. Set
uk.co.fractalmotion.mugshot.isolateTests=falsefor the old wiring.
Removed
useDeviceResolution. It only ever meant "render at full resolution", which isdownscale = 1;
the two produce byte-identical images.
Fixed
- A module using Kotlin test fixtures builds again.
debugTestFixturesis not named like a test
source set, so the preview catalogue was generated into the fixtures compilation, whose classpath
never carries the annotations -- and the failure named that compilation rather than Mugshot. - Adding Mugshot to a module no longer breaks that module's Robolectric tests. Layoutlib and
Robolectric both instrument theandroid.*classes and cannot share a JVM; the generated
screenshot test now runs in its own.
Performance
- The Android framework's resources are resolved once per configuration rather than once per
snapshot. It is the most expensive thing building a session does, and the answer depends only on
the configuration -- a matrix of previews asks for the same two dozen over and over. Worth about
10% wherever verification is CPU-bound, and nothing where cores are spare.
3.2.1
Changed
- A golden's name is capped at 200 characters, keeping its readable beginning and ending in a
hash of the whole of it. The name is the package, the class, the method and any label run
together, none of which is bounded, and a filesystem rejects a name over 255. Real names are
nowhere near the cap, so recorded goldens keep the names they have.
Fixed
- Snapshots no longer keep a file open for every layout they inflate. The parser was handed a
stream and never closed it, and layoutlib asks for a parser per layout, nested includes
included, so a suite of View based tests leaked a descriptor per XML file for the life of the
test JVM. - Loading the Android framework resources now fails the build rather than logging and carrying
on. Without them every attribute and style Android defines is missing, so the render was wrong
andrecordMugshot*wrote that down as the expected image. - Where a golden belongs is chosen rather than taken from whichever source directory the Android
plugin registered first. A module with flavours draws on several, and nothing guarantees that
order; if it changed, recording would write to a new place while verifying looked in the old
one. - A file in the failure directory that is not a golden is skipped rather than crashing the report.
A.DS_Store, which macOS writes into any folder opened in Finder, was enough. - A dependency whose package name file is empty is named in the error. It used to fail with a
NoSuchElementExceptionthat identified nothing. DeviceConfigcloses the two files it reads when the renderer starts.- The unsupported JRE message names the version Mugshot actually needs, 21, rather than 11.
- The preview processor no longer overwrites another annotation processor's generated file. It
wrote the source set name intocodeGenerator.generatedFile.firstOrNull(), taking that to be
the marker file it had just created, when the list holds everything generated in the round by
every processor. In a module with Hilt this replaced one of Hilt's generated classes with a
single word, and the compiler reported the error against Hilt. Modules with no other processor
were unaffected, which is why it went unnoticed.
3.2.0
Changed
- Breaking: a generated preview's snapshot is named after the file and function that declare
it, e.g.MugshotChipCatalog.MugshotChipCatalogPreview.Light, rather than after its package
path with underscores. The name is both the golden image's filename and what the test runner
reports, so this shortens the line a run prints as well. Re-record withrecordMugshot*and
delete the goldens under the old names; the processor fails the build if two previews would
now share a name. - Breaking: the standalone snapshot gallery is gone, and
HtmlReportWriterwith it. It was
written only byrecordMugshot*, soverifyMugshot*printed a link to a report that did not
exist. Recording now usesSnapshotRecorder, which writes goldens and nothing else. - Breaking:
Mugshot.snapshottakes asourcebetweennameand the composable, and
MugshotPreviewCasetakes asourceaftersnapshotName. Kotlin callers ofsnapshot { }and
snapshot("label") { }are unaffected, but anything compiled against an earlier version needs
recompiling. - A test run is quiet. The framework's log priority was discarded and every message logged at
info, so each render put its verbose and debug lines in front of whoever ran the tests; the
priority is now mapped to a matching level. The framework's own resources carry duplicate style
definitions, and those are no longer warned about. The Byte Buddy agent Mugshot attaches is
declared, so the JVM stops warning that an agent was loaded dynamically. - A failed snapshot is reported as three labelled images, the golden, the difference between them,
and this run's render, rather than one combined image behind a toggle. The report link points
at the test report, which is where those images now appear. - A failure from a generated preview names the screen's own declaration, e.g.
com.example.feature.profile.ProfileScreen(ProfileScreen.kt:31) [Dark_Default], rather than the
generated test class, which is the same for every preview in a module. The(File.kt:line)tail
is the shape an IDE turns into a link. Hand-written tests are unchanged: they render an arbitrary
composable, so there is no screen to name, and the message still uses the snapshot's name. - A failure from a hand-written test names the test as a qualified name, e.g.
com.example.screen.ScreenSnapshotTest.profile, rather than the golden image's filename. A test
renders an arbitrary composable, so there is no screen to name and the test itself is the most
specific thing there is. - The report drops the exception class and the source location from a snapshot failure. The
location is there so a console can turn it into a link, which a page cannot do, and the class is
the same on every one. Other failures keep both. - The failure message is three lines: what failed and by how much, the golden to update, and the
difference image. It used to add the rendered image's path, amvcommand, and a copy of itself
on standard output, which buried the part worth reading. It is also thrown without a stack,
since every snapshot failure carried the same frames of JUnit and Gradle plumbing. - The difference image marks a changed pixel in flat ruby red and leaves everything else white.
It used to shade each pixel by how far every colour channel had moved, which encoded the
direction and size of the change but was hard to read, and wrapped around to near-grey on the
largest changes. The white background replaces a transparent one, which blended into the page
and left no way to see where the render ended. Pixels that differ by little enough to pass are
no longer marked either, since they are not something to act on.
Added
- Verification writes
reference-anddiff-images alongside the render in
build/mugshot/failures, so CI can collect the panels individually. The combined image the
console error links to is still written, with its labels, since nothing else identifies the
panels when it is opened on its own. mugshot-preview-lintsis published. It was built and tested on every run but its publish plugin
was commented out, so nobody could depend on it. Add it withlintChecksto have a@Mugshot
preview that cannot produce a golden reported as a lint error rather than skipped in silence.
Removed
- Breaking: the
mssim,sift,flipandde2000image differs are gone, leavingoffbytwo
(the default) andpixelperfect. Settinguk.co.fractalmotion.mugshot.differto a removed value
now fails withUnknown differ typerather than falling back silently.
The four measured worse on the job they were being asked to do. Against a 600x1000 golden with an
80x20 block painted into it, a change nobody could miss,flipreported Identical andmssim,
siftandde2000reported Similar, so none of them would have failed a build. Only the two
that survive reported Different. They were also 8 to 30 times slower: 82ms to 260ms per
comparison against 9ms.
sifthad two further defects. It reported two identical images as different whenever they held
no detectable features, so any flat colour or blank screen failed against itself, and it ran out
of heap on a full screen render.
3.1.1
No library changes. Build and release tooling only: the documentation site is gone,
releases are tagged and published from main automatically, and snapshots are
published on every merge.
3.1.0
Fixed
@MugshotLocalesdid not infer right-to-left layout from Android's BCP 47 locale qualifiers.
The language subtag was read as everything before the first-, so ab+-prefixed qualifier
such asb+ar+u+nu+arabwas never matched against the right-to-left languages and rendered
left to right. That form matters because it is the only way to pin a locale's numbering system:
without it, digit shapes in formatted strings come from the host JDK's CLDR data and change
between JDK releases, so the same preview yields different goldens on different JDKs.
3.0.2
Fixed
- Mugshot tasks failed when Gradle's configuration cache was enabled, with
Querying the mapped value of and(property 'static', property 'all') before task ':<module>:generateMugshot<Variant>PreviewTests' has completed is not supported. Resolving the
snapshot directory zipped the unit test source set'sstaticandalldirectories, andall
carries the generated-test task as a producer, so serialising the task graph read it before that
task had run. The fallback toallis now reached only whenstaticis empty, as its
documentation always claimed. Consumers no longer needorg.gradle.configuration-cache=false.
3.0.1
Added
-
Screenshot coverage from a single annotation. A
@Previewcomposable marked@Mugshotnow gets
a golden image with no test file to write: the Gradle plugin generates a parameterised JUnit test
that renders every annotated preview in the module, one case each so a failure names the preview
that broke. Apply KSP alongside the plugin and the annotations, preview runtime and processor are
supplied for you — thekspblock, the processor dependency and the namespace argument are gone. -
Axis annotations that configure what gets rendered, and multiply when stacked:
@MugshotShrink,
@MugshotFullScreen,@MugshotDevices,@MugshotWear,@MugshotLightDark,
@MugshotFontScales,@MugshotLocalesand@MugshotMatrix. All of them target annotation
classes as well as functions, so a team can bundle its house style behind one name. RTL layout is
inferred from the locale. -
mugshot-preview-junit, a new artifact holding the bridge from a generated preview case to a
Mugshotrule. It is added totestImplementationby the plugin. -
Lint warning
MugshotPreviewArgumentsIgnored, reported when a@Mugshotfunction's@Preview
sets configuration Mugshot does not read, so the IDE preview and the golden cannot silently
diverge. -
@Mugshotpreviews may take a@PreviewParameter. The processor cannot enumerate a
PreviewParameterProviderat compile time, so generated code defers toparameterizedFramesin
mugshot-preview-runtime, which expands the provider's values when the test runs. Each value
becomes its own image, named by position (_0,_1, ...) rather than by the value, so golden
filenames stay deterministic. ThePreviewParametersNotSupportedlint check is gone.
Fixed
- A
@Mugshotfunction carrying more than one@Previewgenerated one entry per@Preview, each
with an identicalsnapshotName. No@Previewargument is read, so those entries were
indistinguishable and their goldens collided. One entry is now generated per function. - The
PreviewAnnotationNotFoundlint check only looked at direct annotations while the processor
resolved@Previewrecursively. A@Mugshotfunction reaching@Previewthrough a custom
multi-preview annotation generated correctly and then failed lint. The check now walks annotation
trees the same way the processor does.
Changed
-
Breaking:
mugshot-preview-runtimenow exposesMugshotPreviewCaseand
MugshotPreviewConfigin place ofMugshotPreviewData, andparameterizedFramesin place of
parameterizedPreviews. The generated property ismugshotPreviewCases, notmugshotPreviews.
Hand-written tests that iterated the old list can be deleted — the plugin generates one. -
Breaking:
@Previewarguments are not read. Configuration comes from the Mugshot annotations
instead; see the lint warning above. -
Breaking: the project is renamed from Paparazzi to Mugshot. There is no
compatibility layer — every name below changes at once:- Maven group
app.cash.paparazzi->uk.co.fractalmotion.mugshot, and the
artifactspaparazzi,paparazzi-gradle-plugin,paparazzi-annotations,
paparazzi-preview-{runtime,processor,lints}gain themugshotprefix. - Gradle plugin id
app.cash.paparazzi->uk.co.fractalmotion.mugshot. - Kotlin package
app.cash.paparazzi.**->uk.co.fractalmotion.mugshot.**. - Types
Paparazzi->Mugshot,PaparazziSdk->MugshotSdk, and the
@Paparazziannotation ->@Mugshot. - Tasks
recordPaparazzi*,verifyPaparazzi*,cleanRecordPaparazzi*,
deletePaparazziSnapshots-> the same names withMugshot. - System properties
paparazzi.*->mugshot.*, and Gradle properties
app.cash.paparazzi.*->uk.co.fractalmotion.mugshot.*. - Output directories
build/reports/paparazzi->build/reports/mugshotand
build/paparazzi/failures->build/mugshot/failures.
Golden snapshot filenames embed the test's package name, so renaming your own
test packages renames your goldens. Rename the files rather than re-recording
if you want to prove the rebrand changed no pixels. - Maven group
-
Breaking: snapshots are now stored as lossless WebP (
.webp) instead of PNG. Re-record your
goldens with./gradlew recordMugshot<Variant>; existing.pnggoldens are not read.
Removed
- Breaking: removed accessibility snapshot rendering and validation.
- Breaking: removed animated snapshot support.
Mugshot#gifandMugshotSdk#gifare gone,
along with thesnapshots/videosdirectory. Mugshot now records a single frame per snapshot.
UseMugshot#snapshot(view, offsetMillis)to capture a specific point in an animation, or
InstantAnimationsRuleto snapshot an animation's terminal state. - Breaking:
SnapshotHandler#newFrameHandlerno longer takesframeCountandfps. Custom
SnapshotHandlerimplementations must drop those parameters.