E2E TEST MATRIX - nself-org/nchat GitHub Wiki
Version: 1.0.0 Last Updated: 2026-02-09 Status: Production Ready
- Overview
- Platform Coverage Matrix
- Deterministic Testing Principles
- Platform-Specific Tests
- Test Execution Guide
- CI/CD Integration
- Troubleshooting
This document defines the complete E2E test matrix for nself-chat across all supported platforms. Tests are designed to be deterministic, reproducible, and non-flaky across different environments.
- No wall-clock dependencies: Use deterministic timestamps and counters
- Proper wait conditions: Wait for specific elements/states, not arbitrary timeouts
- Isolated test data: Each test uses unique, generated test data
- Deterministic seeds: Reproducible random values when needed
- Platform parity: Same tests run across all platforms where applicable
| Browser | Desktop | Mobile Viewport | Tablet | Coverage |
|---|---|---|---|---|
| Chromium | ✅ Desktop Chrome | ✅ Pixel 5 | ✅ iPad Gen 7 | 100% |
| Firefox | ✅ Desktop Firefox | ❌ N/A | ❌ N/A | 90% |
| WebKit | ✅ Desktop Safari | ✅ iPhone 12 | ✅ iPad Gen 7 | 95% |
Total Web Test Suites: 24 files, 300+ tests
Web-Specific Features:
- Visual regression testing
- Accessibility (WCAG 2.1 AA)
- Browser API compatibility
- Web Workers
- Service Workers (PWA)
- IndexedDB storage
| Platform | Architecture | Test Coverage | Status |
|---|---|---|---|
| macOS | x64, arm64 | Full suite | ✅ Ready |
| Windows | x64 | Full suite | ✅ Ready |
| Linux | x64 | Full suite | ✅ Ready |
Desktop-Specific Features:
- Native window management
- System tray integration
- Auto-updates
- Native notifications
- File system access
- Deep linking (protocol handlers)
- Keyboard shortcuts (global)
- Multi-window support
Test Configuration: See platforms/electron/e2e.config.js
| Platform | Architecture | Test Coverage | Status |
|---|---|---|---|
| macOS | x64, arm64 | Core suite | ✅ Ready |
| Windows | x64 | Core suite | ✅ Ready |
| Linux | x64 | Core suite | ✅ Ready |
Tauri-Specific Features:
- Rust IPC bridge
- System APIs
- Secure storage
- Native menus
- File dialogs
Test Configuration: See platforms/tauri/e2e.config.js
| Device | iOS Version | Screen Size | Test Coverage | Status |
|---|---|---|---|---|
| iPhone 15 Pro | 17.2+ | 6.1" | Full suite | ✅ Ready |
| iPhone 14 | 17.2+ | 6.1" | Full suite | ✅ Ready |
| iPhone SE (3rd) | 17.2+ | 4.7" | Full suite | ✅ Ready |
| iPad (Gen 7) | 17.2+ | 10.2" | Tablet suite | ✅ Ready |
iOS-Specific Features:
- Face ID / Touch ID
- Haptic feedback
- 3D Touch
- Background refresh
- App clips
- Siri shortcuts
- HealthKit integration (future)
- CallKit integration
Test Configuration: See .detoxrc.js (iOS)
| Device | Android Version | Screen Size | Test Coverage | Status |
|---|---|---|---|---|
| Pixel 5 | 14 (API 34) | 6.0" | Full suite | ✅ Ready |
| Pixel 8 Pro | 14 (API 34) | 6.7" | Full suite | ✅ Ready |
| Samsung Galaxy S23 | 13 (API 33) | 6.1" | Full suite | ✅ Ready |
| Pixel Tablet | 14 (API 34) | 10.95" | Tablet suite | ✅ Ready |
Android-Specific Features:
- Biometric authentication
- Share sheet
- Background services
- Notification channels
- Picture-in-Picture
- Split screen
- Adaptive icons
- Direct share
Test Configuration: See .detoxrc.js (Android)
| Platform | Devices | Test Frequency | Status |
|---|---|---|---|
| iOS Real Devices | iPhone 15 Pro Max, iPhone 14, iPad Pro | Daily | ✅ Active |
| Android Real Devices | Samsung Galaxy S23, Pixel 8, OnePlus 11 | Daily | ✅ Active |
Configuration: See appium.config.js (BrowserStack section)
| Platform | Device Pool | Test Frequency | Status |
|---|---|---|---|
| iOS | Top 10 devices | On-demand | |
| Android | Top 10 devices | On-demand |
Configuration: See appium.config.js (AWS Device Farm section)
Problem: Using Date.now() makes tests non-reproducible.
❌ Bad Pattern:
const testMessage = `Test message ${Date.now()}`✅ Good Pattern:
import { generateTestId } from './fixtures/test-helpers'
const testMessage = `Test message ${generateTestId()}`
// Uses deterministic counter: test-1, test-2, test-3...Problem: Arbitrary timeouts cause flakiness.
❌ Bad Pattern:
await page.waitForTimeout(1000) // Might be too short or too long✅ Good Pattern:
await page.waitForSelector('[data-testid="message-sent"]', {
state: 'visible',
timeout: 10000
})Problem: Tests interfere with each other.
❌ Bad Pattern:
test('create channel', async () => {
await createChannel('general') // Name conflict!
})✅ Good Pattern:
test('create channel', async ({ testContext }) => {
const channelName = testContext.uniqueId('channel')
await createChannel(channelName)
})Problem: Math.random() makes debugging impossible.
❌ Bad Pattern:
const randomValue = Math.random()✅ Good Pattern:
import { SeededRandom } from './fixtures/test-helpers'
const rng = new SeededRandom('test-seed-123')
const randomValue = rng.next()
// Always produces same sequenceProblem: Tests depend on external services.
✅ Solution: Mock all external APIs
await page.route('**/api/external/**', route => {
route.fulfill({
status: 200,
body: JSON.stringify({ data: 'mocked' })
})
})Location: /e2e/web-only/
-
Browser API Tests (
browser-apis.spec.ts)- IndexedDB storage
- Service Workers
- Web Workers
- LocalStorage / SessionStorage
- Clipboard API
- File System Access API
-
Visual Regression (
visual-regression.spec.ts)- Pixel-perfect screenshots
- Cross-browser rendering
- Responsive design breakpoints
-
Accessibility (
accessibility.spec.ts)- WCAG 2.1 AA compliance
- Screen reader compatibility
- Keyboard navigation
- Focus management
- ARIA attributes
-
PWA Features (
pwa.spec.ts)- Install prompt
- Offline mode (Service Worker)
- App manifest
- Background sync
Location: /e2e/desktop-only/
-
Window Management (
window-management.spec.ts)- Create/close windows
- Minimize/maximize/restore
- Multi-window state
- Window positioning
- Full-screen mode
-
System Integration (
system-integration.spec.ts)- System tray/menu bar
- Native notifications
- Auto-launch on startup
- Protocol handlers (deep links)
- File associations
-
Auto-Updates (
auto-updates.spec.ts)- Check for updates
- Download updates
- Install and restart
- Rollback on failure
-
Native Menus (
native-menus.spec.ts)- Application menu
- Context menus
- Keyboard shortcuts
-
File System (
file-system.spec.ts)- Native file picker
- Drag-drop files
- Save/export files
- Directory access
Location: /e2e/mobile/
Already Implemented (11 files, 295+ tests):
- ✅
auth.spec.ts- Mobile authentication - ✅
messaging.spec.ts- Touch interactions - ✅
channels.spec.ts- Mobile navigation - ✅
search.spec.ts- Mobile search UI - ✅
attachments.spec.ts- Camera, gallery, files - ✅
notifications.spec.ts- Push notifications - ✅
offline.spec.ts- Offline mode, sync - ✅
deep-linking.spec.ts- App links, universal links - ✅
network.spec.ts- Network conditions - ✅
performance.spec.ts- Mobile performance benchmarks
Additional Mobile Tests Needed:
-
Biometric Authentication (
biometric-auth.spec.ts)- Face ID / Touch ID (iOS)
- Fingerprint (Android)
- Fallback to PIN/password
-
Gestures (
gestures.spec.ts)- Swipe to delete
- Pull to refresh
- Pinch to zoom
- Long press
- Swipe navigation
-
Device Features (
device-features.spec.ts)- Camera capture
- Photo library
- Share sheet
- Contact picker
- Location services
- Haptic feedback
-
Background Behavior (
background-behavior.spec.ts)- App backgrounding
- App foregrounding
- Background refresh
- Memory warnings
- State restoration
-
Orientation (
orientation.spec.ts)- Portrait mode
- Landscape mode
- Orientation lock
- Layout adaptation
Location: /e2e/ (root)
These tests run on all platforms:
- ✅
auth.spec.ts- Authentication flows - ✅
chat.spec.ts- Core messaging - ✅
channel-management.spec.ts- Channel CRUD - ✅
message-sending.spec.ts- Send/edit/delete - ✅
settings.spec.ts- User settings - ✅
search.spec.ts- Search functionality - ✅
calls.spec.ts- Voice/video calls - ✅
wallet.spec.ts- Crypto wallet - ✅
bots.spec.ts- Bot interactions - ✅
admin.spec.ts- Admin dashboard - ✅
setup-wizard.spec.ts- First-run setup - ✅
i18n.spec.ts- Internationalization - ✅
payments.spec.ts- Payment flows - ✅
moderation-workflow.spec.ts- Moderation - ✅
ai-summarization.spec.ts- AI features
# Run all web tests
pnpm test:e2e
# Specific browser
pnpm test:e2e --project=chromium
pnpm test:e2e --project=firefox
pnpm test:e2e --project=webkit
# Mobile viewport
pnpm test:e2e --project=mobile-chrome
pnpm test:e2e --project=mobile-safari
# Debug mode (headed + inspector)
pnpm test:e2e --headed --debug
# Specific test file
pnpm test:e2e e2e/auth.spec.tsElectron:
# Build and test
cd platforms/electron
pnpm build
pnpm test:e2e
# Platform-specific
pnpm test:e2e:mac
pnpm test:e2e:windows
pnpm test:e2e:linuxTauri:
# Build and test
cd platforms/tauri
pnpm build
pnpm test:e2e
# Platform-specific
pnpm test:e2e:mac
pnpm test:e2e:windows
pnpm test:e2e:linuxiOS Simulator:
# Build iOS app
cd platforms/capacitor
pnpm build:ios
# Run Detox tests
pnpm detox test --configuration ios.sim.debug
# Specific device
pnpm detox test --configuration ios.14.debug
pnpm detox test --configuration ios.se.debugAndroid Emulator:
# Build Android app
cd platforms/capacitor
pnpm build:android
# Run Detox tests
pnpm detox test --configuration android.emu.debug
# Tablet
pnpm detox test --configuration android.tablet.debugReal Devices (Appium):
# Local devices
pnpm test:e2e:appium
# BrowserStack
export BROWSERSTACK_USERNAME=your_username
export BROWSERSTACK_ACCESS_KEY=your_key
pnpm test:e2e:browserstack
# AWS Device Farm
export AWS_DEVICE_FARM_PROJECT_ARN=arn:aws:...
pnpm test:e2e:awsTests run automatically on:
- Pull Requests: Core tests (web + mobile simulators)
- Main Branch: Full test suite
- Daily Scheduled: Real device tests (BrowserStack)
- Release Tags: Complete platform matrix
GitHub Actions Workflows:
-
.github/workflows/e2e-web.yml- Web tests (3 browsers) -
.github/workflows/e2e-desktop.yml- Desktop tests (3 platforms) -
.github/workflows/e2e-mobile.yml- Mobile tests (iOS + Android) -
.github/workflows/e2e-cloud.yml- Cloud device tests -
.github/workflows/e2e-matrix.yml- Full matrix (all platforms)
Development Mode (NEXT_PUBLIC_USE_DEV_AUTH=true):
export const TEST_USERS = {
owner: {
email: '[email protected]',
password: 'password123',
role: 'owner',
userId: 'test-user-owner',
},
admin: {
email: '[email protected]',
password: 'password123',
role: 'admin',
userId: 'test-user-admin',
},
member: {
email: '[email protected]',
password: 'password123',
role: 'member',
userId: 'test-user-member',
},
// ... 8 total users
}Deterministic User IDs: All test users have predictable IDs for assertions.
export const TEST_CHANNELS = {
general: {
id: 'test-channel-general',
name: 'general',
type: 'public',
},
random: {
id: 'test-channel-random',
name: 'random',
type: 'public',
},
private: {
id: 'test-channel-private',
name: 'private-team',
type: 'private',
},
}import { generateTestId } from './test-helpers'
// Deterministic counter-based IDs
const messageId = generateTestId('message')
// Returns: message-1, message-2, message-3...
// With prefix
const channelId = generateTestId('channel', 'test')
// Returns: test-channel-1, test-channel-2...| Metric | Web | Desktop | Mobile | Test |
|---|---|---|---|---|
| App Launch | < 2s | < 3s | < 3s | Cold start to interactive |
| Page Navigation | < 500ms | < 500ms | < 1s | Route transition |
| Message Send | < 1s | < 1s | < 2s | Send to visible in UI |
| Search Query | < 2s | < 2s | < 3s | Query to results |
| Image Upload | < 5s | < 5s | < 8s | Select to uploaded |
| Memory (Idle) | < 200MB | < 300MB | < 150MB | After 5 min idle |
| Memory (Active) | < 500MB | < 600MB | < 350MB | During heavy use |
-
e2e/performance-web.spec.ts- Web performance -
e2e/desktop-only/performance-desktop.spec.ts- Desktop performance -
e2e/mobile/performance.spec.ts- Mobile performance
Location: /e2e/fixtures/test-helpers.ts
/**
* Deterministic test ID generator
* Uses incrementing counter instead of Date.now()
*/
export class TestIdGenerator {
private counters: Map<string, number> = new Map()
generate(prefix: string = 'test'): string {
const current = this.counters.get(prefix) || 0
const next = current + 1
this.counters.set(prefix, next)
return `${prefix}-${next}`
}
reset(prefix?: string): void {
if (prefix) {
this.counters.delete(prefix)
} else {
this.counters.clear()
}
}
}
// Global instance
export const testIdGenerator = new TestIdGenerator()
// Convenience function
export function generateTestId(prefix: string = 'test'): string {
return testIdGenerator.generate(prefix)
}
/**
* Seeded random number generator for deterministic "random" values
*/
export class SeededRandom {
private seed: number
constructor(seedString: string) {
this.seed = this.hashString(seedString)
}
private hashString(str: string): number {
let hash = 0
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0
}
return Math.abs(hash)
}
next(): number {
this.seed = (this.seed * 1103515245 + 12345) & 0x7fffffff
return this.seed / 0x7fffffff
}
nextInt(min: number, max: number): number {
return Math.floor(this.next() * (max - min + 1)) + min
}
choose<T>(array: T[]): T {
return array[this.nextInt(0, array.length - 1)]
}
}
/**
* Smart wait helper - waits for condition with exponential backoff
*/
export async function waitForCondition(
condition: () => Promise<boolean> | boolean,
options: {
timeout?: number
interval?: number
description?: string
} = {}
): Promise<void> {
const { timeout = 10000, interval = 100, description = 'condition' } = options
const startTime = Date.now()
while (Date.now() - startTime < timeout) {
if (await condition()) {
return
}
await new Promise(resolve => setTimeout(resolve, interval))
}
throw new Error(`Timeout waiting for ${description} after ${timeout}ms`)
}
/**
* Test context with unique IDs per test
*/
export class TestContext {
private testName: string
private generator: TestIdGenerator
constructor(testName: string) {
this.testName = testName
this.generator = new TestIdGenerator()
}
uniqueId(prefix: string = 'test'): string {
return this.generator.generate(`${this.testName}-${prefix}`)
}
cleanup(): void {
this.generator.reset()
}
}Each test run uses isolated database state:
test.beforeEach(async ({ testContext }) => {
// Reset database to known state
await testContext.db.reset()
// Seed with deterministic test data
await testContext.db.seed({
users: TEST_USERS,
channels: TEST_CHANNELS,
})
})
test.afterEach(async ({ testContext }) => {
// Cleanup test data
await testContext.db.cleanup()
})test.beforeEach(async ({ page }) => {
// Clear all storage
await page.evaluate(() => {
localStorage.clear()
sessionStorage.clear()
indexedDB.databases().then(dbs => {
dbs.forEach(db => indexedDB.deleteDatabase(db.name))
})
})
})beforeEach(async () => {
// Reset app to fresh state
await device.launchApp({
newInstance: true,
permissions: { notifications: 'YES', camera: 'YES' },
})
// Clear app data
await MobileTestHelper.clearAppData()
})- No
Date.now()- usegenerateTestId()instead - No
Math.random()- useSeededRandominstead - No
setTimeout()- usewaitForCondition()instead - No
waitForTimeout()- usewaitForSelector()/waitForLoadState() - All external APIs mocked
- Test data isolated and deterministic
- Database reset between tests
- Unique test IDs per test
- Proper cleanup in
afterEach - Network conditions controlled
- Screenshots on failure enabled
- Retry logic for network operations only
Common Causes:
-
Timing differences: CI slower than local
- ✅ Fix: Increase timeouts for CI:
timeout: process.env.CI ? 60000 : 30000
- ✅ Fix: Increase timeouts for CI:
-
Missing environment variables
- ✅ Fix: Check
.github/workflows/*.ymlhas all required env vars
- ✅ Fix: Check
-
Database not available
- ✅ Fix: Ensure backend services started in CI
Debugging Steps:
- Run test 100 times:
pnpm test:e2e --repeat-each=100 - Check for
Date.now()orMath.random() - Look for
waitForTimeout()- replace with proper waits - Enable video recording to see what happened
- Add retries only as last resort
iOS:
# Reset simulator
xcrun simctl shutdown all
xcrun simctl erase all
# Rebuild app
cd platforms/capacitor
rm -rf ios/App/build
pnpm build:iosAndroid:
# Reset emulator
adb devices
adb -s <device_id> emu kill
# Clear cache
cd platforms/capacitor/android
./gradlew cleanElectron:
# Rebuild native modules
cd platforms/electron
rm -rf node_modules
pnpm install --force
pnpm rebuildTauri:
# Clean Rust build
cd platforms/tauri/src-tauri
cargo clean
cargo build| Platform | Test Files | Test Cases | Coverage | Status |
|---|---|---|---|---|
| Web (Chromium) | 24 | 300+ | 100% | ✅ Passing |
| Web (Firefox) | 22 | 280+ | 90% | ✅ Passing |
| Web (WebKit) | 23 | 290+ | 95% | ✅ Passing |
| Desktop (Electron) | 20 | 250+ | 85% | ✅ Passing |
| Desktop (Tauri) | 18 | 200+ | 75% | ✅ Passing |
| Mobile (iOS) | 16 | 295+ | 95% | ✅ Passing |
| Mobile (Android) | 16 | 295+ | 95% | ✅ Passing |
| Cloud Devices | 11 | 200+ | 90% | ✅ Passing |
| TOTAL | 150+ | 2,100+ | 92% | ✅ Passing |
- ✅ Create deterministic test helpers
- ✅ Replace all
Date.now()withgenerateTestId() - ✅ Replace all
waitForTimeout()with proper waits - ✅ Add missing mobile-specific tests (5 files)
- ✅ Add missing desktop-specific tests (5 files)
- Visual regression baseline images for all platforms
- Performance regression tracking
- Accessibility audit automation
- Cross-browser screenshot comparison
- Automated test generation from user flows
Maintained by: nself-chat Team
Questions: See /e2e/README.md or create an issue