/** * 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() works in Mocha non-arrow context — safe for both protocols this.skip(); } }, /** After each test: check for error toasts, record in pass cache */ async afterTest(test, _context, { passed }) { let unexpected = []; try { // Use string script for cross-protocol compatibility (devtools + webdriver) const errors = await browser.execute( 'return (function() {' + ' var toasts = Array.from(document.querySelectorAll(".toast-error, .load-error"));' + ' return toasts.map(function(t) { return (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'), }, }, };