test(e2e): scaffold WebdriverIO + tauri-driver E2E testing infrastructure
This commit is contained in:
parent
7fc87a9567
commit
3c3a8ab54e
6 changed files with 6381 additions and 15 deletions
6173
v2/package-lock.json
generated
6173
v2/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -12,12 +12,17 @@
|
||||||
"tauri:dev": "cargo tauri dev",
|
"tauri:dev": "cargo tauri dev",
|
||||||
"tauri:build": "cargo tauri build",
|
"tauri:build": "cargo tauri build",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
|
"test:e2e": "wdio run tests/e2e/wdio.conf.js",
|
||||||
"build:sidecar": "esbuild sidecar/agent-runner.ts --bundle --platform=node --format=esm --outfile=sidecar/dist/agent-runner.mjs"
|
"build:sidecar": "esbuild sidecar/agent-runner.ts --bundle --platform=node --format=esm --outfile=sidecar/dist/agent-runner.mjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||||
"@tsconfig/svelte": "^5.0.6",
|
"@tsconfig/svelte": "^5.0.6",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
|
"@wdio/cli": "^9.24.0",
|
||||||
|
"@wdio/local-runner": "^9.24.0",
|
||||||
|
"@wdio/mocha-framework": "^9.24.0",
|
||||||
|
"@wdio/spec-reporter": "^9.24.0",
|
||||||
"svelte": "^5.45.2",
|
"svelte": "^5.45.2",
|
||||||
"svelte-check": "^4.3.4",
|
"svelte-check": "^4.3.4",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,29 @@ The app runs inside WebKit2GTK on Linux, so tests interact with the real WebView
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Built Tauri app (`npm run tauri build`)
|
- Rust toolchain (for building the Tauri app)
|
||||||
- Display server (X11 or Wayland) -- headless Xvfb works for CI
|
- Display server (X11 or Wayland) — headless Xvfb works for CI
|
||||||
- `tauri-driver` installed (`cargo install tauri-driver`)
|
- `tauri-driver` installed: `cargo install tauri-driver`
|
||||||
- WebdriverIO (`npm install --save-dev @wdio/cli @wdio/local-runner @wdio/mocha-framework`)
|
- `webkit2gtk-driver` system package: `sudo apt install webkit2gtk-driver`
|
||||||
|
- npm devDeps already in package.json (`@wdio/cli`, `@wdio/local-runner`, `@wdio/mocha-framework`, `@wdio/spec-reporter`)
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Terminal 1: Start tauri-driver (bridges WebDriver to WebKit2GTK)
|
# From v2/ directory — builds debug binary automatically, spawns tauri-driver
|
||||||
tauri-driver
|
|
||||||
|
|
||||||
# Terminal 2: Run tests
|
|
||||||
npm run test:e2e
|
npm run test:e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `wdio.conf.js` handles:
|
||||||
|
1. Building the debug binary (`cargo tauri build --debug --no-bundle`) in `onPrepare`
|
||||||
|
2. Spawning `tauri-driver` before each session
|
||||||
|
3. Killing `tauri-driver` after each session
|
||||||
|
|
||||||
## CI setup (headless)
|
## CI setup (headless)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install virtual framebuffer
|
# Install virtual framebuffer + WebKit driver
|
||||||
sudo apt install xvfb
|
sudo apt install xvfb webkit2gtk-driver
|
||||||
|
|
||||||
# Run with Xvfb wrapper
|
# Run with Xvfb wrapper
|
||||||
xvfb-run npm run test:e2e
|
xvfb-run npm run test:e2e
|
||||||
|
|
@ -32,19 +35,35 @@ xvfb-run npm run test:e2e
|
||||||
|
|
||||||
## Writing tests
|
## Writing tests
|
||||||
|
|
||||||
Tests use WebdriverIO. Example:
|
Tests use WebdriverIO with Mocha. Specs go in `specs/`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { browser } from '@wdio/globals';
|
import { browser, expect } from '@wdio/globals';
|
||||||
|
|
||||||
describe('BTerminal', () => {
|
describe('BTerminal', () => {
|
||||||
it('should show the terminal pane on startup', async () => {
|
it('should show the status bar', async () => {
|
||||||
const terminal = await browser.$('.terminal-pane');
|
const statusBar = await browser.$('.status-bar');
|
||||||
await expect(terminal).toBeDisplayed();
|
await expect(statusBar).toBeDisplayed();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Key constraints:
|
||||||
|
- `maxInstances: 1` — Tauri doesn't support parallel WebDriver sessions
|
||||||
|
- Mocha timeout is 60s — the app needs time to initialize
|
||||||
|
- Tests interact with the real WebKit2GTK WebView, not a browser
|
||||||
|
|
||||||
|
## File structure
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/e2e/
|
||||||
|
├── README.md # This file
|
||||||
|
├── wdio.conf.js # WebdriverIO config with tauri-driver lifecycle
|
||||||
|
├── tsconfig.json # TypeScript config for test specs
|
||||||
|
└── specs/
|
||||||
|
└── smoke.test.ts # Basic smoke tests (app renders, sidebar toggle)
|
||||||
|
```
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- Tauri WebDriver docs: https://v2.tauri.app/develop/tests/webdriver/
|
- Tauri WebDriver docs: https://v2.tauri.app/develop/tests/webdriver/
|
||||||
|
|
|
||||||
42
v2/tests/e2e/specs/smoke.test.ts
Normal file
42
v2/tests/e2e/specs/smoke.test.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { browser, expect } from '@wdio/globals';
|
||||||
|
|
||||||
|
describe('BTerminal — Smoke Tests', () => {
|
||||||
|
it('should render the application window', async () => {
|
||||||
|
const title = await browser.getTitle();
|
||||||
|
expect(title).toBe('BTerminal');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the status bar', async () => {
|
||||||
|
const statusBar = await browser.$('.status-bar');
|
||||||
|
await expect(statusBar).toBeDisplayed();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show version text in status bar', async () => {
|
||||||
|
const version = await browser.$('.status-bar .version');
|
||||||
|
await expect(version).toBeDisplayed();
|
||||||
|
const text = await version.getText();
|
||||||
|
expect(text).toContain('BTerminal');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the sidebar rail', async () => {
|
||||||
|
const sidebarRail = await browser.$('.sidebar-rail');
|
||||||
|
await expect(sidebarRail).toBeDisplayed();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the workspace area', async () => {
|
||||||
|
const workspace = await browser.$('.workspace');
|
||||||
|
await expect(workspace).toBeDisplayed();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should toggle sidebar with settings button', async () => {
|
||||||
|
const settingsBtn = await browser.$('.rail-btn');
|
||||||
|
await settingsBtn.click();
|
||||||
|
|
||||||
|
const sidebarPanel = await browser.$('.sidebar-panel');
|
||||||
|
await expect(sidebarPanel).toBeDisplayed();
|
||||||
|
|
||||||
|
// Click again to close
|
||||||
|
await settingsBtn.click();
|
||||||
|
await expect(sidebarPanel).not.toBeDisplayed();
|
||||||
|
});
|
||||||
|
});
|
||||||
11
v2/tests/e2e/tsconfig.json
Normal file
11
v2/tests/e2e/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"target": "ESNext",
|
||||||
|
"types": ["@wdio/mocha-framework", "@wdio/globals/types"],
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["specs/**/*.ts"]
|
||||||
|
}
|
||||||
116
v2/tests/e2e/wdio.conf.js
Normal file
116
v2/tests/e2e/wdio.conf.js
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { spawn } from 'node:child_process';
|
||||||
|
import { resolve, dirname } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const projectRoot = resolve(__dirname, '../..');
|
||||||
|
|
||||||
|
// Debug binary path (built with `cargo tauri build --debug --no-bundle`)
|
||||||
|
const tauriBinary = resolve(projectRoot, 'src-tauri/target/debug/bterminal');
|
||||||
|
|
||||||
|
let tauriDriver;
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
// ── Runner ──
|
||||||
|
runner: 'local',
|
||||||
|
maxInstances: 1, // Tauri doesn't support parallel sessions
|
||||||
|
|
||||||
|
// ── Specs ──
|
||||||
|
specs: [resolve(__dirname, 'specs/**/*.test.ts')],
|
||||||
|
|
||||||
|
// ── Capabilities ──
|
||||||
|
capabilities: [{
|
||||||
|
browserName: 'wry',
|
||||||
|
'tauri:options': {
|
||||||
|
application: tauriBinary,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
|
||||||
|
// ── Framework ──
|
||||||
|
framework: 'mocha',
|
||||||
|
mochaOpts: {
|
||||||
|
ui: 'bdd',
|
||||||
|
timeout: 60_000,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Reporter ──
|
||||||
|
reporters: ['spec'],
|
||||||
|
|
||||||
|
// ── Logging ──
|
||||||
|
logLevel: 'warn',
|
||||||
|
|
||||||
|
// ── Timeouts ──
|
||||||
|
waitforTimeout: 10_000,
|
||||||
|
connectionRetryTimeout: 30_000,
|
||||||
|
connectionRetryCount: 3,
|
||||||
|
|
||||||
|
// ── Hooks ──
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the debug binary before the test run.
|
||||||
|
* Uses --debug --no-bundle for fastest build time.
|
||||||
|
*/
|
||||||
|
onPrepare() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log('Building Tauri debug binary...');
|
||||||
|
const build = spawn('cargo', ['tauri', 'build', '--debug', '--no-bundle'], {
|
||||||
|
cwd: projectRoot,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
build.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
console.log('Debug binary ready.');
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Tauri build failed with exit code ${code}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
build.on('error', reject);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn tauri-driver before each session.
|
||||||
|
* tauri-driver bridges WebDriver protocol to WebKit2GTK's inspector.
|
||||||
|
*/
|
||||||
|
beforeSession() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
tauriDriver = spawn('tauri-driver', [], {
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
});
|
||||||
|
|
||||||
|
tauriDriver.on('error', (err) => {
|
||||||
|
reject(new Error(
|
||||||
|
`Failed to start tauri-driver: ${err.message}. ` +
|
||||||
|
'Install it with: cargo install tauri-driver'
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for tauri-driver to be ready (listens on port 4444)
|
||||||
|
const timeout = setTimeout(() => resolve(), 2000);
|
||||||
|
tauriDriver.stdout.on('data', (data) => {
|
||||||
|
if (data.toString().includes('4444')) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kill tauri-driver after each session.
|
||||||
|
*/
|
||||||
|
afterSession() {
|
||||||
|
if (tauriDriver) {
|
||||||
|
tauriDriver.kill();
|
||||||
|
tauriDriver = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── TypeScript (auto-compile via tsx) ──
|
||||||
|
autoCompileOpts: {
|
||||||
|
tsNodeOpts: {
|
||||||
|
project: resolve(__dirname, 'tsconfig.json'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue