Jump to content

Connect Leeroopedia MCP: Equip your AI agents to search best practices, build plans, verify code, diagnose failures, and look up hyperparameter defaults.

Heuristic:Teamcapybara Capybara Negative Predicate Matching

From Leeroopedia
Knowledge Sources
Domains Testing, Debugging, Best_Practices
Last Updated 2026-02-12 06:00 GMT

Overview

Always use `has_no_xpath?` instead of `!has_xpath?` when testing for element absence in asynchronous applications to avoid false positives.

Description

Capybara's predicate matchers (`has_xpath?`, `has_css?`, `has_text?`, etc.) use the auto-waiting mechanism by default (when `predicates_wait = true`). However, the positive and negative predicates behave differently with respect to waiting. A positive predicate like `has_xpath?('//a')` returns `true` immediately if the element is found, and waits up to `default_max_wait_time` before returning `false`. A negative predicate like `has_no_xpath?('//a')` returns `true` immediately if the element is absent, and waits up to `default_max_wait_time` before returning `false` (meaning the element persists). This asymmetry means that `!page.has_xpath?('a')` is not equivalent to `page.has_no_xpath?('a')` in async contexts.

Usage

Apply this heuristic whenever you are asserting the absence of an element on a page that may have asynchronous updates. This is especially critical for testing element removal after AJAX operations (e.g., deleting an item, closing a modal, navigating away).

The Insight (Rule of Thumb)

  • Action: Use `page.has_no_xpath?('//a')` or `expect(page).to have_no_xpath('//a')` when asserting an element is absent or has been removed.
  • Value: Never use `!page.has_xpath?('//a')` or `expect(page).not_to have_xpath('//a')` for absence checks in async contexts.
  • Trade-off: None — the negative predicate is strictly better for absence checks. The positive negation (`!has_xpath?`) returns immediately on first check failure without waiting, missing elements that are about to be removed asynchronously.
  • RSpec pattern: Use `expect(page).to have_no_css('.deleted-item')` instead of `expect(page).not_to have_css('.deleted-item')`.

Reasoning

Consider an AJAX deletion: the user clicks "Delete" and the element disappears after 500ms. With `!has_xpath?('//item')`:

  1. Capybara immediately checks for `//item` — it is still present (AJAX not done yet)
  2. `has_xpath?` returns `true` immediately (element found, no need to wait)
  3. `!true` = `false` — test incorrectly concludes the element is still there

With `has_no_xpath?('//item')`:

  1. Capybara immediately checks for `//item` — it is still present
  2. `has_no_xpath?` sees the element exists, so it waits and retries
  3. After 500ms, AJAX completes, element is removed
  4. On next retry, element is absent — `has_no_xpath?` returns `true`

The asymmetry exists because positive predicates optimize for the common success case (element found quickly), while negative predicates optimize for the common absence case (element removed eventually).

Code Evidence

Configuration default from `lib/capybara.rb:92`:

# - **predicates_wait** (Boolean = `true`) - Whether Capybara's predicate matchers use waiting behavior by default.

The asymmetry is inherent in how `assert_selector` (used by positive predicates) and `assert_no_selector` (used by negative predicates) interact with the `synchronize` loop in `lib/capybara/node/base.rb:76-102`. The positive predicate returns immediately on match success; the negative predicate returns immediately on match failure (absence). Both wait on their respective failure cases.

Related Pages

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment