From c370c189d27c630f1f19c99fdb846f22ee80879c Mon Sep 17 00:00:00 2001 From: Enmanuel Date: Sun, 8 Mar 2026 17:33:29 +0000 Subject: [PATCH] feat: extraer dismissAllToasts a element-utils.ts Mueve la logica de cierre de toasts a un modulo compartido (element-utils.ts) para evitar duplicacion. persistent-context.ts importa dismissAllToasts desde ahi y la invoca despues de verificar el sidebar. Se mejora tambien la deteccion del sidebar usando multiples locators alternativos (mx_RoomList, mx_LeftPanel_roomListContainer, mx_RoomTile) para mayor compatibilidad con distintas versiones de Element Web. --- e2e/fixtures/element-utils.ts | 53 +++++++++++++++++++++++++++ e2e/fixtures/persistent-context.ts | 59 ++++++++++++++++++++++++++---- 2 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 e2e/fixtures/element-utils.ts diff --git a/e2e/fixtures/element-utils.ts b/e2e/fixtures/element-utils.ts new file mode 100644 index 0000000..6d7186c --- /dev/null +++ b/e2e/fixtures/element-utils.ts @@ -0,0 +1,53 @@ +import { Page } from "@playwright/test"; + +/** + * Cierra todos los toasts/notificaciones de Element que bloquean clicks. + * Incluye: Notifications, Threads Activity Centre, y cualquier toast generico. + */ +export async function dismissAllToasts(page: Page) { + // Dar un momento para que los toasts aparezcan + await page.waitForTimeout(1_500); + + // Estrategia directa: buscar botones conocidos de toasts de Element + const knownDismissButtons = [ + page.getByRole("button", { name: "Dismiss" }), + page.locator("button").filter({ hasText: /^OK$/ }), + page.getByRole("button", { name: "Close" }), + page.getByRole("button", { name: "Not now" }), + page.getByRole("button", { name: "Got it" }), + page.getByRole("button", { name: "Skip" }), + ]; + + for (const btn of knownDismissButtons) { + try { + if (await btn.first().isVisible()) { + const text = await btn.first().textContent().catch(() => "?"); + console.log(`[element] Dismissing toast: clicking "${text}"`); + await btn.first().click({ force: true }); + await page.waitForTimeout(500); + } + } catch { + // Ignorar errores — el boton pudo desaparecer entre check y click + } + } + + // Segunda pasada: verificar si queda algun toast con boton visible + const remainingToastBtns = page.locator( + '.mx_ToastContainer button, .mx_Toast_buttons button' + ); + const remaining = await remainingToastBtns.count(); + if (remaining > 0) { + for (let i = 0; i < remaining; i++) { + try { + if (await remainingToastBtns.nth(i).isVisible()) { + const text = await remainingToastBtns.nth(i).textContent(); + console.log(`[element] Closing remaining toast button: "${text}"`); + await remainingToastBtns.nth(i).click({ force: true }); + await page.waitForTimeout(300); + } + } catch { + // Ignorar + } + } + } +} diff --git a/e2e/fixtures/persistent-context.ts b/e2e/fixtures/persistent-context.ts index b0214ed..dd7af0a 100644 --- a/e2e/fixtures/persistent-context.ts +++ b/e2e/fixtures/persistent-context.ts @@ -1,5 +1,6 @@ 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. @@ -50,10 +51,12 @@ export const test = base.extend< }); /** - * Maneja dialogos de Element que bloquean la carga: + * Maneja dialogos y toasts de Element que bloquean la carga: * - "Element is open in another window" → click Continue - * - Spinner de carga → esperar * - "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("/") */ @@ -84,11 +87,53 @@ export async function handleElementDialogs(page: Page) { ); } - // 3. Esperar a que rooms sidebar aparezca (sesion cargada) - console.log("[element] Esperando rooms sidebar..."); - const roomsTree = page.locator('[role="tree"][aria-label="Rooms"]'); - await roomsTree.waitFor({ state: "visible", timeout: 30_000 }); - console.log("[element] Rooms sidebar visible"); + // 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";