Locators
Locators are the central building block of Specter's auto-waiting and retry-ability. In a nutshell, a locator represents a way to find 3D game Actors or 2D UMG UI Widgets in Unreal Engine at any moment.
Core Concepts
Unlike raw engine object pointers, a Specter Locator is lazy-evaluated. When you create a locator, Specter does not immediately search the engine memory. Instead, the locator remembers how to find the target object and evaluates it right when an action (e.g. pressKey(), click()) or assertion (e.g. expect()) is performed.
ActorLocator (3D World)
Represents native AActor instances, Pawns, Characters, or Components residing in the Unreal Engine game world.
WidgetLocator (2D UMG UI)
Represents Slate and UMG UUserWidget elements, buttons, text blocks, progress bars, and inventory slots.
Quick Guide: Actor Locators
Querying actors in 3D space supports multiple strategies—from Blueprint class basenames to Gameplay Tags and controller ownership filters:
// 1. Class Name Selector (Default)
const player = world.actor('BP_Hero_Steel');
// 2. Tag Prefix Selector (Playwright-style)
const boss = world.actor('Tag:Enemy.Boss');
const bossAlt = world.actor('tag=Enemy.Boss');
// 3. Explicit Tag Method
const bossExplicit = world.actorByTag('Enemy.Boss');
// 4. Narrow down to human player vs AI bots
const localPlayer = world.actor('BP_Hero_Shooter').isLocallyControlled(true);
// 5. Access specific components on an actor
const movement = player.getByComponent('CharacterMovementComponent');Quick Guide: Widget Locators
Locating UI elements is straightforward using UMG widget names, hierarchy child getters, or text matching:
const modal = world.widget('WBP_ConfirmModal');
// 1. Fetch widget text into a variable using .getText()
const text = await modal.getChild('TitleText').getText();
expect(text).toBe('Are you sure?');
// 2. Or use Auto-Retrying Matchers (auto-polls engine ticks for up to 5s)
await expect(modal.getChild('TitleText')).toHaveText('Are you sure?');
await expect(modal.getChild('SubtitleText')).toContainText('cannot be undone');
// 3. Assert input fields & interactive state
await expect(modal.getChild('UsernameInput')).toHaveValue('Player1');
await expect(modal).toBeVisible();
await expect(modal.getChild('ConfirmButton')).toBeEnabled();
// 4. Custom timeout & negation
await expect(modal.getChild('TitleText')).toHaveText('Success!', { timeout: 10000 });
await expect(modal).not.toBeVisible();Auto-Waiting & Retry-Ability
Actions performed on locators (like pressKey(), triggerAbilityByTag(), or click()) automatically wait for the underlying actor or widget to be spawned, visible, and interactive before executing.
// Specter automatically waits for the actor/widget to spawn before performing actions
await steel.pressKey('SpaceBar');
await modal.getChild('ConfirmButton').click();
// Engine-aware auto-retrying assertions (No snapshot getText() antipatterns!)
await expect(steel).toBeFalling();
await expect(modal.getChild('TitleText')).toHaveText('Are you sure?');Filtering & Indexing
When a locator matches multiple actors or widgets in the engine, you can narrow down your target using zero-indexed selectors like .first(), .last(), and .nth(), or chain filters like .isLocallyControlled():
// Select multiple matching actors
const minions = world.actor('BP_Minion');
// Pick the first, last, or specific index (0-indexed)
const leaderMinion = minions.first();
const trailingMinion = minions.last();
const thirdMinion = minions.nth(2);
// Iterate over all matching locators using for...of
for (const minion of await minions.all()) {
await expect(minion).toHavePropertyValue('IsActive', true);
}
// Filter by controller ownership or custom criteria
const localHero = world.actor('BP_Hero').isLocallyControlled(true).first();đź’ˇ Best Practice: Game Object Model
Instead of scattering raw locator strings like world.actor('BP_Hero_Steel') across your tests, encapsulate them into reusable Game Object Model classes. Learn more in the Game Object Model Guide!