Screenshot tests without an emulator

Screenshot testing on Android has a good answer — Paparazzi renders through LayoutLib, no emulator, fast. Compose Multiplatform does not, because LayoutLib is Android's renderer and the code under test is not Android code.

viddik takes the other route: render through a real Compose Desktop window on Skiko, on a plain JVM. No emulator, no AVD, and — this is the part that matters — the same renderer that will actually draw the component.

One annotation, two outputs

@ViddikScreenshot
@Composable
fun EmptyBasket() = BasketScreen(state = Basket.empty)

A KSP processor collects every annotated composable into a registry, and that registry is consumed twice.

ViddikEngine captures each one to PNG and diffs it against the golden, in record or verify mode, as a JUnit 5 test. ViddikShowroom takes the same registry and renders it as a live, browsable component gallery — the Showkase half of the idea, without a second set of declarations to keep in sync.

That is the reason the two features live in one library. A component browser that drifts from the tests is worse than no browser at all.

Three artefacts, and why

The module split is not organisational tidiness; it is about what may appear on which classpath.

module what it is where it may go
viddik-annotations the @ViddikScreenshot marker, ViddikComponent, ViddikShowroom anywhere, including android()
viddik-processor KSP codegen — registry and generated tests a KSP configuration
viddik-testing-core capture, diff and record, on Compose Desktop + JUnit 5 test only, never main

The annotations artefact is deliberately tiny because it is the one that has to be safe to depend on from production source sets. The capture engine drags in Compose Desktop and JUnit, and has no business anywhere near an app's main.

The one that catches everyone

Wiring KSP for a KMP target is where the time goes, and the failure is quiet — the processor simply does not run and your registry is empty.

dependencies {
    testImplementation("ru.workinprogress:viddik-annotations:<VERSION>")
    testImplementation("ru.workinprogress:viddik-testing-core:<VERSION>")
    add("kspDesktopTest", "ru.workinprogress:viddik-processor:<VERSION>")
}

The kspDesktopTest configuration name is derived from your target name. Name your JVM target something else and that string changes with it; use a plain kotlin("jvm") project and it is kspTest, with per-target artifact coordinates instead of the base ones. There is no error for getting this wrong, only zero generated tests — which is exactly the shape of failure worth knowing about in advance.

Goldens that travel

The reason to care about the renderer is that goldens get committed, and then somebody else runs the suite.

Because the capture goes through Compose Desktop rather than a platform toolkit, the same golden holds on macOS, Linux and Windows. That is what makes goldens reviewable in a pull request instead of a source of noise every time CI changes machines.


Source: github.com/youndie/viddik