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
113
tests/e2e/wdio.shared.conf.js
Normal file
113
tests/e2e/wdio.shared.conf.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* Shared WebDriverIO configuration — common settings for both Tauri and Electrobun.
|
||||
*
|
||||
* Stack-specific configs (wdio.tauri.conf.js, wdio.electrobun.conf.js)
|
||||
* import and extend this with their own lifecycle hooks and capabilities.
|
||||
*/
|
||||
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { getResultsDb } from './infra/results-db.ts';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = resolve(__dirname, '../..');
|
||||
|
||||
export const sharedConfig = {
|
||||
// ── Runner ──
|
||||
runner: 'local',
|
||||
maxInstances: 1,
|
||||
|
||||
// ── Connection defaults (overridden per-stack) ──
|
||||
hostname: 'localhost',
|
||||
path: '/',
|
||||
|
||||
// ── Specs — unified set, all shared ──
|
||||
specs: [
|
||||
resolve(projectRoot, 'tests/e2e/specs/splash.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/smoke.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/groups.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/settings.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/theme.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/terminal.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/agent.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/keyboard.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/search.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/notifications.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/files.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/comms.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/tasks.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/status-bar.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/context.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/diagnostics.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/worktree.test.ts'),
|
||||
resolve(projectRoot, 'tests/e2e/specs/llm-judged.test.ts'),
|
||||
],
|
||||
|
||||
// ── 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 ──
|
||||
|
||||
/** Smart test caching: skip tests with 3+ consecutive passes */
|
||||
beforeTest(test) {
|
||||
browser.__expectedErrors = [];
|
||||
|
||||
if (process.env.FULL_RESCAN) return;
|
||||
|
||||
const db = getResultsDb();
|
||||
const specFile = test.file?.replace(/.*specs\//, '') ?? '';
|
||||
if (db.shouldSkip(specFile, test.title)) {
|
||||
const stats = db.getCacheStats();
|
||||
console.log(`Skipping (3+ consecutive passes): ${test.title} [${stats.skippable}/${stats.total} skippable]`);
|
||||
this.skip();
|
||||
}
|
||||
},
|
||||
|
||||
/** After each test: check for error toasts, record in pass cache */
|
||||
async afterTest(test, _context, { passed }) {
|
||||
let unexpected = [];
|
||||
try {
|
||||
const errors = await browser.execute(() => {
|
||||
const toasts = [...document.querySelectorAll('.toast-error, .load-error')];
|
||||
return toasts.map(t => t.textContent?.trim()).filter(Boolean);
|
||||
});
|
||||
|
||||
const expected = browser.__expectedErrors || [];
|
||||
unexpected = errors.filter(e => !expected.some(exp => e.includes(exp)));
|
||||
|
||||
if (unexpected.length > 0 && passed) {
|
||||
throw new Error(
|
||||
`Unexpected error toast(s) during "${test.title}": ${unexpected.join('; ')}`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.message?.includes('Unexpected error toast')) throw e;
|
||||
}
|
||||
|
||||
const db = getResultsDb();
|
||||
const specFile = test.file?.replace(/.*specs\//, '') ?? '';
|
||||
db.recordTestResult(specFile, test.title, passed && unexpected.length === 0);
|
||||
},
|
||||
|
||||
// ── TypeScript ──
|
||||
autoCompileOpts: {
|
||||
tsNodeOpts: {
|
||||
project: resolve(projectRoot, 'tests/e2e/tsconfig.json'),
|
||||
},
|
||||
},
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue