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 explicitement sa carte la plus // FAIBLE en guerre (mode 'min') — indispensable pour garantir un gain // non-nul à chaque tour (gain = max(0, ma_valeur - valeur_voisin_gauche)), // sinon le test devient flaky selon les cartes distribuées aléatoirement. let gauges = await getMyGauges(p1) let attempts = 0 while (gauges.war < 3 && attempts < 12) { await playFullTurn([p1, p2], ['war', { attr: 'war', mode: 'min' }]) 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() }) test('un achat en attente affiche une pulsation dorée sur le jeton, avant que le tour ne soit résolu', 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) let gauges = await getMyGauges(p1) let attempts = 0 while (gauges.war < 3 && attempts < 12) { await playFullTurn([p1, p2], ['war', { attr: 'war', mode: 'min' }]) gauges = await getMyGauges(p1) attempts++ } expect(gauges.war).toBeGreaterThanOrEqual(3) const bought = await buyShopItem(p1, 'trebuchet') expect(bought).toBe(true) // AVANT la résolution du tour : le jeton reste dans la boutique (encore // dans availableItems côté serveur), mais doit maintenant afficher l'état // "pending" (pulsation dorée + bouton grisé "EN ATTENTE..."). const pendingState = await p1.evaluate(() => { const game = window.__ALL_GAMES__[window.__ALL_GAMES__.length - 1] const scene = game.scene.getScene('GameScene') const token = scene.itemTokens.find(t => t.itemData.id === 'trebuchet') return { exists: !!token, pending: token?.pending ?? null, affordable: token?.affordable ?? null, hasPulseTween: !!token?._pulseTween } }) expect(pendingState.exists).toBe(true) expect(pendingState.pending).toBe(true) expect(pendingState.affordable).toBe(false) expect(pendingState.hasPulseTween).toBe(true) // Une seconde salle voit-elle aussi cet état ? Non — "pending" est // propre à CE joueur (pendingBuyItemId scoped par socketId côté serveur), // Bob ne doit pas voir le jeton de Trébuchet en état "pending". const bobView = await p2.evaluate(() => { const game = window.__ALL_GAMES__[window.__ALL_GAMES__.length - 1] const scene = game.scene.getScene('GameScene') const token = scene.itemTokens.find(t => t.itemData.id === 'trebuchet') return token?.pending ?? null }) expect(bobView).toBe(false) // APRÈS résolution du tour : le jeton disparaît complètement de la // boutique (objet acheté, stock épuisé) — plus d'état "pending" à vérifier. await playFullTurn([p1, p2], ['war', 'health']) const afterResolve = await p1.evaluate(() => { const game = window.__ALL_GAMES__[window.__ALL_GAMES__.length - 1] const scene = game.scene.getScene('GameScene') return scene.itemTokens.find(t => t.itemData.id === 'trebuchet') }) expect(afterResolve).toBeUndefined() await ctx1.close() await ctx2.close() }) })