teamcapybara/capybara · error · Capybara::ExpectationNotMet
result.negative_failure_message
Error message
result.negative_failure_message
What it means
assert_no_selector is the negated form of assert_selector: it raises ExpectationNotMet with Result#negative_failure_message ('expected NOT to find <description> ... but found N matches: ...') when the selector still matches the expected number of elements after the wait time. Because count options are part of the selector, it also raises when an expects-none query (e.g. count: 0) is satisfied — the docs show page.assert_no_selector('a', count: 5) passing with zero anchors but count: 0 with zero anchors failing.
Source
Thrown at lib/capybara/node/matchers.rb:236
#
# Asserts that a given selector is not on the page or a descendant of the current node.
# Usage is identical to {#assert_selector}.
#
# Query options such as `:count`, `:minimum`, `:maximum`, and `:between` are
# considered to be an integral part of the selector. This will return
# `true`, for example, if a page contains 4 anchors but the query expects 5:
#
# page.assert_no_selector('a', minimum: 1) # Found, raises Capybara::ExpectationNotMet
# page.assert_no_selector('a', count: 4) # Found, raises Capybara::ExpectationNotMet
# page.assert_no_selector('a', count: 5) # Not Found, returns true
#
# @param (see #assert_selector)
# @raise [Capybara::ExpectationNotMet] If the selector exists
#
def assert_no_selector(*args, &optional_filter_block)
_verify_selector_result(args, optional_filter_block) do |result, query|
if result.matches_count? && (!result.empty? || query.expects_none?)
raise Capybara::ExpectationNotMet, result.negative_failure_message
end
end
end
##
#
# Checks if a given XPath expression is on the page or a descendant of the current node.
#
# page.has_xpath?('.//p[@id="foo"]')
#
# By default it will check if the expression occurs at least once,
# but a different number can be specified.
#
# page.has_xpath?('.//p[@id="foo"]', count: 4)
#
# This will check if the expression occurs exactly 4 times.
#
# It also accepts all options that {Capybara::Node::Finders#all} accepts,View on GitHub (pinned to 15b5fdb76e)
Solutions
- Wait for actual disappearance: assert_no_selector('.modal', wait: 10) or RSpec expect(page).to have_no_selector('.modal', wait: 10)
- If the node stays mounted but hidden, assert on visibility: assert_no_selector('.modal', visible: true)
- Remove count options you did not intend (count: 0 flips the semantics); a bare assert_no_selector just means 'matches nothing'
- Drive the UI to the state where the element is gone (click close/delete) before the negative assertion
Example fix
# before
page.assert_no_selector('.modal')
# after
click_button('Close')
page.assert_no_selector('.modal', wait: 10)
# if modal is merely hidden, not removed
page.assert_no_selector('.modal', visible: true, wait: 10) Defensive patterns
Strategy: validation
Validate before calling
return unless page.has_selector?('.modal')
page.assert_no_selector('.modal', wait: 10) # only assert absence while it is expected Type guard
def modal_gone?(page, wait: 5)
!page.has_selector?('.modal', wait: wait)
end Try / catch
begin
page.assert_no_selector('.modal', wait: 10)
rescue Capybara::ExpectationNotMet
page.execute_script('document.querySelector(".modal")&.remove()') # last-resort cleanup in test teardown
raise
end Prevention
- Trigger removal actions before negative assertions
- Distinguish hidden vs removed: pair visible: true with the right expectation
- Never pass count: 0 to assert_no_selector unless you intend the inverted semantics
When it happens
Trigger: page.assert_no_selector('.modal') while the modal is still in the DOM; assert_no_selector('a', minimum: 1) on a page with anchors (docs example); assert_no_selector('flash', text: 'Saved') when the flash message persists; assert_no_selector('tr', count: 5) when there are exactly 5 rows; using count: 0 instead of a plain call on an empty page.
Common situations: Elements hidden but not removed from the DOM (modals using display:none — pair with visible: true or assert removal), frontend frameworks keeping nodes mounted, cache/session leftovers, negative assertions run too early before removal animation completes.
Related errors
- result.failure_message
- res.join(' or ')
- Item does not match the provided selector
- Item matched the provided selector
- query.negative_failure_message
AI-assisted analysis of teamcapybara/capybara@15b5fdb76e (2026-08-21).
Data as JSON: /api/errors/fbe855b8a0e869b8.
Report an issue: GitHub.