Skip to content

Commit

Permalink
test(e2e): add toHaveCommand() assertion (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
garrappachc authored Nov 11, 2024
1 parent 9a4a853 commit eced45e
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 44 deletions.
5 changes: 5 additions & 0 deletions src/shared/errors/timeout.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class TimeoutError extends Error {
constructor(public readonly timeoutMs: number) {
super(`timed out after ${timeoutMs}ms`)
}
}
13 changes: 13 additions & 0 deletions src/utils/with-timeout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { TimeoutError } from '../shared/errors/timeout.error'

export const withTimeout = async <T>(promise: Promise<T>, timeoutMs: number): Promise<T> => {
let timeout: ReturnType<typeof setTimeout>
return Promise.race([
promise,
new Promise<never>((_resolve, reject) => {
timeout = setTimeout(() => {
reject(new TimeoutError(timeoutMs))
}, timeoutMs)
}),
]).finally(() => clearTimeout(timeout))
}
5 changes: 2 additions & 3 deletions tests/20-game/01-configure-game-server.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { expect } from '@playwright/test'
import { launchGame } from '../fixtures/launch-game'
import { launchGame, expect } from '../fixtures/launch-game'
import { secondsToMilliseconds } from 'date-fns'
import { GamePage } from '../pages/game.page'

Expand All @@ -22,7 +21,7 @@ launchGame('configure game server', async ({ players, gameNumber, page, gameServ
await expect(page.gameEvent('Game server initialized')).toBeVisible()
await expect(page.joinGameButton()).toBeVisible()

expect(gameServer.addedPlayers.some(p => p.steamId64 === player.steamId)).toBe(true)
await expect(gameServer).toHaveCommand(`sm_game_player_add ${player.steamId}`)
}),
)

Expand Down
16 changes: 5 additions & 11 deletions tests/20-game/04-cleanup-game-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,9 @@ launchGameAndStartMatch('cleanup game server', async ({ gameNumber, gameServer }
await waitABit(secondsToMilliseconds(3))
await gameServer.matchEnds()

await expect
.poll(
() =>
gameServer.commands.some(cmd => /logaddress_del/.test(cmd)) &&
gameServer.addedPlayers.length === 0 &&
gameServer.commands.some(cmd => cmd === 'sm_game_player_whitelist 0'),
{
timeout: secondsToMilliseconds(40),
},
)
.toBe(true)
await expect(gameServer).toHaveCommand(/^logaddress_del/, {
timeout: secondsToMilliseconds(40),
})
await expect(gameServer).toHaveCommand('sm_game_player_whitelist 0')
expect(gameServer.addedPlayers.length).toBe(0)
})
34 changes: 9 additions & 25 deletions tests/30-player-substitutes/03-manages-in-game.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,16 @@ launchGameAndStartMatch('manages in-game', async ({ gameNumber, users, gameServe
await expect(adminsPage.playerLink(mayflower.playerName)).toBeVisible()
await adminsPage.requestSubstitute(mayflower.playerName)

await expect
.poll(
() =>
gameServer.commands.some(command =>
command.includes(`say Looking for replacement for ${mayflower.playerName}...`),
),
{ timeout: secondsToMilliseconds(1) },
)
.toBe(true)
await expect(gameServer).toHaveCommand(
`say Looking for replacement for ${mayflower.playerName}...`,
{ timeout: secondsToMilliseconds(1) },
)

await tommyGunsPage.replacePlayer(mayflower.playerName)

await expect
.poll(
() =>
gameServer.commands.some(command =>
command.includes(`sm_game_player_add ${tommyGun.steamId}`),
) &&
gameServer.commands.some(command =>
command.includes(`sm_game_player_del ${mayflower.steamId}`),
) &&
gameServer.commands.some(command =>
command.includes(
`say ${mayflower.playerName} has been replaced by ${tommyGun.playerName}`,
),
),
)
.toBe(true)
await expect(gameServer).toHaveCommand(`sm_game_player_add ${tommyGun.steamId}`)
await expect(gameServer).toHaveCommand(`sm_game_player_del ${mayflower.steamId}`)
await expect(gameServer).toHaveCommand(
`say ${mayflower.playerName} has been replaced by ${tommyGun.playerName}`,
)
})
2 changes: 1 addition & 1 deletion tests/fixtures/launch-game-and-initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ export const launchGameAndInitialize = launchGame.extend({
},
})

export { expect } from '@playwright/test'
export { expect } from './launch-game'
2 changes: 1 addition & 1 deletion tests/fixtures/launch-game-and-start-match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ export const launchGameAndStartMatch = launchGameAndInitialize.extend({
},
})

export { expect } from '@playwright/test'
export { expect } from './launch-game-and-initialize'
2 changes: 1 addition & 1 deletion tests/fixtures/launch-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ export const launchGame = mergeTests(authUsers, simulateGameServer).extend<{
},
})

export { expect } from '@playwright/test'
export { expect } from './simulate-game-server'
66 changes: 64 additions & 2 deletions tests/fixtures/simulate-game-server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { test } from '@playwright/test'
import { test, expect as baseExpect } from '@playwright/test'
import { GameServerSimulator } from '../game-server-simulator'
import { withTimeout } from '../../src/utils/with-timeout'
import { secondsToMilliseconds } from 'date-fns'
import { TimeoutError } from '../../src/shared/errors/timeout.error'

export const simulateGameServer = test.extend<{ gameServer: GameServerSimulator }>({
// eslint-disable-next-line no-empty-pattern
Expand All @@ -18,4 +21,63 @@ export const simulateGameServer = test.extend<{ gameServer: GameServerSimulator
},
})

export { expect } from '@playwright/test'
export const expect = baseExpect.extend({
async toHaveCommand(
gameServer: GameServerSimulator,
command: string | RegExp,
options?: { timeout?: number },
) {
const assertionName = 'toHaveCommand'
let pass = false

const hasCommand = () =>
gameServer.commands.some(cmd => {
if (command instanceof RegExp) {
return command.test(cmd)
} else {
return cmd.includes(command)
}
})

try {
await withTimeout(
new Promise<void>(resolve => {
if (hasCommand()) {
resolve()
}

setInterval(() => {
if (hasCommand()) {
resolve()
}
}, secondsToMilliseconds(0.5))
}),
options?.timeout ?? this.timeout,
)
pass = true
} catch (error) {
if (error instanceof TimeoutError) {
} else {
throw error
}
}

const message = () => {
const header = this.utils.matcherHint(
assertionName,
gameServer.commands.join('\n'),
command,
{ isNot: this.isNot },
)
return `${header} \n\nExpected: ${this.utils.printExpected(command)}\nReceived: ${this.utils.printReceived(gameServer.commands.join('\n'))}`
}

return {
pass,
message,
name: assertionName,
expected: command,
actual: gameServer.commands.join('\n'),
}
},
})

0 comments on commit eced45e

Please sign in to comment.