Files
noteflow/client/wdio.conf.ts
2026-01-14 23:23:01 -05:00

340 lines
9.8 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* WebdriverIO Configuration for Native Tauri Testing
*
* This config runs tests against the actual Tauri desktop app using tauri-driver.
* Requires: cargo install tauri-driver
*
* Usage:
* 1. Build the app: npm run tauri:build
* 2. Run tests: npm run test:native
*/
import type { Options } from '@wdio/types';
import * as path from 'node:path';
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { spawn, spawnSync, type ChildProcess } from 'node:child_process';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Track tauri-driver process
let tauriDriverProcess: ChildProcess | null = null;
const tauriDriverPath = getTauriDriverPath();
const tauriDriverStatus = getTauriDriverStatus(tauriDriverPath);
const shouldRunNative = tauriDriverStatus === 'supported';
const isWslEnv = isWsl();
if (isWslEnv && process.env.NOTEFLOW_ENCRYPT_AUDIO === undefined) {
process.env.NOTEFLOW_ENCRYPT_AUDIO = 'false';
console.log('WSL detected; disabling audio encryption for native tests');
}
if (process.env.NOTEFLOW_DISABLE_AUDIO_MONITOR === undefined) {
process.env.NOTEFLOW_DISABLE_AUDIO_MONITOR = '1';
}
if (process.env.NOTEFLOW_DISABLE_AUDIO_CAPTURE === undefined) {
process.env.NOTEFLOW_DISABLE_AUDIO_CAPTURE = '1';
}
if (process.env.NOTEFLOW_DISABLE_AUDIO_DEVICES === undefined) {
process.env.NOTEFLOW_DISABLE_AUDIO_DEVICES = '1';
}
if (process.env.NOTEFLOW_DISABLE_AUDIO_TESTS === undefined) {
process.env.NOTEFLOW_DISABLE_AUDIO_TESTS = '1';
}
if (process.env.NOTEFLOW_REQUEST_TIMEOUT_SECS === undefined) {
process.env.NOTEFLOW_REQUEST_TIMEOUT_SECS = '300';
}
if (process.env.NOTEFLOW_E2E_NATIVE === undefined) {
process.env.NOTEFLOW_E2E_NATIVE = '1';
}
if (tauriDriverStatus === 'not_supported') {
console.warn('tauri-driver not supported on this platform; skipping native e2e tests.');
}
function isWsl(): boolean {
if (process.platform !== 'linux') {
return false;
}
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP || process.env.WSLENV) {
return true;
}
try {
const release = fs.readFileSync('/proc/sys/kernel/osrelease', 'utf8').toLowerCase();
return release.includes('microsoft') || release.includes('wsl');
} catch {
return false;
}
}
// Detect the built Tauri binary path based on platform
function getTauriBinaryPath(): string {
const projectRoot = path.resolve(__dirname, 'src-tauri');
if (process.platform === 'win32') {
// Windows: look for .exe in release or debug
// Binary name comes from Cargo.toml package name
const releasePath = path.join(projectRoot, 'target', 'release', 'noteflow-tauri.exe');
const debugPath = path.join(projectRoot, 'target', 'debug', 'noteflow-tauri.exe');
if (fs.existsSync(releasePath)) {
return releasePath;
}
if (fs.existsSync(debugPath)) {
return debugPath;
}
// Fallback to release path (will error if not built)
return releasePath;
} else if (process.platform === 'darwin') {
// macOS: .app bundle
const releasePath = path.join(
projectRoot,
'target',
'release',
'bundle',
'macos',
'NoteFlow.app',
'Contents',
'MacOS',
'noteflow-tauri'
);
const debugPath = path.join(projectRoot, 'target', 'debug', 'noteflow-tauri');
if (fs.existsSync(releasePath)) {
return releasePath;
}
if (fs.existsSync(debugPath)) {
return debugPath;
}
return releasePath;
} else {
// Linux: AppImage or direct binary
const releasePath = path.join(projectRoot, 'target', 'release', 'noteflow-tauri');
const debugPath = path.join(projectRoot, 'target', 'debug', 'noteflow-tauri');
if (fs.existsSync(releasePath)) {
return releasePath;
}
if (fs.existsSync(debugPath)) {
return debugPath;
}
return releasePath;
}
}
// Get tauri-driver path
function getTauriDriverPath(): string {
if (process.platform === 'win32') {
// On Windows, tauri-driver is in cargo bin
const cargoHome = process.env.CARGO_HOME || path.join(process.env.USERPROFILE || '', '.cargo');
return path.join(cargoHome, 'bin', 'tauri-driver.exe');
}
return 'tauri-driver';
}
type TauriDriverStatus = 'supported' | 'not_supported' | 'missing' | 'error';
function getTauriDriverStatus(driverPath: string): TauriDriverStatus {
const result = spawnSync(driverPath, ['--help'], { encoding: 'utf8' });
const error = result.error as NodeJS.ErrnoException | undefined;
if (error?.code === 'ENOENT') {
return 'missing';
}
if (error) {
return 'error';
}
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`.toLowerCase();
if (output.includes('not supported')) {
return 'not_supported';
}
return result.status === 0 ? 'supported' : 'error';
}
// Get msedgedriver path (Windows only)
async function getMsEdgeDriverPath(): Promise<string | null> {
if (process.platform !== 'win32') {
return null;
}
// Try edgedriver npm package first
try {
const edgedriver = await import('edgedriver');
const downloadedPath = await edgedriver.download();
if (fs.existsSync(downloadedPath)) {
return downloadedPath;
}
} catch {
// Package not available or failed
}
// Check common locations
const possiblePaths = [
// Custom env var
process.env.MSEDGEDRIVER_PATH,
// Common install locations
'C:\\Program Files\\Microsoft\\Edge\\msedgedriver.exe',
'C:\\Program Files (x86)\\Microsoft\\Edge\\msedgedriver.exe',
path.join(process.env.USERPROFILE || '', 'msedgedriver.exe'),
path.join(process.env.USERPROFILE || '', '.webdrivers', 'msedgedriver.exe'),
];
for (const p of possiblePaths) {
if (p && fs.existsSync(p)) {
return p;
}
}
return null;
}
export const config: Options.Testrunner = {
// Test specs
specs: ['./e2e-native/**/*.spec.ts'],
exclude: [],
// Capabilities
maxInstances: 1, // Tauri apps should run one at a time
capabilities: [
{
// Use tauri-driver as the WebDriver server
'tauri:options': {
application: getTauriBinaryPath(),
},
},
],
// Test framework
framework: 'mocha',
mochaOpts: {
ui: 'bdd',
timeout: 180000,
},
// Reporters
reporters: ['spec'],
// Log level
logLevel: 'info',
// Connection settings for tauri-driver
hostname: '127.0.0.1',
port: 4444,
// No built-in service - tauri-driver started via onPrepare hook
services: [],
// Timeouts
connectionRetryTimeout: 120000,
connectionRetryCount: 3,
// Hooks
onPrepare: async () => {
if (!shouldRunNative) {
if (tauriDriverStatus === 'missing') {
throw new Error(
`tauri-driver not found at: ${tauriDriverPath}\nInstall it with: cargo install tauri-driver`
);
}
if (tauriDriverStatus === 'not_supported') {
process.exit(0);
}
throw new Error('tauri-driver failed to start');
}
console.log(`Starting tauri-driver: ${tauriDriverPath}`);
// On Windows, check for msedgedriver
const edgeDriverPath = await getMsEdgeDriverPath();
if (process.platform === 'win32' && !edgeDriverPath) {
console.warn(
'\n⚠ msedgedriver.exe not found in common locations.\n' +
' Download from: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/\n' +
' Then either:\n' +
' - Add to PATH\n' +
' - Set MSEDGEDRIVER_PATH environment variable\n' +
' - Place in your home directory\n'
);
}
// Build args
const args = ['--port', '4444'];
if (edgeDriverPath) {
args.push('--native-driver', edgeDriverPath);
console.log(`Using msedgedriver: ${edgeDriverPath}`);
}
// Start tauri-driver
tauriDriverProcess = spawn(tauriDriverPath, args, {
stdio: ['ignore', 'pipe', 'pipe'],
});
tauriDriverProcess.stdout?.on('data', (data: Buffer | string) => {
const text = typeof data === 'string' ? data : data.toString();
console.log(`[tauri-driver] ${text.trim()}`);
});
tauriDriverProcess.stderr?.on('data', (data: Buffer | string) => {
const text = typeof data === 'string' ? data : data.toString();
console.error(`[tauri-driver] ${text.trim()}`);
});
// Wait for tauri-driver to be ready
await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('tauri-driver failed to start within 10s'));
}, 10000);
const checkReady = async () => {
try {
const response = await fetch('http://127.0.0.1:4444/status');
if (response.ok) {
clearTimeout(timeout);
console.log('tauri-driver is ready');
resolve();
}
} catch {
// Not ready yet, retry
setTimeout(checkReady, 200);
}
};
// Start checking after a brief delay
setTimeout(checkReady, 500);
});
},
onComplete: async () => {
// Stop tauri-driver
if (tauriDriverProcess) {
console.log('Stopping tauri-driver');
tauriDriverProcess.kill();
tauriDriverProcess = null;
}
},
beforeSession: async () => {
if (!shouldRunNative) {
return;
}
const binaryPath = getTauriBinaryPath();
if (!fs.existsSync(binaryPath)) {
throw new Error(
`Tauri binary not found at: ${binaryPath}\n` +
'Please build the app first with: npm run tauri:build'
);
}
console.log(`Using Tauri binary: ${binaryPath}`);
},
afterTest: async (test, _context, { error }) => {
if (error) {
// Take screenshot on failure
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const screenshotPath = `./e2e-native/screenshots/${test.title}-${timestamp}.png`;
await browser.saveScreenshot(screenshotPath);
console.log(`Screenshot saved: ${screenshotPath}`);
}
},
};