Skip to content

Selectors

A selector tells spana which element to interact with or assert on. Selectors are used by all app interaction methods and expect().

{
testID: "login-button";
}

The preferred selector. Maps to the platform’s primary element identifier:

PlatformAttribute
Webdata-testid attribute
Androidresource-id (last segment matched)
iOSaccessibilityIdentifier

Add testID props to your React Native components:

<Pressable testID="login-button">Sign In</Pressable>

On web, this renders as data-testid="login-button".

{
text: "Sign In";
}

Matches against the visible label text of an element. Partial matching is supported — the element text only needs to contain the provided string.

Use this when a testID is not available, such as for dynamic or third-party content.

{
accessibilityLabel: "Close dialog";
}

Matches the OS-level accessibility label. Useful for elements that have a label distinct from their visible text (e.g. icon buttons).

{ point: { x: 150, y: 340 } }

Taps at absolute screen coordinates. Use as a last resort — coordinates are device-specific and break across screen sizes.

A plain string is treated as a testID:

await app.tap("login-button");
// equivalent to:
await app.tap({ testID: "login-button" });

When multiple elements match the same selector, use relative positioning to disambiguate. A relative selector wraps a base selector with directional constraints:

// Tap the "Edit" button below the "Email" label
await app.tap({ selector: { text: "Edit" }, below: { text: "Email" } });
// Tap the button to the right of the "Username" label
await app.tap({ selector: { testID: "action-btn" }, rightOf: { text: "Username" } });
// Find an element inside a specific container
await app.tap({ selector: { text: "Submit" }, childOf: { testID: "login-form" } });
ConstraintDescription
belowElement must be below the anchor (closest first)
aboveElement must be above the anchor (closest first)
leftOfElement must be left of the anchor (closest first)
rightOfElement must be right of the anchor (closest first)
childOfElement must be a descendant of the anchor

Constraints can be combined — all must match:

// Button below the header AND to the right of the label
await app.tap({
selector: { testID: "btn" },
below: { text: "Header" },
rightOf: { text: "Label" },
});

Relative selectors work with all interaction methods (tap, doubleTap, longPress) and assertions (expect().toBeVisible(), etc.).

When spana builds a suggested selector (e.g. in spana selectors output), it uses this priority:

  1. testID — most stable, preferred
  2. accessibilityLabel
  3. text
  4. point — last resort

All selector-based methods accept an optional WaitOptions object to override the global defaults for that specific call:

await app.tap({ testID: "slow-element" }, { timeout: 10000 });
await expect({ testID: "result" }).toBeVisible({ timeout: 15000, pollInterval: 500 });
OptionTypeDescription
timeoutnumberms to wait before failing
pollIntervalnumberms between hierarchy polls
settleTimeoutnumberms the element must be stable before matching