In an Appium `-ios class chain`, what does the index `[-1]` select?
answer
- counting starts where humans start
- negative counts from the far end
- the index narrows one step only
- no zero index, no last function
basics
~10 sA minus-one index selects the last element that step matched. Appium's iOS class chain numbers a step's matches from 1, not 0, and a negative index counts backwards from the end of that list.
solid answer
~40 sIn the Appium **XCUITest driver**, an index in square brackets is the last thing applied to a class chain step: the step's type name and any bracket segment produce a list of matching iOS elements, and the index takes one out of it. Numbering is **1-based** — `[1]` is the first match — and negative numbers count from the end, so `[-1]` is the last and `[-2]` the one before it. That is the cheap way to say *the last roster row* without first counting rows. The index binds to the step it is attached to, not to the path as a whole: `**/XCUIElementTypeCell[-1]/XCUIElementTypeButton` means *buttons inside the last cell*, not *the last button on screen*.
go deeper
Remember two numbers: matches count from 1, and a negative index counts back from the end. Be able to say what minus one picks out of a step's matches on an iOS screen.
Explain that the index is the last thing applied to a step, so a bracket segment narrows the list first, and that each index reduces its own step before the next separator runs.
Show that you know what an index binds to — hierarchy order at find time — and can say which roster assertions position genuinely expresses and which ones it only appears to.
Own the cost side: positional addressing is cheap to write and expensive to trust, and a suite that leans on it stays green while asserting against the wrong row after a re-sort.
## Where an index sits in a class chain step In Appium's **XCUITest driver**, an iOS class chain step is an `XCUIElementType` name, optionally narrowed by a bracket segment, optionally followed by an index in square brackets: `XCUIElementTypeCell[3]`, or ``XCUIElementTypeCell[`visible == 1`][2]``. The index is applied **last**. Everything before it produces a list of matching elements; the index reaches into that list and takes exactly one. That ordering is worth stating out loud, because it decides what the number counts. In the second example the index does not count all the cells on the iOS screen — it counts only the cells that survived the backtick segment. ## One-based, and negative from the end - Numbering starts at **1**. `[1]` is the first match, not the second, and there is no `[0]` step to write. - A **negative** index counts backwards: `[-1]` is the last match, `[-2]` the one before it. - Negative indexing is the reason this rule is worth learning on its own — it expresses *the last row* without the test first having to fetch every row and count them. - An index past the end of the list simply matches nothing; the find fails the way any unmatched locator fails rather than silently returning a neighbour. If you have written XPath the notation looks familiar and the vocabulary is not: a class chain has no `last()` function, and its index never counts across a whole document. It counts one step's matches. ## The index belongs to its step, not to the path This is where the mechanic surprises people. Read `**/XCUIElementTypeCell[-1]/XCUIElementTypeButton` as two instructions in order: *take the last matching cell*, then *look for buttons inside it*. It does not mean *the last button on screen*. Moving the index to another step gives a different query entirely, because each index reduces the step it is attached to before the next separator is applied. | path | what the iOS driver returns | |---|---| | `**/XCUIElementTypeCell[-1]` | the last matching cell | | `**/XCUIElementTypeCell[-1]/XCUIElementTypeButton` | buttons that are direct children of that last cell | | `**/XCUIElementTypeTable/XCUIElementTypeCell[2]` | the second cell that is a direct child of the table | ## A worked case on the bell-ringing tower roster The roster screen of a bell-ringing tower roster app lists one cell per ringer, appended in the order the tower captain adds them, and the case under test is *the ringer added most recently keeps their slot after a save*. 1. `**/XCUIElementTypeTable/XCUIElementTypeCell[-1]` addresses the newest row without the test knowing how many ringers the tower has. 2. Stepping down from that match — `/XCUIElementTypeStaticText[1]` — reads the first label inside that row. 3. Re-running the same path after the save re-evaluates it against the current iOS hierarchy, so the assertion is about *the last row now*, which is exactly what the case means. Step 3 is what makes an index honest here: nothing is cached. Each find re-resolves the whole path, so `[-1]` means the last match at the moment of that find, not the element that was last when the screen opened. ## What an index actually binds to An index binds to the **order the iOS hierarchy lists the matches in**, which is not a promise about the order a person reads the screen in. For a plain vertical list the two usually coincide; on a screen with overlays, off-screen rows or a custom container they may not. Two consequences follow: - An index is a positional handle. When the roster re-sorts — by tower, by band, by practice night — the same index points at a different ringer, and the test keeps passing while asserting the wrong thing. - An index is at its best where position *is* the meaning: the last row, the first cell of a section, the newest entry. Where the meaning is identity, a bracket segment matching the element's own attributes says what you mean and survives a re-sort. How far a whole suite should lean on positional addressing is a selector-strategy question owned elsewhere; the mechanic to carry away here is what the number counts. ## Common mistakes - Writing `[0]` for the first match, on the assumption that a class chain index is a programming-language array index. - Reading `[-1]` as *the last element on screen* rather than *the last match of this step*. - Attaching the index to the wrong step and then debugging the app instead of the path. - Assuming an out-of-range index throws something distinctive; it just leaves the step with no match. - Treating hierarchy order as visual order on a screen with overlays or reordered content.
- How does an index interact with a bracket segment on the same class chain step?The segment runs first and the index runs on what is left. ``XCUIElementTypeCell[`visible == 1`][2]`` means *the second visible cell*, not *the second cell, if it happens to be visible*. Getting that order backwards is a common source of a locator that matches in one iOS screen state and not another.
- When is a positional index the right way to address a roster row on iOS?When position is the meaning of the case — the newest entry, the first row of a section, the last item appended. When the case is really about a named ringer, match the element by its attributes instead; an index quietly re-points as soon as the list re-sorts and the test keeps passing.
saying these in an interview costs you the question
- Says class chain indexes start at zero like an array
- Reads a minus-one index as the last element on the whole screen
- Thinks the index applies to the entire path rather than one step
- Expects an XPath last() function inside a class chain
- Assumes an out-of-range index raises a distinctive index error