- Fix: UIScene n'était jamais lancée (active: false, jamais scene.launch)
donc l'event 'game-over' du serveur n'était jamais affiché — la partie
semblait ne pas avoir de fin. Remplacé par un panneau Vue dans GameView
(vainqueur, classement trié, bouton Quitter via vue-router). UIScene.js
supprimée (code mort).
- Extraction de client/src/utils/hudUtils.js (constantes ATTRS/couleurs et
gaugePercent) depuis GameView.vue pour les rendre testables isolément.
- Tests unitaires client (Vitest) : store Pinia (game.js) et hudUtils
(24 tests, client/test/unit/).
- Tests d'intégration E2E (Playwright, e2e/) avec de vrais clients
Socket.io et rendu Phaser réel dans Chromium :
* game-flow.spec.js : lobby → démarrage → main de 4 cartes → résolution
* item-shop.spec.js : boutique, bouton désactivé si jauge insuffisante,
achat retire l'objet pour tous + effet appliqué + affiché sur la table
* game-over.spec.js : partie complète (12 tours) → panneau de fin
webServer démarre automatiquement client+serveur ; ajout d'une route
GET /health côté Express pour permettre à Playwright de détecter un
serveur déjà démarré.
- README : documentation des 3 niveaux de tests (unitaires serveur,
unitaires client, intégration E2E) et de la nouvelle arborescence.
113 lines
4.0 KiB
JavaScript
113 lines
4.0 KiB
JavaScript
import { test, expect } from '@playwright/test'
|
|
import {
|
|
installPhaserDebugHook, createRoom, joinRoom, startGame,
|
|
playCard, playFullTurn, getShopItemIds, getMyGauges, buyShopItem, hoverShopItem
|
|
} from './helpers.js'
|
|
|
|
/**
|
|
* Tests d'intégration E2E : boutique d'objets (achat, effets permanents,
|
|
* disparition de la boutique une fois acheté, retrait bloqué si jauge
|
|
* insuffisante).
|
|
*/
|
|
test.describe('Boutique d\'objets', () => {
|
|
test('la boutique affiche les 4 objets au démarrage de la partie', async ({ browser }) => {
|
|
const ctx1 = await browser.newContext()
|
|
const ctx2 = await browser.newContext()
|
|
const p1 = await ctx1.newPage()
|
|
const p2 = await ctx2.newPage()
|
|
await installPhaserDebugHook(p1)
|
|
await installPhaserDebugHook(p2)
|
|
|
|
const roomId = await createRoom(p1, 'Alice')
|
|
await joinRoom(p2, 'Bob', roomId)
|
|
await startGame(p1)
|
|
await p1.waitForTimeout(2000)
|
|
|
|
const ids = await getShopItemIds(p1)
|
|
expect(ids.sort()).toEqual(['medkit', 'plow', 'royal_seal', 'trebuchet'].sort())
|
|
|
|
await ctx1.close()
|
|
await ctx2.close()
|
|
})
|
|
|
|
test('le bouton ACHETER n\'est pas interactif tant que la jauge est insuffisante', async ({ browser }) => {
|
|
const ctx1 = await browser.newContext()
|
|
const ctx2 = await browser.newContext()
|
|
const p1 = await ctx1.newPage()
|
|
const p2 = await ctx2.newPage()
|
|
await installPhaserDebugHook(p1)
|
|
await installPhaserDebugHook(p2)
|
|
|
|
const roomId = await createRoom(p1, 'Alice')
|
|
await joinRoom(p2, 'Bob', roomId)
|
|
await startGame(p1)
|
|
await p1.waitForTimeout(2000)
|
|
|
|
// Jauge à 0 en tout début de partie : le bouton ACHETER du Trébuchet ne
|
|
// doit pas être interactif (hoverShopItem retourne null dans ce cas).
|
|
const btnCenter = await hoverShopItem(p1, 'trebuchet')
|
|
expect(btnCenter).toBeNull()
|
|
|
|
await ctx1.close()
|
|
await ctx2.close()
|
|
})
|
|
|
|
test('acheter un objet le retire de la boutique pour TOUS les joueurs, et applique son effet permanent', async ({ browser }) => {
|
|
const ctx1 = await browser.newContext()
|
|
const ctx2 = await browser.newContext()
|
|
const p1 = await ctx1.newPage()
|
|
const p2 = await ctx2.newPage()
|
|
await installPhaserDebugHook(p1)
|
|
await installPhaserDebugHook(p2)
|
|
|
|
const roomId = await createRoom(p1, 'Alice')
|
|
await joinRoom(p2, 'Bob', roomId)
|
|
await startGame(p1)
|
|
await p1.waitForTimeout(2000)
|
|
|
|
// Accumuler assez de guerre pour P1 en jouant sa meilleure carte de guerre
|
|
// à chaque tour, pendant que P2 joue sur un autre attribut pour ne pas
|
|
// rivaliser sur la jauge visée.
|
|
let gauges = await getMyGauges(p1)
|
|
let attempts = 0
|
|
while (gauges.war < 3 && attempts < 6) {
|
|
await playFullTurn([p1, p2], ['war', 'health'])
|
|
gauges = await getMyGauges(p1)
|
|
attempts++
|
|
}
|
|
expect(gauges.war).toBeGreaterThanOrEqual(3)
|
|
|
|
const idsBefore = await getShopItemIds(p1)
|
|
expect(idsBefore).toContain('trebuchet')
|
|
|
|
const bought = await buyShopItem(p1, 'trebuchet')
|
|
expect(bought).toBe(true)
|
|
|
|
// Jouer le tour pour déclencher la résolution de l'achat en attente
|
|
await playFullTurn([p1, p2], ['war', 'health'])
|
|
|
|
const idsAfterP1 = await getShopItemIds(p1)
|
|
const idsAfterP2 = await getShopItemIds(p2)
|
|
expect(idsAfterP1).not.toContain('trebuchet')
|
|
expect(idsAfterP2).not.toContain('trebuchet')
|
|
expect(idsAfterP1.length).toBe(3)
|
|
|
|
// Vérifier que l'objet apparaît dans "mes objets" (posés à côté de ma main)
|
|
const myItemsCount = await p1.evaluate(() => {
|
|
const game = window.__ALL_GAMES__[window.__ALL_GAMES__.length - 1]
|
|
return game.scene.getScene('GameScene').myItemTokens.length
|
|
})
|
|
expect(myItemsCount).toBe(1)
|
|
|
|
// Et sous le slot de P1 depuis la perspective de P2 (adversaire)
|
|
const opponentItemsCount = await p2.evaluate(() => {
|
|
const game = window.__ALL_GAMES__[window.__ALL_GAMES__.length - 1]
|
|
return game.scene.getScene('GameScene').opponentItemTokens.length
|
|
})
|
|
expect(opponentItemsCount).toBe(1)
|
|
|
|
await ctx1.close()
|
|
await ctx2.close()
|
|
})
|
|
})
|