/** * WebDriverIO config for Electrobun stack E2E tests. * * Uses CDP (Chrome DevTools Protocol) via CEF mode for reliable E2E automation. * Electrobun must be built/run with AGOR_CEF=1 to bundle CEF and expose * --remote-debugging-port=9222 (configured in electrobun.config.ts). * * Port conventions: * 9222 — CDP debugging port (CEF) * 9760 — Vite dev server (HMR) * 9761 — (reserved, was WebKitWebDriver) */ import { execSync, spawn } from 'node:child_process'; import { resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { existsSync, rmSync } from 'node:fs'; import { sharedConfig } from './wdio.shared.conf.js'; import { waitForPort } from './helpers/actions.ts'; const __dirname = dirname(fileURLToPath(import.meta.url)); const projectRoot = resolve(__dirname, '../..'); const electrobunRoot = resolve(projectRoot, 'ui-electrobun'); const CDP_PORT = 9222; // Use the Electrobun fixture generator (different groups.json format) let fixture; try { const { createTestFixture } = await import('../../ui-electrobun/tests/e2e/fixtures.ts'); fixture = createTestFixture('agor-ebun-cdp'); } catch { const { createTestFixture } = await import('./infra/fixtures.ts'); fixture = createTestFixture('agor-ebun-cdp'); } process.env.AGOR_TEST = '1'; process.env.AGOR_CEF = '1'; process.env.AGOR_TEST_DATA_DIR = fixture.dataDir; process.env.AGOR_TEST_CONFIG_DIR = fixture.configDir; let viteProcess; console.log(`[electrobun-cdp] Test fixture at ${fixture.rootDir ?? fixture.configDir}`); let appProcess; export const config = { ...sharedConfig, // Use ChromeDriver to attach to existing CEF via debuggerAddress // ChromeDriver connects to the existing CDP port WITHOUT navigating/destroying the page automationProtocol: 'webdriver', services: [['chromedriver', { args: ['--verbose'] }]], capabilities: [{ browserName: 'chrome', 'goog:chromeOptions': { debuggerAddress: `localhost:${CDP_PORT}`, }, }], async onPrepare() { // Find existing binary or build const candidates = [ resolve(electrobunRoot, 'build/dev-linux-x64/AgentOrchestrator-dev/AgentOrchestrator-dev'), resolve(electrobunRoot, 'build/Agent Orchestrator'), resolve(electrobunRoot, 'build/AgentOrchestrator'), ]; let electrobunBinary = candidates.find(p => existsSync(p)); if (!electrobunBinary && !process.env.SKIP_BUILD) { console.log('[electrobun-cdp] Building with CEF...'); try { execSync('npx vite build', { cwd: electrobunRoot, stdio: 'inherit' }); execSync('npx electrobun build --env=dev', { cwd: electrobunRoot, stdio: 'inherit' }); } catch (e) { console.warn('[electrobun-cdp] Build failed:', e.message); } electrobunBinary = candidates.find(p => existsSync(p)); } if (!electrobunBinary) { // Kill any stale process on CDP port before launching try { execSync(`fuser -k ${CDP_PORT}/tcp 2>/dev/null || true`); } catch {} // Start Vite dev server first (Electrobun dev mode loads JS from it) console.log('[electrobun-cdp] Starting Vite dev server on port 9760...'); viteProcess = spawn('npx', ['vite', 'dev', '--port', '9760', '--host', 'localhost'], { cwd: electrobunRoot, env: { ...process.env }, stdio: 'pipe', }); viteProcess.stdout?.on('data', (d) => { const msg = d.toString(); if (msg.includes('ready') || msg.includes('Local:')) console.log(`[vite] ${msg.trim()}`); }); viteProcess.stderr?.on('data', (d) => process.stderr.write(`[vite] ${d}`)); // Wait for Vite to be ready const viteStart = Date.now(); while (Date.now() - viteStart < 15000) { try { const r = await fetch('http://localhost:9760/'); if (r.ok) break; } catch {} await new Promise(r => setTimeout(r, 500)); } console.log('[electrobun-cdp] Vite dev server ready.'); // Launch Electrobun with CEF console.log('[electrobun-cdp] Launching via electrobun dev...'); appProcess = spawn('npx', ['electrobun', 'dev'], { cwd: electrobunRoot, env: { ...process.env, AGOR_CEF: '1', AGOR_TEST: '1', AGOR_TEST_DATA_DIR: fixture.dataDir, AGOR_TEST_CONFIG_DIR: fixture.configDir, }, stdio: 'pipe', }); } else { console.log(`[electrobun-cdp] Launching binary: ${electrobunBinary}`); appProcess = spawn(electrobunBinary, [], { env: { ...process.env, AGOR_CEF: '1', AGOR_TEST: '1', AGOR_TEST_DATA_DIR: fixture.dataDir, AGOR_TEST_CONFIG_DIR: fixture.configDir, }, stdio: 'pipe', }); } appProcess.stdout?.on('data', (d) => process.stdout.write(`[app] ${d}`)); appProcess.stderr?.on('data', (d) => process.stderr.write(`[app] ${d}`)); appProcess.on('exit', (code) => console.log(`[electrobun-cdp] App exited with code ${code}`)); // Wait for CDP port to become available return waitForPort(CDP_PORT, 30_000); }, async before() { // Navigate CEF to Vite dev server (views:// protocol can't serve ES modules in CEF) console.log('[electrobun-cdp] Navigating to Vite dev server...'); await browser.url('http://localhost:9760/'); await browser.pause(3000); // Wait for Svelte to mount // Wait for app to render await browser.waitUntil( async () => { const hasEl = await browser.execute( 'return document.querySelector(".left-sidebar") !== null' + ' || document.querySelector(".project-card") !== null' + ' || document.querySelector(".status-bar") !== null' + ' || document.querySelector("#app")?.children?.length > 0' ); return hasEl; }, { timeout: 20_000, interval: 500, timeoutMsg: 'Electrobun app did not render in 20s' }, ); console.log('[electrobun-cdp] App loaded.'); }, onComplete() { if (appProcess) { console.log('[electrobun-cdp] Stopping app...'); appProcess.kill('SIGTERM'); appProcess = undefined; } if (viteProcess) { console.log('[electrobun-cdp] Stopping Vite dev server...'); viteProcess.kill('SIGTERM'); viteProcess = undefined; } const cleanup = fixture.cleanup ?? (() => { try { if (fixture.rootDir) rmSync(fixture.rootDir, { recursive: true, force: true }); } catch { /* best-effort */ } }); cleanup(); }, };