Files
IGP-Fan-Reference/docs/AI Gen Docs/test-blazor-hydration-timing.md
T
2026-05-31 14:33:58 -04:00

3.5 KiB

type, status, category, isAgentGenerated
type status category isAgentGenerated
Task AI Gen TODO QA true

Test: Blazor WASM Hydration Timing and State Reconciliation

Description

Verify that the Blazor WASM client correctly reconciles state during hydration — the prerendered HTML should match the client-rendered output, and user interactions initiated before Blazor finishes loading should either be queued or gracefully ignored, not cause a crash or a stale render.

Rationale

The app uses Blazor WASM with server-side prerendering. During the hydration gap (between window.onload and Blazor attaching its event handlers), the page shows static prerendered HTML. If a user (or test) interacts with a <select> or button during this gap, Blazor may:

  • Lose the native-DOM change on re-render (the select value reverts).
  • Miss the event entirely (no handler is connected yet).
  • Throw a JS interop exception after hydration completes.

Our existing tests all navigate with waitUntil: 'load' and immediately start clicking. The debug_initial_state.js script revealed that Blazor components re-render asynchronously after load, destroying and recreating the DOM elements. This gap is a real source of flakiness.

Playwright Feature

This test uses page.waitForResponse() to wait for a specific WASM resource (dotnet.js or dotnet.wasm) to confirm the .NET runtime has loaded, plus page.evaluate() with polling to detect when Blazor's component tree is fully hydrated.

Approach

// Wait until the Blazor runtime signals it's ready
await page.waitForResponse(response =>
  response.url().includes('dotnet.wasm') && response.status() === 200
);

// OR poll for a Blazor-specific DOM signal
await page.waitForFunction(() => {
  // Blazor sets the `_blazorCircuitId` or similar on the root element
  // Note: adjust the selector to match the app's actual signal
  return document.querySelector('select')?.classList.contains('blazor-hydrated')
    || document.querySelector('.keyContainer > div') !== null;
});

Test Cases

Scenario Interaction Timing Expected Behavior
Early select Select faction BEFORE dotnet.wasm loads Select reverts on hydration, but WASM should apply the correct default filter
Early click Click "Clear Build Order" before Blazor ready Click is ignored or queued; no crash
Early keyboard Press Q/W/E before key handler attached Keys are ignored; no crash; no entity added
Mid-hydration Interact during component re-render (DOM destruction phase) Interaction should be lost but never crash
Late interaction Wait for dotnet.wasm response, THEN interact Should work correctly (this is the happy path that already passes)

Detecting Hydration Completion

Since Blazor WASM with prerendering doesn't emit a built-in "I'm done" event, the most reliable signal is:

await page.waitForFunction(() => {
  // Wait for at least one Blazor-triggered re-render by checking
  // the keyContainer has child divs with keyboard buttons
  const container = document.querySelector('.keyContainer');
  if (!container) return false;
  const buttons = container.querySelectorAll(':scope > div');
  return buttons.length === 19 || buttons.length > 10;
});

What This Test Catches

  • Race conditions between page.goto('waitUntil: load') and Blazor hydration.
  • DOM elements that are destroyed and re-created during hydration (stale locator references).
  • Test flakiness caused by Blazor's async initialization.
  • Missing error boundaries when JS interop calls fail during hydration.