Skip to content

Releases: TRazDev/Mugshot

3.4.2

Choose a tag to compare

@github-actions github-actions released this 14 Sep 16:50
f64bc01

Fixed

  • A Kotlin Multiplatform module using Compose Multiplatform resources works with the configuration
    cache. Storing it failed with Querying 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.
  • recordMugshot works with the configuration cache in a Kotlin Multiplatform module that has
    @Mugshot previews. The snapshot directory is now src/androidHostTest/snapshots by name,
    where goldens already were, rather than resolved from sources that include the generated test.

Changed

  • prepareMugshot<Variant>Resources runs 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

Choose a tag to compare

@github-actions github-actions released this 13 Sep 18:10
8223aa0

Fixed

  • mugshot-annotations and mugshot-preview-runtime are published. 3.4.0 reached Maven Central
    without them, so a build applying the 3.4.0 plugin fails with Could not find uk.co.fractalmotion.mugshot:mugshot-annotations:3.4.0. Use 3.4.1 instead; nothing else changed.

3.4.0

Choose a tag to compare

@github-actions github-actions released this 13 Sep 17:31
af22e7e

Added

  • @Mugshot previews in Kotlin Multiplatform modules. The plugin used to skip a multiplatform
    module entirely, and mugshot-annotations and mugshot-preview-runtime were JVM-only, so a
    preview in commonMain could be neither annotated nor generated. Both modules are now
    multiplatform and are added to commonMain; a preview declared there is found like one in
    androidMain. Apply the plugin alongside com.android.kotlin.multiplatform.library.
  • A commonMain preview that reads Compose Multiplatform resources with stringResource now
    renders. Mugshot hands that resource system an Android Context before each render; projects
    that do not use it are unaffected.

Changed

  • mugshot-preview-runtime exposes Compose Multiplatform's runtime as an api dependency rather
    than compileOnly, 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 NoClassDefFoundError on KspExtension. Mugshot declares
    KSP compileOnly, 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

Choose a tag to compare

@github-actions github-actions released this 08 Sep 19:15
84a8fae

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.downscale changes it; 1 is the device's own resolution and the
    most detail available, and values below that are rejected. Reach for 1 in 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. verifyMugshot used 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=false for the old wiring.

Removed

  • useDeviceResolution. It only ever meant "render at full resolution", which is downscale = 1;
    the two produce byte-identical images.

Fixed

  • A module using Kotlin test fixtures builds again. debugTestFixtures is 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 the android.* 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

Choose a tag to compare

@github-actions github-actions released this 07 Sep 15:20
173e8c0

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
    and recordMugshot* 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
    NoSuchElementException that identified nothing.
  • DeviceConfig closes 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 into codeGenerator.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

Choose a tag to compare

@github-actions github-actions released this 06 Sep 19:38
1955360

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 with recordMugshot* 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 HtmlReportWriter with it. It was
    written only by recordMugshot*, so verifyMugshot* printed a link to a report that did not
    exist. Recording now uses SnapshotRecorder, which writes goldens and nothing else.
  • Breaking: Mugshot.snapshot takes a source between name and the composable, and
    MugshotPreviewCase takes a source after snapshotName. Kotlin callers of snapshot { } 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, a mv command, 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- and diff- 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-lints is published. It was built and tested on every run but its publish plugin
    was commented out, so nobody could depend on it. Add it with lintChecks to have a @Mugshot
    preview that cannot produce a golden reported as a lint error rather than skipped in silence.

Removed

  • Breaking: the mssim, sift, flip and de2000 image differs are gone, leaving offbytwo
    (the default) and pixelperfect. Setting uk.co.fractalmotion.mugshot.differ to a removed value
    now fails with Unknown differ type rather 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, flip reported Identical and mssim,
    sift and de2000 reported 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.
    sift had 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

Choose a tag to compare

@github-actions github-actions released this 03 Sep 18:14
0a0dd42

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

Choose a tag to compare

@github-actions github-actions released this 03 Sep 10:02
0f4eb0e

Fixed

  • @MugshotLocales did not infer right-to-left layout from Android's BCP 47 locale qualifiers.
    The language subtag was read as everything before the first -, so a b+-prefixed qualifier
    such as b+ar+u+nu+arab was 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

Choose a tag to compare

@github-actions github-actions released this 01 Sep 19:22

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's static and all directories, and all
    carries the generated-test task as a producer, so serialising the task graph read it before that
    task had run. The fallback to all is now reached only when static is empty, as its
    documentation always claimed. Consumers no longer need org.gradle.configuration-cache=false.

3.0.1

Choose a tag to compare

@TRazDev TRazDev released this 31 Aug 17:18

Added

  • Screenshot coverage from a single annotation. A @Preview composable marked @Mugshot now 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 — the ksp block, 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, @MugshotLocales and @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
    Mugshot rule. It is added to testImplementation by the plugin.

  • Lint warning MugshotPreviewArgumentsIgnored, reported when a @Mugshot function's @Preview
    sets configuration Mugshot does not read, so the IDE preview and the golden cannot silently
    diverge.

  • @Mugshot previews may take a @PreviewParameter. The processor cannot enumerate a
    PreviewParameterProvider at compile time, so generated code defers to parameterizedFrames in
    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. The PreviewParametersNotSupported lint check is gone.

Fixed

  • A @Mugshot function carrying more than one @Preview generated one entry per @Preview, each
    with an identical snapshotName. No @Preview argument is read, so those entries were
    indistinguishable and their goldens collided. One entry is now generated per function.
  • The PreviewAnnotationNotFound lint check only looked at direct annotations while the processor
    resolved @Preview recursively. A @Mugshot function reaching @Preview through 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-runtime now exposes MugshotPreviewCase and
    MugshotPreviewConfig in place of MugshotPreviewData, and parameterizedFrames in place of
    parameterizedPreviews. The generated property is mugshotPreviewCases, not mugshotPreviews.
    Hand-written tests that iterated the old list can be deleted — the plugin generates one.

  • Breaking: @Preview arguments 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
      artifacts paparazzi, paparazzi-gradle-plugin, paparazzi-annotations,
      paparazzi-preview-{runtime,processor,lints} gain the mugshot prefix.
    • 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
      @Paparazzi annotation -> @Mugshot.
    • Tasks recordPaparazzi*, verifyPaparazzi*, cleanRecordPaparazzi*,
      deletePaparazziSnapshots -> the same names with Mugshot.
    • System properties paparazzi.* -> mugshot.*, and Gradle properties
      app.cash.paparazzi.* -> uk.co.fractalmotion.mugshot.*.
    • Output directories build/reports/paparazzi -> build/reports/mugshot and
      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.

  • Breaking: snapshots are now stored as lossless WebP (.webp) instead of PNG. Re-record your
    goldens with ./gradlew recordMugshot<Variant>; existing .png goldens are not read.

Removed

  • Breaking: removed accessibility snapshot rendering and validation.
  • Breaking: removed animated snapshot support. Mugshot#gif and MugshotSdk#gif are gone,
    along with the snapshots/videos directory. Mugshot now records a single frame per snapshot.
    Use Mugshot#snapshot(view, offsetMillis) to capture a specific point in an animation, or
    InstantAnimationsRule to snapshot an animation's terminal state.
  • Breaking: SnapshotHandler#newFrameHandler no longer takes frameCount and fps. Custom
    SnapshotHandler implementations must drop those parameters.