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.