BLUF (Bottom Line Up Front): Flaky system tests (tests that pass locally but fail randomly on CI) destroy trust in the test suite. In legacy Rails applications, 90% of Capybara flakiness is caused by race conditions between asynchronous JavaScript execution and the test runner. The solution is strictly prohibiting sleep and forcing Capybara’s internal synchronization API to wait for DOM state changes.
Phase 1: The Race Condition
Glossary entry: Flaky System Tests.
Capybara executes Ruby commands incredibly fast. If a test clicks a button that triggers an AJAX request, Capybara immediately moves to the next line of code before the DOM has time to update.
Synthetic Engineering Context: The Unreliable Test
# The Bad Code: Race Condition
it 'creates a new comment' do
visit post_path(post)
fill_in 'Comment', with: 'Great article!'
click_button 'Submit'
# Fails randomly if the AJAX request takes longer than 10ms
expect(Comment.count).to eq(1)
end
Developers often try to fix this by adding sleep(2). This is an anti-pattern. It artificially inflates test times and still fails if the server is under heavy load.
Phase 2: Capybara Synchronization
Capybara has built-in wait times (Capybara.default_max_wait_time). It will continually poll the DOM until the specified element appears or the timeout is reached.
Execution: Explicit State Checks
You must assert the DOM state changes before you assert database changes.
# The Optimized Code: Explicit Wait
it 'creates a new comment' do
visit post_path(post)
fill_in 'Comment', with: 'Great article!'
click_button 'Submit'
# Capybara polls the DOM for up to 2 seconds (configurable)
# It waits for the AJAX request to finish and render the text.
expect(page).to have_content('Great article!')
# Now it is safe to check the database
expect(Comment.count).to eq(1)
end
Execution: Handling Animations
CSS transitions and animations (like fading out a modal) frequently break clicks. Disable animations entirely in the test environment to stabilize interactions.
<%# app/views/layouts/application.html.erb %>
<% if Rails.env.test? %>
<style>
* {
transition: none !important;
animation: none !important;
}
</style>
<% end %>
Phase 3: Next Steps & Risk Mitigation
Flaky tests hide real regressions. If you simply retry failing tests automatically without addressing the root synchronization issues, you are masking the problem.
Need Help Stabilizing Your Legacy App? We build deterministic, rock-solid test suites. Our team at USEO can audit your Capybara configuration and refactor flaky system tests to restore CI/CD reliability.