Snapshots freeze pixels
A saved screenshot is the usual fixture for a messaging scene: quick to capture, impossible to diff, and frozen on one platform's look. When the conversation changes you retake the shot; when the test needs the same exchange on WhatsApp instead of iMessage you keep a second PNG that drifts from the first. The screenshot records what the scene looked like once. It cannot say what the scene is.
Textmock keeps the document instead. A scene is a JSON object: participants, a contact, a platform, a theme, messages with stable IDs, and an optional timeline that animates fields by JSON pointer. The schema is published at /schema and downloadable as JSON Schema for editor completion. Every render, still or video, is a projection of that document.
The link is the fixture
When a scene is serialized it goes through one canonical grammar: sorted keys, fixed formatting, exactly one byte sequence per document. Its SHA-256 rides inside the URL as v1.<digest>.<payload>, where the payload is the gzipped canonical bytes. A shared link is not a pointer to a stored scene; it is the scene.
Decoding is verification, not trust. The reader re-hashes the decoded bytes and rejects a digest mismatch, rejects base64 padding that would give one payload two spellings, and rejects reserialized JSON that is not the canonical form. Byte and element bounds are checked before anything renders. One document has exactly one address, and the address proves the bytes were not edited in transit. It proves integrity, not authorship; anyone who holds the link can make their own scene.
One document, five platforms
The platform is a field on the scene, not a separate file: imessage, whatsapp, telegram, instagram, google-messages. The same conversation renders in each app's look, so one fixture covers a marketing mockup, a storyboard, and a support doc without drifting copies. Change the field, keep the messages; the diff is one line in reviewable text, not two incomparable images.
Stable message IDs do the same work inside a scene. Replies, reactions, presence, and animation tracks reference messages by ID rather than position, so inserting a message early in the conversation does not renumber everything downstream.
Written by anything that writes JSON
Because the document is plain JSON with a published schema, the author does not have to be the studio editor. A script can generate a fixture set for a story; an agent can draft a scene, open it in the studio to review visually, then edit the JSON again. The content address is the same either way, so a generated scene and a hand-built one are indistinguishable as fixtures.
Validation runs on open, not only in the editor: foreign documents are parsed, bounded, and canonicalized before they render, and a malformed scene fails with a parse error instead of a partial picture.
What the fragment keeps
The scene travels in the URL fragment after #, which browsers do not send with the request. Sharing a link shares the scene with whoever opens it, but link-preview crawlers see only the deliberate social preview: the contact name, platform, theme, and up to 220 characters from each of the first two messages. Uploaded photos stay in the browser's IndexedDB; links carry references to them, never the bytes.
Where a snapshot still wins
An honest line: if the question is whether the renderer's pixels changed, a visual snapshot is still the right check. The scene file replaces the data fixture (the conversation itself) and the screenshot as transport (how the scene moves between people, tools, and renders). Use pixels to test the renderer; use the document to test everything the document describes.