Core Tools intermediate logic predicates

Logic: and, or, not

Combine conditions inside predicates, negate correctly with not(), and express 'has none of' without falling into the node-set != trap.

Logic: and, or, not

Real locators usually need more than one condition. XPath gives you and, or and not(), plus a few idioms that replace the operators other languages have.

and

Both conditions must be true for the same context node:

//input[@type='checkbox' and @checked]
//button[@type='submit' and not(@disabled)]
//a[contains(@href, '/orders/') and contains(@class, 'active')]

Chained predicates are equivalent for non-numeric conditions and some people find them easier to read:

//input[@type='checkbox'][@checked]

or

Either condition is enough:

//button[@type='submit' or @type='button']
//*[@data-testid='save' or @data-test='save' or @data-cy='save']
//input[@type='text' or @type='email' or @type='search']

The union operator | is different: it joins two separate expressions rather than two conditions on one node. //button | //a selects buttons and links. //*[self::button or self::a] does the same thing with a predicate. Use whichever reads better.

not()

not() is a function and needs parentheses around its argument:

//button[not(@disabled)]                    no disabled attribute
//li[not(contains(@class, 'hidden'))]       class does not contain hidden
//div[not(.//input)]                        divs that contain no inputs anywhere
//tr[not(td)]                               rows with no td cells (header rows)
//input[not(@type) or @type='text']         inputs whose type is missing or text

not(@disabled) tests for the absence of the attribute. It is not the same as @disabled='false'. HTML boolean attributes are true when present with any value, so disabled="false" is still disabled in the browser, and XPath will agree with the browser here only if you test presence.

”none of them” versus “some of them are not”

This is the trap. When the left side of a comparison is a node-set, != is true if any node differs:

<ul>
  <li class="done">A</li>
  <li class="todo">B</li>
</ul>
//ul[li/@class != 'done']        true, because B's class is not done
//ul[not(li/@class = 'done')]    false, because at least one li IS done

So:

  • “has at least one li that is not done”: li/@class != 'done'
  • “has no li that is done”: not(li/@class = 'done')
  • “every li is done”: not(li[@class != 'done']), meaning there is no li whose class differs

There is no every or some keyword in XPath 1.0. You build them from not() and existence.

Existence as a condition

A path inside a predicate is true if it selects at least one node. This is how you say “has a child like X” or “contains X somewhere”:

//tr[td[@class='error']]               rows with an error cell
//form[.//input[@type='password']]      forms with a password field somewhere inside
//div[@class='card'][h2]                cards that have an h2 child
//li[a]                                 list items that contain a link
//button[svg]                           buttons containing an inline svg (see the SVG lesson for the namespace catch)

The leading . in .//input is required inside a predicate. Without it, //input restarts from the document root and would be true for every form on the page as long as any password input exists anywhere.

Boolean conversion rules

When a value is used where a boolean is expected, XPath converts it:

Valuetrue when
node-setnon-empty
stringnon-empty
numbernot zero and not NaN

That is why [@disabled] works as an existence test and why [string-length(@value)] means “value is not empty”. Prefer explicit forms like string-length(@value) > 0 when readability matters.

Precedence

and binds tighter than or:

@a='1' or @b='2' and @c='3'      means   @a='1' or (@b='2' and @c='3')

Add parentheses whenever both appear in one predicate. Future readers will thank you.

Try It Yourself

Open in Playground →

Next Steps

  1. Text Matching Functions - Conditions on visible text
  2. Dynamic Attributes - Conditions that survive generated ids and classes
  3. Common Errors and Fixes - The error messages you get when logic goes wrong