> 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.

# find

> Locate and interact with an element on the page.

The `find` action locates an element in the current interface based on its display text or a CSS/XPath selector. After finding the element, you can optionally interact with it, such as clicking it or typing into it.

If multiple elements match the specified criteria (`elementText` and/or `selector`), the step operates on the first matched element. Ensure your criteria uniquely identify the target element.

Specify the target element directly with a string for basic text or selector lookup. Use an object for more detailed options and interactions:

* **String Shorthand:** Provide the display text or CSS/XPath selector directly as the value for the `find` key. If elements are found by both text and selector, the element found by matching text is used.
  * Example: `"find": "Login"` or `"find": "#username"`.
* **Object Format:** Use an object with the following properties:
  * `elementText`: (Optional) The display text of the element to find. If combined with `selector`, the element must match both. See [How text matching works](#how-text-matching-works) below.
  * `selector`: (Optional) The [CSS selector](/docs/selectors/css) or [XPath selector](/docs/selectors/xpath) of the element to find. If combined with `elementText`, the element must match both. *At least one of `elementText` or `selector` is required.*
  * `timeout`: (Optional) Maximum duration in milliseconds to wait for the element to exist (default: 5000).
  * `moveTo`: (Optional) Move the cursor to the element. An element that isn't visible is scrolled into view. Defaults to `false`. A bare `find` leaves the viewport and cursor where they're already sitting. Set this explicitly when a step should scroll, or when a recording needs the cursor to travel to the element.
  * `click`: (Optional) Click the element after finding it. Can be `true` (for a default left click), `"left"`, `"right"`, `"middle"`, or an object like `{ "button": "right" }` or `{ "duration": 800 }` (a long-press / press-and-hold, in milliseconds).
  * `type`: (Optional) Type keys after finding the element. Requires the element to be made active first (for example, by using `click: true`). Accepts a string or an object like `{ "keys": "my text", "inputDelay": 100 }`. See [`type`](type) for details.
  * `surface`: (Optional) The browser session, window, or tab the element lives in, such as `{ "browser": "chrome", "tab": "cart" }` or a named browser `{ "browser": "firefox", "name": "admin" }`. It can also be the [native app surface](/docs/actions/startsurface) the element lives in, such as `{ "app": "notepad" }` or a specific app window `{ "app": "notepad", "window": { "title": "/Open/" } }`. Omit it to act on the active surface, the most recently opened, focused, or explicitly targeted one, whether a browser tab or an app window. Specifying a surface switches the active surface for the steps that follow. On an app surface, `elementText`, `elementId`, and `elementAria` map to native controls. `selector` there accepts a native locator, either XPath or a `~`-prefixed accessibility ID, instead of a CSS selector. See [Test across multiple tabs, windows, and browsers](/docs/test-docs/multiple-tabs-and-windows), [Target an app surface](/docs/actions/startsurface#target-an-app-surface), and [App windows](#app-windows) below.

## How text matching works

`elementText` (and the string shorthand's text form) matches an element's
**full visible text** with whitespace normalized. Doc Detective trims leading and
trailing whitespace and collapses internal runs of spaces, tabs, and newlines to a
single space. Two consequences follow from this:

* **Framework-rendered text just works.** Frameworks like React and Svelte often
  split an element's text across several DOM text nodes, sometimes with an empty
  leading node. The match uses the element's whole text. A heading like
  `Garden companion API` therefore matches even when it isn't a single, tidy text
  node. The result stays the same on Chrome and Firefox no matter how each
  browser's driver reports whitespace.
* **It's a whole-element match, not a substring.** `elementText` matches an element
  whose *entire* normalized text equals your string. A broad `selector` like
  `body` combined with `elementText` therefore won't match, since the body's text
  is the whole page. Target a tighter element, or match a substring with
  a regular expression by wrapping the value in slashes: `"elementText": "/Garden companion API/"`.

## App windows

On Windows and macOS app surfaces, `window` selects which of the app's windows the step acts on. That includes dialogs and secondary document windows:

* `"window": { "title": "Create New Data Source" }` selects by title; wrap the value in slashes for a regex (`"/Data Source/"`).
* `"window": -1` selects the newest window (typically a dialog the app just opened).
* A plain string or `{ "name": … }` matches the window title exactly.
* On macOS, a non-negative `"window": 2` selects by position; on Windows, positional indexes aren't supported (select by title or `-1` instead).

Window selection is **sticky**. Later steps on the same app surface act in the selected window until another selector changes it. [`click`](click), [`type`](type), [`screenshot`](screenshot), [`record`](record), [`swipe`](swipe), and [`closeSurface`](closesurface) all accept the same selector. Android and iOS app surfaces are single-window, so a `window` selector there fails with guidance to omit it.

On Windows, resolving a selector probes the app's candidate windows to read their titles, which can briefly bring each probed window to the foreground. This matters for recordings: if a `record` is running, a probe can flash another window into a frame or two of the capture. Select the window *before* starting the recording (selection is sticky) to avoid it.

**Mobile app surfaces auto-scroll.** On an Android or iOS app surface, an element that isn't on screen makes `find` scroll toward content further down the list before giving up. It takes up to 5 scroll steps, within `timeout`. That matches how browser `find` scrolls elements into view. [`click`](click) and element-targeted [`type`](type) locate elements the same way, so they inherit this. For a different gesture, sideways or a precise drag, use [`swipe`](swipe).

**Setting Variables:** to capture a found element's attributes into variables for later steps, use the step-level `variables` object. You can assign values based on the element using expressions like `$$element.text`.

**Outputs:** the action also exposes `outputs.found`, a boolean indicating whether an element matching your criteria was located. To capture it for later steps, use the step-level `variables` object with the expression `$$found`.

> For comprehensive options, see the [`find`](/reference/schemas/find) reference.

## Examples

Here are a few ways you might use the `find` action:

### Find an element by its text (string shorthand)

```json
{
  "tests": [
    {
      "steps": [
        {
          "description": "Find the login button by its text.",
          "find": "Login"
        }
      ]
    }
  ]
}
```

### Find an element by its selector (string shorthand)

```json
{
  "tests": [
    {
      "steps": [
        {
          "description": "Find the username field by its ID (CSS selector).",
          "find": "#username"
        }
      ]
    }
  ]
}
```

### Find an element by selector and click it (object format)

```json
{
  "tests": [
    {
      "steps": [
        {
          "description": "Find the submit button by selector and click it.",
          "find": {
            "selector": "button[type='submit']",
            "click": true
          }
        }
      ]
    }
  ]
}
```

### Find an element by selector, click it, and type into it (object format)

```json
{
  "tests": [
    {
      "steps": [
        {
          "description": "Find the search input by selector, click, and type.",
          "find": {
            "selector": "#input",
            "click": true,
            "type": "find action"
          }
        }
      ]
    }
  ]
}
```

### Find an element combining selector and text with a timeout

```json
{
  "tests": [
    {
      "steps": [
        {
          "description": "Find a specific link within a nav bar, waiting up to 10 seconds.",
          "find": {
            "selector": "nav > ul > li > a",
            "elementText": "Downloads",
            "timeout": 10000
          }
        }
      ]
    }
  ]
}
```

### Find an element and right-click it

```json
{
  "tests": [
    {
      "steps": [
        {
          "description": "Find an image by selector and right-click it.",
          "find": {
            "selector": "img.product-image",
            "click": "right"
          }
        }
      ]
    }
  ]
}
```

### Find an element and middle-click it

```json
{
  "tests": [
    {
      "steps": [
        {
          "description": "Find a button by text and middle-click it.",
          "find": {
            "elementText": "Open New Tab",
            "click": "middle"
          }
        }
      ]
    }
  ]
}
```