feat: unified E2E testing engine — 205 tests, dual-stack support
Infrastructure: - adapters/: base, tauri (port 9750), electrobun (port 9761 + PTY daemon) - helpers/: 120+ centralized selectors, reusable actions, custom assertions - wdio.shared.conf.js + stack-specific configs 18 unified specs (205 tests): splash(6) smoke(15) settings(19) terminal(14) agent(15) search(12) files(15) comms(10) tasks(10) theme(12) groups(12) keyboard(8) notifications(10) diagnostics(8) status-bar(12) context(9) worktree(8) llm-judged(10) Daemon: --stack tauri|electrobun|both flag Scripts: test:e2e:tauri, test:e2e:electrobun, test:e2e:both
This commit is contained in:
parent
1995f03682
commit
77b9ce9f62
31 changed files with 3547 additions and 344 deletions
139
tests/e2e/adapters/electrobun-adapter.ts
Normal file
139
tests/e2e/adapters/electrobun-adapter.ts
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/**
|
||||
* Electrobun stack adapter — spawns WebKitWebDriver or electrobun binary,
|
||||
* manages PTY daemon lifecycle for terminal tests.
|
||||
*/
|
||||
|
||||
import { spawn, type ChildProcess } from 'node:child_process';
|
||||
import { createConnection } from 'node:net';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { StackAdapter, type StackCapabilities } from './base-adapter.ts';
|
||||
import type { TestFixture } from '../infra/fixtures.ts';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PROJECT_ROOT = resolve(__dirname, '../../..');
|
||||
const ELECTROBUN_ROOT = resolve(PROJECT_ROOT, 'ui-electrobun');
|
||||
|
||||
export class ElectrobunAdapter extends StackAdapter {
|
||||
readonly name = 'electrobun';
|
||||
readonly port = 9761;
|
||||
|
||||
getBinaryPath(): string {
|
||||
return resolve(ELECTROBUN_ROOT, 'build/Agent Orchestrator');
|
||||
}
|
||||
|
||||
getDataDir(fixture: TestFixture): string {
|
||||
return fixture.dataDir;
|
||||
}
|
||||
|
||||
getCapabilities(_fixture: TestFixture): StackCapabilities {
|
||||
return {
|
||||
capabilities: {
|
||||
'wdio:enforceWebDriverClassic': true,
|
||||
browserName: 'webkit',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
setupDriver(): Promise<ChildProcess> {
|
||||
return new Promise((res, reject) => {
|
||||
// Check port is free
|
||||
const preCheck = createConnection({ port: this.port, host: 'localhost' }, () => {
|
||||
preCheck.destroy();
|
||||
reject(new Error(
|
||||
`Port ${this.port} already in use. Kill: lsof -ti:${this.port} | xargs kill`
|
||||
));
|
||||
});
|
||||
|
||||
preCheck.on('error', () => {
|
||||
preCheck.destroy();
|
||||
|
||||
// Try WebKitWebDriver first (system-installed), fall back to electrobun binary
|
||||
const driverBin = this.findWebDriver();
|
||||
const driver = spawn(driverBin, ['--port', String(this.port)], {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: {
|
||||
...process.env,
|
||||
AGOR_TEST: '1',
|
||||
},
|
||||
});
|
||||
|
||||
driver.on('error', (err) => {
|
||||
reject(new Error(
|
||||
`Failed to start WebDriver for Electrobun: ${err.message}. ` +
|
||||
'Ensure WebKitWebDriver or the Electrobun binary is available.'
|
||||
));
|
||||
});
|
||||
|
||||
// TCP readiness probe
|
||||
const deadline = Date.now() + 15_000;
|
||||
const probe = () => {
|
||||
if (Date.now() > deadline) {
|
||||
reject(new Error(`WebDriver not ready on port ${this.port} within 15s`));
|
||||
return;
|
||||
}
|
||||
const sock = createConnection({ port: this.port, host: 'localhost' }, () => {
|
||||
sock.destroy();
|
||||
res(driver);
|
||||
});
|
||||
sock.on('error', () => {
|
||||
sock.destroy();
|
||||
setTimeout(probe, 300);
|
||||
});
|
||||
};
|
||||
setTimeout(probe, 500);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
teardownDriver(driver: ChildProcess): void {
|
||||
driver.kill();
|
||||
}
|
||||
|
||||
async startPtyDaemon(): Promise<ChildProcess> {
|
||||
const daemonPath = resolve(ELECTROBUN_ROOT, 'src/pty-daemon/agor-ptyd');
|
||||
const altPath = resolve(PROJECT_ROOT, 'target/debug/agor-ptyd');
|
||||
const bin = existsSync(daemonPath) ? daemonPath : altPath;
|
||||
|
||||
if (!existsSync(bin)) {
|
||||
throw new Error(`PTY daemon binary not found at ${daemonPath} or ${altPath}`);
|
||||
}
|
||||
|
||||
const daemon = spawn(bin, [], {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: { ...process.env, AGOR_TEST: '1' },
|
||||
});
|
||||
|
||||
// Wait for daemon to be ready (simple delay — daemon binds quickly)
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
return daemon;
|
||||
}
|
||||
|
||||
stopPtyDaemon(daemon: ChildProcess): void {
|
||||
daemon.kill('SIGTERM');
|
||||
}
|
||||
|
||||
verifyBinary(): void {
|
||||
if (!existsSync(this.getBinaryPath())) {
|
||||
throw new Error(
|
||||
`Electrobun binary not found at ${this.getBinaryPath()}. ` +
|
||||
'Build with: cd ui-electrobun && bun run build:canary'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private findWebDriver(): string {
|
||||
// Check common WebKitWebDriver locations
|
||||
const candidates = [
|
||||
'/usr/bin/WebKitWebDriver',
|
||||
'/usr/local/bin/WebKitWebDriver',
|
||||
resolve(ELECTROBUN_ROOT, 'node_modules/.bin/webkitwebdriver'),
|
||||
];
|
||||
for (const c of candidates) {
|
||||
if (existsSync(c)) return c;
|
||||
}
|
||||
// Fall back to PATH resolution
|
||||
return 'WebKitWebDriver';
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue