feat: import agents_and_robots platform as unibots (Matrix-out, unibus transport)
Reemplaza el scaffold del echobot por la plataforma completa de bots traida desde ~/DataProyects/Github/agents_and_robots tras la operacion Matrix-out: los bots ya no hablan por Matrix sino por el bus unibus (modelo todo-rooms + E2E via shell/transportunibus sobre github.com/enmanuel/unibus/pkg/client). - go.mod: replace de unibus -> ../unibus y de fn-registry -> ../../../.. (paths relativos reajustados a la nueva ubicacion dentro de fn_registry). - app.md: bump a 0.2.0, descripcion + arquitectura + comandos + gotchas reales. - modulo Go conservado como github.com/enmanuel/agents (sin reescribir imports). agents_and_robots queda archivado como museo de la era Matrix.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
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";
|
||||
Reference in New Issue
Block a user