Capybara::ElementNotFound in a system test in CI
Capybara could not locate an element within its wait time. In headless CI this is usually a timing or rendering difference: the element appears later, behind JS that did not run, or under an asset that did not build.
What this error means
A system/feature spec fails with Capybara::ElementNotFound naming the selector it could not find. It often passes locally with a visible browser but fails headless in CI.
Capybara::ElementNotFound:
Unable to find visible button "Save" within #<Capybara::Node...>Common causes
Element appears after the wait
JavaScript renders the element after Capybara stopped waiting, or an animation/transition delays it past the default wait time.
Assets/JS not built for tests
The JS bundle or stylesheet that would render the element was not compiled in the test environment, so the control never appears.
Wrong or non-visible selector
The element exists but is hidden, or the locator differs from what is actually rendered in CI.
How to fix it
Wait for the right state and build assets
- Use Capybara matchers that wait (have_button, have_selector) instead of immediate finds, and increase Capybara.default_max_wait_time if needed.
- Precompile JS/CSS for the test environment so the UI renders.
- Assert on the actual visible selector; debug with a saved screenshot/page on failure.
Capture failure artifacts
# config: save a screenshot on system-test failure
page.save_screenshot('tmp/capybara/failure.png')
puts page.html # inspect what actually renderedHow to prevent it
- Use Capybara waiting matchers, not bare finds, for async UI.
- Build JS/CSS assets before system tests.
- Save screenshots/HTML on failure to diagnose headless rendering.