Follow-up to #390. PHONE_MAX had become 599, an inclusive bound, while three of its four consumers still read it as exclusive (width < PHONE_MAX for phone); the one site that switched to <= disagreed with getDeviceType(). It is 600 again with < at every site. The breakpoint table in docs/mobile-testing-report.md says 600, and the three 430px visual baselines are removed: they depict the tablet tier now, and the visual suite recreates a missing baseline on its next run on the machine that owns them. device-matrix.test.ts is also run through Prettier, which the commit hook demanded and the format gate (src/ only) never did. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
8.4 KiB
Mobile E2E Testing Report
Date: 2026-01-31 Status: All 32 tests passing
Overview
Comprehensive mobile E2E testing was performed using Playwright with Chromium in mobile emulation mode. Tests validate touch interactions, responsive design, mobile-specific UI behaviors, and edge cases across various device viewports.
Test Coverage Summary
| Test File | Tests | Description |
|---|---|---|
mobile-safari.e2e.ts |
6 | Core mobile Safari/iPhone tests |
mobile-comprehensive.e2e.ts |
13 | UI components, modals, interactions |
mobile-edge-cases.e2e.ts |
13 | Edge cases: orientation, narrow screens, safe areas |
Bugs Found and Fixed
1. Monitor Panel Overlapping Toolbar on Mobile
File: src/web/public/styles.css (lines 7969-7982)
Problem: The monitor panel was positioned at bottom: var(--toolbar-height) (40px), but the mobile toolbar has height: auto with flex-wrap: wrap, causing it to be taller than 40px. This resulted in the monitor panel header intercepting tap events on the "Run Claude" button.
Error message:
<div class="monitor-panel-title">Monitor</div> from <div id="monitorPanel" class="monitor-panel">…</div> subtree intercepts pointer events
Fix: Hide monitor and subagents panels on phones by default:
@media (max-width: 430px) {
.monitor-panel,
.subagents-panel {
display: none !important;
}
}
Rationale: On phone screens (<430px), there isn't enough space for these panels anyway. Users can still access session info via the header and session options modal.
2. WebKit Browser Missing System Dependencies
File: test/e2e/fixtures/mobile-browser.fixture.ts
Problem: WebKit requires system libraries (libgtk-4, libgstreamer, etc.) that may not be installed on all systems, causing mobile tests to fail.
Fix: Added fallback to Chromium with mobile emulation:
try {
browser = await webkit.launch({ headless: true });
userAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X)...';
} catch {
// WebKit failed, use Chromium with mobile emulation
browser = await chromium.launch({ headless: true, args: [...] });
userAgent = 'Mozilla/5.0 (Linux; Android 14; Pixel 8)...';
}
3. Race Condition in Session Tab Detection
File: test/e2e/workflows/mobile-safari.e2e.ts
Problem: Test waited for .session-tab selector but then checked .session-tab.active, causing timing issues where the tab existed but wasn't yet marked as active.
Fix: Wait for the active tab directly:
// Before (race condition)
await page.waitForSelector('.session-tab', { timeout: ... });
const tabVisible = await page.isVisible('.session-tab.active');
// After (correct)
await page.waitForSelector('.session-tab.active', { timeout: ... });
const tabVisible = await page.isVisible('.session-tab.active');
Known Limitations
No Kill All Button on Mobile
Status: By design (not a bug)
The "Kill All" button is located in the Monitor panel, which is hidden on mobile devices (<430px). Users can close sessions individually via the close button on each session tab.
Consideration for future: Could add a "Kill All" option in the app settings modal or a long-press context menu on session tabs.
No Help Button on Mobile
Status: By design
There is no dedicated help button in the mobile UI. Help is accessible via:
- Keyboard shortcut (
?key) - App settings modal
Test Coverage
mobile-safari.e2e.ts (Port 3191)
| Test | Description |
|---|---|
| Touch-friendly UI rendering | Verifies touch-device and device-mobile body classes |
| 44px minimum touch targets | Ensures buttons meet WCAG AA touch target requirements |
| Tap gestures for session creation | Creates session via tap on Run Claude button |
| Always-visible close buttons | Verifies opacity:1 on touch devices (no hover dependency) |
| Header hiding on small screens | Brand, stats, font controls hidden on phones |
| Tablet viewport rendering | iPad Pro 11" (834x1194) renders with device-desktop + touch-device |
mobile-comprehensive.e2e.ts (Port 3192)
| Test | Description |
|---|---|
| Welcome overlay buttons | Touch-friendly welcome overlay with 44px+ button height |
| Run Claude button prominence | Button visible with flex: 1 on mobile |
| Case dropdown visibility | Dropdown accessible and functional |
| Version display hiding | .toolbar-center hidden on phones |
| Horizontal tab scrolling | Session tabs allow overflow-x: auto scrolling |
| Tab switching on tap | Tapping tabs switches active session |
| Full-screen modals | Modals use 100% width/height on phones |
| Create case modal | Case creation modal accessible via + button |
| Notification button | Notification bell visible and tappable |
| Settings button | Settings gear has adequate touch target |
| Close confirmation modal | Close button triggers confirmation dialog |
| Token count display | Token counter visible in header |
| Ralph wizard full-screen | Wizard modal renders full-screen |
mobile-edge-cases.e2e.ts (Port 3193)
| Test | Description |
|---|---|
| Landscape orientation handling | 874x402 landscape mode with proper classes |
| Terminal in landscape | Terminal renders with adequate height |
| Very narrow viewport (280px) | Galaxy Fold folded state usable |
| Narrow screen toolbar | Toolbar doesn't overflow on 280px |
| Session options via gear icon | Gear icon visible, modal opens on tap |
| Modal tab switching | Session options modal tabs work on touch |
| Terminal tap interactions | Terminal responds to touch events |
| Primary touch targets | Main buttons meet 44px height requirement |
| iOS safe area CSS variables | --safe-area-* variables defined |
| Double-tap zoom prevention | touch-action styles applied |
| Modal body scrolling | overflow-y: auto for touch scrolling |
| Viewport meta tag | Proper mobile viewport configuration |
| Android Pixel viewport | 412x915 Pixel 7a renders correctly |
Mobile CSS Breakpoints
| Breakpoint | Class | Description |
|---|---|---|
| < 600px | device-mobile |
Phone - most features hidden/simplified |
| 600-768px | device-tablet |
Tablet - intermediate layout |
| > 768px | device-desktop |
Desktop - full features |
Touch devices also get touch-device class regardless of screen size.
Viewports Tested
| Device | Width | Height | Scale | Notes |
|---|---|---|---|---|
| iPhone 17 Pro | 402 | 874 | 3x | Primary phone test |
| iPhone 17 Pro Landscape | 874 | 402 | 3x | Orientation testing |
| iPhone 17 Pro Max | 440 | 956 | 3x | Larger phone |
| iPad Pro 11" | 834 | 1194 | 2x | Tablet testing |
| Galaxy Fold (folded) | 280 | 653 | 3x | Extreme narrow test |
| Pixel 7a | 412 | 915 | 2.625x | Android testing |
Running Mobile Tests
# Install Playwright browsers (Chromium is required, WebKit optional)
npx playwright install chromium
# Run individual test files
npx vitest run test/e2e/workflows/mobile-safari.e2e.ts
npx vitest run test/e2e/workflows/mobile-comprehensive.e2e.ts
npx vitest run test/e2e/workflows/mobile-edge-cases.e2e.ts
# Run all mobile tests together
npx vitest run test/e2e/workflows/mobile-safari.e2e.ts test/e2e/workflows/mobile-comprehensive.e2e.ts test/e2e/workflows/mobile-edge-cases.e2e.ts
Port Allocations
| Port | Test File |
|---|---|
| 3191 | mobile-safari.e2e.ts |
| 3192 | mobile-comprehensive.e2e.ts |
| 3193 | mobile-edge-cases.e2e.ts |
Key Mobile UI Behaviors
- Monitor/Subagents panels: Hidden on phones (<430px)
- Toolbar: Wraps content with
flex-wrap: wrap, variable height - Session tabs: Horizontal scroll with hidden scrollbar
- Modals: Full-screen on phones (100% width/height)
- Touch targets: Minimum 44px height for WCAG compliance
- Close buttons: Always visible (opacity: 1) on touch devices
- Header: Brand, stats, font controls hidden on phones
- Safe areas: CSS variables for iOS notch handling
Future Improvements
- Add swipe gesture tests for tab navigation
- Add virtual keyboard handling tests (show/hide behavior)
- Add orientation change tests (dynamic portrait/landscape switching)
- Add safe area inset tests for iOS notch handling with actual device values
- Consider showing a condensed monitor indicator on mobile
- Add "Kill All" option accessible from mobile UI
- Test pull-to-refresh prevention on iOS Safari