Docs
API 1.39
Status
Concepts

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:

{
  "candidates": [
    { "testid": "product-price" },
    { "role": "cell", "name": "Amount" },
    { "css": ".product-card .price" },
    { "xpath": "//*[@data-testid='row']/td[3]" }
  ]
}
KeyFinds
cssa CSS selector (also the meaning of a bare string)
xpathan 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="…"]
nthpicks one of several matches, counted from 0
framethe 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.

#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:

RankCandidateExample
100Test iddata-testid="product-price"
90Role and accessible namebutton named "Sepete ekle"
80Stable id#checkout-form
75Role alonethe page's main, its contentinfo
70Exact textthe element that says "Devam"
50Tag and meaningful classes.product-card .price
45Attributeinput[name="email"], a[href*="/orders/"]
20Structural path//*[@data-testid="row"]/td[3]
10Position#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 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

"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:

"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

Was this page helpful?