feat: CEF mode for Electrobun E2E — CDP automation, WebGL unlocked
- electrobun.config.ts: AGOR_CEF=1 enables bundleCEF + chromiumFlags (remote-debugging-port=9222). Production stays on WebKitGTK. - wdio.electrobun.conf.js: rewritten for CDP via devtools protocol. Spawns app, waits for CDP port, kills on complete. - helpers/actions.ts: waitForPort() polls CDP /json endpoint - docs/testing.md: CEF mode docs, CI setup, troubleshooting - npm script: test:e2e:electrobun prepends AGOR_CEF=1 CEF also enables: WebGL xterm.js addon (unlimited terminals), WebGPU, full Chrome DevTools — all for dev/test only.
This commit is contained in:
parent
fea6e267b0
commit
c79d489e1a
5 changed files with 189 additions and 27 deletions
78
docs/testing.md
Normal file
78
docs/testing.md
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
# E2E Testing — CEF Mode
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Electrobun build supports two rendering backends:
|
||||||
|
|
||||||
|
| Backend | Use case | E2E automation | WebGL | xterm limit |
|
||||||
|
|---------|----------|---------------|-------|-------------|
|
||||||
|
| **WebKitGTK** (default) | Production | None (no WebDriver) | No | 4 instances |
|
||||||
|
| **CEF** (opt-in) | Dev/test | CDP via port 9222 | Yes | Unlimited |
|
||||||
|
|
||||||
|
Production always ships with WebKitGTK — lighter footprint, no Chromium dependency, system-native rendering. CEF mode is for development and CI testing only.
|
||||||
|
|
||||||
|
## Enabling CEF Mode
|
||||||
|
|
||||||
|
Set the `AGOR_CEF` environment variable before building or running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build with CEF
|
||||||
|
AGOR_CEF=1 electrobun build --env=dev
|
||||||
|
|
||||||
|
# Dev mode with CEF
|
||||||
|
AGOR_CEF=1 electrobun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This does three things in `electrobun.config.ts`:
|
||||||
|
1. Sets `bundleCEF: true` — downloads and bundles CEF libraries
|
||||||
|
2. Sets `defaultRenderer: "cef"` — uses CEF instead of WebKitGTK
|
||||||
|
3. Adds `chromiumFlags` — enables `--remote-debugging-port=9222` and `--remote-allow-origins=*`
|
||||||
|
|
||||||
|
## Running E2E Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run Electrobun E2E tests (sets AGOR_CEF=1 automatically)
|
||||||
|
npm run test:e2e:electrobun
|
||||||
|
|
||||||
|
# Run both Tauri and Electrobun E2E suites
|
||||||
|
npm run test:e2e:both
|
||||||
|
|
||||||
|
# Full test suite including E2E
|
||||||
|
npm run test:all:e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
The test runner (`wdio.electrobun.conf.js`):
|
||||||
|
1. Creates an isolated test fixture (temp dirs, test groups.json)
|
||||||
|
2. Builds the app if no binary exists
|
||||||
|
3. Launches the app with `AGOR_CEF=1` and `AGOR_TEST=1`
|
||||||
|
4. Waits for CDP port 9222 to respond
|
||||||
|
5. Connects WebDriverIO via `automationProtocol: 'devtools'`
|
||||||
|
6. Runs all shared spec files
|
||||||
|
7. Kills the app and cleans up fixtures
|
||||||
|
|
||||||
|
## CI Setup
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: E2E (Electrobun + CEF)
|
||||||
|
run: xvfb-run AGOR_CEF=1 npm run test:e2e:electrobun
|
||||||
|
env:
|
||||||
|
AGOR_TEST: '1'
|
||||||
|
```
|
||||||
|
|
||||||
|
CEF requires a display server. Use `xvfb-run` on headless CI.
|
||||||
|
|
||||||
|
## Port Assignments
|
||||||
|
|
||||||
|
| Port | Service |
|
||||||
|
|------|---------|
|
||||||
|
| 9222 | CDP remote debugging (CEF) |
|
||||||
|
| 9760 | Vite dev server (HMR) |
|
||||||
|
| 9761 | Reserved (was WebKitWebDriver) |
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**"CDP port 9222 not ready after 30000ms"** — The app failed to start or CEF was not bundled. Check that the build used `AGOR_CEF=1`. Look for `[LAUNCHER] ERROR: CEF libraries found but LD_PRELOAD not set` in stderr.
|
||||||
|
|
||||||
|
**"No binary found"** — The test runner falls back to `electrobun dev`, which builds and launches in one step. This is slower but works without a pre-built binary.
|
||||||
|
|
||||||
|
**Tests pass with CEF but fail with WebKitGTK** — Expected. WebKitGTK has no WebDriver/CDP support in Electrobun. E2E tests only run against CEF. Manual testing covers WebKitGTK.
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
"test:cargo": "cd src-tauri && cargo test",
|
"test:cargo": "cd src-tauri && cargo test",
|
||||||
"test:e2e": "wdio run tests/e2e/wdio.conf.js",
|
"test:e2e": "wdio run tests/e2e/wdio.conf.js",
|
||||||
"test:e2e:tauri": "wdio run tests/e2e/wdio.tauri.conf.js",
|
"test:e2e:tauri": "wdio run tests/e2e/wdio.tauri.conf.js",
|
||||||
"test:e2e:electrobun": "wdio run tests/e2e/wdio.electrobun.conf.js",
|
"test:e2e:electrobun": "AGOR_CEF=1 wdio run tests/e2e/wdio.electrobun.conf.js",
|
||||||
"test:e2e:both": "npm run test:e2e:tauri && npm run test:e2e:electrobun",
|
"test:e2e:both": "npm run test:e2e:tauri && npm run test:e2e:electrobun",
|
||||||
"test:all": "bash scripts/test-all.sh",
|
"test:all": "bash scripts/test-all.sh",
|
||||||
"test:all:e2e": "bash scripts/test-all.sh --e2e",
|
"test:all:e2e": "bash scripts/test-all.sh --e2e",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,26 @@
|
||||||
import { browser } from '@wdio/globals';
|
import { browser } from '@wdio/globals';
|
||||||
import * as S from './selectors.ts';
|
import * as S from './selectors.ts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for a TCP port to accept connections. Used by CDP-based E2E configs
|
||||||
|
* to wait for the debugging port before connecting WebDriverIO.
|
||||||
|
*
|
||||||
|
* Polls `http://localhost:{port}/json` (CDP discovery endpoint) every 500ms.
|
||||||
|
*/
|
||||||
|
export async function waitForPort(port: number, timeout: number): Promise<void> {
|
||||||
|
const start = Date.now();
|
||||||
|
while (Date.now() - start < timeout) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`http://localhost:${port}/json`);
|
||||||
|
if (res.ok) return;
|
||||||
|
} catch {
|
||||||
|
// Port not ready yet — retry
|
||||||
|
}
|
||||||
|
await new Promise(r => setTimeout(r, 500));
|
||||||
|
}
|
||||||
|
throw new Error(`CDP port ${port} not ready after ${timeout}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
/** Open settings panel via gear icon click */
|
/** Open settings panel via gear icon click */
|
||||||
export async function openSettings(): Promise<void> {
|
export async function openSettings(): Promise<void> {
|
||||||
// Try clicking settings button — may need multiple attempts on WebKitGTK
|
// Try clicking settings button — may need multiple attempts on WebKitGTK
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,119 @@
|
||||||
/**
|
/**
|
||||||
* WebDriverIO config for Electrobun stack E2E tests.
|
* WebDriverIO config for Electrobun stack E2E tests.
|
||||||
*
|
*
|
||||||
* Extends shared config with WebKitWebDriver lifecycle and optional
|
* Uses CDP (Chrome DevTools Protocol) via CEF mode for reliable E2E automation.
|
||||||
* PTY daemon management. Port: 9761 (per project convention).
|
* 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 } from 'node:child_process';
|
import { execSync, spawn } from 'node:child_process';
|
||||||
import { resolve, dirname } from 'node:path';
|
import { resolve, dirname } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { existsSync, rmSync } from 'node:fs';
|
import { existsSync, rmSync } from 'node:fs';
|
||||||
import { sharedConfig } from './wdio.shared.conf.js';
|
import { sharedConfig } from './wdio.shared.conf.js';
|
||||||
|
import { waitForPort } from './helpers/actions.ts';
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const projectRoot = resolve(__dirname, '../..');
|
const projectRoot = resolve(__dirname, '../..');
|
||||||
const electrobunRoot = resolve(projectRoot, 'ui-electrobun');
|
const electrobunRoot = resolve(projectRoot, 'ui-electrobun');
|
||||||
|
|
||||||
|
const CDP_PORT = 9222;
|
||||||
|
|
||||||
// Use the Electrobun fixture generator (different groups.json format)
|
// Use the Electrobun fixture generator (different groups.json format)
|
||||||
let fixture;
|
let fixture;
|
||||||
try {
|
try {
|
||||||
const { createTestFixture } = await import('../../ui-electrobun/tests/e2e/fixtures.ts');
|
const { createTestFixture } = await import('../../ui-electrobun/tests/e2e/fixtures.ts');
|
||||||
fixture = createTestFixture('agor-ebun-unified');
|
fixture = createTestFixture('agor-ebun-cdp');
|
||||||
} catch {
|
} catch {
|
||||||
// Fall back to the Tauri fixture generator if Electrobun fixtures not available
|
|
||||||
const { createTestFixture } = await import('./infra/fixtures.ts');
|
const { createTestFixture } = await import('./infra/fixtures.ts');
|
||||||
fixture = createTestFixture('agor-ebun-unified');
|
fixture = createTestFixture('agor-ebun-cdp');
|
||||||
}
|
}
|
||||||
|
|
||||||
process.env.AGOR_TEST = '1';
|
process.env.AGOR_TEST = '1';
|
||||||
|
process.env.AGOR_CEF = '1';
|
||||||
process.env.AGOR_TEST_DATA_DIR = fixture.dataDir;
|
process.env.AGOR_TEST_DATA_DIR = fixture.dataDir;
|
||||||
process.env.AGOR_TEST_CONFIG_DIR = fixture.configDir;
|
process.env.AGOR_TEST_CONFIG_DIR = fixture.configDir;
|
||||||
|
|
||||||
const WEBDRIVER_PORT = 9761;
|
console.log(`[electrobun-cdp] Test fixture at ${fixture.rootDir ?? fixture.configDir}`);
|
||||||
|
|
||||||
console.log(`[electrobun] Test fixture at ${fixture.rootDir ?? fixture.configDir}`);
|
let appProcess;
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
...sharedConfig,
|
...sharedConfig,
|
||||||
|
|
||||||
port: WEBDRIVER_PORT,
|
// Use devtools protocol (CDP) instead of WebDriver
|
||||||
|
automationProtocol: 'devtools',
|
||||||
|
|
||||||
capabilities: [{
|
capabilities: [{
|
||||||
'wdio:enforceWebDriverClassic': true,
|
browserName: 'chromium',
|
||||||
browserName: 'webkit',
|
'goog:chromeOptions': {
|
||||||
|
debuggerAddress: `localhost:${CDP_PORT}`,
|
||||||
|
},
|
||||||
}],
|
}],
|
||||||
|
|
||||||
onPrepare() {
|
onPrepare() {
|
||||||
// Try multiple binary paths (dev vs canary vs production)
|
// Find existing binary or build
|
||||||
const candidates = [
|
const candidates = [
|
||||||
resolve(electrobunRoot, 'build/dev-linux-x64/AgentOrchestrator-dev/AgentOrchestrator-dev'),
|
resolve(electrobunRoot, 'build/dev-linux-x64/AgentOrchestrator-dev/AgentOrchestrator-dev'),
|
||||||
resolve(electrobunRoot, 'build/Agent Orchestrator'),
|
resolve(electrobunRoot, 'build/Agent Orchestrator'),
|
||||||
resolve(electrobunRoot, 'build/AgentOrchestrator'),
|
resolve(electrobunRoot, 'build/AgentOrchestrator'),
|
||||||
];
|
];
|
||||||
const electrobunBinary = candidates.find(p => existsSync(p));
|
let electrobunBinary = candidates.find(p => existsSync(p));
|
||||||
|
|
||||||
if (!electrobunBinary && !process.env.SKIP_BUILD) {
|
if (!electrobunBinary && !process.env.SKIP_BUILD) {
|
||||||
console.log('Building Electrobun...');
|
console.log('[electrobun-cdp] Building with CEF...');
|
||||||
try {
|
try {
|
||||||
execSync('npx vite build', { cwd: electrobunRoot, stdio: 'inherit' });
|
execSync('npx vite build', { cwd: electrobunRoot, stdio: 'inherit' });
|
||||||
// electrobun build may not be available — skip if missing
|
execSync('electrobun build --env=dev', { cwd: electrobunRoot, stdio: 'inherit' });
|
||||||
try { execSync('electrobun build --env=dev', { cwd: electrobunRoot, stdio: 'inherit' }); } catch {}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Build failed:', e.message);
|
console.warn('[electrobun-cdp] Build failed:', e.message);
|
||||||
}
|
}
|
||||||
|
electrobunBinary = candidates.find(p => existsSync(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalBinary = candidates.find(p => existsSync(p));
|
if (!electrobunBinary) {
|
||||||
if (!finalBinary) {
|
// Fall back to `electrobun dev` which builds + launches in one step
|
||||||
console.warn('Electrobun binary not found — tests will use WebKitWebDriver with dev server');
|
console.log('[electrobun-cdp] No binary found, launching via electrobun dev...');
|
||||||
|
appProcess = spawn('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() {
|
async before() {
|
||||||
// Wait for Electrobun app to load
|
// Wait for Electrobun app to render
|
||||||
await browser.waitUntil(
|
await browser.waitUntil(
|
||||||
async () => {
|
async () => {
|
||||||
const hasEl = await browser.execute(() =>
|
const hasEl = await browser.execute(() =>
|
||||||
|
|
@ -83,10 +125,16 @@ export const config = {
|
||||||
},
|
},
|
||||||
{ timeout: 20_000, interval: 500, timeoutMsg: 'Electrobun app did not load in 20s' },
|
{ timeout: 20_000, interval: 500, timeoutMsg: 'Electrobun app did not load in 20s' },
|
||||||
);
|
);
|
||||||
console.log('[electrobun] App loaded.');
|
console.log('[electrobun-cdp] App loaded.');
|
||||||
},
|
},
|
||||||
|
|
||||||
afterSession() {
|
onComplete() {
|
||||||
|
if (appProcess) {
|
||||||
|
console.log('[electrobun-cdp] Stopping app...');
|
||||||
|
appProcess.kill('SIGTERM');
|
||||||
|
appProcess = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const cleanup = fixture.cleanup ?? (() => {
|
const cleanup = fixture.cleanup ?? (() => {
|
||||||
try {
|
try {
|
||||||
if (fixture.rootDir) rmSync(fixture.rootDir, { recursive: true, force: true });
|
if (fixture.rootDir) rmSync(fixture.rootDir, { recursive: true, force: true });
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,15 @@
|
||||||
import type { ElectrobunConfig } from "electrobun";
|
import type { ElectrobunConfig } from "electrobun";
|
||||||
|
|
||||||
|
// CEF mode: opt-in via AGOR_CEF=1. Used for dev/test (E2E automation via CDP,
|
||||||
|
// WebGL support, unlimited xterm instances). Production uses WebKitGTK (lighter,
|
||||||
|
// no Chromium dependency, system-native).
|
||||||
|
const useCEF = process.env.AGOR_CEF === "1";
|
||||||
|
|
||||||
|
// When CEF is active, enable remote debugging for CDP-based E2E automation.
|
||||||
|
const cefFlags: Record<string, string | boolean> | undefined = useCEF
|
||||||
|
? { "remote-debugging-port": "9222", "remote-allow-origins": "*" }
|
||||||
|
: undefined;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
app: {
|
app: {
|
||||||
name: "Agent Orchestrator",
|
name: "Agent Orchestrator",
|
||||||
|
|
@ -16,16 +26,22 @@ export default {
|
||||||
},
|
},
|
||||||
watchIgnore: ["dist/**"],
|
watchIgnore: ["dist/**"],
|
||||||
mac: {
|
mac: {
|
||||||
bundleCEF: false,
|
bundleCEF: useCEF,
|
||||||
bundleWGPU: true,
|
bundleWGPU: true,
|
||||||
|
...(useCEF && { defaultRenderer: "cef" as const }),
|
||||||
|
...(cefFlags && { chromiumFlags: cefFlags }),
|
||||||
},
|
},
|
||||||
linux: {
|
linux: {
|
||||||
bundleCEF: false,
|
bundleCEF: useCEF,
|
||||||
bundleWGPU: true,
|
bundleWGPU: true,
|
||||||
|
...(useCEF && { defaultRenderer: "cef" as const }),
|
||||||
|
...(cefFlags && { chromiumFlags: cefFlags }),
|
||||||
},
|
},
|
||||||
win: {
|
win: {
|
||||||
bundleCEF: false,
|
bundleCEF: useCEF,
|
||||||
bundleWGPU: true,
|
bundleWGPU: true,
|
||||||
|
...(useCEF && { defaultRenderer: "cef" as const }),
|
||||||
|
...(cefFlags && { chromiumFlags: cefFlags }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} satisfies ElectrobunConfig;
|
} satisfies ElectrobunConfig;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue