Skip to content

Commit ec900ee

Browse files
committed
Add macOS E2E tests
1 parent 4a24562 commit ec900ee

22 files changed

Lines changed: 1576 additions & 205 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//! E2E test support commands.
2+
3+
/// Returns the `CMDR_E2E_START_PATH` env var when the `automation` feature is enabled.
4+
/// The frontend uses this to override startup paths for E2E tests.
5+
#[tauri::command]
6+
#[cfg(feature = "automation")]
7+
pub fn get_e2e_start_path() -> Option<String> {
8+
std::env::var("CMDR_E2E_START_PATH").ok()
9+
}
10+
11+
/// No-op fallback when the `automation` feature is disabled.
12+
#[tauri::command]
13+
#[cfg(not(feature = "automation"))]
14+
pub fn get_e2e_start_path() -> Option<String> {
15+
None
16+
}

apps/desktop/src-tauri/src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Tauri commands module.
22
3+
pub mod e2e;
34
pub mod file_system;
45
pub mod file_viewer;
56
pub mod font_metrics;

apps/desktop/src-tauri/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,8 @@ pub fn run() {
695695
commands::indexing::cancel_nav_priority,
696696
commands::indexing::clear_drive_index,
697697
commands::indexing::set_indexing_enabled,
698+
// E2E test support
699+
commands::e2e::get_e2e_start_path,
698700
])
699701
.on_window_event(|window, event| {
700702
// When the main window is closed, quit the entire app (including settings/debug/viewer windows)

apps/desktop/src/lib/file-explorer/pane/DualPaneExplorer.svelte

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
type UnlistenFn,
2727
updateFocusedPane,
2828
findFileIndex,
29+
getE2eStartPath,
2930
} from '$lib/tauri-commands'
3031
import type {
3132
VolumeInfo,
@@ -772,6 +773,13 @@
772773
rightViewMode = status.rightViewMode
773774
leftPaneWidthPercent = status.leftPaneWidthPercent
774775
776+
// E2E test override: use CMDR_E2E_START_PATH subdirectories when set
777+
const e2eStartPath = await getE2eStartPath()
778+
if (e2eStartPath) {
779+
leftPath = `${e2eStartPath}/left`
780+
rightPath = `${e2eStartPath}/right`
781+
}
782+
775783
// Load sort state
776784
leftSortBy = status.leftSortBy
777785
rightSortBy = status.rightSortBy
@@ -784,23 +792,23 @@
784792
// Exception: 'network' is a virtual volume, trust the stored ID for that
785793
const defaultId = await getDefaultVolumeId()
786794
787-
if (status.leftVolumeId === 'network') {
795+
if (status.leftVolumeId === 'network' && !e2eStartPath) {
788796
leftVolumeId = 'network'
789797
} else {
790-
const leftContaining = await findContainingVolume(status.leftPath)
798+
const leftContaining = await findContainingVolume(leftPath)
791799
leftVolumeId = leftContaining?.id ?? defaultId
792800
}
793801
794-
if (status.rightVolumeId === 'network') {
802+
if (status.rightVolumeId === 'network' && !e2eStartPath) {
795803
rightVolumeId = 'network'
796804
} else {
797-
const rightContaining = await findContainingVolume(status.rightPath)
805+
const rightContaining = await findContainingVolume(rightPath)
798806
rightVolumeId = rightContaining?.id ?? defaultId
799807
}
800808
801809
// Initialize history with loaded paths and their volumes
802-
leftHistory = createHistory(leftVolumeId, status.leftPath)
803-
rightHistory = createHistory(rightVolumeId, status.rightPath)
810+
leftHistory = createHistory(leftVolumeId, leftPath)
811+
rightHistory = createHistory(rightVolumeId, rightPath)
804812
805813
initialized = true
806814

apps/desktop/src/lib/tauri-commands/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ export {
231231
updateServiceResolveTimeout,
232232
setIndexingEnabled,
233233
getDirStatsBatch,
234+
getE2eStartPath,
234235
getAiStatus,
235236
getAiModelInfo,
236237
startAiDownload,

apps/desktop/src/lib/tauri-commands/settings.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,22 @@ export async function isAiOptedOut(): Promise<boolean> {
139139
return invoke<boolean>('is_ai_opted_out')
140140
}
141141

142+
// ============================================================================
143+
// E2E test support
144+
// ============================================================================
145+
146+
/**
147+
* Returns the CMDR_E2E_START_PATH env var when the automation feature is enabled.
148+
* Returns null when the feature is disabled or the env var is not set.
149+
*/
150+
export async function getE2eStartPath(): Promise<string | null> {
151+
return invoke<string | null>('get_e2e_start_path')
152+
}
153+
154+
// ============================================================================
155+
// AI commands
156+
// ============================================================================
157+
142158
/** Gets AI-generated folder name suggestions for the current directory. */
143159
export async function getFolderSuggestions(
144160
listingId: string,
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Linux E2E tests (Docker + tauri-driver)
2+
3+
WebDriverIO E2E tests for Cmdr on Linux, using tauri-driver with WebKitGTK's WebKitWebDriver.
4+
5+
This is the workhorse test suite. All platform-independent app logic lives here: dialog flows, keyboard nav, selection,
6+
view modes, file viewer, settings, command palette, and file operations. macOS tests only cover platform integration
7+
(APFS ops, volume detection).
8+
9+
## Architecture
10+
11+
```
12+
WebDriverIO ──HTTP:4444──> tauri-driver ──> WebKitWebDriver ──> WebKitGTK (in-app)
13+
```
14+
15+
Runs inside a Docker container (Ubuntu 24.04) with Xvfb for headless GUI. The host's source code is mounted in, so the
16+
Tauri app is built inside the container.
17+
18+
## Running
19+
20+
```bash
21+
cd apps/desktop
22+
pnpm test:e2e:linux # Docker (recommended, works from macOS)
23+
pnpm test:e2e:linux:build # force rebuild Docker image
24+
pnpm test:e2e:linux:shell # interactive shell in container for debugging
25+
pnpm test:e2e:linux:native # native Linux only (requires Tauri prereqs)
26+
./scripts/e2e-linux.sh --clean # nuke build cache (try this when tests fail oddly)
27+
```
28+
29+
## Fixture system
30+
31+
Tests use a shared fixture helper (`../e2e-shared/fixtures.ts`) that creates a temp directory tree at
32+
`/tmp/cmdr-e2e-<timestamp>/` with `left/` (text files, sub-dir, hidden file, bulk .dat files) and `right/` (empty).
33+
34+
The `CMDR_E2E_START_PATH` env var tells the app where to open. Fixtures are fully recreated before each test via
35+
`recreateFixtures()` in the `beforeTest` hook so tests don't affect each other.
36+
37+
## WebKitGTK WebDriver quirks
38+
39+
These are critical for writing tests. Without these workarounds, tests will silently fail.
40+
41+
### 1. Native clicks fail on non-form elements
42+
43+
WebKitGTK's WebDriver rejects clicks on non-interactive container elements. **Use `jsClick()` (JS `el.click()`)
44+
instead:**
45+
46+
```typescript
47+
async function jsClick(element: WebdriverIO.Element): Promise<void> {
48+
await browser.execute((el: HTMLElement) => el.click(), element as unknown as HTMLElement)
49+
}
50+
```
51+
52+
### 2. `browser.keys(' ')` doesn't deliver Space
53+
54+
The Space character hits a CharKey/VirtualKey ambiguity in WebKitWebDriver. **Use the W3C Actions API instead:**
55+
56+
```typescript
57+
await browser.action('key').down(' ').pause(50).up(' ').perform()
58+
await browser.releaseActions()
59+
```
60+
61+
## Files
62+
63+
| File | Purpose |
64+
| -------------------------- | --------------------------------------------------------------- |
65+
| `wdio.conf.ts` | WebDriverIO config: spawns tauri-driver, manages fixtures |
66+
| `app.spec.ts` | 14 tests: rendering, keyboard nav, mouse interaction, dialogs |
67+
| `file-operations.spec.ts` | 8 tests: copy, move, rename, mkdir, view modes, hidden, palette |
68+
| `settings.spec.ts` | 5 tests: settings panel |
69+
| `viewer.spec.ts` | 10 tests: file viewer |
70+
| `docker/Dockerfile` | Ubuntu 24.04 image with Tauri prereqs, Xvfb, Node, Rust |
71+
| `docker/entrypoint.sh` | Xvfb/dbus setup for headless GUI |
72+
| `tsconfig.json` | TypeScript config for WDIO types |
73+
74+
## Related
75+
76+
- Shared fixture helper: `test/e2e-shared/fixtures.ts`
77+
- Full guide: `docs/tooling/e2e-testing-guide.md`
78+
- macOS E2E tests: `test/e2e-macos/` (platform integration only — APFS ops, volume detection)
79+
- Linux stubs: `src-tauri/src/stubs/` (volumes, network, permissions use stubs on Linux)

0 commit comments

Comments
 (0)