Root cause: WebDriverIO devtools protocol wraps functions in a polyfill that puts `return` inside eval() (not a function body) → "Illegal return". Fix: exec() wrapper in helpers/execute.ts converts function args to IIFE strings before passing to browser.execute(). Works identically on both WebDriver (Tauri) and CDP/devtools (Electrobun CEF). - 35 spec files updated (browser.execute → exec) - 4 config files updated (string-form expressions) - helpers/actions.ts + assertions.ts updated - 560 vitest + 116 cargo passing
150 lines
5.1 KiB
JavaScript
150 lines
5.1 KiB
JavaScript
/**
|
|
* 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 */ }
|
|
},
|
|
};
|