# Instructions - Following Playwright test failed. - Explain why, be concise, respect Playwright best practices. - Provide a snippet of code with the fix, if possible. # Test info - Name: specs/epic-1-visibility.spec.js >> Epic 1: Core Camera Visibility Control >> FR-4: AV Tile Visual State Indicators >> Self-muted state shows camera-off icon - Location: specs/epic-1-visibility.spec.js:132:5 # Error details ``` Error: page.waitForFunction: Test ended. ``` # Test source ```ts 1 | /** 2 | * Helpers pour les tests E2E avec FoundryVTT 3 | * 4 | * Fournit des fonctions utilitaires pour : 5 | * - Attendre que Foundry soit prêt 6 | * - Attendre que le module soit chargé 7 | * - Interagir avec l'UI FoundryVTT 8 | * - Gérer les sélecteurs spécifiques au module 9 | */ 10 | 11 | import { expect } from '@playwright/test'; 12 | 13 | /** 14 | * Attend que FoundryVTT soit complètement chargé 15 | * @param {import('@playwright/test').Page} page - La page Playwright 16 | * @param {number} timeout - Timeout en ms (défaut: 30000) 17 | */ 18 | export async function waitForFoundryReady(page, timeout = 30000) { > 19 | await page.waitForFunction(() => { | ^ Error: page.waitForFunction: Test ended. 20 | return typeof game !== 'undefined' && game.ready; 21 | }, { timeout }); 22 | } 23 | 24 | /** 25 | * Attend que le module Video View Manager soit actif 26 | * @param {import('@playwright/test').Page} page - La page Playwright 27 | * @param {number} timeout - Timeout en ms (défaut: 15000) 28 | */ 29 | export async function waitForVVMModule(page, timeout = 15000) { 30 | await page.waitForFunction(() => { 31 | const module = game.modules?.get?.('video-view-manager'); 32 | return module?.active === true; 33 | }, { timeout }); 34 | } 35 | 36 | /** 37 | * Attend qu'un élément du module soit présent 38 | * @param {import('@playwright/test').Page} page - La page Playwright 39 | * @param {string} selector - Sélecteur CSS 40 | * @param {number} timeout - Timeout en ms (défaut: 10000) 41 | */ 42 | export async function waitForVVMElement(page, selector, timeout = 10000) { 43 | await page.waitForSelector(selector, { 44 | state: 'visible', 45 | timeout 46 | }); 47 | } 48 | 49 | /** 50 | * Clique sur un bouton dans l'UI Foundry avec retry 51 | * @param {import('@playwright/test').Page} page - La page Playwright 52 | * @param {string|import('@playwright/test').Locator} button - Sélecteur ou Locator 53 | * @param {number} retries - Nombre de tentatives (défaut: 3) 54 | */ 55 | export async function clickFoundryButton(page, button, retries = 3) { 56 | for (let i = 0; i < retries; i++) { 57 | try { 58 | const locator = typeof button === 'string' ? page.locator(button) : button; 59 | await locator.click({ timeout: 5000 }); 60 | return; 61 | } catch (error) { 62 | if (i === retries - 1) throw error; 63 | await page.waitForTimeout(1000); 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * Ouvre le sidebar de configuration Foundry 70 | * @param {import('@playwright/test').Page} page - La page Playwright 71 | */ 72 | export async function openFoundrySidebar(page) { 73 | await clickFoundryButton(page, 'button[aria-label="Configure Settings"]'); 74 | await page.waitForSelector('.app-v2.settings', { state: 'visible', timeout: 10000 }); 75 | } 76 | 77 | /** 78 | * Ouvre le Director's Board (Epic 2) 79 | * @param {import('@playwright/test').Page} page - La page Playwright 80 | */ 81 | export async function openDirectorsBoard(page) { 82 | // Le Director's Board a un bouton dédié dans la sidebar 83 | await page.waitForSelector('button[aria-label*="Director\'s Board"]', { timeout: 10000 }); 84 | await clickFoundryButton(page, 'button[aria-label*="Director\'s Board"]'); 85 | 86 | // Attendre que le board soit ouvert 87 | await page.waitForSelector('.scrying-pool-directors-board', { 88 | state: 'visible', 89 | timeout: 10000 90 | }); 91 | } 92 | 93 | /** 94 | * Ouvre le Player Privacy Panel pour un utilisateur 95 | * @param {import('@playwright/test').Page} page - La page Playwright 96 | * @param {string} userId - L'ID de l'utilisateur 97 | */ 98 | export async function openPlayerPrivacyPanel(page, userId) { 99 | // Le panel s'ouvre via les paramètres du module 100 | await openFoundrySidebar(page); 101 | 102 | // Naviguer vers les paramètres du module 103 | await clickFoundryButton(page, 'button:has-text("Video View Manager")'); 104 | await page.waitForTimeout(1000); 105 | 106 | // Cliquer sur le bouton Player Privacy 107 | await clickFoundryButton(page, 'button:has-text("Player Privacy")'); 108 | 109 | // Attendre le panel 110 | await page.waitForSelector('.sp-player-privacy-panel', { 111 | state: 'visible', 112 | timeout: 10000 113 | }); 114 | } 115 | 116 | /** 117 | * Sélectionne un utilisateur dans une liste Foundry 118 | * @param {import('@playwright/test').Page} page - La page Playwright 119 | * @param {string} username - Le nom de l'utilisateur ```