Repository navigation
Search.Spatial
Filter elements by where they appear on screen, and read element positions.
Spatial filters are useful for:
- Finding elements in specific UI regions (top bar, bottom nav, …)
- Skipping items scrolled out of a list's viewport
- Disambiguating identical elements by screen location
Positions are taken from each element's screen-space centre, which works for Overlay, Camera and World Space canvases and for 3D objects with a Renderer or Collider.
Keep elements whose centre falls in one cell of a 3×3 screen grid.
await Click(Type<Button>().InRegion(ScreenRegion.TopRight));
await Click(Name("*Item*").InRegion(ScreenRegion.Center));┌─────────────┬─────────────┬─────────────┐
│ TopLeft │ TopCenter │ TopRight │
├─────────────┼─────────────┼─────────────┤
│ MiddleLeft │ Center │ MiddleRight │
├─────────────┼─────────────┼─────────────┤
│ BottomLeft │BottomCenter │ BottomRight │
└─────────────┴─────────────┴─────────────┘
Keep elements whose centre lies inside normalized bounds (0–1, inclusive). Values outside 0–1 throw ArgumentOutOfRangeException.
// Left half of the screen
await Click(Type<Button>().InRegion(0f, 0f, 0.5f, 1f));
// Top quarter
await Click(Type<Button>().InRegion(0f, 0.75f, 1f, 1f));
// Custom rectangle
await Click(Type<Slider>().InRegion(0.2f, 0.3f, 0.8f, 0.7f));Parameters: InRegion(xMin, yMin, xMax, yMax) where x runs left (0) to right (1) and y runs bottom (0) to top (1).
Keep elements whose centre is on screen and, if they sit inside a ScrollRect, whose rect overlaps that ScrollRect's viewport.
await Click(Type<Button>().Visible().First());
var visibleItems = await FindAll<RectTransform>(Name("Item*").Visible());Visible() does not raycast, so it doesn't detect elements covered by other UI. Use RequiresReceiver for that.
Keep elements where an EventSystem raycast at the element's screen centre hits a GameObject implementing one of the given handler interfaces. Use it to make sure a control is actually reachable rather than covered by something else.
using UnityEngine.EventSystems;
// Buttons that something will actually receive a click on
await Click(Type<Button>().RequiresReceiver(typeof(IPointerClickHandler)));
// Draggable elements
var draggables = await FindAll<RectTransform>(
Name("Item*").RequiresReceiver(typeof(IDragHandler), typeof(IBeginDragHandler))
);
// Inspect the receivers found by the last check
var search = new Search().Name("MyButton").RequiresReceiver(typeof(IPointerClickHandler));
var element = await search.Find();
foreach (var receiver in search.Receivers)
{
Debug.Log($"Found receiver: {receiver.name}");
}| Interface | Use Case |
|---|---|
IPointerClickHandler |
Click targets |
IPointerUpHandler |
Click/tap release |
IPointerDownHandler |
Press/hold |
IDragHandler |
Drag operations |
IBeginDragHandler |
Drag start |
IScrollHandler |
Scroll targets |
ISelectHandler |
Selection/focus |
When a RequiresReceiver search fails, the failure message lists the receivers found at the element's position.
await Click(Name("*Back*|*Close*").InRegion(ScreenRegion.TopLeft));
await Click(Text("Next|Continue").InRegion(ScreenRegion.BottomRight));
await Click(Texture("settings_*").InRegion(ScreenRegion.TopRight));await Click(Name("ListItem*").Visible().First());
var visible = await FindAll<RectTransform>(Name("Item*").Visible());
Debug.Log($"{visible.Count()} items currently visible");
// Scroll until the item is in view, then click it
await ScrollTo(Name("ItemList"), Text("Target Item"));
await Click(Text("Target Item").Visible());
// Or in one call
await ScrollToAndClick(Name("ItemList"), Text("Target Item"));await Click(Text("Submit").InRegion(ScreenRegion.BottomCenter));
await Click(Name("CloseButton").InRegion(ScreenRegion.TopRight).First());await Click(
Type<Button>()
.Visible()
.Interactable()
.InRegion(ScreenRegion.Center)
.First()
);
await ClickSlider(
Type<Slider>()
.HasAncestor("SettingsPanel")
.InRegion(0f, 0f, 0.5f, 1f),
0.75f
);// Navigation stays in the bottom 15% of the screen
var navButtons = await FindAll<Button>(
Type<Button>().HasAncestor("BottomNav").InRegion(0f, 0f, 1f, 0.15f)
);
Assert.Greater(navButtons.Count(), 0, "Navigation should be at bottom");Read positions of found elements with the static helpers on Search, or await a search's screen position directly:
| Member | Returns |
|---|---|
await search.GetScreenPosition(float searchTime = 10f) |
Vector2 screen centre of the first match; throws TimeoutException if none |
Search.GetScreenCenter(GameObject go) |
Vector2 screen-space centre |
Search.GetScreenBounds(GameObject go) |
Rect screen-space bounds |
Search.GetWorldBounds(GameObject go) |
Bounds from the Renderer, else Collider, else zero-size at the transform |
Comparisons are plain Vector2/Rect maths on top of these:
var header = await Name("Header").Find();
var footer = await Name("Footer").Find();
// Header above footer (screen y grows upwards)
Assert.Greater(Search.GetScreenCenter(header).y, Search.GetScreenCenter(footer).y);
// Label and input on the same row (within 5 px)
var labelPos = await Name("Label").GetScreenPosition();
var inputPos = await Name("InputField").GetScreenPosition();
Assert.Less(Mathf.Abs(labelPos.y - inputPos.y), 5f);
// Container fully contains its child
var container = Search.GetScreenBounds(await Name("Container").Find());
var child = Search.GetScreenBounds(await Name("ChildElement").Find());
Assert.IsTrue(container.Contains(child.min) && container.Contains(child.max));
// 3D overlap
var a = Search.GetWorldBounds(await Name("Crate").IncludeOffScreen().Find());
var b = Search.GetWorldBounds(await Name("Truck").IncludeOffScreen().Find());
Assert.IsTrue(a.Intersects(b));Basic Filters
Proximity
Hierarchy
Spatial
Ordering
Modifiers
Combining
Reflection
- Test Actions
- Click Actions
- Text Input
- Drag Actions
- Gesture Input
- Wait and Find
- Assertions
- GameObject Manipulation