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

  1. Wait for actual disappearance: assert_no_selector('.modal', wait: 10) or RSpec expect(page).to have_no_selector('.modal', wait: 10)
  2. If the node stays mounted but hidden, assert on visibility: assert_no_selector('.modal', visible: true)
  3. Remove count options you did not intend (count: 0 flips the semantics); a bare assert_no_selector just means 'matches nothing'
  4. 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

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


AI-assisted analysis of teamcapybara/capybara@15b5fdb76e (2026-08-21). Data as JSON: /api/errors/fbe855b8a0e869b8. Report an issue: GitHub.