# test\(web\): ban frame and transition waits in browser tests · PR \#22561 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/22561)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: tests/no-frame-waits

Target branch: tests/guardrails-base

Head commit: 85eb3371170ef9e423a1d38d1de99f35a6d6a0ac

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/22561)

[Commits](https://git.cafe/pingdotgg/t3code/pull/22561/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/22561/diffs?format=markdown)

## Description

---

## Problem
Browser tests waited on `requestAnimationFrame`, `setTimeout`-in-Promise, `transitionend`, or partially faked timers. Those race the renderer on a loaded runner; `WorkflowOpeningBlock`, `AttachmentFilePreview`, `AcpRegistrySearchStep` and `ComposerBannerStack` all failed on main this way.

## Fix
- New rule `t3code/no-frame-waits-in-browser-tests` for `apps/web/**/*.browser.test.*`: flags rAF outside spies, timers inside `new Promise`, `transitionend`/`animationend` listeners, raw `vi.useFakeTimers`, and partial `toFake` sets.
- Rewrote 16 test files to assert through `expect.element`/`vi.waitFor`/`expect.poll`, or to install the full `fakeTimers()` and advance frames explicitly. Each touched file passed 3 consecutive runs.
- One suppression (MessagesTimeline negative check that must outlast two settle frames).

Part of the anti-flake stack (#22563). Context: main was red in ~600 of ~2,900 runs since September; the genuinely flaky tests all waited on real time, scheduler budgets, or renderer frames. These rules make those tests fail lint instead of CI.

Made with Claude Opus 5.5 and Claude Fable 5.1 in Claude Code (T3 Code).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
