Heuristic:Teamcapybara Capybara Animation Disabling For Tests
| Knowledge Sources | |
|---|---|
| Domains | Testing, Optimization, Best_Practices |
| Last Updated | 2026-02-12 06:00 GMT |
Overview
Disable CSS transitions, animations, and scroll behavior in tests via `Capybara.disable_animation` to eliminate timing-dependent flakiness.
Description
CSS animations and transitions introduce non-deterministic timing into browser-based tests. An element that is animating into position may not be clickable, visible, or at its expected coordinates during the animation. Capybara provides a built-in `AnimationDisabler` Rack middleware that injects CSS rules to disable all transitions and animations, and a JavaScript snippet to disable jQuery animations (`jQuery.fx.off = true`). This middleware is activated by setting `Capybara.disable_animation = true` (for all elements) or `Capybara.disable_animation = '.my-selector'` (for specific elements).
Usage
Apply this heuristic when your test suite experiences intermittent failures related to element clicking, visibility, or position — especially when elements use CSS transitions, animations, or smooth scrolling. This is a common source of flakiness in Selenium-based tests.
The Insight (Rule of Thumb)
- Action: Set `Capybara.disable_animation = true` in your test configuration (e.g., `spec_helper.rb` or `rails_helper.rb`).
- Value: Boolean `true` disables all animations. A CSS selector string (e.g., `'.modal, .dropdown'`) targets specific elements only.
- Trade-off: Tests no longer verify animation behavior. If animation correctness matters, test it separately with `disable_animation = false`.
- Scope: Applies only to the embedded test server responses — it injects CSS/JS into HTML responses served by Capybara's Rack middleware.
Reasoning
The AnimationDisabler works by injecting a `<style>` tag before `</head>` and a `<script>` tag before `</body>` into every HTML response. The CSS rules set `transition: none !important`, `animation-duration: 0s !important`, `animation-delay: 0s !important`, and `scroll-behavior: auto !important` on the selected elements and their `::before`/`::after` pseudo-elements. The JavaScript disables jQuery's animation queue entirely. This approach is reliable because it uses `!important` declarations that override any application CSS, and it operates at the server middleware level so it affects all pages served during tests.
The middleware also respects Content-Security-Policy nonces: it extracts `style-src` and `script-src` nonces from the CSP header and adds them to the injected tags, ensuring the injected code is not blocked by the application's security policy.
Code Evidence
CSS injection template from `lib/capybara/server/animation_disabler.rb:64-71`:
DISABLE_CSS_MARKUP_TEMPLATE = <<~CSS
%<selector>s, %<selector>s::before, %<selector>s::after {
transition: none !important;
animation-duration: 0s !important;
animation-delay: 0s !important;
scroll-behavior: auto !important;
}
CSS
jQuery animation disabling from `lib/capybara/server/animation_disabler.rb:73-77`:
DISABLE_JS_MARKUP_TEMPLATE = <<~SCRIPT
//<![CDATA[
(typeof jQuery !== 'undefined') && (jQuery.fx.off = true);
//]]>
SCRIPT
CSP nonce extraction from `lib/capybara/server/animation_disabler.rb:50-61`:
def directive_nonces(headers)
headers.fetch('Content-Security-Policy', '')
.split(';')
.map(&:split)
.to_h do |s|
[
s[0], s[1..].filter_map do |value|
/^'nonce-(?<nonce>.+)'/ =~ value
nonce
end[0]
]
end
end
HTML injection from `lib/capybara/server/animation_disabler.rb:45-47`:
def insert_disable(html, nonces)
html.sub(%r{(</head>)}, "<style #{nonces['style-src']}>#{disable_css_markup}</style>\\1")
.sub(%r{(</body>)}, "<script #{nonces['script-src']}>#{disable_js_markup}</script>\\1")
end