You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(locators): elevate semantic+context as the default pattern
Reframe semantic locators (`I.click('Save', '.header')`) as the
recommended default for stable scenarios, not a prototyping
shortcut. Combined with a context, they read like prose, survive
ARIA/CSS refactors, and disambiguate duplicate labels — so they're
more precise than ARIA or CSS used alone.
- Intro: lead with the "semantic + context" recipe.
- Locator-types table: split semantic into "with context" (default)
and "no context" (unique label / prototyping); document the
combined pattern as a first-class type.
- Semantic section: front-load the "pair with a context" guidance
and drop the "switch to strict locators once stable" line.
- Context section: explain why scoping every action is the
default, not the special case.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/locators.md
+46-15Lines changed: 46 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,20 +10,29 @@ Locators tell CodeceptJS which element on the page a step acts on. Every action
10
10
CodeceptJS accepts locators in two forms:
11
11
12
12
-**Strict locator** — an object whose single key names the strategy: `{ css: 'button' }`, `{ role: 'button', name: 'Submit' }`, `{ xpath: '//td[1]' }`, `{ id: 'email' }`. The strategy is explicit, so the helper runs exactly one query.
13
-
-**Fuzzy locator** — a plain string. CodeceptJS guesses the strategy from shape (`#foo` → id, `//td` → xpath, `.row` → css) and falls back to semantic matching (labels, button text, placeholders). Convenient, but slower and sometimes ambiguous.
13
+
-**Semantic locator** — a plain string like `'Sign In'` or `'Email'`. CodeceptJS matches it against labels, button text, placeholders, and `aria-*` attributes the way a user would read the page.
14
14
15
-
Prefer strict locators in stable test suites. Reach for fuzzy strings when prototyping.
15
+
Both are idiomatic. The strongest pattern in CodeceptJS — readable, resilient, and unambiguous — is a **semantic locator scoped to a context**:
The context narrows the search to one region of the page, and the semantic string says what the user actually clicks. This is **more precise than ARIA or CSS alone** because it combines structural scope with human-readable intent.
16
24
17
25
Supported strategies: `css`, `xpath`, `id`, `name`, `role`, `frame`, `shadow`, `pw`. Shadow DOM and React selectors have their own pages — see [Shadow DOM](/shadow) and [React](/react). Playwright-specific locators (`_react`, `_vue`, `data-testid`) use the `pw` strategy: `{ pw: '_react=Button[name="Save"]' }`.
18
26
19
27
## Locator types at a glance
20
28
21
29
| Type | Example | Strengths | Weaknesses | Reach for it when |
|**Semantic + context**|`I.click('Save', '.header')`| Reads like prose; survives CSS and ARIA refactors; the context disambiguates duplicates | Needs a stable region to scope into |**Default for stable suites.** Anywhere a label, button text, or placeholder identifies the element |
23
32
|**ARIA role**|`{ role: 'button', name: 'Save' }`| Survives markup changes; matches how users and screen readers identify elements; exposes accessibility gaps | Needs correct ARIA roles and accessible names; slower than CSS | The app follows accessibility guidelines and you want tests that mirror user intent |
33
+
|**Semantic (no context)**|`'Sign In'`, `'Email'`| No locator to maintain; reads like prose | Ambiguous when the same label appears more than once on the page | A label is unique on the page, or you are prototyping |
24
34
|**CSS**|`{ css: '.btn-save' }` or `.btn-save`| Fast; familiar to every web developer; composes with class, attribute, and pseudo-selectors | Couples tests to styling; breaks on CSS refactors; cannot match by visible text | A stable class, id, or data-attribute exists on the target |
25
35
|**XPath**|`{ xpath: '//table//tr[2]/td[last()]' }`| Walks the tree in any direction (`ancestor`, `following-sibling`); matches visible text | Verbose; slow; harder to read than CSS | You need text matching or axis navigation that CSS cannot express |
26
-
|**Semantic (fuzzy)**|`'Sign In'`, `'Email'`| No locator to maintain; reads like prose | Several lookups per call; ambiguous when labels repeat | Writing a quick scenario or prototyping |
27
36
|**ID / name**|`#email`, `{ name: 'user[email]' }`| Shortest possible locator; unambiguous | Requires an `id` or `name` attribute to exist | Forms and elements with stable ids |
28
37
|**Accessibility id**|`~login-button`| Works in both web (`aria-label`) and mobile | Mobile apps need to expose the id | Cross-platform web and mobile tests |
29
38
|**Custom (`$foo`)**|`$register_button`| Encodes team convention (`data-qa`, `data-test`) in two characters | Needs the [customLocator plugin](/plugins#customlocator)| Your team uses dedicated test attributes |
@@ -97,26 +106,41 @@ Long XPath expressions become unreadable fast. The [`locate()` builder](#combini
97
106
98
107
## Semantic locators
99
108
100
-
When you pass a plain string to a form or click action, CodeceptJS tries several strategies in order: links, buttons, labels, placeholders,`aria-label`.
109
+
A plain string is a semantic locator. CodeceptJS reads it the way a user would: as a button label, a link, a field name, a placeholder, or an`aria-label`.
101
110
102
111
```js
103
-
I.click('Sign In') // matches <a>, <button>, or <input type="submit">
104
-
I.fillField('Email', 'u@t.com') // matches label, placeholder, or name
112
+
I.click('Sign In') // matches <a>, <button>, or <input type="submit">
113
+
I.fillField('Email', 'u@t.com') // matches label, placeholder, name, or aria-label
105
114
I.checkOption('I accept the terms')
106
115
```
107
116
108
-
The order `fillField` actually uses is:
117
+
### Pair semantic locators with a context
109
118
110
-
1. Is the locator an ARIA role locator (`{ role: 'textbox', name: 'Email' }`)? If so, resolve through the accessibility tree and stop.
111
-
2. Is it a strict locator (`{ css: ... }`, `{ xpath: ... }`, `{ id: ... }`, …)? Run it directly and stop.
112
-
3. Otherwise treat the string as fuzzy and try, in order:
113
-
1. A field whose `name`, `id`+`label[for]`, or `placeholder`**equals** the string — or a `<label>` with that exact text wrapping an input.
119
+
The same label often appears in more than one place — a "Save" button in the toolbar, the modal, and the inline editor. **Pass a context as the last argument** and the lookup is unambiguous, fast, and still readable:
120
+
121
+
```js
122
+
I.click('Save', '.toolbar')
123
+
I.fillField('Search', 'Item 1', '.topbar')
124
+
I.click('Edit', { css:'tr.acme' })
125
+
I.see('Welcome', '.header')
126
+
```
127
+
128
+
The context can be any locator (CSS, XPath, ARIA, [`locate()` chain](#locate-builder-compose-css-and-xpath)). The action runs only inside it, so duplicate labels elsewhere on the page no longer cause flaky matches. This is the recommended default for stable scenarios — production-grade, not a prototyping shortcut.
129
+
130
+
### How matching works
131
+
132
+
For `fillField` and similar actions, CodeceptJS resolves the locator in this order:
133
+
134
+
1. ARIA role locator (`{ role: 'textbox', name: 'Email' }`) — resolved through the accessibility tree.
3. Plain string treated as semantic, tried in order:
137
+
1. Field whose `name`, `id`+`label[for]`, or `placeholder`**equals** the string — or a `<label>` with that exact text wrapping an input.
114
138
2. The same match with **contains**, extended to `aria-label`, `aria-labelledby`, and `title`.
115
139
3. An input with that `name` attribute.
116
-
4.Finally, the string as a CSS selector.
140
+
4.The string as a CSS selector.
117
141
4. Nothing matched? Throw `ElementNotFound`.
118
142
119
-
So fuzzy `fillField` already covers labels, placeholders, names, ids referenced by labels, and the ARIA attributes (`aria-label`, `aria-labelledby`, `title`) — no extra syntax needed. Each lookup runs several queries, though, so switch to strict locators (`{ role: ... }` or `{ css: ... }`) once a scenario is stable.
143
+
A semantic lookup runs several queries, but each query is cheap and the second argument (context) prunes the search space dramatically.
120
144
121
145
## ID locators
122
146
@@ -164,15 +188,22 @@ Two mechanisms narrow a locator to a region of the page:
164
188
165
189
### Context: scope any locator to a region
166
190
167
-
Every action that targets an element accepts a context locator as its last argument. The action searches only inside the context.
191
+
Every action that targets an element accepts a context locator as its last argument. The action searches only inside the context.**Use it by default** — even a one-line scenario reads better and survives more refactors when the lookup is scoped:
0 commit comments