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!