skip to content

Predicate Strings

iOS lets you filter elements with the same predicate language Cocoa uses everywhere else, and XCTest evaluates it natively, which is why it stays fast where an XPath expression does not.

on this pageshow

explore

questions

4

In Appium, what does the `-ios predicate string` locator strategy match, and which driver owns it?

level: middleimportance: must knowfreq 70%

answer

  1. same filter language Cocoa uses
  2. attributes only, no path steps
  3. type, name, label, value
  4. BEGINSWITH, CONTAINS, LIKE, MATCHES

basics

~20 s

Appium's XCUITest driver, which automates Apple platforms, accepts -ios predicate string: an NSPredicate that filters elements by attribute — type, name, label, value — using comparison and logical operators. It matches attributes only, never hierarchy, and Android's drivers do not accept it.

solid answer

~40 s

`-ios predicate string` is one of the eight native locator strategies Appium's XCUITest driver declares, and it exists only for Apple platforms — on Android the UiAutomator2 driver offers `-android uiautomator` instead. Its value is an `NSPredicate`: the same filter language Cocoa uses, evaluated natively by XCUITest against elements rather than against a serialised document. You compare element attributes — `type`, `name`, `label`, `value` — with `==`, `!=`, `BEGINSWITH`, `CONTAINS`, `LIKE`, `MATCHES` and `IN`, join clauses with `AND`, `OR` and `NOT`, and append `[c]`, `[d]` or `[cd]` to make a text comparison case- or diacritic-insensitive. Because it filters one element's own attributes, it cannot take a path step: in an ice-rink session app you can ask for a button whose `name` begins with `rink.session`, but not for the button inside a particular session cell.

code

json · 4 lines
json
{
  "using": "-ios predicate string",
  "value": "type == 'XCUIElementTypeButton' AND name == 'rink.session.book'"
}

go deeper

for a junior

Be able to say that -ios predicate string is Appium's Apple-platform strategy in the XCUITest driver, and that its value is an attribute filter rather than a path.

for a middle

Explain the mechanics: the driver hands an NSPredicate to XCUITest, which evaluates it natively against element attributes, so you name attributes and operators and never write steps.

for a senior

Show what the language cannot express — hierarchy, index, sibling order — and how you keep predicates unambiguous on a screen full of near-identical ice-rink session rows.

for a principal

Own the policy: which attributes your iOS predicates are permitted to compare, and what stability that commits the app team to for identifiers versus visible copy.

## Where the strategy comes from Appium addresses an element by sending a **strategy** and a **selector**: a W3C find is `POST /session/:sessionId/element` carrying `{"using": "...", "value": "..."}`. Every driver declares the strategies it will accept, and those lists differ by driver. Appium's XCUITest driver, the driver that automates Apple platforms, declares eight native strategies, and `-ios predicate string` is one of them. Appium's UiAutomator2 driver on Android declares a shorter, different list built around `-android uiautomator`, and it has no predicate strategy at all. That is the first fact about this locator: it is Apple-only, and a shared page object cannot reuse it on an Android run. The selector itself is an **`NSPredicate` expression** — the same string filter language Cocoa uses to filter collections everywhere else on Apple platforms. The XCUITest driver hands the expression to XCUITest, which evaluates it natively against elements in the accessibility hierarchy. You are giving the framework a filter to run, not a document to search. ## What a predicate compares A predicate tests the attributes of **one element at a time**. Four attribute names carry most real selectors, shown here against an ice-rink session app: - `type` — the element's class, written as its full `XCUIElementType…` name, such as `XCUIElementTypeButton`, `XCUIElementTypeCell` or `XCUIElementTypeStaticText`. - `name` — the identifying string attached to the control, for example `rink.session.book` on the booking button. - `label` — the human-visible text, for example `Book 18:30 public session`; this is the copy a translator edits. - `value` — the control's current value, for example the skate size showing in a picker wheel. Everything else in the expression is operators and literals. String literals must be quoted, with single or double quotes. Drop the quotes and the parser reads the bare word as another attribute name, so `name == rink.session.book` is a different query from `name == 'rink.session.book'` — and one that matches nothing. ## The operator vocabulary | Operator | True when | Ice-rink example | |---|---|---| | `==`, `!=` | exact equality or inequality | `name == 'rink.session.book'` | | `BEGINSWITH` | the attribute starts with the literal | `name BEGINSWITH 'rink.session.'` | | `CONTAINS` | the literal appears anywhere inside it | `label CONTAINS 'public session'` | | `LIKE` | glob match, `*` for any run, `?` for one character | `name LIKE 'rink.session.*.book'` | | `MATCHES` | the **whole** attribute matches an ICU regular expression | `label MATCHES '.*[0-2][0-9]:[0-5][0-9].*'` | | `IN` | the attribute is one of a listed set | `type IN {'XCUIElementTypeCell','XCUIElementTypeButton'}` | Clauses join with `AND`, `OR` and `NOT`, and parentheses group them, so a single selector can carry a compound condition such as `type == 'XCUIElementTypeCell' AND (label CONTAINS 'Freestyle' OR label CONTAINS 'Public')`. Three modifiers attach to the text operators, in square brackets immediately after the operator: 1. `[c]` — case-insensitive, so `label CONTAINS[c] 'public'` also matches `Public`. 2. `[d]` — diacritic-insensitive, so `label BEGINSWITH[d] 'Seance'` also matches `Séance`. 3. `[cd]` — both at once, which is what a comparison against localised copy usually needs. ## What it deliberately cannot do The boundary is as much of the answer as the syntax, because most misuse is someone expecting a path where the language offers only a filter: - **No hierarchy step.** A predicate cannot say "inside that cell" or "the parent of this one". Appium's XCUITest driver ships a separate strategy, `-ios class chain`, for expressions with steps. - **No positional selection.** There is no index in the language; a predicate that matches three controls matches three controls. - **No comparison between two elements.** Every clause is about the element currently under test. - **No guarantee of a single hit.** Find Element returns the first element the framework yields and Find Elements returns them all, so tighten an ambiguous predicate with `AND` rather than trusting order. - **No invented attribute names.** A typo in an attribute name is not an error; it is a clause nothing satisfies. ## Using it on a real screen Three shapes cover most of an ice-rink session app: - Pin the identifier where the app sets one: `type == 'XCUIElementTypeButton' AND name == 'rink.session.book'`. - Pin a family where identifiers are generated per row: `name BEGINSWITH 'rink.session.row.'`. - Fall back to visible copy only when nothing else exists, and then defensively: `type == 'XCUIElementTypeStaticText' AND label CONTAINS[cd] 'freestyle'`. Scope follows the endpoint you send the selector to. Posted to `POST /session/:sessionId/element` it filters from the session's root; posted to `POST /session/:sessionId/element/:elementId/element` it filters the descendants of an element you already hold, which is how you get "inside this cell" behaviour out of a language with no path steps.

  • Does an `-ios predicate string` search the whole app, or can you scope it?
    Scope comes from the endpoint, not the language. Sent to `POST /session/:sessionId/element` the predicate is filtered from the session's root; sent to `POST /session/:sessionId/element/:elementId/element` it filters only the descendants of the element you already hold. That is how you constrain a match to one ice-rink session cell without a path expression.
  • What happens when your predicate matches several controls on the ice-rink session list?
    Nothing fails. A predicate is a filter, so Find Element returns the first element the framework yields and Find Elements returns every match. Relying on that ordering is how a suite starts booking the wrong session after a layout change; add another `AND` clause — usually on `type` or on an identifying `name` — until the match is unambiguous.

It is the move you make when filtering a booking spreadsheet with a WHERE clause: you describe the rows you want by their columns. You cannot express "the row inside that other row", because a filter has no notion of nesting.

saying these in an interview costs you the question

  • Thinks Appium's Android drivers also accept -ios predicate string.
  • Writes type == 'Button' instead of the full XCUIElementType name.
  • Expects a predicate to select a child or parent of another element.
  • Leaves string literals unquoted, so the word is read as an attribute name.
  • Assumes name, label and value all hold the same string on iOS.
open as a page

An Appium `-ios predicate string` finds nothing though the ice-rink control is visible — how do you diagnose it?

level: seniorimportance: must knowfreq 52%

basics

~20 s

Work down the expression: full XCUIElementType name for type, quoted string literals, the attribute you actually meant, case and diacritics, and MATCHES matching the whole string. Then confirm the control is really in the session's source with those attributes.

open as a page

Your Appium ice-rink suite's `-ios predicate string` matches in the English build but not the French one — why?

level: seniorimportance: must knowfreq 55%

basics

~20 s

A predicate that compares label is comparing translated, accented copy with a case- and diacritic-sensitive operator. In Appium's iOS strategy, append [cd] to CONTAINS or BEGINSWITH, or match a stable name attribute instead of visible text.

open as a page

In Appium, how much should one `-ios predicate string` pin down, and why?

level: principalimportance: should knowfreq 38%

basics

~20 s

Pin the smallest set of attributes that makes the iOS match unambiguous — usually type plus one identifier. Every extra clause buys precision but costs a rewrite when that attribute changes, and makes a failure harder to read.

open as a page