Cause : le serveur émet 'round-result' (déclenche l'animation de révélation
des cartes/gains sur le plateau Phaser, ~3.5s avant le tour suivant) et
'game-state' en phase 'resolve' (déjà avec les jauges/scores post-résolution
via getPlayersInfo) au même instant. Le HUD Vue (alimenté directement par
gameStore.players) sautait donc à ses nouvelles valeurs INSTANTANÉMENT, en
même temps que l'animation de résolution démarrait tout juste sur le
plateau — les compteurs changeaient avant/pendant que le joueur voie
l'animation qui justifie ce changement, au lieu d'être synchronisés avec
elle.
Fix : le store Pinia (client/src/stores/game.js) gèle désormais
players/currentTurn pendant toute la phase 'resolve' — les valeurs reçues
sont stockées dans un buffer interne (_pendingPlayers/_pendingCurrentTurn)
au lieu d'être appliquées immédiatement, et ne sont propagées à l'affichage
qu'au game-state suivant dont la phase n'est PLUS 'resolve' (typiquement
'selecting' du tour suivant, ou 'end'). Le reste de l'état (phase, main,
objets/bâtiments/événements) continue de se mettre à jour normalement et
immédiatement — seul l'affichage des jauges/scores est différé.
6 nouveaux tests unitaires (game.store.test.js) couvrant le gel/dégel du
buffer, la rediffusion de plusieurs game-state en phase 'resolve'
successifs, la transition directe vers 'end', et le nettoyage du buffer
par reset().
Nouveau test E2E (hud-resolution-timing.spec.js) confirmé discriminant :
échoue de façon reproductible (2/2 tentatives) sans le fix ("0 pts" attendu
pendant resolve, "5 pts" déjà affiché), passe systématiquement avec le fix
(stable sur 3 répétitions). Attente active de la phase 'resolve' via
expect.poll plutôt qu'un délai fixe, pour éviter la fenêtre d'observation
manquée sous charge variable.
60 tests unitaires client + 166 tests unitaires serveur + suite
game-flow.spec.js (4 tests, non liée à ce fix) toujours au vert.
91 lines
4.1 KiB
JavaScript
91 lines
4.1 KiB
JavaScript
import { test, expect } from '@playwright/test'
|
|
import {
|
|
installPhaserDebugHook, createRoom, joinRoom, startGame,
|
|
selectHandCard, confirmSelectedCard
|
|
} from './helpers.js'
|
|
|
|
/**
|
|
* Régression : le HUD Vue (jauges/scores en haut de l'écran) sautait à sa
|
|
* nouvelle valeur DÈS que le serveur résolvait le tour ('round-result' +
|
|
* 'game-state' en phase 'resolve' émis au même instant), en même temps que
|
|
* l'animation de révélation des cartes/gains démarrait tout juste sur le
|
|
* plateau Phaser (qui dure ~3.5s avant le tour suivant) — donnant
|
|
* l'impression que les compteurs se mettaient à jour "au mauvais moment".
|
|
*
|
|
* Fix : le store Pinia (client/src/stores/game.js) gèle désormais
|
|
* players/currentTurn pendant toute la phase 'resolve', et ne les applique
|
|
* qu'au game-state suivant dont la phase n'est plus 'resolve' (typiquement
|
|
* 'selecting' du tour suivant).
|
|
*/
|
|
test.describe('Synchronisation du HUD avec la résolution du tour', () => {
|
|
test('le score du HUD reste figé pendant la phase resolve, puis se met à jour au tour suivant', async ({ browser }) => {
|
|
test.setTimeout(60_000)
|
|
|
|
const ctx1 = await browser.newContext()
|
|
const ctx2 = await browser.newContext()
|
|
const p1 = await ctx1.newPage()
|
|
const p2 = await ctx2.newPage()
|
|
const errors = []
|
|
p1.on('pageerror', e => errors.push(e.message))
|
|
await installPhaserDebugHook(p1)
|
|
await installPhaserDebugHook(p2)
|
|
|
|
const roomId = await createRoom(p1, 'Alice')
|
|
await joinRoom(p2, 'Bob', roomId)
|
|
await startGame(p1)
|
|
await p1.waitForTimeout(2000)
|
|
|
|
// Score initial affiché dans le HUD Vue (panneau du joueur local, premier panneau).
|
|
const scoreBefore = await p1.locator('.player-panel').first().locator('.total-score').textContent()
|
|
|
|
// Alice maximise "war", Bob joue explicitement sa carte la plus FAIBLE
|
|
// en guerre (mode 'min') — indispensable pour garantir un gain non-nul
|
|
// (gain = max(0, ma_valeur - valeur_voisin_gauche)), sinon le score ne
|
|
// change pas du tout et le test ne serait pas discriminant (il passerait
|
|
// aussi bien avec ou sans le fix, faute de changement à observer).
|
|
await selectHandCard(p1, 'war')
|
|
await confirmSelectedCard(p1)
|
|
await selectHandCard(p2, 'war', 'min')
|
|
await confirmSelectedCard(p2)
|
|
|
|
// Attendre ACTIVEMENT la phase 'resolve' plutôt qu'un délai fixe : sous
|
|
// charge, le round-result peut mettre plus ou moins de temps à arriver,
|
|
// et un délai fixe trop court laissait parfois passer la fenêtre
|
|
// d'observation (le tour suivant ayant déjà démarré entretemps).
|
|
await expect.poll(async () => {
|
|
return p1.evaluate(() => {
|
|
const game = window.__ALL_GAMES__?.[window.__ALL_GAMES__.length - 1]
|
|
return game?.scene?.getScene('GameScene')?.store?.phase
|
|
})
|
|
}, { timeout: 3000, intervals: [100] }).toBe('resolve')
|
|
|
|
// PENDANT la phase resolve (avant les ~3.5s de délai serveur), le score
|
|
// du HUD doit rester identique à sa valeur d'AVANT le tour — même si le
|
|
// serveur a déjà calculé et appliqué le nouveau score en interne.
|
|
const scoreDuringResolve = await p1.locator('.player-panel').first().locator('.total-score').textContent()
|
|
expect(scoreDuringResolve).toBe(scoreBefore)
|
|
|
|
// Attendre la fin du délai serveur (3.5s) + un peu de marge : le tour
|
|
// suivant démarre, phase repasse à 'selecting', le score doit alors se
|
|
// mettre à jour (ou au minimum ne plus être bloqué en 'resolve').
|
|
await p1.waitForTimeout(4200)
|
|
|
|
const phaseAfter = await p1.evaluate(() => {
|
|
const game = window.__ALL_GAMES__?.[window.__ALL_GAMES__.length - 1]
|
|
return game?.scene?.getScene('GameScene')?.store?.phase
|
|
})
|
|
expect(phaseAfter).not.toBe('resolve')
|
|
|
|
// Le score doit désormais avoir changé (le gain a bien été appliqué) —
|
|
// confirme que le test observe une vraie différence, pas un cas où le
|
|
// score n'aurait de toute façon jamais bougé.
|
|
const scoreAfter = await p1.locator('.player-panel').first().locator('.total-score').textContent()
|
|
expect(scoreAfter).not.toBe(scoreBefore)
|
|
|
|
expect(errors).toEqual([])
|
|
|
|
await ctx1.close()
|
|
await ctx2.close()
|
|
})
|
|
})
|