4.0 KiB
Failure triage
Before debugging, classify. Most OHIF test failures are timing or hydration — not real regressions.
| Category | Symptom | Fix |
|---|---|---|
| Timing | Element not visible, action timeout | Add / increase the delay param of visitStudy; for actions that re-render viewports, use waitForViewportRenderCycle(page) (started before the action) instead of waitForTimeout; wrap the assertion in expect.toPass({ timeout }) |
| Selector | Element not found | Verify data-cy on the target; confirm the panel is open (toggle() / select() before interacting); check for capital D in DOMOverlayPageObject when destructuring |
| Hydration | Segmentation/RT not interactive | Ensure the segmentationHydration.yes.click() fired; add waitForTimeout(3000) after loadSeriesByModality('SEG'|'RTSTRUCT'|'SR') |
| Data | Study not found, empty viewport | Confirm the UID is in the canonical list (see patterns-by-feature.md); confirm the mode supports the feature (segmentation tools aren't in viewer mode) |
| Visual drift | Screenshot mismatch but feature works | Have a human review the diff, then regenerate the baseline with yarn playwright test --update-snapshots. Do not adjust maxDiffPixelRatio or threshold to make a failing screenshot pass. |
| Real regression | Feature is actually broken | Report as a bug — this is the test doing its job |
Prefer render-cycle waits over sleeps
If you're tempted to add await page.waitForTimeout(2000) after an action, ask whether the action re-rendered the viewport. If it did, use:
const cycle = waitForViewportRenderCycle(page);
await action();
await cycle;
await check();
The watcher must be created before the action — it waits for needsRender first, and that transition is gone by the time the action returns. See the "Wait for renders, don't sleep" section in SKILL.md and tests/SEGHydrationFromMPR.spec.ts.
When the cycle helper times out at waitForAnyViewportNeedsRender
Symptom: the test fails inside waitForAnyViewportNeedsRender after 5s, with the action having actually completed in the UI. The action just doesn't transition the viewport through needsRender synchronously. Known cases:
- Hanging-protocol changes.
- RTSTRUCT / contour segmentation hydration confirm. SEG (labelmap) hydration does fire
needsRender; contour does not. The fix is to gate on the actual end-state — for hydration inbeforeEach,await expect(page.getByTestId('data-row')).toHaveCount(N)is the right wait.
Don't react by raising the cycle's timeout — the transition isn't coming. Replace the cycle wrapper with an auto-retrying DOM/SVG assertion, or expect.toPass({ timeout }) around the assertion block.
The toPass pattern
When an assertion needs to wait for async render / propagation:
await expect(async () => {
await rightPanelPageObject.labelMapSegmentationPanel.panel.segmentByText('Spleen').click();
await expect(activeViewport.overlayText.bottomRight.instanceNumber).toContainText('17/');
}).toPass({ timeout: 10_000 });
toPass reruns the assertion block until it succeeds or the timeout expires — cleaner than a hand-rolled retry loop and surfaces the last failure reason if it times out.
Common DOMOverlayPageObject mistake
The fixture key is capital-D DOMOverlayPageObject, not lowercase. If your destructure is silently undefined, check the casing.
press import mistake
press is NOT re-exported from ./utils. import { press } from './utils' resolves to undefined and fails at runtime. Use:
import { press } from './utils/keyboardUtils';
await press({ page, key: 'ArrowDown', nTimes: 50 }); // object param, not (page, key)
Visual regression iteration
Screenshots live under tests/screenshots/chromium/<testFilePath>/. To accept new output as the baseline:
yarn playwright test tests/YourSpec.spec.ts --update-snapshots
Review the resulting PNGs carefully — an agent-accepted baseline that's subtly wrong is worse than a failing test.