Files
unibots/e2e/global-setup.ts
T
agent fc644ecd6e 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.
2026-06-07 11:50:13 +02:00

120 lines
3.8 KiB
TypeScript

import { chromium } from "@playwright/test";
import * as path from "path";
import * as fs from "fs";
import * as dotenv from "dotenv";
import { loginToElement } from "./fixtures/element-auth";
dotenv.config({ path: path.resolve(__dirname, ".env") });
const USER_DATA_DIR = path.resolve(__dirname, ".auth", "chrome-profile");
const MARKER_PATH = path.resolve(__dirname, ".auth", "login-done.marker");
const SCREENSHOTS_DIR = path.resolve(__dirname, "test-results", "global-setup");
/**
* Global setup: ejecuta login una vez usando persistent context.
*
* A diferencia de storageState, el persistent context preserva IndexedDB
* (crypto keys de E2EE). Los tests usan el mismo userDataDir via el
* custom fixture persistent-context.ts.
*
* Si el marker file existe y no esta expirado, asumimos que la sesion
* sigue activa y saltamos el login.
*/
async function globalSetup() {
const elementURL = process.env.ELEMENT_URL || "http://localhost:8080";
const user = process.env.MATRIX_USER;
const password = process.env.MATRIX_PASSWORD;
const recoveryKey = process.env.MATRIX_RECOVERY_KEY;
if (!user || !password || !recoveryKey) {
throw new Error(
"Faltan variables de entorno: MATRIX_USER, MATRIX_PASSWORD, MATRIX_RECOVERY_KEY"
);
}
// Reutilizar sesion cacheada si el marker existe y tiene menos de 12 horas
if (isMarkerFresh(MARKER_PATH, 12 * 60 * 60 * 1000)) {
console.log("[global-setup] Reutilizando sesion de persistent context");
return;
}
console.log("[global-setup] Ejecutando login en Element Web con persistent context...");
console.log(`[global-setup] URL: ${elementURL}`);
console.log(`[global-setup] User: ${user}`);
console.log(`[global-setup] UserDataDir: ${USER_DATA_DIR}`);
// Limpiar perfil anterior para login fresco
if (fs.existsSync(USER_DATA_DIR)) {
fs.rmSync(USER_DATA_DIR, { recursive: true });
console.log("[global-setup] Perfil anterior eliminado");
}
// Asegurar que los directorios existen
fs.mkdirSync(USER_DATA_DIR, { recursive: true });
fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
// Usar persistent context — preserva IndexedDB (crypto keys E2EE)
const context = await chromium.launchPersistentContext(USER_DATA_DIR, {
headless: true,
viewport: { width: 1280, height: 720 },
});
const page = context.pages()[0] || (await context.newPage());
// Capturar logs de consola del browser
page.on("console", (msg) => {
const type = msg.type();
if (type === "error" || type === "warning") {
console.log(`[browser-${type}] ${msg.text()}`);
}
});
page.on("pageerror", (err) => {
console.error(`[browser-error] ${err.message}`);
});
try {
await loginToElement(page, {
url: elementURL,
user,
password,
recoveryKey,
screenshotsDir: SCREENSHOTS_DIR,
});
// Crear marker de sesion exitosa
fs.writeFileSync(MARKER_PATH, new Date().toISOString());
console.log("[global-setup] Login completado, marker creado");
await page.screenshot({
path: path.join(SCREENSHOTS_DIR, "05-login-complete.png"),
fullPage: true,
});
} catch (err) {
console.error("[global-setup] ERROR durante login:", err);
await page.screenshot({
path: path.join(SCREENSHOTS_DIR, "ERROR-login-failed.png"),
fullPage: true,
});
const html = await page.content();
fs.writeFileSync(
path.join(SCREENSHOTS_DIR, "ERROR-page-content.html"),
html
);
throw err;
} finally {
await context.close();
}
}
/** Verifica si el marker file existe y tiene menos de maxAge ms. */
function isMarkerFresh(filePath: string, maxAge: number): boolean {
try {
const stat = fs.statSync(filePath);
return Date.now() - stat.mtimeMs < maxAge;
} catch {
return false;
}
}
export default globalSetup;