assert_png_snapshot! or similar macros for image snapshots
I'm interested in using insta for integration testing of UI code via screenshots. Some desiderata:
1. Snapshots are represented as ordinary image files on disk (presumably in the same way that binary snapshots currently have their content in separate files, rather than encoded in a `.snap` file).
2. Images are compared on the basis of image content (so two images that are compressed differently but decompress to the same image are considered to match, and extraneous metadata fields are ignored).
Binary snapshots almost provide this behavior, but I don't see a straightforward way to get (2). I have looked at a couple of different ways to implement this, and I have a working implementation at https://github.com/dstu/insta/tree/feature/assert_png_snapshot of less than 1000 lines that tries to be minimally disruptive. I haven't opened a PR yet because some discussion about design may be warranted.
The short of it is that this feature branch adds a late-binding pathway for injecting semantics about how to handle a particular kind of `Snapshot` data.
This is done via a trait (which I have called `Comparator`) that provides a function which compares two `Snapshot`s:
```rust
fn matches(&self, config: &ToolConfig, reference: &Snapshot, test: &Snapshot) -> bool;
```
An implementation of `Comparator` is passed to `runtime::assert_snapshot`, and the current behavior of `Snapshot::matches`/`Snapshot::matches_fully` is moved to a default `Comparator` implementation.
To compare PNGs, a new macro that is structurally identical to `assert_binary_snapshot` is introduced. Its chief difference is that it passes a `Comparator` that decodes image data and does a comparison on the basis of the decoded data.
This design is not perfect. In particular:
1. The coupling between snapshot contents and `Comparator` is loose. It might arguably be better for snapshot metadata to record how the comparison should be done. But I imagine that this would necessitate some sort of internal registry of `Comparator`s (or an analogous comparison function type), with `Comparator` selection done on the basis of a field in snapshot metadata. It seems simpler to keep `Snapshot`s concerned with the data (binary or otherwise) that is kept on disk and for *behavior* about how to handle `Snapshot`s to be specified at an assertion macro call site.
2. `Comparator` takes a `ToolConfig` parameter. Future customization of `Comparator` behavior would presumably be done by adding fields to `ToolConfig`. This may be undesirable because it will result in a proliferation of fields (fostering a "configuration god object"). It might be better for `Comparator`-specific behavior to be specified at an assertion macro call site. A simple way to do this would be to have callers of assertion macros provide a `Comparator` *factory method* and configuration parameters for it. This stays true to the principle of specifying comparison behavior at a macro call site. I find this appealing, but it would require redesigning the snapshot macro interfaces, which seems out of scope for this endeavor.
3. `png::Comparator` decompresses PNGs from binary data at comparison time. This is a striking difference from how textual data which can be deserialized into Rust objects is handled. This asymmetry in design is annoying, and it makes it unclear which direction future changes should go in. (If we want to support decoding some other format, do we just put it in a custom `Comparator`, or do we add another field to `SerializationFormat`?) An important point to consider is that some use cases will just have a blob of binary data (as is the case for images), and others will have structured data. But one could arguably harmonize things by factoring `Comparator` differently (so that image data is decoded separately from comparing against it). Again, this seems to broaden scope too much, but I'm open to discussing it.
4. If you squint a little, there is already a mechanism for "decoding" snapshot contents before doing a comparison: redactions. One could argue that `Comparator` and redactions could/should be unified somehow. (After all, if you work hard enough, you can model any data transformation as a composition of a bunch of simple functions.) But the DSL that is provided for redactions seems to occupy its niche well, and, again, cracking this open seems like too much scope creep.
Hope this helps. I'd love to get image snapshots into insta soon.
关闭于 2026-03-27 5 条评论