Add E2E testing infrastructure with WebdriverIO + Appium

Set up E2E test framework for Android using WebdriverIO, Appium, and
UiAutomator2. Add testID props to key components (AuthButton, BaseButton,
ChatBubble, CustomTextInput, ProposedEventCard) and apply testIDs to
login screen, chat screen, tab bar, and settings. Include initial tests
for app launch detection and login flow validation. Update CLAUDE.md
with E2E docs.
This commit is contained in:
2026-02-26 21:37:40 +01:00
parent 4f5737d27e
commit 27602aee4c
21 changed files with 12549 additions and 41 deletions

View File

@@ -25,6 +25,7 @@ npm run ios -w @calchat/client # Start on iOS
npm run web -w @calchat/client # Start web version
npm run lint -w @calchat/client # Run ESLint
npm run build:apk -w @calchat/client # Build APK locally with EAS
npm run test:e2e -w @calchat/client # Run E2E tests (requires Appium server running)
```
### Shared (packages/shared)
@@ -61,6 +62,8 @@ npm run test -w @calchat/server # Run Jest unit tests
| | tsdav | CalDAV client library |
| | ical.js | iCalendar parsing/generation |
| Testing | Jest / ts-jest | Server unit tests |
| | WebdriverIO + Appium | E2E tests (Android) |
| | UiAutomator2 | Android UI automation driver |
| Deployment | Docker | Server containerization (multi-stage build) |
| | Drone CI | CI/CD pipelines (build, test, format check, deploy, APK build + Gitea release) |
| Planned | iCalendar | Event export/import |
@@ -132,6 +135,19 @@ src/
│ └── ThemeStore.ts # theme, setTheme() - reactive theme switching with Zustand
└── hooks/
└── useDropdownPosition.ts # Hook for positioning dropdowns relative to trigger element
e2e/ # E2E tests (WebdriverIO + Appium)
├── jest.config.ts # Jest config (ts-jest, 120s timeout)
├── tsconfig.json # TypeScript config (ES2020, CommonJS)
├── .env # Test credentials, device name, Appium host/port
├── config/
│ └── capabilities.ts # Appium capabilities (Dev Client vs APK mode)
├── helpers/
│ ├── driver.ts # WebdriverIO driver singleton (init, get, quit)
│ ├── selectors.ts # testID constants for all screens
│ └── utils.ts # Helpers (waitForTestId, performLogin, ensureLoggedIn, ensureOnLoginScreen)
└── tests/
├── 01-app-launch.test.ts # App startup & screen detection
└── 02-login.test.ts # Login flow (empty fields, invalid creds, success)
```
**Routing:** Tab-based navigation with Chat, Calendar, and Settings as main screens. Auth screens (login, register) outside tabs. Dynamic routes for event detail and note editing.
@@ -672,6 +688,13 @@ NODE_ENV=development # development = pretty logs, production = JSON
- **Chat mode**: Edit AI-proposed events before confirming - updates ChatStore locally and persists to server via ChatService.updateProposalEvent()
- Route params: `mode` ('calendar' | 'chat'), `id?`, `date?`, `eventData?` (JSON), `proposalContext?` (JSON with messageId, proposalId, conversationId)
- Supports recurring events with RRULE configuration (daily/weekly/monthly/yearly)
- **E2E testing infrastructure:**
- WebdriverIO + Appium with UiAutomator2 driver for Android
- testID props added to key components (`AuthButton`, `BaseButton`, `ChatBubble`, `CustomTextInput`, `ProposedEventCard`)
- testIDs applied to login screen, chat screen, tab bar, settings logout button, and event proposal buttons
- `app.json`: `usesCleartextTraffic: true` for Android (allows HTTP connections needed by Appium)
- Two execution modes: Dev Client (local) and APK (CI)
- Tests: app launch detection, login flow validation (empty fields, invalid creds, success)
## Building
@@ -714,12 +737,49 @@ The project uses Drone CI (`.drone.yml`) with five pipelines:
## Testing
Server uses Jest with ts-jest for unit testing. Config in `apps/server/jest.config.js` ignores `/node_modules/` and `/dist/`.
### Server Unit Tests
Jest with ts-jest for unit testing. Config in `apps/server/jest.config.js` ignores `/node_modules/` and `/dist/`.
**Existing tests:**
- `src/utils/password.test.ts` - Tests for bcrypt hash() and compare()
- `src/utils/recurrenceExpander.test.ts` - Tests for expandRecurringEvents() (non-recurring, weekly/daily/UNTIL recurrence, EXDATE filtering, RRULE: prefix stripping, invalid RRULE fallback, multi-day events, sorting)
### E2E Tests (Client)
WebdriverIO + Appium for Android E2E testing. Tests run sequentially (`--runInBand`) sharing a singleton Appium driver.
**Two execution modes:**
- **Dev Client mode** (local): Connects to running Expo app (`host.exp.exponent`), `noReset: true`
- **APK mode** (CI): Installs APK via `APK_PATH` env var, `noReset: false`
**Running locally:**
```bash
# Terminal 1: Start Appium server
appium
# Terminal 2: Start Expo dev server on Android emulator
npm run android -w @calchat/client
# Terminal 3: Run E2E tests
npm run test:e2e -w @calchat/client
```
**Environment variables** (`apps/client/e2e/.env`):
```
TEST_USER=test # Login credentials for tests
TEST_PASSWORD=test
DEVICE_NAME=emulator-5554 # Android device/emulator
APPIUM_HOST=localhost
APPIUM_PORT=4723
```
**Element selection:** Uses Android UiAutomator2 with `resource-id` selectors (React Native maps `testID``resource-id` on Android).
**testID conventions:** Components with testID support: `AuthButton`, `BaseButton`, `ChatBubble`, `CustomTextInput`, `ProposedEventCard`. Key testIDs: `login-title`, `login-identifier-input`, `login-password-input`, `login-button`, `login-error-text`, `tab-chat`, `tab-calendar`, `tab-settings`, `chat-message-input`, `chat-send-button`, `chat-bubble-left`, `chat-bubble-right`, `settings-logout-button`, `event-accept-button`, `event-reject-button`.
**Existing E2E tests:**
- `01-app-launch.test.ts` - App startup, detects login or auto-logged-in state
- `02-login.test.ts` - Empty field validation, invalid credentials error, successful login
## Documentation
Detailed architecture diagrams are in `docs/`: