Events & Telemetry (waitForVariable)
world.waitForVariable() is a push-based, event-driven synchronization method in the Specter TypeScript SDK. Instead of repeatedly polling the engine over WebSocket, it pauses test execution until Unreal Engine pushes an instant telemetry event confirming that a tracked variable has transitioned to a specific target value.
What is world.waitForVariable()?
Traditional game test frameworks poll the game state every 100ms over RPCs or WebSockets. This introduces network overhead, creates flakiness, and misses microsecond-level state transitions.
With Specter's event-driven telemetry, the engine notifies your test runner the exact millisecond a state change occurs on the game thread.
Opt-In Performance Model
Unreal Engine has tens of thousands of UPROPERTY variables in memory at any given time. Broadcasting every single variable change by default would destroy CPU frame rates. Instead, you tell Specter which variable to monitor using actor.trackProperty() (or actor.trackAttribute() for GAS).
The Complete Workflow
A standard event-driven test workflow follows four clean steps:
import { test, expect } from '@specter-automation/test';
test('Wave Survival Match Flow', async ({ world }) => {
// 1. Tell Specter to monitor 'MatchState' on the GameState
const gameState = world.actor('GameStateBase');
const matchStateVar = await gameState.trackProperty('MatchState');
// 2. Also track the wave counter on the Spawner actor
const spawner = world.actor('BP_WaveSpawner');
const waveVar = await spawner.trackProperty('CurrentWave');
// 3. Trigger game flow
const menu = world.widget('WBP_MainMenu');
await menu.getByRole('button', { name: 'Start Match' }).click();
// 4. Now waitForVariable flawlessly catches the exact actor's event!
await world.waitForVariable(matchStateVar, 'InProgress');
await world.waitForVariable(waveVar, '5', 10000); // 10s timeout
});
Summary of Tracking Methods
Specter provides specialized tracking registration methods depending on what engine system you are monitoring:
| Method | What It Monitors |
|---|---|
| actor.trackProperty('PropertyName') | Any Blueprint / C++ UPROPERTY (integers, strings, enums, booleans). |
| actor.trackAttribute('Set', 'Attr') | Any GAS Gameplay Attribute (e.g. Health, Mana, Shield on an AttributeSet). |
| world.waitForVariable('Name', 'Value', timeout?) | Waits for any tracked property or attribute to transition to Value. |
How trackProperty Connects to waitForVariable
actor.trackProperty('MatchState')Registers property to monitorUSpecterSubsystemListens on Game Thread & broadcasts FSpecterTimelineEventworld.waitForVariable(...)Promise resolves instantly with event metadata1. Engine-Side Monitoring (USpecterSubsystem in C++)
- Unreal Engine monitors only registered
UPROPERTYvariables. - When a variable value changes on the game thread, the engine immediately constructs an
FSpecterTimelineEventcontaining:eventType: "StandardVariable"
eventName: "MatchState"
oldValue: "WaitingToStart"
newValue: "InProgress"
timestamp: 14.285 // game world seconds - The subsystem broadcasts this event over WebSocket to all connected test clients.
2. Client-Side Event Listener (@specter-automation/test)
- Calling
await world.waitForVariable('MatchState', 'InProgress')registers a one-shot event listener on the WebSocket transport (GLOBAL_timeline-event). - The exact millisecond the engine broadcasts the change, the Promise resolves with the full
SpecterTimelineEventmetadata. - If the variable does not reach the expected value before the timeout (default 5,000ms), it throws a descriptive timeout error.
Inspecting Timestamp Telemetry
waitForVariable returns the resolved event object, allowing you to extract precise engine timestamps for performance benchmarking and analytics:
const boss = world.actor('BP_Boss');
const bossDefeatedVar = await boss.trackProperty('bBossDefeated');
// Trigger combat damage
await player.triggerAbilityByTag('Ability.Attack.Ultimate');
const event = await world.waitForVariable(bossDefeatedVar, 'true', 30000);
console.log(`Boss defeated at engine timestamp: ${event.timestamp}s`);
console.log(`Event type: ${event.eventType}`); // "StandardVariable"
Event Telemetry vs Assertion Matchers
Many of Specter's expect(actor) matchers already use event-driven C++ delegates under the hood (for example, toHaveGameplayTag hooks directly into UAbilitySystemComponent tag delegates, and actor.waitForEvent listens to native Blueprint multicast delegates).
The primary distinction between world.waitForVariable() and expect() matchers lies in timeline telemetry vs test validation:
| Capability | world.waitForVariable() | expect(actor).toHave...() |
|---|---|---|
| Primary Purpose | Timeline Synchronization & Telemetry (pauses until engine event broadcasts) | Test Validation & Assertion (verifies state with rich diffs, tolerances, and soft assertions) |
| Under the Hood | Registers tracked property with USpecterSubsystem | Hybrid engine delegates (GAS, delegates) & auto-retrying fast-path queries |
| Output & Return Value | Returns full SpecterTimelineEvent with exact game timestamps | Resolves to void on pass; outputs detailed assertion failure diff on fail |
| Negation Support | N/A (waits for specific target value) | Full expect().not... negation support |
| Best Used For | Global match phases, level milestones, wave counters, benchmark timestamps | Actor/component state assertions, transforms, health thresholds, visual checks |