agent-orchestrator/tests/e2e/wdio.conf.js
Hibryda e76bc341f2 refactor(e2e): extract infrastructure into tests/e2e/infra/ module
- Move fixtures.ts, llm-judge.ts, results-db.ts to tests/e2e/infra/
- Deduplicate wdio.conf.js: use createTestFixture() instead of inline copy
- Replace __dirname paths with projectRoot-anchored paths
- Create test-mode-constants.ts (typed env var names, flag registry)
- Create scripts/preflight-check.sh (validates tauri-driver, display, Claude CLI)
- Create scripts/check-test-flags.sh (CI lint for AGOR_TEST flag drift)
- Rewrite tests/e2e/README.md with full documentation
- Update spec imports for moved infra files
2026-03-18 03:06:57 +01:00

171 lines
5 KiB
JavaScript

import { spawn } from 'node:child_process';
import { createConnection } from 'node:net';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { rmSync } from 'node:fs';
import { createTestFixture } from './infra/fixtures.ts';
const __dirname = dirname(fileURLToPath(import.meta.url));
const projectRoot = resolve(__dirname, '../..');
// Debug binary path (Cargo workspace target at repo root)
const tauriBinary = resolve(projectRoot, 'target/debug/agent-orchestrator');
let tauriDriver;
// ── Test Fixture ──
// IMPORTANT: Must be created at module top-level (synchronously) because the
// capabilities object below references fixtureDataDir/fixtureConfigDir at eval time.
// tauri:options.env may not reliably set process-level env vars, so we also
// inject into process.env for tauri-driver inheritance.
const fixture = createTestFixture('agor-e2e');
process.env.AGOR_TEST = '1';
process.env.AGOR_TEST_DATA_DIR = fixture.dataDir;
process.env.AGOR_TEST_CONFIG_DIR = fixture.configDir;
console.log(`Test fixture created at ${fixture.rootDir}`);
export const config = {
// ── Runner ──
runner: 'local',
maxInstances: 1, // Tauri doesn't support parallel sessions
// ── Connection (external tauri-driver on port 4444) ──
hostname: 'localhost',
port: 4444,
path: '/',
// ── Specs ──
// Single spec file — Tauri launches one app instance per session,
// and tauri-driver can't re-create sessions between spec files.
specs: [
resolve(projectRoot, 'tests/e2e/specs/agor.test.ts'),
resolve(projectRoot, 'tests/e2e/specs/agent-scenarios.test.ts'),
resolve(projectRoot, 'tests/e2e/specs/phase-b.test.ts'),
resolve(projectRoot, 'tests/e2e/specs/phase-c.test.ts'),
],
// ── Capabilities ──
capabilities: [{
// Disable BiDi negotiation — tauri-driver doesn't support webSocketUrl
'wdio:enforceWebDriverClassic': true,
'tauri:options': {
application: tauriBinary,
// Test isolation: fixture-created data/config dirs, disable watchers/telemetry
env: fixture.env,
},
}],
// ── Framework ──
framework: 'mocha',
mochaOpts: {
ui: 'bdd',
timeout: 180_000,
},
// ── Reporter ──
reporters: ['spec'],
// ── Logging ──
logLevel: 'warn',
// ── Timeouts ──
waitforTimeout: 10_000,
connectionRetryTimeout: 30_000,
connectionRetryCount: 3,
// ── Hooks ──
/**
* Build the debug binary before the test run.
* Uses --debug --no-bundle for fastest build time.
*/
onPrepare() {
if (process.env.SKIP_BUILD) {
console.log('SKIP_BUILD set — using existing debug binary.');
return Promise.resolve();
}
return new Promise((resolve, reject) => {
console.log('Building Tauri debug binary...');
const build = spawn('cargo', ['tauri', 'build', '--debug', '--no-bundle'], {
cwd: projectRoot,
stdio: 'inherit',
});
build.on('close', (code) => {
if (code === 0) {
console.log('Debug binary ready.');
resolve();
} else {
reject(new Error(`Tauri build failed with exit code ${code}`));
}
});
build.on('error', reject);
});
},
/**
* Spawn tauri-driver before the session.
* tauri-driver bridges WebDriver protocol to WebKit2GTK's inspector.
* Uses TCP probe to confirm port 4444 is accepting connections.
*/
beforeSession() {
return new Promise((res, reject) => {
tauriDriver = spawn('tauri-driver', [], {
stdio: ['ignore', 'pipe', 'pipe'],
});
tauriDriver.on('error', (err) => {
reject(new Error(
`Failed to start tauri-driver: ${err.message}. ` +
'Install it with: cargo install tauri-driver'
));
});
// TCP readiness probe — poll port 4444 until it accepts a connection
const maxWaitMs = 10_000;
const intervalMs = 200;
const deadline = Date.now() + maxWaitMs;
function probe() {
if (Date.now() > deadline) {
reject(new Error('tauri-driver did not become ready within 10s'));
return;
}
const sock = createConnection({ port: 4444, host: 'localhost' }, () => {
sock.destroy();
res();
});
sock.on('error', () => {
sock.destroy();
setTimeout(probe, intervalMs);
});
}
// Give it a moment before first probe
setTimeout(probe, 300);
});
},
/**
* Kill tauri-driver after the test run.
*/
afterSession() {
if (tauriDriver) {
tauriDriver.kill();
tauriDriver = null;
}
// Clean up test fixture
try {
rmSync(fixture.rootDir, { recursive: true, force: true });
console.log('Test fixture cleaned up.');
} catch { /* best-effort cleanup */ }
},
// ── TypeScript (auto-compile via tsx) ──
autoCompileOpts: {
tsNodeOpts: {
project: resolve(projectRoot, 'tests/e2e/tsconfig.json'),
},
},
};