---
name: mobile-debugging
description: Remote JavaScript console access and debugging on mobile devices. Use when debugging web pages on phones/tablets, accessing console errors without desktop DevTools, testing responsive designs on real devices, or diagnosing mobile-specific issues. Covers Eruda, vConsole, Chrome/Safari remote debugging, and cloud testing platforms.
---
# Mobile debugging methodology
Patterns for accessing JavaScript console and debugging web pages on mobile devices without traditional desktop DevTools.
## Quick-start: Inject console on any page
### Eruda bookmarklet (recommended)
Add this as a bookmark on your mobile browser, then tap it on any page:
```javascript
javascript:(function(){var script=document.createElement('script');script.src='https://cdn.jsdelivr.net/npm/eruda';document.body.append(script);script.onload=function(){eruda.init();}})();
```
### vConsole bookmarklet
```javascript
javascript:(function(){var script=document.createElement('script');script.src='https://unpkg.com/vconsole@latest/dist/vconsole.min.js';document.body.append(script);script.onload=function(){new VConsole();}})();
```
## In-page console tools
### Eruda setup
Eruda provides a full DevTools-like experience in a floating panel.
```html
```
```javascript
// NPM installation
// npm install eruda --save-dev
import eruda from 'eruda';
// Initialize with options
eruda.init({
container: document.getElementById('eruda-container'),
tool: ['console', 'elements', 'network', 'resources', 'info'],
useShadowDom: true,
autoScale: true
});
// Add custom buttons
eruda.add({
name: 'Clear Storage',
init($el) {
$el.html('');
$el.find('button').on('click', () => {
localStorage.clear();
sessionStorage.clear();
console.log('Storage cleared');
});
}
});
// Remove when done
eruda.destroy();
```
**Eruda features:**
- Console (logs, errors, warnings)
- Elements (DOM inspector)
- Network (XHR/fetch requests)
- Resources (localStorage, cookies, sessionStorage)
- Sources (page source code)
- Info (page/device information)
- Snippets (saved code snippets)
### vConsole setup
Lighter weight alternative, official tool for WeChat debugging.
```html
```
```javascript
// NPM
// npm install vconsole
import VConsole from 'vconsole';
// Initialize with options
const vConsole = new VConsole({
theme: 'dark',
onReady: function() {
console.log('vConsole is ready');
},
log: {
maxLogNumber: 1000
}
});
// Dynamic configuration
vConsole.setOption('log.maxLogNumber', 5000);
// Destroy when done
vConsole.destroy();
```
**vConsole features:**
- Log panel (console.log, info, warn, error)
- System panel (device info)
- Network panel (XHR, fetch)
- Element panel (DOM tree)
- Storage panel (cookies, localStorage)
### Comparison: Eruda vs vConsole
| Feature | Eruda | vConsole |
|---------|-------|----------|
| Size | ~100KB | ~85KB |
| DOM Editing | Yes | View only |
| Network Details | Full | Basic |
| Plugin System | Yes | Yes |
| Dark Theme | Via plugin | Built-in |
| Best For | Full debugging | Quick logging |
## Native remote debugging
### Chrome DevTools (Android)
```bash
# 1. Enable USB debugging on Android
# Settings → Developer Options → USB Debugging = ON
# 2. Connect via USB to computer
# 3. Open Chrome on computer, navigate to:
# chrome://inspect#devices
# 4. Enable "Discover USB devices"
# 5. Accept debugging prompt on Android device
# 6. Click "Inspect" next to the page you want to debug
```
**Port forwarding for localhost:**
```bash
# In chrome://inspect, click "Port forwarding"
# Add: localhost:3000 → localhost:3000
# Now Android Chrome can access your dev server at localhost:3000
```
### Safari Web Inspector (iOS)
```bash
# 1. On iPhone/iPad:
# Settings → Safari → Advanced → Web Inspector = ON
# 2. On Mac:
# Safari → Preferences → Advanced → "Show Develop menu" = ON
# 3. Connect device via USB (or enable Wi-Fi debugging)
# 4. Open Safari on Mac:
# Develop → [Device Name] → [Page to debug]
# Wireless debugging (after initial USB setup):
# Develop → [Device] → Connect via Network
```
### Firefox Remote Debugging (Android)
```bash
# 1. On Android Firefox:
# Settings → Advanced → Remote debugging = ON
# 2. On Desktop Firefox:
# Open about:debugging
# 3. Connect Android via USB
# 4. Enable USB devices in about:debugging
# 5. Click "Connect" next to your device
```
## iOS debugging without Mac
### Using ios-webkit-debug-proxy
```bash
# Install on Windows (via Scoop)
scoop bucket add extras
scoop install ios-webkit-debug-proxy
# Install on Linux
sudo apt-get install ios-webkit-debug-proxy
# Install on Mac
brew install ios-webkit-debug-proxy
# Run the proxy
ios_webkit_debug_proxy -f chrome-devtools://devtools/bundled/inspector.html
# Connect to http://localhost:9221 to see connected devices
```
### Commercial: Inspect.dev
Inspect.dev provides iOS debugging from Windows/Linux with a familiar DevTools interface.
```bash
# Download from https://inspect.dev/
# 1. Install application
# 2. Connect iOS device via USB
# 3. Enable Web Inspector on iOS
# 4. Inspect.dev auto-detects pages
# 5. Click to open DevTools interface
```
## Cloud testing platforms
### LambdaTest (freemium)
```python
# LambdaTest provides real device cloud with console access
# Free tier: 100 minutes/month
import requests
# LambdaTest REST API for automation
LAMBDATEST_API = "https://api.lambdatest.com/automation/api/v1"
# For manual testing:
# 1. Go to https://www.lambdatest.com/
# 2. Select device/browser
# 3. Enter URL
# 4. DevTools available in toolbar
# Selenium/Playwright integration for automated console capture
from playwright.sync_api import sync_playwright
def test_on_lambdatest():
with sync_playwright() as p:
# Connect to LambdaTest
browser = p.chromium.connect(
f"wss://cdp.lambdatest.com/playwright?capabilities="
f"{{\"browserName\":\"Chrome\",\"platform\":\"android\"}}"
)
page = browser.new_page()
# Capture console logs
logs = []
page.on('console', lambda msg: logs.append(msg.text()))
page.goto('https://example.com')
browser.close()
return logs
```
### BrowserStack
```python
# BrowserStack: $29/month+, 10,000+ real devices
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
def get_browserstack_driver():
"""Create BrowserStack WebDriver with console logging."""
capabilities = {
'browserName': 'chrome',
'device': 'Samsung Galaxy S21',
'realMobile': 'true',
'os_version': '11.0',
'browserstack.console': 'verbose', # Capture console logs
'browserstack.networkLogs': 'true',
'browserstack.user': 'YOUR_USERNAME',
'browserstack.key': 'YOUR_KEY'
}
driver = webdriver.Remote(
command_executor='https://hub-cloud.browserstack.com/wd/hub',
desired_capabilities=capabilities
)
return driver
# After test, retrieve logs from BrowserStack dashboard or API
```
## Programmatic console capture
### Playwright console capture
```javascript
const { chromium, devices } = require('playwright');
async function captureConsoleLogs(url) {
const browser = await chromium.launch();
// Emulate mobile device
const context = await browser.newContext({
...devices['iPhone 13']
});
const page = await context.newPage();
// Capture all console messages
const logs = [];
page.on('console', msg => {
logs.push({
type: msg.type(),
text: msg.text(),
location: msg.location(),
timestamp: new Date().toISOString()
});
});
// Capture page errors
const errors = [];
page.on('pageerror', error => {
errors.push({
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});
});
// Capture failed requests
const failedRequests = [];
page.on('requestfailed', request => {
failedRequests.push({
url: request.url(),
failure: request.failure().errorText,
timestamp: new Date().toISOString()
});
});
await page.goto(url);
await page.waitForLoadState('networkidle');
await browser.close();
return { logs, errors, failedRequests };
}
// Usage
captureConsoleLogs('https://example.com')
.then(result => console.log(JSON.stringify(result, null, 2)));
```
### Puppeteer console capture
```javascript
const puppeteer = require('puppeteer');
async function debugMobilePage(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Set mobile viewport
await page.setViewport({
width: 375,
height: 812,
isMobile: true,
hasTouch: true
});
// Mobile user agent
await page.setUserAgent(
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) ' +
'AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1'
);
// Console capture with full details
page.on('console', async msg => {
const args = await Promise.all(
msg.args().map(arg => arg.jsonValue().catch(() => arg.toString()))
);
console.log(`[${msg.type().toUpperCase()}]`, ...args);
// Get source location
const location = msg.location();
if (location.url) {
console.log(` at ${location.url}:${location.lineNumber}`);
}
});
// Unhandled promise rejections
page.on('pageerror', err => {
console.error('[PAGE ERROR]', err.message);
});
await page.goto(url, { waitUntil: 'networkidle0' });
// Execute JavaScript and capture result
const result = await page.evaluate(() => {
// Check for common mobile issues
return {
viewportWidth: window.innerWidth,
devicePixelRatio: window.devicePixelRatio,
touchSupport: 'ontouchstart' in window,
errors: window.__capturedErrors || []
};
});
console.log('Page info:', result);
await browser.close();
}
```
## Error monitoring services
### Sentry integration
```javascript
// npm install @sentry/browser
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: 'YOUR_SENTRY_DSN',
environment: 'production',
// Capture console.error
integrations: [
new Sentry.BrowserTracing(),
new Sentry.Replay() // Session replay for debugging
],
// Sample rates
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
beforeSend(event) {
// Filter or modify events
return event;
}
});
// Manual error capture
try {
riskyOperation();
} catch (error) {
Sentry.captureException(error);
}
// Add context
Sentry.setUser({ id: 'user123' });
Sentry.setTag('page', 'checkout');
```
### LogRocket for session replay
```javascript
// npm install logrocket
import LogRocket from 'logrocket';
LogRocket.init('your-app/your-project');
// Identify user
LogRocket.identify('user123', {
name: 'Test User',
email: 'user@example.com'
});
// Console logs automatically captured
console.log('This appears in LogRocket');
// Manual logging
LogRocket.log('Custom event', { data: 'value' });
// Track errors
LogRocket.captureException(new Error('Something went wrong'));
```
## Android screen mirroring with Scrcpy
```bash
# Install scrcpy
# Windows: scoop install scrcpy
# Mac: brew install scrcpy
# Linux: apt install scrcpy
# Basic mirroring
scrcpy
# With specific options
scrcpy --max-size 1024 --bit-rate 2M
# Wireless connection (after initial USB)
adb tcpip 5555
adb connect :5555
scrcpy
# Record session
scrcpy --record session.mp4
# Turn off device screen while mirroring
scrcpy --turn-screen-off
```
## Mobile debugging workflow
```
┌─────────────────────────────────────────────────────────────────┐
│ MOBILE DEBUGGING DECISION TREE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Q: Do you have physical access to the device? │
│ │ │
│ ├─ YES: Can you connect via USB? │
│ │ │ │
│ │ ├─ Android: Use Chrome DevTools Remote │
│ │ │ chrome://inspect#devices │
│ │ │ │
│ │ └─ iOS: Have a Mac? │
│ │ │ │
│ │ ├─ YES: Use Safari Web Inspector │
│ │ │ │
│ │ └─ NO: Use Inspect.dev or │
│ │ ios-webkit-debug-proxy │
│ │ │
│ └─ NO USB: Inject Eruda/vConsole via bookmarklet │
│ │
│ Q: Remote/production debugging? │
│ │ │
│ ├─ Add conditional Eruda loading │
│ │ (?eruda=true parameter) │
│ │ │
│ └─ Set up Sentry/LogRocket for error monitoring │
│ │
│ Q: Automated testing? │
│ │ │
│ ├─ Playwright/Puppeteer with mobile emulation │
│ │ │
│ └─ Cloud platforms (LambdaTest, BrowserStack) │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## Common mobile debugging issues
### Touch events not firing
```javascript
// Check if touch events are supported
eruda.init();
console.log('Touch support:', 'ontouchstart' in window);
console.log('Pointer events:', 'onpointerdown' in window);
// Debug touch events
document.addEventListener('touchstart', e => {
console.log('touchstart', e.touches.length, 'touches');
}, { passive: true });
document.addEventListener('click', e => {
console.log('click at', e.clientX, e.clientY);
});
```
### Viewport issues
```javascript
// Log viewport information
console.log('Viewport:', {
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
outerWidth: window.outerWidth,
outerHeight: window.outerHeight,
devicePixelRatio: window.devicePixelRatio,
orientation: screen.orientation?.type
});
// Check meta viewport
const viewport = document.querySelector('meta[name="viewport"]');
console.log('Viewport meta:', viewport?.content);
```
### Performance debugging
```javascript
// Check performance timing
const perf = performance.getEntriesByType('navigation')[0];
console.log('Page load timing:', {
dns: perf.domainLookupEnd - perf.domainLookupStart,
tcp: perf.connectEnd - perf.connectStart,
request: perf.responseStart - perf.requestStart,
response: perf.responseEnd - perf.responseStart,
domParsing: perf.domInteractive - perf.responseEnd,
domComplete: perf.domComplete - perf.domInteractive,
total: perf.loadEventEnd - perf.navigationStart
});
// Check memory (Chrome only)
if (performance.memory) {
console.log('Memory:', {
usedJSHeapSize: (performance.memory.usedJSHeapSize / 1048576).toFixed(2) + ' MB',
totalJSHeapSize: (performance.memory.totalJSHeapSize / 1048576).toFixed(2) + ' MB'
});
}
```
## Platform comparison
| Tool | Cost | Platforms | Setup Difficulty | Best For |
|------|------|-----------|------------------|----------|
| **Eruda** | Free | All browsers | Easy (bookmarklet) | Quick debugging |
| **vConsole** | Free | All browsers | Easy | WeChat apps |
| **Chrome Remote** | Free | Android only | Medium | Full DevTools |
| **Safari Inspector** | Free | iOS only | Easy (Mac required) | Full DevTools |
| **Inspect.dev** | Paid | iOS from any OS | Easy | iOS without Mac |
| **LambdaTest** | Freemium | All | Easy | Cloud testing |
| **BrowserStack** | Paid | All | Easy | Real devices |
| **Sentry** | Freemium | All | Medium | Error monitoring |