Skip to content

Instantly share code, notes, and snippets.

@hi-ogawa
Last active April 21, 2026 05:39
Show Gist options
  • Select an option

  • Save hi-ogawa/402813e81847cfa5ed2b593256737d75 to your computer and use it in GitHub Desktop.

Select an option

Save hi-ogawa/402813e81847cfa5ed2b593256737d75 to your computer and use it in GitHub Desktop.
DOM snapshot / replay architecture comparison for Vitest trace view

DOM Snapshot / Replay Architecture Comparison

This is a related resarch for Vites trace view discussion vitest-dev/vitest#10156.

Note

This note may be shared publicly as extra background for the trace-view discussion. Sibling-repo references in this note assume these source repositories:

This note compares DOM snapshot/replay approaches at the technical architecture level.

It is intentionally not framed as "which API is nicer" or "what Vitest should adopt right now". The point is to separate the underlying implementation patterns:

  • structural serialization + generic rebuild
  • structural snapshot + resource virtualization
  • runtime-assisted DOM transplant replay
  • with full timeline record/replay treated as a layered concrete system rather than a peer bucket

This also clarifies an important ambiguity around rrweb: rrweb-snapshot and full rrweb are related but materially different layers.

Terms

Capture artifact

The persisted representation produced at capture time.

Examples:

  • a serialized DOM tree
  • a DOM tree plus side metadata
  • a frame snapshot plus archived resources
  • a mutation/event timeline

Replay runtime

The code and environment that consumes the capture artifact and turns it back into something viewable.

Structural serialization

Capture as pure tree data that can be rebuilt into another document.

Resource archiving

Capture CSS/images/fonts/etc as separate resource records or files.

Resource virtualization

Replay archived resources through a fetch-like layer so the rebuilt page still "loads" them by URL.

Runtime-assisted replay

Replay depends on a cooperating restore runtime, not just the artifact itself. The runtime knows how to transplant DOM, reattach styles, replace iframes, add highlights, and so on.

Timeline record/replay

A different layer from point-in-time snapshots. Instead of replaying one captured state, it replays a sequence of changes over time.

Comparison Axes

The useful comparison axes are:

  • capture representation
  • replay contract
  • resource handling model
  • artifact portability
  • replay runtime dependency
  • fidelity envelope
  • implementation complexity
  • extensibility

Architecture A: Structural Serialization + Generic Rebuild

This is the rrweb-snapshot style.

Core idea:

  • capture a DOM tree as pure serialized data
  • rebuild that tree into another document with a generic rebuilder
  • optionally store side metadata such as viewport, scroll, node ids, pseudo-class ids, or selected inlined assets

Artifact shape

The artifact is fundamentally a serialized DOM tree, typically produced by snapshot(document, ...).

Snapshot API and defaults: packages/rrweb-snapshot/src/snapshot.ts#L1267

The important architectural trait is that this style tends to bake more replay-relevant state directly into the serialized DOM artifact itself. In rrweb terms, the tree is not just structure: it can carry replay-facing data such as stylesheet text, form state, shadow markers, optional image/canvas payloads, and stable node ids.

Resource handling

This is also where one of the main trade-offs appears.

For CSS, rrweb-snapshot primarily pulls in browser-visible stylesheet state through CSSOM APIs:

  • for <link rel="stylesheet">, it looks up the matching CSSStyleSheet from document.styleSheets
  • it stringifies the sheet through rules / cssRules
  • if successful, it removes rel / href and stores the stylesheet text in _cssText

CSS capture from document.styleSheets: packages/rrweb-snapshot/src/snapshot.ts#L588 CSS stringification via cssRules: packages/rrweb-snapshot/src/utils.ts#L114

For <style>, rrweb-snapshot similarly serializes style.sheet into _cssText.

Inline <style> capture: packages/rrweb-snapshot/src/snapshot.ts#L604

For images, rrweb-snapshot can optionally inline the currently loaded rendered image by:

  • drawing the image into a canvas
  • storing a data URL in rr_dataURL

Optional image inlining: packages/rrweb-snapshot/src/snapshot.ts#L689

For canvas, rrweb-snapshot can optionally store a canvas data URL in the same general way.

Optional canvas capture: packages/rrweb-snapshot/src/snapshot.ts#L657

Defaults matter here:

  • inlineStylesheet = true
  • inlineImages = false
  • recordCanvas = false

Defaults: packages/rrweb-snapshot/src/snapshot.ts#L1268

Replay contract

Replay is generic rebuild into a target document, using rebuild(serialized, ...) plus whatever local post-processing the embedding viewer wants to add.

  • rebuild the serialized DOM tree
  • let the embedding layer optionally restore viewport/scroll/highlights/pseudo-classes
  • consume baked-in resource fields such as _cssText or rr_dataURL during rebuild

Rebuild API: packages/rrweb-snapshot/src/rebuild.ts#L192

For the resource side, replay consumes those baked-in artifact fields directly:

  • a serialized <link> with _cssText is rebuilt as a <style>
  • rr_dataURL on <img> can replace the image source
  • rr_dataURL on <canvas> can be drawn back into the replay canvas

_cssText replay as <style>: packages/rrweb-snapshot/src/rebuild.ts#L58 and packages/rrweb-snapshot/src/rebuild.ts#L268 rr_dataURL replay for <img> / <canvas>: packages/rrweb-snapshot/src/rebuild.ts#L336 and packages/rrweb-snapshot/src/rebuild.ts#L355

Strengths

  • clean separation between artifact and viewer
  • tree artifact is easy to store, ship, diff, and post-process
  • replay does not require the original page session
  • node-id based lookup works naturally across shadow DOM

Weaknesses

  • external resource fidelity is weak unless another layer is added
  • rebuild is structural, not browser-session-faithful
  • authored resource bytes and loading semantics are not inherently preserved
  • dynamic runtime behavior is not replayed, only captured state

Serialization trade-off

Compared with Playwright's trace snapshots, rrweb-style serialization is more self-contained at the DOM artifact layer. That usually makes the DOM artifact itself richer, but also pushes more responsibility into capture-time serialization and into the rebuilt tree representation.

The resource trade-off follows directly from those APIs:

  • rrweb gets good results when browser-exposed CSS/image state can be captured directly from the live page
  • but this is still mostly "capture what the browser currently exposes" rather than "archive original resource bytes with an independent replay server contract"
  • cross-origin/inaccessible stylesheets, non-inlined images, fonts, and related subresources are where this model starts to show its limits

Architecture B: Structural Snapshot + Resource Virtualization

This is the Playwright-style family.

Core idea:

  • capture structural frame snapshots as data
  • archive fetched resources separately
  • replay through a renderer/server layer that serves archived resources back by URL

Artifact shape

Playwright's FrameSnapshot includes structural HTML, resource overrides, viewport, ids, and timestamps.

Shape: packages/trace/src/snapshot.ts#L40

This is not just "serialized DOM plus some convenience fields". The format is designed to cooperate with archived resources and multi-snapshot rendering.

At the same time, Playwright's DOM artifact is less self-sufficient than rrweb's if viewed in isolation. A meaningful part of replay fidelity is intentionally offloaded to the replay contract:

  • archived ResourceSnapshots
  • resourceOverrides
  • SnapshotStorage
  • SnapshotServer

Replay contract

Replay is mediated by storage and server layers:

  • SnapshotStorage groups frame snapshots with per-context resources and creates SnapshotRenderer instances
  • SnapshotServer can render snapshot HTML and serve resources back by request URL/method

Storage: packages/isomorphic/trace/snapshotStorage.ts#L24 Server: packages/isomorphic/trace/snapshotServer.ts#L22

This means the replay contract is closer to "render an archived browsing surface" than "rebuild a tree into a document".

Playwright's serializer is not necessarily simpler overall. The complexity is distributed differently:

  • less resource payload is baked straight into the DOM snapshot
  • more fidelity is recovered through the replay-time resource contract
  • the snapshotter itself still supports incremental references and stylesheet override tracking

Injected snapshotter: packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts#L19 Capture pipeline: packages/playwright-core/src/server/trace/recorder/snapshotter.ts#L121

Strengths

  • stronger replay fidelity for CSS/images/fonts and other fetched assets
  • artifact is substantially more self-sufficient at replay time
  • preserves more of the page's original URL/resource behavior
  • scales better to offline or trace-artifact scenarios

Weaknesses

  • much larger implementation surface
  • requires resource storage, lookup, and replay infrastructure
  • more moving parts around caching, content types, overrides, and URL normalization

Architecture C: Runtime-Assisted DOM Transplant Replay

This is the abstracted Cypress-style family.

Core idea:

  • temporarily capture DOM state in memory while the test is running
  • clone/import live DOM fragments into a transient document
  • sanitize or replace parts that are unsafe to replay directly
  • store styles and metadata in side channels
  • restore that DOM state on the fly inside the same tested-app iframe environment for runner debugging

Artifact shape

Cypress snapshots are not primarily "pure serialized DOM tree" artifacts. At a high level, the stored artifact is a restore-oriented object built around:

  • a cloned <body> subtree
  • document-level metadata needed to restore the page shell
  • side-channel style data or style references

Capture/shape: packages/driver/src/cy/snapshots.ts#L124 Snapshot creation: packages/driver/src/cy/snapshots.ts#L149 Style side channel: packages/driver/src/cy/snapshots.ts#L106

Strictly speaking, this is not the same category as DOM snapshot serialization in the rrweb/Playwright sense. It is closer to a temporary in-memory DOM clone/import plus side-channel replay state.

Important clarification:

  • importNode and adoptNode are standard DOM APIs
  • Cypress builds its own restore-oriented snapshot object on top of those DOM primitives
  • the model is best understood as temporary in-memory DOM-state capture and restore inside the same tested-app iframe environment, rather than as a standalone serialized DOM artifact

The important emphasis is:

  • temporary, not durable artifact-first storage
  • in-memory, not primarily serialized JSON/tree persistence
  • same tested-app iframe runtime environment, not a separate replay surface
  • on-the-fly restore for runner interaction/debugging

Capture mechanics

The capture flow is not "serialize DOM tree into JSON". It is closer to:

  1. collect document metadata and style side data
  2. clone the live <body> into a transient Document with snapshotDocument.importNode(..., true)
  3. sanitize the cloned body by replacing/removing things Cypress does not want to replay directly
  4. store the cloned body in a restore-oriented snapshot object

The key DOM API here is importNode, which clones a node from one document into another document.

Cloning into transient document: packages/driver/src/cy/snapshots.ts#L165 and packages/driver/src/cy/snapshots.ts#L176

The note in Cypress is important: they explicitly avoid plain cloneNode here because cloning can trigger side effects with custom elements, so they use importNode into a transient document instead.

Replay contract

Replay assumes a cooperating runtime that knows how to restore the snapshot:

  • reset iframe origin constraints if necessary
  • fetch styles through Cypress runtime helpers
  • replace <html> attrs
  • remove the old body
  • insert the restored body
  • re-apply highlights and runner overlays

Restore: packages/app/src/runner/aut-iframe.ts#L173

This is a different replay contract from generic rebuild. The replay runtime is a first-class part of the mechanism.

Replay mechanics

Replay again uses standard DOM APIs plus Cypress-owned restore logic:

  1. the stored body node is moved into the current tested-app iframe document with adoptNode
  2. Cypress restore code replaces document shell metadata such as <html> attributes
  3. Cypress restore code replaces/remaps styles
  4. the old body is removed and the adopted body is appended

The key DOM API here is adoptNode, which moves a node so that it belongs to the target document.

Adoption into current document: packages/driver/src/cy/snapshots.ts#L256 and packages/driver/src/cy/snapshots.ts#L270 Restore in tested-app iframe document: packages/app/src/runner/aut-iframe.ts#L192

Strengths

  • pragmatic when capture and replay live inside one controlled runner
  • easy to integrate runner-specific highlighting and UI affordances
  • can be simpler than generic rebuild for an in-runner debugger

Weaknesses

  • artifact is less self-describing than a pure serialized tree
  • replay depends more on restore-runtime behavior and side channels
  • portability is weaker than artifact-first approaches
  • sanitization/replacement rules are part of correctness, not just presentation

Concrete Exemplars

full rrweb

full rrweb is best treated here as a higher-level concrete system built on top of the lower-level snapshot/rebuild primitive.

In practical terms:

  • rrweb-snapshot answers "how do I serialize and rebuild DOM state?"
  • full rrweb answers "how do I record and replay DOM evolution over time?"

More concretely:

  • record(...) emits a typed event stream
  • that stream includes Meta, FullSnapshot, and IncrementalSnapshot
  • the full snapshot path internally uses snapshot(document, ...)
  • the replay side uses Replayer, which rebuilds full snapshots and then applies incremental events over time

Recorder: packages/rrweb/src/record/index.ts#L65 Full snapshot emission: packages/rrweb/src/record/index.ts#L337 Incremental event sources: packages/types/src/index.ts#L62 Replayer: packages/rrweb/src/replay/index.ts#L117

So full rrweb is not a peer architecture bucket next to A/B/C in this note. It is a concrete layered system whose DOM-state foundation is closest to Architecture A, with mutation/event timeline machinery added on top.

A practical consequence of this sequence-aware design is that replay can share/dedupe state across snapshots/events rather than treating every captured point as a fully isolated artifact.

Vitest current trace view

Vitest is currently in Architecture A.

It captures point-in-time rrweb snapshots per trace entry and adds Vitest-specific replay metadata around the raw rrweb tree:

  • serialized rrweb snapshot tree
  • viewport
  • window scroll
  • selectorId
  • pseudo-class ids

Trace snapshot shape: packages/browser/src/client/tester/trace.ts#L29 Capture: packages/browser/src/client/tester/trace.ts#L104 Replay: packages/ui/client/components/trace/TraceView.vue#L46

Minor replay-specific detail:

  • rrweb already has replay-oriented handling for :hover-style visual restoration
  • Vitest extends this locally by recording pseudo-class ids and replaying :hover, :focus, and :focus-within
  • Playwright's trace viewer appears to lean more on explicit target highlighting than on a comparable generic pseudo-class reapplication layer
  • this is best viewed as visual replay polish, not as a major architecture distinction

Pseudo-class capture: packages/browser/src/client/tester/trace.ts#L126 Pseudo-class replay: packages/ui/client/components/trace/TraceView.vue#L63 Local rrweb patch: patches/rrweb-snapshot.patch Playwright highlight-oriented replay: packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts#L150 and packages/isomorphic/trace/snapshotRenderer.ts#L344

So Vitest today is not using:

  • Playwright-style resource virtualization
  • Cypress-style DOM transplant replay
  • full rrweb event timeline replay

This also means current Vitest trace view does not currently exploit cross-snapshot sharing/dedup. Each trace entry is effectively stored as its own point-in-time snapshot payload, which keeps the implementation simple but can duplicate repeated DOM/resource state across steps.

Chromatic e2e

Chromatic is structurally closest to Architecture A extended toward B.

Capture/replay primitive:

  • rrweb snapshot JSON is the DOM artifact
  • replay still uses rrweb rebuild

Replay in Storybook: packages/shared/storybook-config/preview.ts#L60

But Chromatic adds a resource archive layer:

  • intercept network responses with CDP Fetch
  • archive allowed-domain resources
  • rewrite snapshot CSS/image URLs to archived local paths

Archiver: packages/shared/src/resource-archiver/index.ts#L31 Snapshot URL rewriting: packages/shared/src/write-archive/dom-snapshot.ts#L24

Technically, this is a hybrid:

  • artifact starts as rrweb structural serialization
  • portability is improved through separate resource capture and path rewriting
  • replay is still rebuild-driven, not full resource virtualization

Playwright trace

Playwright is Architecture B.

It uses its own frame/resource snapshot model and replay server contract rather than rrweb rebuild.

Snapshot format: packages/trace/src/snapshot.ts#L40 Storage: packages/isomorphic/trace/snapshotStorage.ts#L24 Resource-serving replay: packages/isomorphic/trace/snapshotServer.ts#L77

Playwright is also a good example of sequence-aware snapshot/replay paying off in dedup/sharing. Its snapshot model and replay contract can coordinate repeated state across many snapshots, instead of forcing each snapshot to be a completely standalone fully repeated blob.

Cypress runner snapshots

Cypress is Architecture C.

It is useful to understand as a technical family, not merely as "a custom UI". The core distinction is the replay contract:

  • temporary in-memory DOM-state storage
  • body transplant
  • style side channels
  • same tested-app iframe runtime environment
  • on-the-fly page/document restore inside that iframe
  • runner-managed highlighting and DOM normalization

Capture: packages/driver/src/cy/snapshots.ts#L149 Restore: packages/app/src/runner/aut-iframe.ts#L173

Trade-off Matrix

Architecture Capture artifact Replay contract Resource model Portability Runtime dependency Main trade-off
A. Structural serialization + generic rebuild Pure tree + small side metadata Generic rebuild into target doc Mostly leave URLs as-is, maybe inline selected assets Good for DOM artifact portability Low to medium Simple and generic, but weak on resource fidelity
B. Structural snapshot + resource virtualization Tree/frame data + archived resources Renderer/server serves snapshot HTML and resources Archived resources replayed by URL Strong Medium to high Best fidelity, highest machinery
C. Runtime-assisted DOM transplant replay DOM fragment + side channels Restore/transplant into controlled runtime Runtime restores styles/resources via helper logic Moderate to weak High Practical in a runner, but less artifact-self-contained

Side note:

  • cross-snapshot sharing/dedup is an orthogonal concern rather than a pure A/B/C split
  • sequence-aware systems such as Playwright trace or full rrweb can reduce duplication across many snapshots/events
  • current Vitest trace view is closer to independent per-step point-in-time snapshots, which simplifies the implementation but gives up that dedup opportunity unless another layer is added

Key Observations

1. Resource strategy is the main separator between A and B

The core jump from rrweb-style rebuild to Playwright-style replay is not "better DOM snapshots". It is adding a resource replay contract.

2. Cypress is best understood by replay contract, not branding

The important distinction is not that Cypress has its own viewer. Playwright also has its own viewer. The technical distinction is that Cypress-style replay is runtime-assisted DOM transplant, while Playwright-style replay is artifact-first resource-backed rendering.

3. rrweb vs Playwright is partly about where complexity lives

For DOM-state capture alone, rrweb-style snapshots are more self-contained and replay-oriented at the artifact layer. Playwright's format keeps more fidelity responsibility in the replay contract. That does not make Playwright simpler overall; it means complexity is split differently between serializer and replay runtime.

4. Timeline replay is a separate dimension

Full rrweb should not be compared as if it were only another snapshot shape. It changes the system from state reconstruction to temporal replay.

5. Chromatic is a meaningful middle point

Chromatic demonstrates that rrweb-style structural snapshots can be extended significantly by adding resource archiving and path rewriting, without going all the way to Playwright's replay server model.

6. Same-runtime replay is inherently constrained

The Cypress-style model is best understood as an extension of a live runner iframe paradigm, not as a durable replay-artifact architecture.

That has real consequences:

  • replay fidelity depends on the original runtime still being alive in a compatible state
  • replay is harder to treat as a durable, portable artifact
  • switching freely across many tests/snapshots becomes harder unless orchestration keeps those runtime contexts alive
  • residual global state can make replay look richer while also making it less isolated and less predictable

This makes the family feel closer to Vitest's current browser-iframe-view paradigm than to rrweb/Playwright-style artifact-first replay:

  • Vitest current browser iframe view: inspect a live tested page inside an active iframe
  • Cypress snapshot extension: temporarily capture and restore DOM state inside that same live iframe paradigm
  • rrweb / Playwright: move toward replay from captured artifacts instead of from runtime liveness

So the architectural limitation is not accidental. It follows directly from relying on same-runtime, on-the-fly restore.

This also shows up directly in user-facing behavior:

  • Cypress gates normal snapshot restore while tests are still running
  • Playwright can support live/in-progress snapshot display because it renders snapshots into dedicated replay surfaces rather than mutating the live tested page context

Cypress running-test gate: packages/app/src/runner/iframe-model.ts#L112 and packages/reporter/src/commands/command.tsx#L448 Playwright live snapshots: packages/playwright-core/src/server/trace/recorder/tracing.ts#L178, packages/playwright-core/src/server/trace/recorder/tracing.ts#L463, and packages/trace-viewer/src/ui/snapshotTab.tsx#L191

Fidelity Notes

DOM-state fidelity vs runtime/JS fidelity

It helps to separate at least three notions of fidelity:

  • DOM-state fidelity
  • resource fidelity
  • runtime/JS fidelity

DOM-state fidelity asks whether the replay reproduces the rendered DOM structure and state:

  • element tree
  • attributes and form values
  • shadow DOM
  • scroll/viewport metadata
  • selected pseudo-class state

Resource fidelity asks whether the replay reproduces the same CSS/images/fonts/subresources that the original page used.

Runtime/JS fidelity asks whether the replayed page still has the original executable runtime behavior:

  • event listeners still wired
  • framework/component instances still alive
  • timers, observers, and app state machines still running
  • custom component logic still executing

These are different problems. A system can have strong DOM-state fidelity while having weak runtime/JS fidelity.

rrweb / Playwright

In the rrweb/Playwright family, the core promise is DOM-state replay, not original JS runtime replay.

For rrweb-style rebuild:

  • the rebuilt document is reconstructed from serialized structure
  • it is not the original JS heap or component instance graph
  • custom elements and shadow DOM can replay structurally, but not as the original running app unless the replay environment separately provides that runtime

For Playwright trace:

  • the replay is stronger on resources and browser-surface fidelity
  • but it is still not a resurrection of the original executing page runtime
  • the trace viewer renders archived state through its replay contract rather than resuming the application process

So custom element snapshots are supported here in the sense of rendered DOM state, not in the sense of reviving framework/component logic.

Cypress

Cypress is even clearer on this point.

Its snapshot capture removes scripts from the stored body:

Script/style stripping: packages/driver/src/cy/snapshots.ts#L129 and packages/driver/src/cy/snapshots.ts#L187

Replay restores DOM and styles by transplant:

Restore: packages/app/src/runner/aut-iframe.ts#L173

That means Cypress snapshots should not be described as "replaying JS" in the strong sense. The mechanism is much closer to:

  • clone/import DOM
  • strip executable/script-loading pieces
  • restore body/html/styles inside a controlled tested-app iframe document
  • overlay Cypress-specific highlighting

The important fidelity point is that clone/import by themselves do not preserve ordinary JS listener/runtime wiring:

  • DOM cloning/import preserves structure and attributes
  • it does not carry over typical addEventListener(...) registrations
  • it does not preserve arbitrary JS object graphs or app/framework instance state

So if a restored Cypress snapshot looks "live", that should not usually be attributed to the snapshot artifact preserving JS behavior. It is more likely because the restore happens inside the same already-loaded tested-app iframe runtime environment.

Concrete example:

  • if original code registered document.addEventListener('click', handler) or window.addEventListener(...)
  • and Cypress replay restores DOM inside that same live document/window runtime context
  • that global listener can still remain effective during replay

By contrast:

  • if original code registered someElement.addEventListener('click', handler) on a DOM node inside the old body
  • Cypress snapshot clone/import does not preserve that listener wiring on the cloned/restored DOM subtree
  • and replay also removes/replaces the old body subtree

So a global listener on document/window can make replay appear "JS-live" in Cypress, while ordinary element-bound listeners are much less likely to survive through the snapshot/restore mechanism itself.

If Cypress sometimes appears to "replay JS", that is more likely one of:

  • the tested app/runtime is still live in the runner outside the pinned snapshot flow
  • the user is seeing the currently mounted/live component rather than a purely restored snapshot
  • some browser-native behavior still works on the restored DOM
  • the current runtime environment still has globals, custom-element definitions, or other loaded code that can interact with the restored DOM

But the snapshot mechanism itself is not reviving the original component/app runtime.

On custom elements

Custom elements fit cleanly into DOM-state fidelity, but only at that level.

What can be preserved well in rrweb/Playwright-style paradigms:

  • the rendered custom element tag in the DOM tree
  • its attributes and light DOM
  • its open shadow DOM content, when captured
  • form/control state reflected into DOM properties or serialized attributes

What is not inherently preserved:

  • the original custom element class instance identity
  • constructor/connectedCallback side effects as they originally happened
  • framework/runtime objects behind the element
  • arbitrary event listener graphs and live JS execution state

So for custom elements, "snapshot/replay works" means:

  • the replay can reconstruct the element's DOM-state representation
  • it does not mean the original component runtime has been resumed

This is precisely where rrweb/Playwright-style approaches are strong: they are good at preserving the browser-observable DOM state of a custom element. That is enough for visual inspection and DOM-state debugging, even though it is not equivalent to reviving the original executing element instance.

For Cypress specifically, the effect can be more confusing because restore happens inside the same tested-app iframe runtime environment.

Concrete example:

  • original code runs customElements.define('my-el', MyElement)
  • that registration lives on the window's CustomElementRegistry
  • Cypress later restores DOM containing <my-el> inside that same live runtime environment

So the restored custom element can appear partially live because the global custom-element registration effect may still exist at replay time.

But that is still different from the snapshot mechanism itself preserving:

  • the original custom element instance identity
  • the original listener graph attached to the old subtree
  • the original framework/app runtime state behind that instance

In other words, Cypress can inherit live global runtime effects that influence restored custom elements, even though the snapshot/restore mechanism itself is still primarily preserving DOM state rather than true JS-runtime state.

Open Questions

  • How far can Architecture A be pushed before resource fidelity forces a move toward B?
  • Is Chromatic's "archive + rewrite" middle ground sufficient for most offline replay needs?
  • Which problems genuinely require timeline replay rather than repeated point-in-time snapshots?
  • How should adopted stylesheets, cross-origin CSS, fonts, canvas, and iframes be classified across these architectures?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment