377 lines
9.9 KiB
Markdown
377 lines
9.9 KiB
Markdown
# 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/)
|