Multiplayer & Dedicated Server Testing

Automating multiplayer games in Unreal Engine has traditionally been one of game development's hardest challenges. Specter leverages Playwright's multi-context architecture to let you drive a Dedicated Server and multiple game clients simultaneously inside a single test file.

Defining Multi-Client Fixtures with createWorld

In Specter, the default world fixture connects to the authoritative Server (or host PIE instance). Using baseTest.extend() and the createWorld helper, you can spin up additional client connection fixtures (e.g. client1 on port 8182, client2 on port 8183):

import { test as baseTest, SpecterWorld } from '@specter/test';

// Extend baseTest: 'world' is the default server, use createWorld for additional clients
export const test = baseTest.extend<{
  client1: SpecterWorld;
  client2: SpecterWorld;
}>({
  client1: async ({ createWorld }, use) => {
    const client = await createWorld({ 
      port: 8182, 
      name: 'Client 1',
      autoJoinHost: true, 
      hostAddress: '127.0.0.1:7777' 
    });
    await use(client);
  },
  client2: async ({ createWorld }, use) => {
    const client = await createWorld({ 
      port: 8183, 
      name: 'Client 2',
      autoJoinHost: true, 
      hostAddress: '127.0.0.1:7777' 
    });
    await use(client);
  },
});

Writing Multi-Client Tests

Once defined, inject your multi-client fixtures into your test parameters. Each client instance controls its own PlayerController and UMG viewport independently.

import { test, expect } from './multiplayer-fixtures';

// 'world' is the default server world; client1 & client2 are injected fixtures
test('Player 1 ability damage replicates to Player 2', async ({ 
  world, 
  client1, 
  client2 
}) => {
  // 1. Client 1 (Steel) triggers an ability targeting Player 2
  await client1.actor('BP_Hero_Steel').pressKey('Q');

  // 2. Assert that Client 2 receives the replicated health reduction on their character
  await expect(client2.actor('BP_Hero_Enemy')).toHavePropertyValue('Health', 80);

  // 3. Verify server authority ground truth directly on the default server world
  await expect(world.actor('BP_Hero_Enemy')).toHavePropertyValue('Health', 80);
});

Replication & Server Authority

When Client 1 triggers a server RPC (like casting an ability or firing a projectile), Specter allows you to assert state on both the receiving client (client2) and the authoritative server process (serverWorld).

  • Client-Side Replication: Ensures RPCs and replicated variables (`UPROPERTY(Replicated)`) reach other connected players.
  • Server Authority Checks: Guarantees that client-predicted state changes don't desync from the dedicated server's ground truth.

Simulating Network Conditions (Ping & Packet Loss)

Don't let your multiplayer game break under high ping or bad connections. Specter provides built-in network condition emulation so you can test client prediction and server reconciliation under laggy conditions:

test('Replication under 150ms latency and 5% packet loss', async ({ world, client1, client2 }) => {
  // 1. Simulate high ping & packet loss on Client 1's network connection
  await client1.network.setEmulatedLatency(150); // 150ms ping
  await client1.network.setPacketLoss(5);       // 5% packet loss

  // 2. Client 1 triggers an ability targeting Player 2
  await client1.actor('BP_Hero_Steel').pressKey('Q');

  // 3. Assert Client 2 receives the replicated health reduction on their character
  await expect(client2.actor('BP_Hero_Enemy')).toHavePropertyValue('Health', 80);

  // 4. Verify authoritative server ground truth on the world fixture
  await expect(world.actor('BP_Hero_Enemy')).toHavePropertyValue('Health', 80);
});

⚡ Multi-PIE & Standalone Process Support

Specter seamlessly connects to Unreal Engine Play-In-Editor (PIE) with multiple viewports enabled, or to standalone dedicated server and client processes running across separate ports in headless CI/CD builds!