/** * Helpers pour les tests E2E avec FoundryVTT * * Fournit des fonctions utilitaires pour : * - Attendre que Foundry soit prêt * - Attendre que le module soit chargé * - Interagir avec l'UI FoundryVTT * - Gérer les sélecteurs spécifiques au module */ import { expect } from '@playwright/test'; /** * Attend que FoundryVTT soit complètement chargé * @param {import('@playwright/test').Page} page - La page Playwright * @param {number} timeout - Timeout en ms (défaut: 30000) */ export async function waitForFoundryReady(page, timeout = 30000) { await page.waitForFunction(() => { return typeof game !== 'undefined' && game.ready; }, { timeout }); } /** * Attend que le module Scrying Pool soit actif * @param {import('@playwright/test').Page} page - La page Playwright * @param {number} timeout - Timeout en ms (défaut: 15000) */ export async function waitForVVMModule(page, timeout = 15000) { await page.waitForFunction(() => { const module = game.modules?.get?.('scrying-pool'); return module?.active === true; }, { timeout }); } /** * Attend qu'un élément du module soit présent * @param {import('@playwright/test').Page} page - La page Playwright * @param {string} selector - Sélecteur CSS * @param {number} timeout - Timeout en ms (défaut: 10000) */ export async function waitForVVMElement(page, selector, timeout = 10000) { await page.waitForSelector(selector, { state: 'visible', timeout }); } /** * Clique sur un bouton dans l'UI Foundry avec retry * @param {import('@playwright/test').Page} page - La page Playwright * @param {string|import('@playwright/test').Locator} button - Sélecteur ou Locator * @param {number} retries - Nombre de tentatives (défaut: 3) */ export async function clickFoundryButton(page, button, retries = 3) { for (let i = 0; i < retries; i++) { try { const locator = typeof button === 'string' ? page.locator(button) : button; await locator.click({ timeout: 5000 }); return; } catch (error) { if (i === retries - 1) throw error; await page.waitForTimeout(1000); } } } /** * Ouvre le sidebar de configuration Foundry * @param {import('@playwright/test').Page} page - La page Playwright */ export async function openFoundrySidebar(page) { await clickFoundryButton(page, 'button[aria-label="Configure Settings"]'); await page.waitForSelector('.app-v2.settings', { state: 'visible', timeout: 10000 }); } /** * Ouvre le Director's Board (Epic 2) * @param {import('@playwright/test').Page} page - La page Playwright */ export async function openDirectorsBoard(page) { // Le Director's Board a un bouton dédié dans la sidebar await page.waitForSelector('button[aria-label*="Director\'s Board"]', { timeout: 10000 }); await clickFoundryButton(page, 'button[aria-label*="Director\'s Board"]'); // Attendre que le board soit ouvert await page.waitForSelector('.scrying-pool-directors-board', { state: 'visible', timeout: 10000 }); } /** * Ouvre le Player Privacy Panel pour un utilisateur * @param {import('@playwright/test').Page} page - La page Playwright * @param {string} userId - L'ID de l'utilisateur */ export async function openPlayerPrivacyPanel(page, userId) { // Le panel s'ouvre via les paramètres du module await openFoundrySidebar(page); // Naviguer vers les paramètres du module await clickFoundryButton(page, 'button:has-text("Scrying Pool")'); await page.waitForTimeout(1000); // Cliquer sur le bouton Player Privacy await clickFoundryButton(page, 'button:has-text("Player Privacy")'); // Attendre le panel await page.waitForSelector('.sp-player-privacy-panel', { state: 'visible', timeout: 10000 }); } /** * Sélectionne un utilisateur dans une liste Foundry * @param {import('@playwright/test').Page} page - La page Playwright * @param {string} username - Le nom de l'utilisateur */ export async function selectUserInList(page, username) { const userItem = page.locator(`.sp-user-item:has-text("${username}")`); await userItem.click({ timeout: 5000 }); await page.waitForTimeout(500); } /** * Attend qu'une notification Foundry apparaisse * @param {import('@playwright/test').Page} page - La page Playwright * @param {string} text - Texte de la notification * @param {number} timeout - Timeout en ms */ export async function waitForNotification(page, text, timeout = 10000) { await page.waitForSelector( `.notification:has-text("${text}")`, { state: 'visible', timeout } ); } /** * Vérifie qu'un AV Tile a un badge de visibilité spécifique * @param {import('@playwright/test').Page} page - La page Playwright * @param {string} userId - L'ID de l'utilisateur * @param {string} state - L'état attendu (active, hidden, etc.) */ export async function verifyAVTileState(page, userId, state) { const tile = page.locator(`[data-user-id="${userId}"] .sp-visibility-badge`); await expect(tile).toBeVisible({ timeout: 5000 }); // Vérifier le texte ou la classe CSS const expectedText = { active: 'Live', hidden: 'Hidden from table', 'self-muted': 'Camera paused', offline: 'Not connected', 'cam-lost': 'Camera unavailable', reconnecting: 'Rejoining view', 'never-connected': 'Not yet connected', ghost: 'Leaving', }[state]; if (expectedText) { await expect(tile).toContainText(expectedText); } } /** * Charge un preset de scène * @param {import('@playwright/test').Page} page - La page Playwright * @param {string} presetName - Le nom du preset */ export async function loadScenePreset(page, presetName) { await openDirectorsBoard(page); // Cliquer sur le bouton Load Preset await clickFoundryButton(page, 'button:has-text("Load Preset")'); // Sélectionner le preset dans la liste await page.waitForSelector('.sp-preset-list', { state: 'visible', timeout: 5000 }); await clickFoundryButton(page, `.sp-preset-item:has-text("${presetName}")`); // Attendre la confirmation await waitForNotification(page, `Applied preset: ${presetName}`, 5000); } /** * Sauvegarde le preset courant * @param {import('@playwright/test').Page} page - La page Playwright * @param {string} presetName - Le nom du preset */ export async function saveScenePreset(page, presetName) { await openDirectorsBoard(page); // Cliquer sur Save Preset await clickFoundryButton(page, 'button:has-text("Save Preset")'); // Remplir le nom du preset await page.waitForSelector('.sp-save-preset-dialog', { state: 'visible', timeout: 5000 }); await page.locator('.sp-save-preset-dialog input[name="presetName"]').fill(presetName); // Confirmer await clickFoundryButton(page, '.sp-save-preset-dialog button:has-text("Save")'); // Attendre la confirmation await waitForNotification(page, `Saved preset: ${presetName}`, 5000); } /** * Hide un participant via le context menu * @param {import('@playwright/test').Page} page - La page Playwright * @param {string} username - Le nom du participant */ export async function hideParticipant(page, username) { // Trouver la tuile AV du participant const tile = page.locator(`.av-tile:has-text("${username}")`); // Clic droit pour ouvrir le context menu await tile.click({ button: 'right', timeout: 5000 }); // Sélectionner "Hide from table" await page.waitForSelector('.context-menu', { state: 'visible', timeout: 5000 }); await clickFoundryButton(page, '.context-menu li:has-text("Hide from table")'); // Attendre que le badge soit mis à jour await page.waitForTimeout(1000); } /** * Show un participant via le context menu * @param {import('@playwright/test').Page} page - La page Playwright * @param {string} username - Le nom du participant */ export async function showParticipant(page, username) { const tile = page.locator(`.av-tile:has-text("${username}")`); await tile.click({ button: 'right', timeout: 5000 }); await page.waitForSelector('.context-menu', { state: 'visible', timeout: 5000 }); await clickFoundryButton(page, '.context-menu li:has-text("Show to table")'); await page.waitForTimeout(1000); } /** * Utilise le Director's Board pour toggler un participant * @param {import('@playwright/test').Page} page - La page Playwright * @param {string} username - Le nom du participant */ export async function toggleParticipantInBoard(page, username) { await openDirectorsBoard(page); // Trouver la carte du participant const card = page.locator(`.sp-participant-card:has-text("${username}")`); await card.click({ timeout: 5000 }); // Le toggle devrait se faire automatiquement au clic await page.waitForTimeout(500); }