Project: OpenCode Nexus - Mobile Client Version: 1.0.0 Last Updated: 2025-11-06 Status: Mobile-First Testing Framework
OpenCode Nexus follows a mobile-first testing approach that prioritizes touch interactions, offline capabilities, and cross-platform compatibility. Testing is integrated throughout the development process with special emphasis on mobile user experience and performance.
- Mobile-First Testing: All tests prioritize mobile user journeys
- Touch Interaction Testing: Comprehensive gesture and touch testing
- Offline Capability Testing: Full offline/online transition testing
- Cross-Platform Validation: iOS, Android, and PWA compatibility
- Performance Benchmarking: Mobile-specific performance targets
- Accessibility Verification: WCAG 2.2 AA compliance testing
/\
/ \
/ E2E \
/______\
/ \
/Integration\
/____________\
/ \
/ Component \
/_________________\
/ \
/ Unit Tests \
/____________________/
Focus: Individual functions and components Coverage Target: 90% for critical mobile components Tools: Vitest (frontend), Rust built-in testing (backend)
Focus: Touch interactions and mobile components Coverage Target: 100% for touch targets and gestures Tools: Playwright for component testing
Focus: API communication and data synchronization Coverage Target: 95% for connection and sync scenarios Tools: Custom integration test suite
Focus: Complete mobile user workflows Coverage Target: 100% for critical user journeys Tools: Playwright for mobile E2E testing
// Touch gesture test example
test('swipe to navigate conversations', async ({ page }) => {
await page.touchstart('#conversation-list', { x: 300, y: 200 });
await page.touchmove('#conversation-list', { x: 100, y: 200 });
await page.touchend('#conversation-list');
await expect(page.locator('.conversation-2')).toBeVisible();
});// Accessibility touch target test
test('all interactive elements meet 44px minimum', async ({ page }) => {
const buttons = await page.locator('button, [role="button"]').all();
for (const button of buttons) {
const box = await button.boundingBox();
expect(box.width).toBeGreaterThanOrEqual(44);
expect(box.height).toBeGreaterThanOrEqual(44);
}
});// Offline message composition
test('compose messages offline', async ({ page, browserContext }) => {
// Simulate offline
await browserContext.setOffline(true);
await page.fill('.message-input', 'Hello AI!');
await page.click('.send-button');
// Verify message queued locally
await expect(page.locator('.queued-message')).toBeVisible();
});// Background sync test
test('automatic sync when online', async ({ page, browserContext }) => {
// Queue messages offline
await browserContext.setOffline(true);
await composeAndSendMessage(page, 'Offline message');
// Restore connection
await browserContext.setOffline(false);
// Verify sync completes
await expect(page.locator('.sync-complete')).toBeVisible();
await expect(page.locator('.ai-response')).toBeVisible();
});// Server connection test
test('connect to OpenCode server', async ({ page }) => {
await page.fill('.server-host', 'opencode.example.com');
await page.fill('.server-port', '443');
await page.click('.connect-button');
await expect(page.locator('.connection-success')).toBeVisible();
});// SSL certificate validation
test('handle SSL certificate issues', async ({ page }) => {
// Mock invalid certificate
await mockInvalidCertificate();
await attemptConnection(page);
await expect(page.locator('.ssl-warning')).toBeVisible();
await expect(page.locator('.certificate-details')).toBeVisible();
});# iOS simulator test command
npx playwright test --config=playwright.ios.config.ts- Internal Testing: Development team testing
- External Testing: Limited user beta testing
- App Store Review: Pre-submission validation
- Touch ID/Face ID: Biometric authentication testing
- iOS Permissions: Camera, microphone, notification permissions
- Background Modes: Background sync and notifications
- iOS Keychain: Secure credential storage testing
# Android emulator test command
npx playwright test --config=playwright.android.config.ts- Internal Testing: Closed beta with development team
- Open Beta: Public beta testing
- Staged Rollout: Gradual release to production
- Biometric Authentication: Fingerprint and face unlock
- Android Permissions: Runtime permission handling
- Material Design: Component behavior validation
- Android Keystore: Secure storage testing
// PWA installation test
test('install as PWA', async ({ browser, page }) => {
// Mock PWA installation prompt
await mockPwaInstallPrompt();
await page.click('.install-button');
// Verify app installed
const manifest = await page.evaluate(() => {
return window.navigator.serviceWorker?.getRegistration();
});
expect(manifest).toBeTruthy();
});- Service Worker: Background sync and caching
- Web App Manifest: Installation and metadata
- Push Notifications: Background message handling
- Responsive Design: Mobile browser compatibility
// App startup time test
test('app startup under 2 seconds', async ({ page }) => {
const startTime = Date.now();
await page.goto('/');
await page.waitForSelector('.chat-interface');
const loadTime = Date.now() - startTime;
expect(loadTime).toBeLessThan(2000);
});// Memory usage monitoring
test('memory usage under 50MB', async ({ page }) => {
// Perform memory-intensive operations
await createLargeConversation(page);
const memoryUsage = await page.evaluate(() => {
return performance.memory.usedJSHeapSize / 1024 / 1024;
});
expect(memoryUsage).toBeLessThan(50);
});// Network condition simulation
test('handle slow 3G connections', async ({ browserContext }) => {
await browserContext.emulateNetworkConditions({
download: 750 * 1024 / 8, // 750 Kbps
upload: 250 * 1024 / 8, // 250 Kbps
latency: 100
});
await sendMessageAndVerifyResponse(page);
});// Network switching test
test('handle network transitions', async ({ browserContext, page }) => {
// Start online
await sendMessage(page, 'Online message');
// Go offline
await browserContext.setOffline(true);
await sendMessage(page, 'Offline message');
// Come back online
await browserContext.setOffline(false);
// Verify sync
await expect(page.locator('.sync-indicator')).toBeVisible();
});// Minimum touch target size
test('44px minimum touch targets', async ({ page }) => {
const interactives = await page.locator(`
button, a, input, select, textarea,
[role="button"], [role="link"], [role="textbox"]
`).all();
for (const element of interactives) {
const box = await element.boundingBox();
expect(box.width).toBeGreaterThanOrEqual(44);
expect(box.height).toBeGreaterThanOrEqual(44);
}
});// Screen reader compatibility
test('screen reader announcements', async ({ page }) => {
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
// Verify focus announcements
const announcements = await page.evaluate(() => {
return window.speechSynthesis.speaking;
});
expect(announcements).toBe(true);
});// Full keyboard accessibility
test('keyboard navigation works', async ({ page }) => {
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Enter');
await expect(page.locator('.menu-open')).toBeVisible();
});// No time limits for interactions
test('no interaction timeouts', async ({ page }) => {
// Simulate slow user
await page.waitForTimeout(10000); // 10 seconds
// Verify no timeout occurred
await expect(page.locator('.timeout-error')).not.toBeVisible();
});// Certificate validation testing
test('reject invalid SSL certificates', async ({ page }) => {
await mockInvalidCertificate();
await attemptConnection(page);
await expect(page.locator('.ssl-error')).toBeVisible();
await expect(page.locator('.connect-button')).toBeDisabled();
});// MITM attack simulation
test('detect MITM attacks', async ({ page }) => {
await setupMitmProxy();
await attemptConnection(page);
await expect(page.locator('.security-warning')).toBeVisible();
});// Offline data encryption
test('conversations encrypted locally', async ({ page }) => {
await composeMessage(page, 'Secret message');
await browserContext.setOffline(true);
// Verify data is encrypted in storage
const storedData = await getIndexedDBData();
expect(storedData).not.toContain('Secret message');
});# .github/workflows/ios-test.yml
name: iOS Tests
on: [push, pull_request]
jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx playwright install
- run: npx playwright test --config=playwright.ios.config.ts- iOS Simulator: Automated iOS simulator testing
- Android Emulator: Automated Android emulator testing
- Browser Testing: Chrome, Safari, Firefox mobile testing
- Device Farm: Real device testing for critical features
# Performance regression testing
- name: Performance Tests
run: |
npm run test:performance
# Fail if startup > 2s or memory > 50MB# Battery drain monitoring
- name: Battery Impact Test
run: |
npm run test:battery
# Monitor background battery usage// Mock server for testing
const mockServer = setupMockOpenCodeServer({
responses: {
'/api/chat': { response: 'Mock AI response' },
'/api/session': { sessionId: 'test-session-123' }
}
});- Valid Connections: Successful server connections
- Invalid Certificates: SSL validation failures
- Network Issues: Connection drops and recovery
- Large Conversations: Performance with 1000+ messages
- Development: Local testing with mock servers
- Staging: Test against staging OpenCode servers
- Production: Limited production testing with safeguards
Testing Status: Mobile-first testing framework established Next Focus: iOS TestFlight testing and PWA validation docs/client/TESTING.md