skip to content

Why can a Cypress snapshot render unstyled when the app's CSS comes from a CDN?

level: middleimportance: nice to knowfreq 28%

answer

  1. Not every stylesheet can be read
  2. Some styling is stored by reference
  3. Restore may hit the network again
  4. Rule text versus a bare href
  5. Cross-origin sheets keep only the URL

basics

~20 s

Cypress stores a stylesheet's rule text only when the browser lets it read the sheet. A cross-origin sheet is kept as its href alone, and restoring refetches it, so a changed or unreachable URL makes the snapshot render wrong.

solid answer

~50 s

When Cypress captures a snapshot it walks the page's `<link rel="stylesheet">` and `<style>` elements and tries to read each sheet's rules. For a sheet the browser lets it read, it stores the rule text — hashed so identical sheets are kept once — and rewrites relative `url()` references to absolute ones so fonts and background images still resolve. For a cross-origin sheet the browser refuses that access, so Cypress falls back to storing only the `href`. Restoring that snapshot writes a fresh `<link rel="stylesheet" href="...">` into the head and the browser fetches it **at that moment**. That makes the styling of an old snapshot depend on a live network fetch: if the CDN is unreachable, has since shipped a new build at the same URL, or requires credentials the runner does not send, the restored ticket queue will not look like what the test actually saw.

go deeper

for a junior

Know that a restored snapshot can look unstyled without the application being broken, and check the live page before raising it as a defect.

for a middle

Explain the split: readable sheets are stored as rule text, unreadable ones as a bare href that the browser fetches again when the snapshot is restored.

for a senior

Diagnose this quickly in a real session — separate a styling-delivery problem from a rendering problem, and lean on structural assertions rather than on appearance.

for a principal

Set the expectation that appearance is never the evidence a snapshot provides, and keep team debugging conventions off anything that depends on a fetch nobody controls.

## Two ways a stylesheet can be captured A Cypress snapshot stores the page's markup and its styling on separate tracks. The clone of `<body>` has every `<style>` and `<link rel="stylesheet">` removed from it; the styling is captured as an ordered list beside it and re-inserted when the snapshot is restored. How each entry in that list is captured depends on one thing: **whether the browser will hand Cypress the sheet's rules**. - An inline `<style>` tag is easy — its rules are read straight off it. - A `<link>` to a same-origin stylesheet is read through the sheet's rule list and stored as **rule text**. - A `<link>` to a cross-origin stylesheet — a design system on a CDN, a font service, a third-party widget's styles — cannot be read. The browser either throws a security error or reports no rules at all, and Cypress stores **only the `href`**. There is a fourth case worth knowing: only stylesheets that apply to the screen are captured at all. A sheet whose `media` attribute is absent, or is `screen` or `all`, is collected; a `media="print"` sheet is skipped. ## What each case costs at restore time | Captured as | What restore does | Failure mode | |---|---|---| | Rule text from a `<style>` tag | Writes a `<style>` with that text | Essentially none | | Rule text from a same-origin `<link>` | Writes a `<style>` with that text | Essentially none | | A bare `href` | Writes a `<link>` the browser fetches now | Depends on the network and on the URL still serving the same bytes | The first two are self-contained: the bytes are already in the snapshot, so restoring it is a pure in-memory operation. The third is not. It is a **promise to fetch**, redeemed only when you go back and look. ## Why the by-reference case bites Storing an `href` seems harmless until you notice how far apart capture time and restore time can be: - **The URL now serves different bytes.** A CDN path that is not content-hashed can pick up a new build between the run and the moment you revisit a command, and the snapshot silently re-styles itself with the new CSS. - **The URL is unreachable.** Working offline, on a VPN that does not route to the CDN, or against a service having an outage leaves the restored page unstyled — everything is present in the DOM but laid out as if no CSS existed. - **The fetch needs credentials the fetch does not carry.** A stylesheet behind an authenticating gateway can be fetched fine by the application and then fail on the re-fetch. - **Timing changes what you see.** The re-fetch is asynchronous, so a restored snapshot can flash unstyled and then settle. None of this is a bug in the application. It is the capture being honest about what it could and could not read. ## Diagnosing it on a support-ticket queue If a restored snapshot of the ticket queue looks structurally right but visually wrong, work through it in this order: 1. **Check whether the styling comes from another origin.** Look at the `<link>` elements the app loads and note which ones are not on the app's own origin. 2. **Reload the page and compare.** If the live application also looks unstyled, the problem is the CDN, not the snapshot mechanism. 3. **Confirm the DOM is intact.** Inspect the restored elements. If the classes and structure are all present, you are looking at a styling-delivery problem, not a rendering bug in the app. 4. **Stop trusting appearance and assert on structure.** Classes, attributes and text in a restored snapshot are exactly what the page had; how it looks is the part that depends on a fetch you do not control. ## The habit this should form Treat a snapshot's **markup as archival** and its **appearance as provisional**. The DOM in a snapshot is a faithful record of an instant; the styling is a faithful record only to the extent that the stylesheets were readable. In practice that means an investigation should be built on assertions about structure and content — the row exists, it carries the escalated class, its text reads what it should — rather than on the restored page looking right, because looking right depends on infrastructure that was never part of the capture.

  • Why does Cypress rewrite relative url() references in captured stylesheet rules?
    The rules are re-inserted as a `<style>` tag in the document rather than from the original stylesheet's location, so a relative `url()` would resolve against the wrong base. Cypress makes those paths absolute at capture time so background images and web fonts still load on restore.
  • Which stylesheets does a Cypress snapshot skip entirely?
    Ones that do not apply to the screen. Cypress keeps a stylesheet only when its `media` attribute is absent or matches `screen` or `all`, so a `media="print"` sheet never reaches a snapshot and print-only styling cannot be inspected this way.

saying these in an interview costs you the question

  • Assumes every stylesheet is inlined into the snapshot
  • Thinks a snapshot is fully offline once captured
  • Blames the app for an unstyled restored snapshot
  • Expects print styles to appear in a snapshot