Test Fixtures

Fixturesare Playwright's dependency injection system, allowing you to automatically initialize and tear down your Game Objects and spawned actors with zero test boilerplate.

The Boilerplate Problem

Without fixtures, instantiating Game Objects and spawning actors at the top of every test creates repetitive setup code:

import { test, expect } from '@specter-automation/test';
import { Steel } from '../objects/Steel';
import { KeepObject } from '../objects/KeepObject';

test('Hero destroys enemy keep', async ({ world }) => {
  // We have to instantiate these in EVERY test...
  const steel = new Steel(world);
  const enemyKeep = new KeepObject(world, 2);
  
  const steelActor = await steel.spawn({
    location: { x: 497, y: 351, z: 100 },
    rotation: { pitch: 0, yaw: 180, roll: 0 },
  });
  
  await steel.moveTo(steelActor, enemyKeep.locator);
  await steelActor.triggerAbilityByTag(steel.abilityShieldBash);
  await expect(enemyKeep.locator).toBeDestroyed();
});

Creating Custom Fixtures

Extend the base test object to automatically provide steelActor and enemyKeep to any test that requests them in a fixtures.ts file:

import { test as base, ActorLocator, SpecterWorld } from '@specter-automation/test';
import { Steel } from '../objects/Steel';
import { KeepObject } from '../objects/KeepObject';

// 1. Declare the types of your fixtures
type MyFixtures = {
  server: { 
    heroes: { steel: Steel } 
  };
  steelActor: ActorLocator;
  enemyKeep: KeepObject;
};

// 2. Extend the base test object with your custom fixtures
export const test = base.extend<MyFixtures>({
  
  // Set up the 'server' systems fixture
  server: async ({ world }, use) => {
    await use({
      heroes: { steel: new Steel(world) }
    });
  },
  
  // Set up the steel actor using the factory method!
  steelActor: async ({ server }, use) => {
    const steelActor = await server.heroes.steel.spawn({
      location: { x: 497.049522, y: 351.416008, z: 100.0 },
      rotation: { pitch: 0, yaw: 180, roll: 0 },
    });
    
    // Yield the spawned actor to the test
    await use(steelActor);
    
    // Teardown phase (optional): Clean up after the test completes
    // e.g. await steelActor.callFunction('Destroy');
  },

  // Define how 'enemyKeep' is created
  enemyKeep: async ({ world }, use) => {
    const keep = new KeepObject(world, 2);
    await use(keep);
  }
});

// Re-export expect so you only need to import from this file
export { expect } from '@specter-automation/test';

Clean Test Execution

Now your tests request exactly what they need in parameters. Playwright lazily initializes each fixture on demand:

// Import 'test' from our custom fixture file, NOT '@specter-automation/test'!
import { test, expect } from '../fixtures';

test('Hero destroys enemy keep', async ({ server, steelActor, enemyKeep }) => {
  // Notice we don't instantiate or spawn anything. Playwright handles it automatically!
  
  // Move toward enemy keep using our GOM helper method
  await server.heroes.steel.moveTo(steelActor, enemyKeep.locator);
  
  // Use properties from our server GOM fixture, and the actor from our steelActor fixture!
  await steelActor.triggerAbilityByTag(server.heroes.steel.abilityShieldBash);
  
  await expect(enemyKeep.locator).toBeDestroyed();
});