Skip to content

percySnapshot: tolerate non-critical font load failures#4688

Merged
habdelra merged 2 commits intomainfrom
cs-preview-flake-diagnostics
May 7, 2026
Merged

percySnapshot: tolerate non-critical font load failures#4688
habdelra merged 2 commits intomainfrom
cs-preview-flake-diagnostics

Conversation

@habdelra
Copy link
Copy Markdown
Contributor

@habdelra habdelra commented May 6, 2026

Summary

Tests that call percySnapshot were failing intermittently in CI with Promise rejected during "<test name>": A network error occurred. — a generic message with no URL, no stack pointing into our code, and no way to attribute it. The fingerprint matched two unrelated tests in run 25460877925 shard 7 (field playground > can preview compound field instance and preview > renders head meta tags preview for a card head format). Both call percySnapshot; no other tests in those modules failed.

Root cause: await Promise.all(Array.from(document.fonts, (f) => f.load())). Chrome rejects FontFace.load() for transient font-fetch failures with exactly DOMException: A network error occurred.. With Promise.all, a single non-critical font hiccup tanked the whole snapshot path and bubbled out of the test.

What this PR does in percy-snapshot.ts

  • Snapshot document.fonts once before kicking off any load() calls. The live set can shift while fonts/stylesheets load, so a later re-enumeration would risk misattributing which face actually failed.
  • Promise.allSettled instead of Promise.all so a non-critical font failure no longer poisons the snapshot path.
  • Explicit IBM Plex Sans guard on the rejected-faces list. Per the WHATWG FontFaceSet.check spec, faces in error status are treated as settled, and document.fonts.check(descriptor, '') with empty text can return true even when the required face is unrenderable. So we cannot rely on check alone to detect a Sans network failure — we inspect the rejected list directly and throw with the descriptor + rejection reason if any rejected face's family is "IBM Plex Sans". Without this, Percy would capture the page with a fallback font silently substituted in.
  • Belt-and-suspenders document.fonts.check loop stays for the unrelated case where a Sans weight was never declared on the page in the first place.
  • [percy-snapshot] warning when any non-critical font fails, naming each failed face (weight/style/family + rejection reason). If this fingerprint resurfaces, the testem log now points at the offender directly.

Test plan

  • CI: shard 7 of CI Host passes (and historical runs of these two tests stop matching the A network error occurred. fingerprint)
  • Sanity: when IBM Plex Sans really is missing the explicit Required font failed to load: ... (rejected face) or Not ready: IBM Plex Sans font could not be loaded (...) (never declared) error still fires, so the load-bearing assertion against fallback-font captures isn't lost
  • No new lint warnings in packages/host

🤖 Generated with Claude Code

`Promise.all(Array.from(document.fonts, (f) => f.load()))` rejects the
whole barrier if any single FontFace.load() rejects — and Chrome rejects
FontFace network failures with the generic `DOMException: A network
error occurred.`, no URL attached. That message bubbled up through
percySnapshot → test → QUnit and surfaced in CI as
"Promise rejected during \"<test name>\": A network error occurred."
on every test that calls percySnapshot, with no breadcrumb pointing at
fonts. Two such failures in shard 7 of run 25460877925 — one in
Acceptance | code-submode | field playground, one in
Integration | preview — share that fingerprint exactly.

Switch to `Promise.allSettled` so a single non-critical font hiccup no
longer poisons the snapshot. The hard-coded IBM Plex Sans
`document.fonts.check` immediately after stays the load-bearing
assertion: if the font that actually moves Percy pixels is the one
that failed, the test still fails — but with a clear,
self-attributing message.

When any font does fail, log a `[percy-snapshot]` warning naming each
failed face (weight/style/family) and the rejection reason, so future
recurrences are diagnosable from the testem log alone instead of
needing to hand-correlate with `[test-fetch]` output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37cc7fb3e0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/host/tests/helpers/percy-snapshot.ts
Per the WHATWG FontFaceSet.check spec, faces in `error` status are
treated as settled, and `document.fonts.check(descriptor, '')` with
empty text can return `true` even when the required face is
unrenderable. So with the previous fix alone, an IBM Plex Sans
network failure would slip past the explicit guard, hit Percy, and
capture the page with a fallback font silently substituted in —
exactly the false-positive class the hard-coded check was supposed
to prevent.

Inspect the rejected-faces list directly: if any rejected face's
family is "IBM Plex Sans", throw with the descriptor + rejection
reason. The `document.fonts.check` loop stays as a belt-and-suspenders
guard for the unrelated case where a Sans weight was never declared
on the page in the first place.

Addresses Codex review on PR #4688.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the percySnapshot test helper in packages/host against transient font-loading failures by switching from failing-fast (Promise.all) to tolerant font loading (Promise.allSettled), while keeping IBM Plex Sans as the explicit “must-load” assertion to avoid pixel diffs.

Changes:

  • Switch font preloading from Promise.all(document.fonts.load()) to Promise.allSettled(...) so non-critical font failures don’t fail the snapshot path.
  • Add warning logging that lists failed font faces and their rejection reasons.
  • Add a small helper (describeFontLoadError) to produce readable error strings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/host/tests/helpers/percy-snapshot.ts
@habdelra habdelra requested a review from a team May 6, 2026 22:08
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

Preview deployments

Host Test Results

    1 files      1 suites   2h 0m 13s ⏱️
2 573 tests 2 558 ✅ 15 💤 0 ❌
2 592 runs  2 577 ✅ 15 💤 0 ❌

Results for commit 8dff3c2.

Realm Server Test Results

    1 files  ±    0      1 suites  +1   16m 36s ⏱️ + 16m 36s
1 262 tests +1 262  1 262 ✅ +1 262  0 💤 ±0  0 ❌ ±0 
1 340 runs  +1 340  1 340 ✅ +1 340  0 💤 ±0  0 ❌ ±0 

Results for commit 8dff3c2. ± Comparison against earlier commit 37cc7fb.

habdelra added a commit that referenced this pull request May 7, 2026
Per the WHATWG FontFaceSet.check spec, faces in `error` status are
treated as settled, and `document.fonts.check(descriptor, '')` with
empty text can return `true` even when the required face is
unrenderable. So with the previous fix alone, an IBM Plex Sans
network failure would slip past the explicit guard, hit Percy, and
capture the page with a fallback font silently substituted in —
exactly the false-positive class the hard-coded check was supposed
to prevent.

Inspect the rejected-faces list directly: if any rejected face's
family is "IBM Plex Sans", throw with the descriptor + rejection
reason. The `document.fonts.check` loop stays as a belt-and-suspenders
guard for the unrelated case where a Sans weight was never declared
on the page in the first place.

Addresses Codex review on PR #4688.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@habdelra habdelra merged commit 9df9c4b into main May 7, 2026
73 of 74 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants