teamcapybara/capybara · error · Capybara::ExpectationNotMet

query.negative_failure_message

Error message

query.negative_failure_message

What it means

assert_no_text raises ExpectationNotMet with query.negative_failure_message ('expected not to find text ...') when the text still occurs (and its count matches the count options) after the wait time. It is the negated TextQuery assertion: any lingering occurrence of the string/regexp is a failure.

Source

Thrown at lib/capybara/node/matchers.rb:721

        _verify_text(type_or_text, *args, **opts) do |count, query|
          unless query.matches_count?(count) && (count.positive? || query.expects_none?)
            raise Capybara::ExpectationNotMet, query.failure_message
          end
        end
      end

      ##
      # Asserts that the page or current node doesn't have the given text content,
      # ignoring any HTML tags.
      #
      # @macro text_query_params
      # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
      # @return [true]
      #
      def assert_no_text(type_or_text, *args, **opts)
        _verify_text(type_or_text, *args, **opts) do |count, query|
          if query.matches_count?(count) && (count.positive? || query.expects_none?)
            raise Capybara::ExpectationNotMet, query.negative_failure_message
          end
        end
      end

      ##
      # Checks if the page or current node has the given text content,
      # ignoring any HTML tags.
      #
      # By default it will check if the text occurs at least once,
      # but a different number can be specified.
      #
      #     page.has_text?('lorem ipsum', between: 2..4)
      #
      # This will check if the text occurs from 2 to 4 times.
      #
      # @macro text_query_params
      # @return [Boolean]                            Whether it exists
      #

View on GitHub (pinned to 15b5fdb76e)

Solutions

  1. Wait out the removal: assert_no_text('Loading', wait: 10)
  2. Scope the assertion to the region where the text must vanish (within('#main') { assert_no_text('Loading') })
  3. Make the regexp precise so legitimate copy does not match (/\bError:/ instead of /error/i)
  4. If the text is hidden but present and that is acceptable, assert on a visible selector instead of raw text

Example fix

# before
page.assert_no_text('Loading')

# after
page.assert_no_text('Loading', wait: 10)
# or scope where it must disappear
within('#content') { page.assert_no_text('Loading', wait: 10) }
Defensive patterns

Strategy: validation

Validate before calling

return unless page.has_text?('Loading')
page.assert_no_text('Loading', wait: 10) # only assert while removal is expected

Type guard

def text_gone?(page, text, wait: 5)
  !page.has_text?(text, wait: wait)
end

Try / catch

begin
  page.assert_no_text('Loading', wait: 10)
rescue Capybara::ExpectationNotMet => e
  puts e.message # shows where the text still occurs
  raise
end

Prevention

When it happens

Trigger: page.assert_no_text('Loading') while a spinner with that text is still visible; assert_no_text('Error') after a form submit that re-rendered an error flash; regexp variants matching unexpectedly (/error/i matching 'No errors'); asserting too early while fade-out of the message is still running.

Common situations: Spinners/loading text driven by JS that outlives the wait, flash messages persisted across requests in the session, copy appearing in hidden elements (TextQuery sees text content regardless of visibility), negative assertions in shared examples hitting pages that legitimately contain the string.

Related errors


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