> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.doc-detective.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.doc-detective.com/_mcp/server.

# XPath selectors

> Learn how to use XPath selectors to target elements in Doc Detective tests.

XPath (XML Path Language) selectors provide a powerful alternative to CSS selectors for targeting elements in Doc Detective tests. While more complex than CSS selectors, XPath offers advanced capabilities for navigating complex HTML structures and selecting elements based on their content or relationships.

## When to use XPath selectors

Use XPath selectors in Doc Detective actions like [`find`](/docs/actions/find), [`click`](/docs/actions/click), and others that accept a `selector` property. XPath is useful when:

* You need to select elements based on their text content
* You need to navigate up the DOM tree (select parent elements)
* CSS selectors aren't powerful enough for your use case
* You need to select elements by their position relative to other elements

> **Note**
>
> For most use cases, [CSS selectors](/docs/selectors/css) are simpler and more readable. Use XPath only when CSS selectors can't accomplish what you need.

## XPath syntax basics

On browser surfaces, Doc Detective treats a selector starting with `//`, `/`, or `(` as XPath (otherwise it treats the selector as CSS). The `(` case covers grouped expressions like `(//button)[2]`, used to index or filter a node set. See [Position-based selection](#position-based-selection).

> **Note**
>
> On [native app surfaces](/docs/actions/startsurface), Doc Detective recognizes only `//` and `(` as XPath. A bare single `/` doesn't count. Stick to `//` (or a `(...)` grouped expression) if the same selector needs to work on both browser and app surfaces.

### Absolute vs. relative paths

* **Absolute path:** Starts with a single `/` and specifies the complete path from the root

  ```
  /html/body/div/form/input
  ```

  > **Avoid absolute paths**
  >
  > They're fragile and break when the page structure changes.

* **Relative path:** Starts with `//` and finds elements anywhere in the document

  ```
  //input[@type='password']
  ```

  > **Prefer relative paths**
  >
  > They're more flexible and maintainable.

## Basic XPath patterns

### Select by element type

Find all elements of a specific type anywhere in the document:

```json
{
  "find": {
    "selector": "//button"
  }
}
```

### Select by attribute

Find elements with a specific attribute value using the `[@attribute='value']` syntax:

```json
{
  "find": {
    "selector": "//input[@type='password']"
  }
}
```

### Select by ID

Target elements by their `id` attribute using the `@id` selector:

```json
{
  "find": {
    "selector": "//*[@id='username']"
  }
}
```

> **Note**
>
> The `*` matches any element type. You can also be more specific: `//input[@id='username']`.

### Select by class

Use the `contains()` function to match elements by their class attribute:

```json
{
  "find": {
    "selector": "//*[contains(@class, 'submit-button')]"
  }
}
```

> **Note**
>
> Use `contains()` because an element can have multiple classes. This matches any element whose `class` attribute contains "submit-button".

### Select by text content

One of XPath's most powerful features is selecting elements by their visible text:

```json
{
  "find": {
    "selector": "//button[text()='Submit']"
  }
}
```

This finds a `button` element with the exact text "Submit".

### Select by partial text

Use the `contains()` function to match elements that contain specific text:

```json
{
  "find": {
    "selector": "//button[contains(text(), 'Submit')]"
  }
}
```

This finds a `button` element whose text contains "Submit" (like "Submit Form" or "Click to Submit").

## Combining conditions

### Multiple attributes

Use the `and` operator to combine multiple attribute conditions:

```json
{
  "find": {
    "selector": "//input[@type='text' and @name='email']"
  }
}
```

### OR conditions

Use the `or` operator to match elements that satisfy any of the specified conditions:

```json
{
  "find": {
    "selector": "//button[@type='submit' or @type='button']"
  }
}
```

### Element type and attribute

Combine element types with attribute matching and text content for highly specific selectors:

```json
{
  "find": {
    "selector": "//button[@class='primary' and contains(text(), 'Sign Up')]"
  }
}
```

## Navigating the DOM

### Parent elements

Select the parent of an element using the `parent::` axis or the `..` shorthand:

```json
{
  "find": {
    "selector": "//input[@type='email']/parent::div"
  }
}
```

Or use the shorter form:

```json
{
  "find": {
    "selector": "//input[@type='email']/.."
  }
}
```

### Ancestor elements

Find any ancestor element (not just the direct parent) using the `ancestor::` axis:

```json
{
  "find": {
    "selector": "//input[@type='email']/ancestor::form"
  }
}
```

### Following siblings

Select sibling elements that come after the current element using the `following-sibling::` axis:

```json
{
  "find": {
    "selector": "//label[text()='Email']/following-sibling::input"
  }
}
```

This finds an `input` that comes after a `label` with text "Email".

### Preceding siblings

Select sibling elements that come before the current element using the `preceding-sibling::` axis:

```json
{
  "find": {
    "selector": "//button[@type='submit']/preceding-sibling::input"
  }
}
```

### Descendant elements

Find elements nested at any depth within other elements using the `//` operator:

```json
{
  "find": {
    "selector": "//div[@class='form-group']//input"
  }
}
```

This finds any `input` within a `div` with class "form-group", regardless of nesting depth.

## Position-based selection

### Select by index

Select a specific occurrence using square brackets with a number (XPath indexes start at 1):

```json
{
  "find": {
    "selector": "(//button)[2]"
  }
}
```

This selects the second `button` element in the document.

> **Note**
>
> Use parentheses to select from all matching elements. Without them, `//button[2]` means "a button that is the second button child of its parent."

### First element

Select the first matching element using index `[1]`:

```json
{
  "find": {
    "selector": "(//input[@type='text'])[1]"
  }
}
```

### Last element

Select the last matching element using the `last()` function:

```json
{
  "find": {
    "selector": "(//button)[last()]"
  }
}
```

### Position greater than

Select elements beyond a certain position using the `position()` function:

```json
{
  "find": {
    "selector": "(//li)[position()>2]"
  }
}
```

This selects all `li` elements from the third onwards.

## Advanced functions

### Attribute contains

Match elements whose attributes contain a specific substring:

```json
{
  "find": {
    "selector": "//*[contains(@data-testid, 'user')]"
  }
}
```

### Attribute starts with

Match elements whose attributes begin with a specific value:

```json
{
  "find": {
    "selector": "//*[starts-with(@id, 'user-')]"
  }
}
```

### Case-insensitive text matching

XPath 2.0+ supports case-insensitive matching, but browser support varies. For compatibility, use:

```json
{
  "find": {
    "selector": "//button[contains(translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'submit')]"
  }
}
```

This converts text to lowercase before matching.

### Not operator

Exclude elements with specific attributes using the `not()` function:

```json
{
  "find": {
    "selector": "//button[not(@disabled)]"
  }
}
```

This finds enabled buttons (those without a `disabled` attribute).

## Best practices

### Prefer CSS selectors when possible

XPath is powerful but complex. Use CSS selectors for more straightforward cases:

> **CSS**
>
> `#username` or `[data-testid='login-button']`

> **XPath**
>
> `//*[@id='username']` or `//*[@data-testid='login-button']`

### Use relative paths

> **Good**
>
> `//input[@type='password']`

> **Avoid**
>
> `/html/body/div[1]/form/div[2]/input[3]`

### Combine text and attributes for specificity

Create highly specific selectors by combining text content with attributes:

```json
{
  "find": {
    "selector": "//button[@type='submit' and contains(text(), 'Sign Up')]"
  }
}
```

### Test selectors in browser console

Use the `$x()` function in the browser console to test XPath selectors:

```javascript
$x("//button[contains(text(), 'Submit')]")
```

This returns an array of matching elements.

### Avoid brittle position-based selectors

> **Fragile**
>
> `//div[3]/span[2]/a[1]`

> **Better**
>
> `//a[contains(@class, 'download-link')]`

## Common patterns

### Find input by label text

Target an input field based on its associated label text:

```json
{
  "find": {
    "selector": "//label[text()='Email']/following-sibling::input"
  }
}
```

Or if the input is nested inside the label:

```json
{
  "find": {
    "selector": "//label[contains(text(), 'Email')]//input"
  }
}
```

### Find element by placeholder

Target input fields by their placeholder text:

```json
{
  "find": {
    "selector": "//input[@placeholder='Enter your email']"
  }
}
```

### Find button by aria-label

Target buttons and other elements by their ARIA accessibility labels:

```json
{
  "find": {
    "selector": "//button[@aria-label='Close dialog']"
  }
}
```

### Find link by partial href

Target links that contain specific URL patterns:

```json
{
  "find": {
    "selector": "//a[contains(@href, '/downloads')]"
  }
}
```

## Examples

### Click a button by text

```json
{
  "steps": [
    {
      "description": "Click the Sign Up button",
      "click": {
        "selector": "//button[text()='Sign Up']"
      }
    }
  ]
}
```

### Find input by following a label

```json
{
  "steps": [
    {
      "description": "Enter email address",
      "find": {
        "selector": "//label[text()='Email Address']/following-sibling::input",
        "click": true,
        "type": "user@example.com"
      }
    }
  ]
}
```

### Find enabled submit button

```json
{
  "steps": [
    {
      "description": "Click the enabled submit button",
      "find": {
        "selector": "//button[@type='submit' and not(@disabled)]",
        "click": true
      }
    }
  ]
}
```

### Select third item in a list

```json
{
  "steps": [
    {
      "description": "Click the third menu item",
      "click": {
        "selector": "(//nav//li)[3]//a"
      }
    }
  ]
}
```

### Find element with specific data attribute

```json
{
  "steps": [
    {
      "description": "Find element by test ID",
      "find": {
        "selector": "//*[@data-testid='user-profile']"
      }
    }
  ]
}
```

## Debugging XPath selectors

### Test in browser console

Use the `$x()` function (available in most browser consoles) to test and debug your XPath selectors:

```javascript
// Test your XPath
$x("//button[contains(text(), 'Submit')]")

// Count matches
$x("//button[contains(text(), 'Submit')]").length

// Click the first match
$x("//button[contains(text(), 'Submit')]")[0].click()
```

### Browser developer tools

1. Open Developer Tools (F12)
2. Go to the Console tab
3. Type `$x("your-xpath-here")` and press Enter
4. The console shows all matching elements
5. Hover over results to highlight them on the page

### Common issues

> **No elements found**
>
> * Verify the element exists in the DOM
> * Check for typos in attribute names or values
> * Ensure the element is loaded (you may need to add a `wait` action)

> **Multiple elements found**
>
> * Add more specific conditions
> * Combine multiple attributes
> * Use text content to narrow the match

> **XPath treated as CSS**
>
> * Doc Detective detects the selector type automatically from the leading characters.
> * On browser surfaces, start your XPath with `//`, `/`, or `(`. Doc Detective treats anything else as CSS.
> * On [native app surfaces](/docs/actions/startsurface), only `//` and `(` count as XPath. A bare single `/` is treated as CSS.

## Related resources

* [CSS selectors](/docs/selectors/css): Simpler selector syntax (recommended for most cases)
* [`find` action](/docs/actions/find): Locate and interact with elements
* [`click` action](/docs/actions/click): Click elements using selectors
* [MDN XPath Documentation](https://developer.mozilla.org/en-US/docs/Web/XPath): Comprehensive XPath reference
* [XPath Cheat Sheet](https://devhints.io/xpath): Quick reference for XPath syntax