/** * Fonctions d'aide partagées par les tests E2E Regnum. * * Toutes les interactions avec le canvas Phaser passent par un hook de debug * injecté via `page.addInitScript` (pas de modification du code source de * l'application) qui expose chaque instance `Phaser.Game` créée sur * `window.__ALL_GAMES__`. Cela permet d'introspecter l'état réel de la scène * (position des cartes/boutons, contenu de la main, etc.) plutôt que de * deviner des coordonnées fixes à l'écran — ce qui serait fragile face aux * changements de mise en page (voir historique des bugs de resize corrigés). */ /** Script à injecter via page.addInitScript avant toute navigation. */ export function phaserDebugHookInitScript() { window.__ALL_GAMES__ = [] const patchWhenReady = () => { if (window.Phaser && window.Phaser.Game && !window.Phaser.Game.__patched__) { const OrigGame = window.Phaser.Game function PatchedGame(config) { const inst = new OrigGame(config) window.__ALL_GAMES__.push(inst) return inst } PatchedGame.prototype = OrigGame.prototype PatchedGame.__patched__ = true window.Phaser.Game = PatchedGame } else { setTimeout(patchWhenReady, 5) } } patchWhenReady() } /** Installe le hook de debug Phaser sur une page (à appeler avant page.goto). */ export async function installPhaserDebugHook(page) { await page.addInitScript(phaserDebugHookInitScript) } /** Récupère la scène GameScene active dans le dernier jeu Phaser créé sur la page. */ export async function getGameScene(page) { return page.evaluateHandle(() => { const game = window.__ALL_GAMES__[window.__ALL_GAMES__.length - 1] return game.scene.getScene('GameScene') }) } /** Crée une salle (P1) et retourne son roomId. */ export async function createRoom(page, playerName) { await page.goto('/') await page.fill('input[placeholder="Ton nom de joueur"]', playerName) await page.click('button:has-text("Créer une partie")') await page.waitForURL('**/lobby/**') return page.url().split('/lobby/')[1] } /** Rejoint une salle existante. */ export async function joinRoom(page, playerName, roomId) { await page.goto('/') await page.fill('input[placeholder="Ton nom de joueur"]', playerName) await page.fill('input[placeholder="Code de la salle"]', roomId) await page.click('button:has-text("Rejoindre")') await page.waitForURL('**/lobby/**') } /** Démarre la partie depuis le lobby (doit être appelé par l'hôte, P1). */ export async function startGame(page) { await page.click('button:has-text("Lancer la partie")') await page.waitForURL('**/game/**') } /** * Joue une carte de la main du joueur et confirme. Sélectionne dynamiquement * la meilleure carte pour l'attribut donné (ou la première carte si aucun * attribut n'est précisé), en lisant l'état réel de la scène — jamais de * coordonnées fixes, pour rester robuste face aux changements de layout. * Ne fait rien si la main est vide (fin de partie). */ export async function playCard(page, attr = null) { const canvas = page.locator('#phaser-container canvas') const box = await canvas.boundingBox() const handLen = await page.evaluate(() => { const game = window.__ALL_GAMES__[window.__ALL_GAMES__.length - 1] const scene = game?.scene?.getScene('GameScene') return scene?.handCards?.length ?? 0 }) if (handLen === 0) return false const cardPos = await page.evaluate((attr) => { const game = window.__ALL_GAMES__[window.__ALL_GAMES__.length - 1] const scene = game.scene.getScene('GameScene') let idx = 0 if (attr) { let bestVal = -1 scene.handCards.forEach((c, i) => { if (c.cardData[attr] > bestVal) { bestVal = c.cardData[attr]; idx = i } }) } const b = scene.handCards[idx].getBounds() return { x: b.centerX, y: b.centerY } }, attr) await page.mouse.click(box.x + cardPos.x, box.y + cardPos.y) await page.waitForTimeout(200) const btnPos = await page.evaluate(() => { const game = window.__ALL_GAMES__[window.__ALL_GAMES__.length - 1] const scene = game.scene.getScene('GameScene') const b = scene.playBtn.getBounds() return { x: b.centerX, y: b.centerY } }) await page.mouse.click(box.x + btnPos.x, box.y + btnPos.y) await page.waitForTimeout(200) return true } /** * Joue un tour complet pour une liste de pages (dans l'ordre donné), puis * attend la résolution + le début du tour suivant (délai serveur fixe de * 3.5s dans index.js + marge de sécurité). */ export async function playFullTurn(pages, attrsByPage = []) { for (let i = 0; i < pages.length; i++) { await playCard(pages[i], attrsByPage[i] ?? null) } await pages[0].waitForTimeout(4200) } /** * Survole un jeton d'objet de la boutique (colonne à gauche du plateau) par * son id, et retourne le centre du bouton ACHETER si l'objet est disponible * et achetable. Retourne null si le jeton n'existe pas (déjà acheté) ou si le * bouton n'est pas interactif (jauge insuffisante). */ export async function hoverShopItem(page, itemId) { const canvas = page.locator('#phaser-container canvas') const box = await canvas.boundingBox() const tokenPos = await page.evaluate((itemId) => { 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 === itemId) return token ? { x: token.x, y: token.y } : null }, itemId) if (!tokenPos) return null await page.mouse.move(box.x + tokenPos.x, box.y + tokenPos.y) await page.waitForTimeout(300) return page.evaluate((itemId) => { 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 === itemId) if (!token || !token._expanded) return null const btnBg = token._expanded.list.find(o => o.type === 'Graphics' && o.input) if (!btnBg) return null // bouton non interactif (jauge insuffisante) const worldPoint = btnBg.getWorldTransformMatrix().transformPoint( btnBg.input.hitArea.centerX, btnBg.input.hitArea.centerY ) return { x: worldPoint.x, y: worldPoint.y } }, itemId) } /** Clique sur le bouton ACHETER d'un objet actuellement affiché en survol. */ export async function buyShopItem(page, itemId) { const btnCenter = await hoverShopItem(page, itemId) if (!btnCenter) return false const canvas = page.locator('#phaser-container canvas') const box = await canvas.boundingBox() await page.mouse.click(box.x + btnCenter.x, box.y + btnCenter.y) await page.waitForTimeout(400) return true } /** Lit les jauges du joueur local depuis le store Pinia exposé par la scène. */ export async function getMyGauges(page) { return page.evaluate(() => { const game = window.__ALL_GAMES__[window.__ALL_GAMES__.length - 1] const scene = game.scene.getScene('GameScene') const me = scene.store.players.find(p => p.teamIndex === scene.store.myTeamIndex) return me?.gauges ?? { food: 0, war: 0, health: 0, diplo: 0 } }) } /** Lit l'état de la boutique (ids d'objets encore affichés dans la colonne). */ export async function getShopItemIds(page) { return page.evaluate(() => { const game = window.__ALL_GAMES__[window.__ALL_GAMES__.length - 1] const scene = game.scene.getScene('GameScene') return scene.itemTokens.map(t => t.itemData.id) }) }