diff --git a/.gitignore b/.gitignore index 92f2bd0..4752434 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,11 @@ logs/ /agentctl /dashboard -/verify \ No newline at end of file +/verify + +# E2E tests +e2e/node_modules/ +e2e/test-results/ +e2e/.auth/ +e2e/.env +e2e/element-web/ \ No newline at end of file diff --git a/dev-scripts/e2e/install.sh b/dev-scripts/e2e/install.sh new file mode 100755 index 0000000..0aca409 --- /dev/null +++ b/dev-scripts/e2e/install.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# install.sh — instalar dependencias para E2E tests +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +E2E_DIR="$REPO_ROOT/e2e" + +echo "=== Instalacion de E2E tests ===" + +# 1. Verificar Node.js +if ! command -v node &>/dev/null; then + echo "ERROR: Node.js no encontrado." + echo "Instalar Node.js v18+ con:" + echo " curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -" + echo " sudo apt-get install -y nodejs" + exit 1 +fi + +NODE_VERSION=$(node -v | sed 's/v//' | cut -d. -f1) +if [ "$NODE_VERSION" -lt 18 ]; then + echo "ERROR: Se requiere Node.js v18+, encontrado v$(node -v)" + exit 1 +fi +echo "Node.js $(node -v) OK" + +# 2. Instalar dependencias del proyecto +echo "Instalando dependencias npm..." +cd "$E2E_DIR" +npm ci + +# 3. Instalar Chromium para Playwright +echo "Instalando Chromium para Playwright..." +npx playwright install chromium + +# 4. Instalar dependencias del sistema para Playwright +echo "Instalando dependencias del sistema (requiere sudo)..." +sudo npx playwright install-deps chromium + +echo "" +echo "=== Instalacion completa ===" +echo "Siguiente paso: copiar e2e/.env.example a e2e/.env y configurar credenciales" diff --git a/dev-scripts/e2e/run.sh b/dev-scripts/e2e/run.sh new file mode 100755 index 0000000..0a056c6 --- /dev/null +++ b/dev-scripts/e2e/run.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# run.sh — ejecutar E2E tests con Playwright +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +E2E_DIR="$REPO_ROOT/e2e" + +# Verificar dependencias instaladas +if [ ! -d "$E2E_DIR/node_modules" ]; then + echo "ERROR: node_modules no encontrado. Ejecutar primero:" + echo " ./dev-scripts/e2e/install.sh" + exit 1 +fi + +# Verificar .env +if [ ! -f "$E2E_DIR/.env" ]; then + echo "ERROR: e2e/.env no encontrado. Crear desde el template:" + echo " cp e2e/.env.example e2e/.env" + echo " # editar e2e/.env con credenciales" + exit 1 +fi + +echo "Los tests E2E se agregan en el issue 0022c." +echo "Cuando esten listos, ejecutar:" +echo " cd $E2E_DIR && npx playwright test" diff --git a/dev/issues/README.md b/dev/issues/README.md index 7991359..11c6d18 100644 --- a/dev/issues/README.md +++ b/dev/issues/README.md @@ -27,6 +27,6 @@ afectados y notas de implementacion. | 20 | Aislar claude -p del repo | [0020-claude-code-sandbox.md](completed/0020-claude-code-sandbox.md) | completado | | 21 | Threads default config | (completado via branch) | completado | | 22 | Tests E2E con Playwright | [0022-e2e-tests-playwright.md](0022-e2e-tests-playwright.md) | pendiente | -| 22a | E2E: Infraestructura base | [0022a-e2e-infra.md](0022a-e2e-infra.md) | pendiente | +| 22a | E2E: Infraestructura base | [0022a-e2e-infra.md](completed/0022a-e2e-infra.md) | completado | | 22b | E2E: Auth fixtures y helpers | [0022b-e2e-auth-helpers.md](0022b-e2e-auth-helpers.md) | pendiente | | 22c | E2E: Tests de agentes + docs | [0022c-e2e-agent-tests.md](0022c-e2e-agent-tests.md) | pendiente | diff --git a/dev/issues/0022a-e2e-infra.md b/dev/issues/completed/0022a-e2e-infra.md similarity index 100% rename from dev/issues/0022a-e2e-infra.md rename to dev/issues/completed/0022a-e2e-infra.md diff --git a/e2e/.env.example b/e2e/.env.example new file mode 100644 index 0000000..35f25a9 --- /dev/null +++ b/e2e/.env.example @@ -0,0 +1,5 @@ +ELEMENT_URL=http://localhost:8090 +MATRIX_HOMESERVER=https://matrix-af2f3d.organic-machine.com +MATRIX_USER=@test-user:matrix-af2f3d.organic-machine.com +MATRIX_PASSWORD= +MATRIX_RECOVERY_KEY= diff --git a/e2e/fixtures/.gitkeep b/e2e/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/e2e/package-lock.json b/e2e/package-lock.json new file mode 100644 index 0000000..81fac7e --- /dev/null +++ b/e2e/package-lock.json @@ -0,0 +1,92 @@ +{ + "name": "agents-e2e", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agents-e2e", + "version": "1.0.0", + "devDependencies": { + "@playwright/test": "^1.50.0", + "dotenv": "^16.4.7" + } + }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 0000000..c6e9209 --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,15 @@ +{ + "name": "agents-e2e", + "version": "1.0.0", + "private": true, + "description": "E2E tests for agents_and_robots via Playwright + Element Web", + "scripts": { + "test": "npx playwright test", + "test:headed": "npx playwright test --headed", + "test:debug": "npx playwright test --debug" + }, + "devDependencies": { + "@playwright/test": "^1.50.0", + "dotenv": "^16.4.7" + } +} diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts new file mode 100644 index 0000000..dc174ee --- /dev/null +++ b/e2e/playwright.config.ts @@ -0,0 +1,35 @@ +import { defineConfig, devices } from "@playwright/test"; +import * as dotenv from "dotenv"; +import * as path from "path"; + +dotenv.config({ path: path.resolve(__dirname, ".env") }); + +export default defineConfig({ + testDir: "./tests", + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + workers: 1, + reporter: "list", + + // LLMs son lentos — timeouts generosos + timeout: 60_000, + expect: { timeout: 30_000 }, + + use: { + baseURL: process.env.ELEMENT_URL || "http://localhost:8080", + headless: true, + screenshot: "only-on-failure", + trace: "on-first-retry", + actionTimeout: 30_000, + }, + + globalSetup: "./global-setup.ts", + + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], +}); diff --git a/e2e/scripts/setup-element.sh b/e2e/scripts/setup-element.sh new file mode 100755 index 0000000..74b9819 --- /dev/null +++ b/e2e/scripts/setup-element.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# setup-element.sh — descargar y servir Element Web localmente +set -euo pipefail + +ELEMENT_VERSION="v1.11.92" +ELEMENT_DIR="$(cd "$(dirname "$0")/.." && pwd)/element-web" +PORT="${ELEMENT_PORT:-8090}" +PIDFILE="$ELEMENT_DIR/.server.pid" +HOMESERVER="${MATRIX_HOMESERVER:-https://matrix-af2f3d.organic-machine.com}" +SERVER_NAME="${MATRIX_SERVER_NAME:-matrix-af2f3d.organic-machine.com}" + +usage() { + echo "Uso: $0 {start|stop|status}" + echo "" + echo " start Descargar Element Web (si falta) y servir en puerto $PORT" + echo " stop Detener el servidor local" + echo " status Verificar si el servidor esta corriendo" + exit 1 +} + +download_element() { + if [ -d "$ELEMENT_DIR" ] && [ -f "$ELEMENT_DIR/index.html" ]; then + echo "Element Web ya descargado en $ELEMENT_DIR" + return 0 + fi + + local tarball="element-${ELEMENT_VERSION}.tar.gz" + local url="https://github.com/element-hq/element-web/releases/download/${ELEMENT_VERSION}/element-${ELEMENT_VERSION}.tar.gz" + + echo "Descargando Element Web ${ELEMENT_VERSION}..." + mkdir -p "$ELEMENT_DIR" + curl -fSL "$url" -o "/tmp/$tarball" + tar xzf "/tmp/$tarball" --strip-components=1 -C "$ELEMENT_DIR" + rm -f "/tmp/$tarball" + + echo "Generando config.json para homeserver $HOMESERVER..." + cat > "$ELEMENT_DIR/config.json" </dev/null; then + echo "Element Web ya corriendo (PID $(cat "$PIDFILE")) en http://localhost:$PORT" + return 0 + fi + + download_element + + echo "Iniciando servidor en http://localhost:$PORT ..." + if command -v python3 &>/dev/null; then + (cd "$ELEMENT_DIR" && python3 -m http.server "$PORT" --bind 0.0.0.0) &>/dev/null & + elif command -v npx &>/dev/null; then + npx --yes serve -s "$ELEMENT_DIR" -l "$PORT" &>/dev/null & + else + echo "Error: necesitas python3 o npx (Node.js) para servir archivos" + exit 1 + fi + + echo $! > "$PIDFILE" + + # Esperar a que el servidor arranque + for i in 1 2 3 4 5; do + if curl -sf "http://localhost:$PORT/" >/dev/null 2>&1; then + echo "Element Web serving en http://localhost:$PORT (PID $!)" + return 0 + fi + sleep 1 + done + echo "WARN: servidor iniciado (PID $!) pero no responde aun en http://localhost:$PORT" +} + +stop_server() { + if [ ! -f "$PIDFILE" ]; then + echo "No hay servidor corriendo (no se encontro pidfile)" + return 0 + fi + + local pid + pid=$(cat "$PIDFILE") + if kill -0 "$pid" 2>/dev/null; then + kill "$pid" + echo "Servidor detenido (PID $pid)" + else + echo "Proceso $pid ya no existe" + fi + rm -f "$PIDFILE" +} + +server_status() { + if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then + echo "Element Web corriendo (PID $(cat "$PIDFILE")) en http://localhost:$PORT" + else + echo "Element Web no esta corriendo" + [ -f "$PIDFILE" ] && rm -f "$PIDFILE" + fi +} + +case "${1:-}" in + start) start_server ;; + stop) stop_server ;; + status) server_status ;; + *) usage ;; +esac diff --git a/e2e/tests/.gitkeep b/e2e/tests/.gitkeep new file mode 100644 index 0000000..e69de29