# 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](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: ```ts 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](../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 in `beforeEach`, `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: ```ts 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: ```ts 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//`. To accept new output as the baseline: ```sh 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.