class: LocatorAssertions
Specter provides custom expect matchers specifically designed for Unreal Engine. These assertions will automatically poll the engine (retrying until a timeout) until the condition is met, exactly like web Playwright.
Gameplay Ability System (GAS)
Assertions for polling and verifying Gameplay Tags, Attributes, Abilities, and Effects.
expect(actor).toHaveGameplayTag
await expect(enemy).toHaveGameplayTag("Status.Stunned");Arguments
| Name | Type | Description |
|---|---|---|
| tag | string | The Gameplay Tag string (e.g., 'Status.Stunned'). |
expect(actor).toHaveAnyGameplayTags
await expect(enemy).toHaveAnyGameplayTags(["Status.Stunned", "Status.Rooted"]);Arguments
| Name | Type | Description |
|---|---|---|
| tags | string[] | An array of Gameplay Tags. |
expect(actor).toHaveAllGameplayTags
await expect(enemy).toHaveAllGameplayTags(["Status.Stunned", "Status.Burning"]);Arguments
| Name | Type | Description |
|---|---|---|
| tags | string[] | An array of Gameplay Tags. |
expect(actor).toHaveGameplayTagCount
await expect(enemy).toHaveGameplayTagCount("Status.Poisoned", 3);Arguments
| Name | Type | Description |
|---|---|---|
| tag | string | The Gameplay Tag. |
| expectedCount | number | The expected exact count. |
expect(actor).toHaveAttributeValue
await expect(defender).toHaveAttributeValue("MMAttributeSet", "Health", 10);Arguments
| Name | Type | Description |
|---|---|---|
| set | string | The Attribute Set class name. |
| attribute | string | The specific attribute name. |
| expectedValue | number | The exact numeric value to wait for. |
expect(actor).toHaveAttributeBaseValue
await expect(defender).toHaveAttributeBaseValue("MMAttributeSet", "MaxHealth", 100);Arguments
| Name | Type | Description |
|---|---|---|
| set | string | The Attribute Set class name. |
| attribute | string | The specific base attribute name. |
| expectedValue | number | The exact numeric value to wait for. |
expect(actor).toHaveAttributeChange
await expect(defender).toHaveAttributeChange("MMAttributeSet", "Health", {
delta: -attackerDamage,
instigator: attacker,
ability: "Default__GA_Marshmallow_Punch_C"
}, async () => {
await attacker.triggerAbilityByTag("Ability.Attack.Melee");
});Arguments
| Name | Type | Description |
|---|---|---|
| set | string | The Attribute Set class name. |
| attribute | string | The specific attribute name. |
| options | Object | Options including { delta?: number, instigator?: ActorLocator, ability?: string }. |
| action optional | Function | An async callback that triggers the attribute change. |
expect(actor).toHaveGameplayEffect
await expect(hero).toHaveGameplayEffect("/Game/Effects/GE_Shield");Arguments
| Name | Type | Description |
|---|---|---|
| effectClassPath | string | The effect class path. |
expect(actor).toHaveGameplayEffectStackCount
await expect(hero).toHaveGameplayEffectStackCount("/Game/Effects/GE_Bleed", 5);Arguments
| Name | Type | Description |
|---|---|---|
| effectClassPath | string | The effect class path. |
| expectedCount | number | The expected stack count. |
expect(actor).toHaveGameplayEffectLevel
await expect(hero).toHaveGameplayEffectLevel("/Game/Effects/GE_Buff", 2);Arguments
| Name | Type | Description |
|---|---|---|
| effectClassPath | string | The effect class path. |
| expectedLevel | number | The expected level. |
expect(actor).toHaveGameplayAbility
await expect(hero).toHaveGameplayAbility("/Game/Abilities/GA_Dash");Arguments
| Name | Type | Description |
|---|---|---|
| abilityClassPath | string | The ability class path. |
| options optional | Object | Configuration object. Supports { timeout: number }. |
expect(actor).toBeExecutingAbility
await expect(hero).toBeExecutingAbility("/Game/Abilities/GA_Ultimate");Arguments
| Name | Type | Description |
|---|---|---|
| abilityClassPath | string | The ability class path. |
expect(actor).toHaveAbilityCooldown
await expect(hero).toHaveAbilityCooldown("/Game/Abilities/GA_Dash", { operator: "<=", expectedValue: 0 });Arguments
| Name | Type | Description |
|---|---|---|
| abilityClassPath | string | The ability class path. |
| options optional | Object | Configuration object: { operator?: string, expectedValue?: number, timeout?: number }. |
Movement & Spatial
Assertions for positioning, physics, and character movement tests.
expect(actor).toBeFalling
await expect(enemy).toBeFalling();expect(actor).toBeCrouching
await expect(player).toBeCrouching();expect(actor).toBeFlying
await expect(drone).toBeFlying();expect(actor).toHaveVelocity
// Pass if speed >= 600 cm/s
await expect(playerActor).toHaveVelocity(600);Arguments
| Name | Type | Description |
|---|---|---|
| expectedSpeed | number | The minimum expected speed threshold. |
expect(actor).toBeWithinDistanceOf
await expect(enemy).toBeWithinDistanceOf(world.actor('Player'), 200);Arguments
| Name | Type | Description |
|---|---|---|
| otherActorLocator | ActorLocator | The locator of the target actor. |
| maxDistance | number | The maximum distance allowed (in cm). |
expect(actor).toBeFacing
await expect(turret).toBeFacing(world.actor('Player'));Arguments
| Name | Type | Description |
|---|---|---|
| otherActorLocator | ActorLocator | The locator of the target actor. |
expect(actor).toHaveMovedFurtherThan
const startPos = await enemy.getLocation();
await world.console('cheat knockback');
await expect(enemy).toHaveMovedFurtherThan(startPos, 500);Arguments
| Name | Type | Description |
|---|---|---|
| startPos | Vector3 | The starting 3D coordinate. |
| distance | number | The minimum distance in Unreal Units (cm) the actor must have moved. |
expect(actor).toHaveLocation
await expect(enemy).toHaveLocation({ x: 100, y: 200, z: 0 }, 10.0);Arguments
| Name | Type | Description |
|---|---|---|
| expectedLocation | Vector3 | The expected {x, y, z} world coordinate. |
| tolerance optional | number | The maximum acceptable distance from the target in cm (Default: 5.0). |
| options optional | Object | Configuration object. Supports { timeout: number }. |
Animation & Montages
Assertions for testing attack hitboxes or ability timings.
expect(skeleton).toBePlayingMontage
await expect(heroSkeleton).toBePlayingMontage("/Game/Animations/AM_Attack");Arguments
| Name | Type | Description |
|---|---|---|
| montageClassPath | string | The montage class path. |
expect(skeleton).toHaveMontageSection
await expect(bossSkeleton).toHaveMontageSection("Phase2");Arguments
| Name | Type | Description |
|---|---|---|
| sectionName | string | The name of the montage section. |
AI & Behavior Trees
Assertions for asserting enemy decision-making.
expect(aiController).toBeRunningBehaviorTree
await expect(aiCon).toBeRunningBehaviorTree("/Game/AI/BT_Zombie");Arguments
| Name | Type | Description |
|---|---|---|
| treeClassPath | string | The behavior tree class path. |
expect(aiController).toHaveBlackboardValue
await expect(aiCon).toHaveBlackboardValue("TargetActor", world.actor('Player'));Arguments
| Name | Type | Description |
|---|---|---|
| keyName | string | The blackboard key name. |
| expectedValue | any | The expected value for the key. |
expect(aiController).toHaveLineOfSightTo
await expect(aiCon).toHaveLineOfSightTo(world.actor('Player'));Arguments
| Name | Type | Description |
|---|---|---|
| otherActorLocator | ActorLocator | The locator of the target actor. |
Camera & FOV
Assertions for camera field of view, camera shakes, and view targets.
expect(actor).toHaveCameraFOV
await expect(player).toHaveCameraFOV(90, { tolerance: 1.0 });Arguments
| Name | Type | Description |
|---|---|---|
| expectedFOV | number | The expected FOV angle in degrees. |
| options optional | Object | Configuration object. Supports { tolerance?: number, timeout?: number }. Default tolerance: 0.5. |
expect(actor).toBeCameraShaking
// Assert camera is shaking after explosion
await expect(player).toBeCameraShaking();
// Assert camera is NOT shaking after it subsides
await expect(player).not.toBeCameraShaking();expect(actor).toHaveCameraViewTarget
await expect(player).toHaveCameraViewTarget('BP_PlayerCharacter');Arguments
| Name | Type | Description |
|---|---|---|
| actorName | string | The expected name of the camera's view target actor. |
AI Navigation
Assertions for AI pathfinding and navigation state.
Team System
Assertions for the Generic Team Agent Interface.
expect(actor).toHaveTeamId
await expect(player).toHaveTeamId(0);
await expect(spectator).not.toHaveTeamId(1);Arguments
| Name | Type | Description |
|---|---|---|
| expectedTeamId | number | The expected team ID (255 = NoTeam). |
expect(actor).toBeHostileTowards
await expect(player).toBeHostileTowards(enemy);
await expect(player).not.toBeHostileTowards(ally);Arguments
| Name | Type | Description |
|---|---|---|
| otherActor | ActorLocator | The other actor to evaluate attitude towards. |
State & Rendering
expect(actor).toHaveCount
await expect(world.actor('BP_Minion')).toHaveCount(5);Arguments
| Name | Type | Description |
|---|---|---|
| expectedCount | number | The expected number of matching elements. |
| options optional | Object | Configuration object. Supports { timeout: number }. |
expect(actor).toBeVisible
await expect(world.actor('BP_Minion_Range')).toBeVisible({ onScreen: true, timeout: 10_000 });Arguments
| Name | Type | Description |
|---|---|---|
| options optional | Object | Configuration object. Supports { onScreen: boolean, timeout: number }. |
expect(actor).toBeInViewport
await expect(enemy).toBeInViewport();Arguments
| Name | Type | Description |
|---|---|---|
| options optional | Object | Configuration object. Supports { timeout: number }. |
expect(actor).toBeDestroyed
// Assert actor is destroyed after lethal damage
await expect(enemyKeep.locator).toBeDestroyed();
// Assert actor has NOT been destroyed
await expect(player).not.toBeDestroyed();Arguments
| Name | Type | Description |
|---|---|---|
| options optional | Object | Configuration object. Supports { timeout: number }. |
expect(actor).toBeNiagaraActive
await expect(explosionEffect).toBeNiagaraActive();Arguments
| Name | Type | Description |
|---|---|---|
| options optional | Object | Configuration object. Supports { timeout: number }. |
expect(actor).toHaveNiagaraAsset
await expect(fireVFX).toHaveNiagaraAsset("NS_Fire_Loop");Arguments
| Name | Type | Description |
|---|---|---|
| expectedAsset | string | The expected Niagara system asset path substring. |
| options optional | Object | Configuration object. Supports { timeout: number }. |
expect(actor).toBeAudioPlaying
await expect(footstepAudio).toBeAudioPlaying();Arguments
| Name | Type | Description |
|---|---|---|
| options optional | Object | Configuration object. Supports { timeout: number }. |
expect(actor).toHaveSoundAsset
await expect(bgmAudio).toHaveSoundAsset("SoundWave_BossTheme");Arguments
| Name | Type | Description |
|---|---|---|
| expectedSound | string | The expected sound asset path or name substring. |
| options optional | Object | Configuration object. Supports { timeout: number }. |
expect(actor).toBePhysicsSimulating
await expect(deadEnemy).toBePhysicsSimulating({ boneName: "pelvis" });Arguments
| Name | Type | Description |
|---|---|---|
| options optional | Object | Configuration object. Supports { boneName?: string, timeout?: number }. |
expect(actor).toBePhysicsAsleep
await expect(crate).toBePhysicsAsleep();Arguments
| Name | Type | Description |
|---|---|---|
| options optional | Object | Configuration object. Supports { boneName?: string, timeout?: number }. |
Enhanced Input (UE5)
Assertions for verifying active Input Mapping Contexts and Input Action values.
expect(actor).toHaveInputMappingContext
await expect(player).toHaveInputMappingContext("IMC_DefaultPlayer");Arguments
| Name | Type | Description |
|---|---|---|
| contextNameOrPath | string | The IMC asset name or path. |
| options optional | Object | Configuration object. Supports { timeout: number }. |
expect(actor).toHaveInputActionValue
await expect(player).toHaveInputActionValue("IA_Sprint", true);Arguments
| Name | Type | Description |
|---|---|---|
| actionNameOrPath | string | The Input Action name or path. |
| expectedValue | any | The expected action value (boolean, float, or Vector3). |
| options optional | Object | Configuration object. Supports { timeout: number }. |
expect(actor).toHavePlayerScore
await expect(player).toHavePlayerScore(100, { operator: ">=" });Arguments
| Name | Type | Description |
|---|---|---|
| expectedScore | number | The expected score threshold. |
| options optional | Object | Configuration object: { operator?: '>' | '<' | '>=' | '<=' | '===', timeout?: number }. |
UI & Widgets
Assertions for validating player HUDs or Menus.
expect(widget).toBeVisible
const modal = world.widget("ConfirmPurchaseModal");
await expect(modal).toBeVisible();expect(widget).toBeEnabled
const submitBtn = world.widget("SubmitButton");
await expect(submitBtn).toBeEnabled();expect(widget).toHaveText
const title = world.widget("HeaderTitle");
await expect(title).toHaveText("Main Menu");Arguments
| Name | Type | Description |
|---|---|---|
| expectedText | string | The exact expected text string. |
| options optional | Object | Configuration object. Supports { timeout: number }. |
expect(widget).toContainText
const subtitle = world.widget("WBP_Modal").getChild("SubtitleText");
await expect(subtitle).toContainText("cannot be undone");Arguments
| Name | Type | Description |
|---|---|---|
| substring | string | The substring to search for within the widget text. |
| options optional | Object | Configuration object. Supports { timeout: number }. |
expect(widget).toHaveValue
const usernameInput = world.widget("WBP_Login").getChild("UsernameInput");
await expect(usernameInput).toHaveValue("Player1");Arguments
| Name | Type | Description |
|---|---|---|
| expectedValue | string | The expected input string value. |
| options optional | Object | Configuration object. Supports { timeout: number }. |