# E2E Testing with WebdriverIO End-to-end tests for JellyTau using WebdriverIO and tauri-driver. These tests run against a real Tauri app instance with an **isolated test database**. ## Quick Start ```bash # 1. Configure test credentials (first time only) cp e2e/.env.example e2e/.env # Edit e2e/.env with your Jellyfin server details # 2. Build the frontend bun run build # 3. Run E2E tests bun run test:e2e ``` ## Configuration ### Test Credentials E2E tests use credentials from `e2e/.env` (gitignored). Copy the example file to get started: ```bash cp e2e/.env.example e2e/.env ``` **e2e/.env** (your private file): ```bash # Your Jellyfin test server TEST_SERVER_URL=https://your-jellyfin.example.com TEST_SERVER_NAME=My Test Server # Test user credentials TEST_USERNAME=testuser TEST_PASSWORD=yourpassword # Optional: Specific test data IDs TEST_MUSIC_LIBRARY_ID=abc123 TEST_ALBUM_ID=xyz789 # ... etc ``` **Important:** - ✅ `.env` is gitignored - your credentials stay private - ✅ Tests fall back to Jellyfin demo server if `.env` doesn't exist - ✅ Share `.env.example` with your team so they can set up their own ### Isolated Test Database **Your production data is safe!** E2E tests use a completely separate database: - **Production:** `~/.local/share/com.dtourolle.jellytau/` - Your real data ✅ - **E2E Tests:** `/tmp/jellytau-test-data/` - Isolated test data ✅ This is configured via the `JELLYTAU_DATA_DIR` environment variable in `wdio.conf.ts`. ## Architecture ### Test Structure ``` e2e/ ├── .env.example # Template for test credentials ├── .env # Your credentials (gitignored) ├── specs/ # Test specifications │ ├── app-launch.e2e.ts # App initialization tests │ ├── auth.e2e.ts # Authentication flow │ └── navigation.e2e.ts # Navigation and routing ├── pageobjects/ # Page Object Model (POM) │ ├── BasePage.ts # Base class with common methods │ ├── LoginPage.ts # Login page interactions │ └── HomePage.ts # Home page interactions └── helpers/ # Test utilities ├── testConfig.ts # Load .env configuration └── testSetup.ts # Setup helpers ``` ### Page Object Model Tests use the Page Object Model pattern for maintainability: ```typescript // Good: Using page objects import LoginPage from "../pageobjects/LoginPage"; await LoginPage.waitForLoginPage(); await LoginPage.connectToServer(testConfig.serverUrl); await LoginPage.login(testConfig.username, testConfig.password); // Bad: Direct selectors in tests await $("#server-url").setValue("https://..."); await $("button").click(); ``` ## Writing Tests ### Using Test Configuration Always use `testConfig` for credentials and server details: ```typescript import { testConfig } from "../helpers/testConfig"; describe("My Feature", () => { it("should test something", async () => { // Use testConfig instead of hardcoded values await LoginPage.connectToServer(testConfig.serverUrl); await LoginPage.login(testConfig.username, testConfig.password); // Access optional test data if (testConfig.albumId) { // Test with specific album } }); }); ``` ### Test Data IDs For tests that need specific content (albums, tracks, etc.): 1. Find the ID in your Jellyfin server (check the URL when viewing an item) 2. Add it to your `e2e/.env`: ```bash TEST_ALBUM_ID=abc123def456 ``` 3. Use it in tests: ```typescript if (testConfig.albumId) { await browser.url(`/album/${testConfig.albumId}`); } ``` ### Example Test ```typescript import { expect } from "@wdio/globals"; import LoginPage from "../pageobjects/LoginPage"; import { testConfig } from "../helpers/testConfig"; describe("Album Playback", () => { beforeEach(async () => { // Login before each test await LoginPage.waitForLoginPage(); await LoginPage.fullLoginFlow( testConfig.serverUrl, testConfig.username, testConfig.password ); }); it("should play an album", async () => { // Skip if no test album configured if (!testConfig.albumId) { console.log("Skipping - no TEST_ALBUM_ID configured"); return; } // Navigate to album await browser.url(`/album/${testConfig.albumId}`); // Click play const playButton = await $('[aria-label="Play"]'); await playButton.click(); // Verify playback started const miniPlayer = await $(".mini-player"); expect(await miniPlayer.isDisplayed()).toBe(true); }); }); ``` ## Running Tests ### Commands ```bash # Run all E2E tests bun run test:e2e # Run in watch mode (development) bun run test:e2e:dev # Run specific test file bun run test:e2e -- e2e/specs/auth.e2e.ts ``` ### Before Running **Always build the frontend first:** ```bash bun run build cd src-tauri && cargo build ``` The debug binary expects built frontend files in the `build/` directory. ## Test Files ### app-launch.e2e.ts Basic app initialization tests: - App launches successfully - UI renders correctly - Unauthenticated users redirect to login **Status:** ✅ Working (no credentials needed) ### auth.e2e.ts Full authentication flow: - Server connection (2-step process) - Login form validation - Error handling - Complete auth flow **Status:** ✅ Working with any Jellyfin server ### navigation.e2e.ts Routing and navigation: - Protected routes - Redirects - Navigation after login **Status:** ⚠️ Needs valid credentials (configure `.env`) ## Configuration Reference ### wdio.conf.ts Main WebdriverIO configuration: ```typescript { port: 4444, // tauri-driver port maxInstances: 1, // Run tests sequentially logLevel: "warn", // Reduce noise framework: "mocha", timeout: 60000, // 60s test timeout capabilities: [{ "tauri:options": { application: "path/to/app", env: { JELLYTAU_DATA_DIR: "/tmp/jellytau-test-data" // Isolated DB } } }] } ``` ### Environment Variables | Variable | Description | Default | |----------|-------------|---------| | `TEST_SERVER_URL` | Jellyfin server URL | `https://demo.jellyfin.org/stable` | | `TEST_SERVER_NAME` | Server display name | `Demo Server` | | `TEST_USERNAME` | Test user username | `demo` | | `TEST_PASSWORD` | Test user password | `` (empty) | | `TEST_MUSIC_LIBRARY_ID` | Music library ID | undefined | | `TEST_ALBUM_ID` | Album ID for playback tests | undefined | | `TEST_TRACK_ID` | Track ID for tests | undefined | | `TEST_TIMEOUT` | Mocha test timeout (ms) | `60000` | | `TEST_WAIT_TIMEOUT` | Element wait timeout (ms) | `15000` | ## Debugging ### View Application During Tests Tests run with a visible window. To pause and inspect: ```typescript it("debug test", async () => { await LoginPage.waitForLoginPage(); // Pause for 10 seconds to inspect await browser.pause(10000); await LoginPage.enterServerUrl(testConfig.serverUrl); }); ``` ### Check Logs - **WebdriverIO logs:** Console output (set `logLevel: "info"` in config) - **tauri-driver logs:** Stdout/stderr from driver process - **App logs:** Check app console (if running with dev tools) ### Common Issues **"Connection refused" in browser body** - Frontend not built: Run `bun run build` - Solution: Always build before testing **"Element not found" errors** - Selector might be wrong - Element not loaded yet - add wait: `await element.waitForDisplayed()` **"Invalid session id"** - Normal when app closes between tests - Each test file gets a fresh app instance **Tests fail with "no .env file"** - Copy `e2e/.env.example` to `e2e/.env` - Configure your Jellyfin server details **Database still using production data** - Check `wdio.conf.ts` has `JELLYTAU_DATA_DIR` env var - Rebuild app: `cd src-tauri && cargo build` ## Platform Support ### Supported - ✅ **Linux** - Primary development platform - ✅ **Windows** - Supported (paths auto-detected) - ✅ **macOS** - Supported (paths auto-detected) ### Not Supported - ❌ **Android** - E2E testing requires Appium + emulators (out of scope) - Desktop tests cover 90% of app logic anyway ## Team Collaboration ### Sharing Test Configuration **DO:** - ✅ Commit `e2e/.env.example` with template values - ✅ Update README when adding new test data requirements - ✅ Use descriptive variable names in `.env.example` **DON'T:** - ❌ Commit `e2e/.env` with real credentials - ❌ Hardcode server URLs in test files - ❌ Skip authentication in tests (always test full flows) ### Setting Up for a New Team Member 1. **Clone repo** 2. **Copy env template:** `cp e2e/.env.example e2e/.env` 3. **Configure credentials:** Edit `e2e/.env` with your Jellyfin server 4. **Build frontend:** `bun run build` 5. **Run tests:** `bun run test:e2e` That's it! No shared credentials needed. ## Best Practices 1. **Use testConfig:** Never hardcode credentials 2. **Use Page Objects:** Keep selectors out of test specs 3. **Wait for Elements:** Always use `.waitForDisplayed()` 4. **Independent Tests:** Each test should work standalone 5. **Skip Gracefully:** Check for optional test data before using 6. **Build First:** Always `bun run build` before running tests 7. **Clear Names:** Use descriptive `describe` and `it` blocks ## Future Enhancements - [ ] Add more page objects (Player, Library, Queue, Settings) - [ ] Create test data fixtures - [ ] Add visual regression testing - [ ] Mock Jellyfin API for faster, more reliable tests - [ ] CI/CD integration (GitHub Actions) - [ ] Test report generation - [ ] Screenshot capture on failure - [ ] Video recording of test runs ## Resources - [WebdriverIO Documentation](https://webdriver.io/) - [Tauri Testing Guide](https://v2.tauri.app/develop/tests/webdriver/) - [tauri-driver GitHub](https://github.com/tauri-apps/tauri/tree/dev/tooling/webdriver) - [Mocha Documentation](https://mochajs.org/) - [Page Object Model Pattern](https://webdriver.io/docs/pageobjects/)