| 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:(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:(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. Eruda 3.x (3.4.3 current as of 2026-05) is the right baseline; it ships ES2020 syntax and assumes a modern mobile browser.
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>eruda.init();</script>
<script>
(function() {
var src = 'https://cdn.jsdelivr.net/npm/eruda';
if (!/eruda=true/.test(window.location) &&
localStorage.getItem('active-eruda') !== 'true') return;
var script = document.createElement('script');
script.src = src;
script.onload = function() { eruda.init(); };
document.body.appendChild(script);
})();
</script>
import eruda from 'eruda';
eruda.init({
container: document.getElementById('eruda-container'),
tool: ['console', 'elements', 'network', 'resources', 'info'],
useShadowDom: true,
autoScale: true
});
eruda.add({
name: 'Clear Storage',
init($el) {
$el.html('<button>Clear All Storage</button>');
$el.find('button').on('click', () => {
localStorage.clear();
sessionStorage.clear();
console.log('Storage cleared');
});
}
});
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.
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script>
var vConsole = new VConsole();
</script>
import VConsole from 'vconsole';
const vConsole = new VConsole({
theme: 'dark',
onReady: function() {
console.log('vConsole is ready');
},
log: {
maxLogNumber: 1000
}
});
vConsole.setOption('log.maxLogNumber', 5000);
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)
Port forwarding for localhost:
Android 11+ wireless debugging (no USB needed):
adb pair <DEVICE_IP>:<PAIRING_PORT>
adb connect <DEVICE_IP>:<DEBUG_PORT>
adb devices
Wireless debugging persists across reboots once paired, but the adb connect step is needed each session.
Safari Web Inspector (iOS)
Firefox Remote Debugging (Android)
iOS debugging without Mac
Using ios-webkit-debug-proxy
scoop bucket add extras
scoop install ios-webkit-debug-proxy
sudo apt-get install ios-webkit-debug-proxy
brew install ios-webkit-debug-proxy
ios_webkit_debug_proxy -f chrome-devtools://devtools/bundled/inspector.html
Commercial: Inspect.dev
Inspect.dev provides iOS debugging from Windows/Linux with a familiar DevTools interface.
Cloud testing platforms
LambdaTest (freemium)
import requests
LAMBDATEST_API = "https://api.lambdatest.com/automation/api/v1"
from playwright.sync_api import sync_playwright
def test_on_lambdatest():
with sync_playwright() as p:
browser = p.chromium.connect(
f"wss://cdp.lambdatest.com/playwright?capabilities="
f"{{\"browserName\":\"Chrome\",\"platform\":\"android\"}}"
)
page = browser.new_page()
logs = []
page.on('console', lambda msg: logs.append(msg.text()))
page.goto('https://example.com')
browser.close()
return logs
BrowserStack
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
def get_browserstack_driver():
"""Create BrowserStack WebDriver with console logging."""
options = Options()
bstack_options = {
'deviceName': 'Samsung Galaxy S21',
'osVersion': '11.0',
'realMobile': 'true',
'consoleLogs': 'verbose',
'networkLogs': 'true',
'userName': 'YOUR_USERNAME',
'accessKey': 'YOUR_KEY'
}
options.set_capability('bstack:options', bstack_options)
options.set_capability('browserName', 'chrome')
driver = webdriver.Remote(
command_executor='https://hub-cloud.browserstack.com/wd/hub',
options=options
)
return driver
Programmatic console capture
Playwright console capture
const { chromium, devices } = require('playwright');
async function captureConsoleLogs(url) {
const browser = await chromium.launch();
const context = await browser.newContext({
...devices['iPhone 15']
});
const page = await context.newPage();
const logs = [];
page.on('console', msg => {
logs.push({
type: msg.type(),
text: msg.text(),
location: msg.location(),
timestamp: new Date().toISOString()
});
});
const errors = [];
page.on('pageerror', error => {
errors.push({
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});
});
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 };
}
captureConsoleLogs('https://example.com')
.then(result => console.log(JSON.stringify(result, null, 2)));
Puppeteer console capture
const puppeteer = require('puppeteer');
async function debugMobilePage(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({
width: 375,
height: 812,
isMobile: true,
hasTouch: true
});
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'
);
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);
const location = msg.location();
if (location.url) {
console.log(` at ${location.url}:${location.lineNumber}`);
}
});
page.on('pageerror', err => {
console.error('[PAGE ERROR]', err.message);
});
await page.goto(url, { waitUntil: 'networkidle0' });
const result = await page.evaluate(() => {
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
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: 'YOUR_SENTRY_DSN',
environment: 'production',
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration()
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
beforeSend(event) {
return event;
}
});
try {
riskyOperation();
} catch (error) {
Sentry.captureException(error);
}
Sentry.setUser({ id: 'user123' });
Sentry.setTag('page', 'checkout');
LogRocket for session replay
import LogRocket from 'logrocket';
LogRocket.init('your-app/your-project');
LogRocket.identify('user123', {
name: 'Test User',
email: 'user@example.com'
});
console.log('This appears in LogRocket');
LogRocket.log('Custom event', { data: 'value' });
LogRocket.captureException(new Error('Something went wrong'));
Android screen mirroring with Scrcpy
scrcpy
scrcpy --max-size 1024 --bit-rate 2M
adb tcpip 5555
adb connect <device-ip>:5555
scrcpy
scrcpy --record session.mp4
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
eruda.init();
console.log('Touch support:', 'ontouchstart' in window);
console.log('Pointer events:', 'onpointerdown' in window);
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
console.log('Viewport:', {
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
outerWidth: window.outerWidth,
outerHeight: window.outerHeight,
devicePixelRatio: window.devicePixelRatio,
orientation: screen.orientation?.type
});
const viewport = document.querySelector('meta[name="viewport"]');
console.log('Viewport meta:', viewport?.content);
Performance debugging
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
});
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 |