import { test as base, chromium, BrowserContext, Page } from "@playwright/test"; import * as path from "path"; import { dismissAllToasts } from "./element-utils"; /** * Custom test fixture que usa un persistent browser context compartido. * * A diferencia de storageState (que solo guarda cookies + localStorage), * un persistent context preserva IndexedDB — donde Element Web guarda * las crypto keys de E2EE. Sin esto, cada test ve "Missing session data". * * El contexto es worker-scoped: se crea una vez y se reutiliza en todos * los tests del worker. Esto evita el dialogo "Element is open in another * window" que aparece cuando se abre/cierra el contexto repetidamente. */ const USER_DATA_DIR = path.resolve(__dirname, "..", ".auth", "chrome-profile"); export const test = base.extend< { page: Page }, { persistentContext: BrowserContext } >({ // Worker-scoped: un solo persistent context para todos los tests persistentContext: [ async ({}, use) => { const context = await chromium.launchPersistentContext(USER_DATA_DIR, { headless: true, baseURL: process.env.ELEMENT_URL || "http://localhost:8080", viewport: { width: 1280, height: 720 }, }); await use(context); await context.close(); }, { scope: "worker" }, ], // Cada test obtiene una pagina del contexto compartido page: async ({ persistentContext }, use) => { // Cerrar paginas sobrantes de tests anteriores for (const p of persistentContext.pages()) { await p.close(); } const page = await persistentContext.newPage(); await use(page); // Cerrar la pagina al finalizar el test await page.close(); }, }); /** * Maneja dialogos y toasts de Element que bloquean la carga: * - "Element is open in another window" → click Continue * - "Missing session data" → error informativo * - "Notifications" toast → click Dismiss * - "Threads Activity Centre" toast → click OK * - Cualquier otro toast → intentar cerrarlo * * Llamar despues de page.goto("/") */ export async function handleElementDialogs(page: Page) { // 1. "Element is open in another window" — click Continue const continueBtn = page.getByRole("button", { name: "Continue" }); const hasContinue = await continueBtn .waitFor({ state: "visible", timeout: 5_000 }) .then(() => true) .catch(() => false); if (hasContinue) { console.log("[element] 'Element is open in another window' — clicking Continue"); await continueBtn.click(); } // 2. "Missing session data" — fatal const missingData = page.locator('text="Missing session data"'); const hasMissing = await missingData .waitFor({ state: "visible", timeout: 3_000 }) .then(() => true) .catch(() => false); if (hasMissing) { throw new Error( "Missing session data: crypto keys perdidas. " + "Borrar .auth/ y re-ejecutar: rm -rf e2e/.auth && ./dev-scripts/e2e/run.sh" ); } // 3. Esperar a que la sidebar aparezca (sesion cargada) // Usamos multiples locators porque Element Web cambia la estructura entre versiones console.log("[element] Esperando sidebar con rooms..."); const sidebarLocators = [ page.locator('[role="tree"][aria-label="Rooms"]'), page.locator(".mx_RoomList"), page.locator(".mx_LeftPanel_roomListContainer"), page.locator('[role="treeitem"]'), // Rooms visibles como items en el sidebar page.locator(".mx_RoomTile"), ]; let sidebarFound = false; for (const locator of sidebarLocators) { const visible = await locator.first() .waitFor({ state: "visible", timeout: 30_000 }) .then(() => true) .catch(() => false); if (visible) { console.log("[element] Sidebar visible"); sidebarFound = true; break; } } if (!sidebarFound) { // Verificar si estamos en la pagina de login const onLoginPage = await page.locator('text="Welcome to Element!"').isVisible().catch(() => false) || await page.getByRole("link", { name: "Sign in" }).isVisible().catch(() => false); if (onLoginPage) { throw new Error( "Sesion no cargada: se muestra la pagina de login. " + "Borrar .auth/ y re-ejecutar: rm -rf e2e/.auth && ./dev-scripts/e2e/run.sh" ); } await page.screenshot({ path: "test-results/ERROR-no-sidebar.png", fullPage: true, }); throw new Error("Sidebar de rooms no encontrado despues de 30s"); } // 4. Cerrar TODOS los toasts que bloquean interacciones await dismissAllToasts(page); } export { dismissAllToasts } from "./element-utils"; export { expect } from "@playwright/test";