# Locators

How Kaabist points at an element, why it stores several candidate selectors, which classes it refuses to use, and how frames and shadow DOM are reached.

A locator is how a step or an extract field points at something on a page, and Kaabist stores an ordered list of them for each target rather than one. How that list is written is the difference between a program that runs for a year and one that breaks on the next deploy.

## When to use it

You rarely write locators by hand: the picker and the recorder write them for you. Read this page when you edit a target in JSON, when a job fails with `target_ambiguous` or a missing target, when lint warns that a locator list is only structural (`W002`), or when you decide what to ask a site's developers for so your programs last.

## What a target looks like

A plain string is a CSS selector. An object names one strategy, or lists several as `candidates`:

```json
{
  "candidates": [
    { "testid": "product-price" },
    { "role": "cell", "name": "Amount" },
    { "css": ".product-card .price" },
    { "xpath": "//*[@data-testid='row']/td[3]" }
  ]
}
```

| Key | Finds |
|---|---|
| `css` | a CSS selector (also the meaning of a bare string) |
| `xpath` | an XPath expression |
| `role` (+ `name`, `exact`) | an element by its accessible role, optionally by its accessible name |
| `text` (+ `exact`) | an element by its visible text |
| `testid` | `[data-testid="…"]` |
| `nth` | picks one of several matches, counted from 0 |
| `frame` | the chain of `<iframe>` selectors to search inside |

Extract fields also accept `regex`, `json` (a JSONPath over the page's embedded data) and `network` (a JSONPath over a response the page received). Those are covered in [JSON and network locators](https://docs.kaab.ist/guides/json-and-network-locators).

## A target is usually a list

Kaabist tries the candidates in order and keeps the first that matches **exactly one** element. A candidate that has become ambiguous (the site now has four elements where it had one) is skipped rather than guessed at. A single locator with no `candidates` that matches several elements fails with `target_ambiguous` unless it has `nth`.

Every attempt is recorded, so a failure tells you which candidates were tried and how many elements each one found, and the healer starts from the same record.

The recorder and the picker emit at least three candidates whenever the page allows it, and after a program version's first successful run Kaabist adds more stable ones to any target whose list is shorter than three.

## The ranking

Candidates are ordered by how well they survive a redesign:

| Rank | Candidate | Example |
|---|---|---|
| 100 | Test id | `data-testid="product-price"` |
| 90 | Role and accessible name | button named "Sepete ekle" |
| 80 | Stable `id` | `#checkout-form` |
| 75 | Role alone | the page's `main`, its `contentinfo` |
| 70 | Exact text | the element that says "Devam" |
| 50 | Tag and meaningful classes | `.product-card .price` |
| 45 | Attribute | `input[name="email"]`, `a[href*="/orders/"]` |
| 20 | Structural path | `//*[@data-testid="row"]/td[3]` |
| 10 | Position | `#products > li:nth-child(3)` |

The generated list always ends with one of the last two. They are the first to break, and they are the only ones that always exist. The same rank is one of the terms of a healed locator's confidence, so a relocation to a test id is trusted more than one to a position.

## Classes Kaabist will not use

A class name is only an anchor if it survives the next build. These are dropped before a selector is written:

- **Build-tool hashes.** `css-1x2y3z`, `sc-bdVaJa`, `Button-9f8e7d`, `emotion-a1b2c3`: anything CSS Modules, styled-components, Emotion or JSS generates. The hash changes when the stylesheet changes.
- **Utility classes.** `flex`, `px-4`, `text-sm`, `col-md-6`, `justify-content-between` and thousands more from Tailwind and Bootstrap. They say how an element looks, not what it is.
- **Generated ids.** `#ember1234`, `#radix-:r7:`, `#headlessui-menu-button-3`, a UUID, anything with a long run of digits. Those name a render, not an element.

Names a person wrote, such as `price`, `product-card` and `btn-primary`, are kept. If your site's markup gives Kaabist nothing else, a `data-testid` on the elements you care about is the single most effective thing you can add.

## Frames and shadow DOM

A target may carry a `frame` path: the chain of `<iframe>` selectors from the top document. Kaabist resolves it before searching. Cross-origin frames work in the browser tiers only.

Open shadow roots are searched automatically, so `.price` finds a price inside a web component. A selector is still matched within a single root, so `product-card .price` will not reach across the boundary; write `product-card >>> .price` when you mean to cross it. A closed shadow root cannot be read by any script, including Kaabist's.

## Select similar

Clicking one value in a repeating list gives Kaabist three things: which element is the repeating item, what contains them, and how to reach the value from inside one item. It recognises product grids, table rows, lists and definition lists, and it reports a confidence: the fraction of items in which the field it worked out actually resolves. A low confidence means the list is less regular than it looks, and the builder shows you which items came up empty.

## Picking in the builder

In the builder, press **Pick** next to a target or a field and click the element, on the live page or on a stored copy of a page a job already visited. The selection comes with up to six candidate locators, best first, each checked to match exactly that element, and the label beside it names the element, its best locator and how many similar elements the page has. Picking on a stored page runs nothing and costs nothing. [Picking and recording](https://docs.kaab.ist/panel/picking-and-recording) walks through it screen by screen.

## Worked example

Three kinds of page show the rules at work.

**Hashed classes.** A shop's product page at `https://shop.example.com/p/1` names every element with build-tool classes, such as `<div class="x7f3a">1.337,40 TL</div>`, and puts a `data-testid` on half of them. Its next deploy re-hashes every class, so the same price is now `<div class="q91bc">`. A field written as

```json
"price": { "candidates": [{ "testid": "product-price" }, { "selector": "main h1 + div" }], "type": "money", "locale": "tr", "currency": "TRY" }
```

reads `1.337,40 TL` before and after the deploy, because the first candidate never depended on a class. A selector the picker would have refused, such as the hashed class itself, breaks on the first deploy. A field with only a structural candidate gets lint warning `W002`.

**Shadow DOM.** The app at `https://app.example.com` renders each product as a `<product-card>` custom element with its price inside the shadow root, and a deeper `<nested-host>` two roots down. In a browser tier, `product-card >>> .price` reads every card's price, and `nested-host >>> .inner-host >>> .deep-price` crosses both boundaries. If the app ships its cards with closed shadow roots instead, the same selectors find nothing: no script can read a closed root.

**Lists.** The shop's catalogue at `https://shop.example.com/c/shoes` prints forty `.product-card` items. Picking one price with select similar produces a list field with the item locator `.product-card`, a field locator `.price` inside it, and a confidence of 1: every card has a price. When the shop ships a redesign, the cards become `article[data-testid=product]` and the price `span[data-col=amount]`; a catalogue program that lists both survives it:

```json
"products": {
  "candidates": [{ "selector": ".product-card" }, { "testid": "product" }],
  "many": true,
  "fields": {
    "price": { "candidates": [{ "selector": ".price" }, { "selector": "[data-col=amount]" }], "type": "money", "locale": "tr", "currency": "TRY" }
  }
}
```

## Pitfalls

- **One candidate is a single point of failure.** Keep the list the picker gave you; it is what lets a job survive a redesign without healing.
- **Text locators follow the copy.** A button found by its label breaks when marketing renames it. Prefer a role and name, which survive restyling, or a test id.
- **`nth` hides ambiguity.** It makes a non-unique locator pass today and silently picks the wrong element tomorrow. Use it only when position is what you mean.
- **`>>>` is needed to cross a shadow root inside a selector.** A plain descendant selector stops at the boundary.
- **The http tier sees the server's HTML.** An element added by JavaScript exists only in a browser tier; a locator that works in the builder's live browser may find nothing at http.

## Reference

- [Targets](https://docs.kaab.ist/reference/dsl/targets) and [selector](https://docs.kaab.ist/reference/dsl/fields/selector)
- [Lint](https://docs.kaab.ist/reference/dsl/lint), including [W002](https://docs.kaab.ist/reference/dsl/lint/w002)
- [Picking and recording](https://docs.kaab.ist/panel/picking-and-recording)
- [How healing works](https://docs.kaab.ist/concepts/self-healing)
