In an Appium `-ios class chain`, how does a `$...$` segment differ from a backtick one?
answer
- same predicate, different subject
- backticks ask the element itself
- dollars ask what it contains
- the container is what comes back
basics
~20 sA backtick segment filters the element the step names by that element's own attributes. A dollar-sign segment matches the same step by one of its descendants instead, so Appium's iOS class chain can address a container by what is inside it.
solid answer
~40 sBoth are bracket segments on a class chain step in Appium's **XCUITest driver**, and both hold a predicate over iOS element attributes such as `name`, `label`, `value` and `type`. The difference is *which element the predicate is tested against*. Backticks — ``XCUIElementTypeCell[`label == 'Alice Marsh'`]`` — test the cell's own attributes. Dollar signs — `XCUIElementTypeCell[$label == 'Alice Marsh'$]` — test the cell's **descendants**, and the element returned is still the cell. That is how a class chain reaches a container it cannot describe directly: on a bell-ringing tower roster the row itself often carries nothing useful, while the static text inside it carries the ringer's name. Match the row by its content, then step down to the **Ring** button you actually want to tap.
code
java · 25 linesimport io.appium.java_client.AppiumBy;
import io.appium.java_client.ios.IOSDriver;
import org.openqa.selenium.WebElement;
public final class TowerRosterScreen {
private static final String LAST_ROW =
"**/XCUIElementTypeTable/XCUIElementTypeCell[-1]";
private final IOSDriver driver;
public TowerRosterScreen(IOSDriver driver) {
this.driver = driver;
}
public WebElement lastRosterRow() {
return driver.findElement(AppiumBy.iOSClassChain(LAST_ROW));
}
public WebElement ringButtonFor(String ringer) {
return driver.findElement(AppiumBy.iOSClassChain(
"**/XCUIElementTypeCell[$label == '" + ringer + "'$]"
+ "/XCUIElementTypeButton[`label == 'Ring'`]"));
}
}go deeper
Learn to spot the two segment spellings and say which element each one asks about. Knowing that a dollar-sign segment returns the container, not the thing inside it, is the point to carry away.
Explain why an iOS layout container often has nothing of its own to match on, and how a descendant segment plus one direct-child step replaces the parent walk a class chain cannot do.
Show that you can write the path that survives a roster re-sort — content-addressed rather than positional — and can spot a descendant segment loose enough that several ancestors satisfy it.
Own the trade: containment addressing rescues screens the app gives you no handles on, and every such path is a standing bet on the app's internal structure that somebody has to maintain.
## Two kinds of bracket segment Every step of an iOS class chain in Appium's **XCUITest driver** names an `XCUIElementType` and may carry one bracket segment that narrows it. There are two spellings of that segment, and they look almost identical: - **Backticks** — ``XCUIElementTypeCell[`label == 'Alice Marsh'`]`` — the predicate is tested against **the element the step names**. - **Dollar signs** — `XCUIElementTypeCell[$label == 'Alice Marsh'$]` — the predicate is tested against **that element's descendants**, and the element the step names is still what matches. The predicate text inside either form draws on the same iOS attribute vocabulary the driver exposes — `name`, `label`, `value`, `type` and the visibility attributes — and the operator vocabulary itself is the `-ios predicate string` strategy's subject rather than this one's. What a class chain adds is the choice of *whose* attributes are being asked about. ## Backticks filter the element the step names A backtick segment is the ordinary case and behaves the way most people first assume. ``**/XCUIElementTypeButton[`label == 'Ring'`]`` reads *any button, at any depth, whose own label is Ring*. It composes with the path: put it on the last step and you are filtering the thing you want; put it on an earlier step and you are filtering the container you will descend through. It fails in one specific and very common way on iOS: **containers frequently carry nothing to match on**. A table cell in a bell-ringing tower roster app is usually a layout element. The tower name, the ringer's name and the practice night live in static texts *inside* it. Asking the cell about its own label finds nothing, and the natural XPath reflex — find the label, then walk up to its parent — has no class chain equivalent, because a class chain only descends. ## Dollar signs match by descendants That is what the `$...$` form is for. `**/XCUIElementTypeCell[$label == 'Alice Marsh'$]` means *any cell that contains an element whose label is Alice Marsh*, and the element the find returns is **the cell**, not the label. The container is addressed by its content. That single change is what lets the strategy reach a parent without a parent axis, and it is why a class chain often replaces an XPath that used a parent step: | segment | predicate tested against | what the find returns | |---|---|---| | ``[`label == 'Ring'`]`` | the element the step names | that element | | `[$label == 'Alice Marsh'$]` | the element's descendants | the containing element | ## A worked path on the tower roster The case is *tapping Ring on Alice Marsh's row rings her bell and not the row above it*. The roster shows one cell per ringer; each cell holds a static text with the name and a button labelled **Ring**, and every one of those buttons is identical. 1. `**/XCUIElementTypeCell[$label == 'Alice Marsh'$]` picks the one row whose content names her. 2. ``/XCUIElementTypeButton[`label == 'Ring'`]`` steps into that row's direct children and filters them by the button's own label. 3. The two segments do different jobs in the same path, and swapping them breaks it: backticks on the cell match nothing, dollar signs on the button match any button containing that text. The result is a locator that survives a re-sort of the roster, because nothing in it is positional, and that names in one string both the row's identity and the control inside it. ## Why this matters on Apple platforms specifically - A class chain **descends only**. There is no parent step and no sibling step, so `$...$` is the mechanism for expressing *the ancestor of something I can describe*. - The other iOS-native strategies do not help here. A predicate string filters one element by its own attributes and knows nothing about containment. - The Android drivers, UiAutomator2 and Espresso, do not declare a class chain at all, so this containment trick has no cross-platform twin. - Getting from the container to the control inside it is a single path, so the test issues one find rather than a find, a parent walk and a second find. ## Common mistakes - Expecting `$...$` to return the descendant that matched. It returns the step's element; the descendant is only the condition. - Reaching for backticks on a layout container that carries no attributes of its own, then concluding the element is missing from the hierarchy. - Assuming a `$...$` segment is a whole-subtree text search; it is a predicate over descendants, evaluated with the same attribute vocabulary as the backtick form. - Writing a `$...$` segment so loose that several ancestors satisfy it — every ancestor of a matching descendant is a candidate, so name a specific step type. - Trying to port the path to an Android session, where no driver accepts the strategy.
- Why can't you just walk up from the Ring button to its roster row on iOS?A class chain has no parent axis — every step descends. The way to express an ancestor is to make the ancestor the step you match and put the descriptive condition in a `$...$` segment, then step back down to the control you want. XPath's parent step has no class chain equivalent.
- What does a `$...$` segment return, the descendant or the container?The container — the element the step names. The descendant only supplies the condition and never appears in the result. Expecting the descendant back is the most common misreading, and it shows up as a test that taps a label instead of the row it meant to select.
saying these in an interview costs you the question
- Says a dollar-sign segment returns the matching descendant
- Thinks backticks and dollar signs are interchangeable spellings
- Tries to walk up to a parent with an XPath-style axis
- Treats a dollar-sign segment as a free-text search of the subtree
- Assumes the same path can address an Android view hierarchy