/** * WebDriverIO config for Tauri stack E2E tests. * * Extends shared config with tauri-driver lifecycle and capabilities. * Port: 9750 (per project convention). */ import { spawn, execSync } from 'node:child_process'; import { createConnection } from 'node:net'; import { resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { rmSync, existsSync } from 'node:fs'; import { createTestFixture } from './infra/fixtures.ts'; import { sharedConfig } from './wdio.shared.conf.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); const projectRoot = resolve(__dirname, '../..'); const tauriBinary = resolve(projectRoot, 'target/debug/agent-orchestrator'); let tauriDriver; const fixture = createTestFixture('agor-e2e-tauri'); process.env.AGOR_TEST = '1'; process.env.AGOR_TEST_DATA_DIR = fixture.dataDir; process.env.AGOR_TEST_CONFIG_DIR = fixture.configDir; const TAURI_DRIVER_PORT = 9750; console.log(`[tauri] Test fixture at ${fixture.rootDir}`); export const config = { ...sharedConfig, port: TAURI_DRIVER_PORT, capabilities: [{ 'wdio:enforceWebDriverClassic': true, 'tauri:options': { application: tauriBinary, env: fixture.env, }, }], onPrepare() { // Kill stale tauri-driver on our port try { const pids = execSync(`lsof -ti:${TAURI_DRIVER_PORT} 2>/dev/null`, { encoding: 'utf8' }).trim(); if (pids) { console.log(`Killing stale process(es) on port ${TAURI_DRIVER_PORT}: ${pids}`); execSync(`kill ${pids} 2>/dev/null`); } } catch { /* no process — good */ } // Verify devUrl port is free const DEV_URL_PORT = 9710; try { const devPids = execSync(`lsof -ti:${DEV_URL_PORT} 2>/dev/null`, { encoding: 'utf8' }).trim(); if (devPids) { throw new Error( `Port ${DEV_URL_PORT} (Tauri devUrl) in use by PIDs: ${devPids}. ` + `Stop that process or use a release build.` ); } } catch (e) { if (e.message.includes(`Port ${DEV_URL_PORT}`)) throw e; } if (!existsSync(tauriBinary)) { if (process.env.SKIP_BUILD) { throw new Error(`Binary not found at ${tauriBinary}. Build first or unset SKIP_BUILD.`); } } if (process.env.SKIP_BUILD) { if (!existsSync(resolve(projectRoot, 'dist/index.html'))) { console.log('Frontend dist/ missing — building...'); execSync('npm run build', { cwd: projectRoot, stdio: 'inherit' }); } return Promise.resolve(); } return new Promise((resolveHook, reject) => { console.log('Building frontend...'); try { execSync('npm run build', { cwd: projectRoot, stdio: 'inherit' }); } catch (e) { reject(new Error(`Frontend build failed: ${e.message}`)); return; } console.log('Building Tauri debug binary...'); const build = spawn('cargo', ['tauri', 'build', '--debug', '--no-bundle'], { cwd: projectRoot, stdio: 'inherit', }); build.on('close', (code) => code === 0 ? resolveHook() : reject(new Error(`Build failed (exit ${code})`))); build.on('error', reject); }); }, beforeSession() { return new Promise((res, reject) => { const preCheck = createConnection({ port: TAURI_DRIVER_PORT, host: 'localhost' }, () => { preCheck.destroy(); reject(new Error(`Port ${TAURI_DRIVER_PORT} already in use.`)); }); preCheck.on('error', () => { preCheck.destroy(); tauriDriver = spawn('tauri-driver', ['--port', String(TAURI_DRIVER_PORT)], { stdio: ['ignore', 'pipe', 'pipe'], }); tauriDriver.on('error', (err) => { reject(new Error(`tauri-driver failed: ${err.message}. Install: cargo install tauri-driver`)); }); const deadline = Date.now() + 10_000; function probe() { if (Date.now() > deadline) { reject(new Error('tauri-driver not ready within 10s')); return; } const sock = createConnection({ port: TAURI_DRIVER_PORT, host: 'localhost' }, () => { sock.destroy(); console.log(`tauri-driver ready on port ${TAURI_DRIVER_PORT}`); res(); }); sock.on('error', () => { sock.destroy(); setTimeout(probe, 200); }); } setTimeout(probe, 300); }); }); }, async before() { await browser.waitUntil( async () => { const title = await browser.getTitle(); const hasEl = await browser.execute( 'return document.querySelector(\'[data-testid="status-bar"]\') !== null' + ' || document.querySelector(".project-grid") !== null' + ' || document.querySelector(".settings-panel") !== null' ); return hasEl || title.toLowerCase().includes('agor') || title.toLowerCase().includes('orchestrator'); }, { timeout: 15_000, interval: 500, timeoutMsg: 'Wrong app — not Agent Orchestrator' }, ); console.log('[tauri] App identity verified.'); }, afterSession() { if (tauriDriver) { tauriDriver.kill(); tauriDriver = null; } try { rmSync(fixture.rootDir, { recursive: true, force: true }); } catch { /* best-effort */ } }, };