The Browser Control API is a development-time tool that allows AI agents to interact with Parse Dashboard through an automated browser during feature implementation and debugging. This enables real-time verification without writing test files.
This is NOT a replacement for unit tests or E2E tests - it's specifically designed for AI agents to validate implementations while actively developing features.
Requirements:
- Node.js 20.19.0 or higher
Note: MongoDB is automatically started when you run npm run browser-control - no manual setup needed!
When you run the browser-control command, MongoDB and Parse Server automatically start alongside the dashboard - zero setup required!
npm run browser-controlThis will:
- Download and start MongoDB 8.0.4 (if not already downloaded)
- Start Parse Server on port 1337 (connects to the auto-started MongoDB)
- Start Dashboard on port 4040
- Auto-configure dashboard with a test app pointing to Parse Server
- Enable Browser Control API at
/browser-control
Or for visible browser (helpful for debugging):
npm run browser-control:visibleBoth scripts include webpack watch mode, so code changes are automatically rebuilt. Just reload the page to see updates.
What happens automatically:
- MongoDB 8.0.4 is downloaded (on first run) and started automatically
- Parse Server spawns and connects to the MongoDB instance
- Dashboard creates a test app called "Browser Control Test App"
- You can immediately start creating browser sessions and testing
- Everything stops cleanly when you exit (Ctrl+C)
Before creating a browser session, wait for webpack to finish compiling the assets:
curl http://localhost:4040/browser-control/ready/waitResponse:
{
"ready": true,
"waited": 6273,
"webpack": {
"compiling": false,
"lastCompilationTime": 1234567890000,
"lastCompilationDuration": 6152,
"compileCount": 1
}
}This is important because webpack runs in watch mode and compiles assets on startup and after file changes. Navigating before compilation completes will result in timeouts.
curl -X POST http://localhost:4040/browser-control/session/start \
-H "Content-Type: application/json" \
-d '{"headless": true, "startServers": false}'Response:
{
"sessionId": "sess_1234567890_abc123",
"dashboardUrl": null,
"parseServerUrl": null
}curl -X POST http://localhost:4040/browser-control/session/sess_1234567890_abc123/navigate \
-H "Content-Type: application/json" \
-d '{"url": "http://localhost:4040/"}'curl http://localhost:4040/browser-control/session/sess_1234567890_abc123/screenshotcurl -X DELETE http://localhost:4040/browser-control/session/sess_1234567890_abc123POST /browser-control/session/start
Create a new browser session, optionally starting Parse Server and Dashboard.
Request Body:
{
"headless": true, // Optional: Run headless (default: true)
"width": 1280, // Optional: Browser width (default: 1280)
"height": 720, // Optional: Browser height (default: 720)
"slowMo": 0, // Optional: Slow down by N ms (default: 0)
"startServers": false, // Optional: Start Parse Server + Dashboard (default: false)
"parseServerOptions": { // Optional: Parse Server config
"port": 1337,
"appId": "testAppId",
"masterKey": "testMasterKey",
"databaseURI": "mongodb://localhost:27017/test"
},
"dashboardOptions": { // Optional: Dashboard config
"port": 4040,
"appName": "TestApp"
}
}Response:
{
"sessionId": "sess_1234567890_abc123",
"dashboardUrl": "http://localhost:4040",
"parseServerUrl": "http://localhost:1337/parse",
"servers": {
"parseServer": { "port": 1337, "appId": "testAppId" },
"dashboard": { "port": 4040 }
}
}GET /browser-control/session/:sessionId/status
Response:
{
"sessionId": "sess_1234567890_abc123",
"active": true,
"pageUrl": "http://localhost:4040/apps",
"createdAt": 1234567890000,
"lastActivity": 1234567895000,
"uptime": 5000
}DELETE /browser-control/session/:sessionId
Cleanup and destroy browser session.
Response:
{
"success": true,
"sessionId": "sess_1234567890_abc123"
}GET /browser-control/sessions
Response:
{
"sessions": [
{
"sessionId": "sess_1234567890_abc123",
"createdAt": 1234567890000,
"lastActivity": 1234567895000,
"uptime": 5000,
"crashed": false,
"pageUrl": "http://localhost:4040/apps"
}
],
"count": 1
}POST /browser-control/session/:sessionId/navigate
Navigate to a URL.
Request Body:
{
"url": "http://localhost:4040/apps",
"waitUntil": "networkidle2", // Optional: load, domcontentloaded, networkidle0, networkidle2
"timeout": 30000 // Optional: timeout in ms (default: 30000)
}Response:
{
"success": true,
"currentUrl": "http://localhost:4040/apps"
}POST /browser-control/session/:sessionId/click
Click an element by CSS selector.
Request Body:
{
"selector": "#login-button",
"timeout": 5000 // Optional: timeout in ms (default: 5000)
}Response:
{
"success": true,
"selector": "#login-button"
}POST /browser-control/session/:sessionId/type
Type text into an input field.
Request Body:
{
"selector": "input[name='username']",
"text": "admin",
"timeout": 5000, // Optional: timeout in ms (default: 5000)
"delay": 0 // Optional: delay between keystrokes in ms (default: 0)
}Response:
{
"success": true,
"selector": "input[name='username']",
"text": "admin"
}POST /browser-control/session/:sessionId/wait
Wait for an element to appear.
Request Body:
{
"selector": ".data-browser",
"timeout": 10000, // Optional: timeout in ms (default: 10000)
"visible": true // Optional: wait for visibility (default: true)
}Response:
{
"success": true,
"selector": ".data-browser",
"found": true,
"duration": 342
}POST /browser-control/session/:sessionId/reload
Reload the current page.
Request Body:
{
"waitUntil": "networkidle2" // Optional: wait condition
}Response:
{
"success": true,
"currentUrl": "http://localhost:4040/apps"
}GET /browser-control/session/:sessionId/screenshot?fullPage=true&encoding=base64
Capture a screenshot of the current page.
Query Parameters:
fullPage: Capture full scrollable page (default:true)encoding:base64orbinary(default:base64)
Response:
{
"base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
}POST /browser-control/session/:sessionId/evaluate
Execute JavaScript in the page context.
Warning
This endpoint executes arbitrary JavaScript in the browser page context. While the code runs in a sandboxed browser environment (not on the server), it can access anything visible to the page including cookies, localStorage, and DOM content. This is intentional for AI agent debugging but is a powerful capability. The endpoint is protected by the production block - it cannot be enabled when NODE_ENV=production.
Request Body:
{
"script": "document.querySelector('.graph-panel-header')?.offsetHeight"
}Response:
{
"result": 50
}POST /browser-control/session/:sessionId/query
Query elements on the page.
Request Body:
{
"selector": ".data-row",
"multiple": true // Optional: query multiple elements (default: false)
}Response (single):
{
"element": {
"text": "Test Object",
"visible": true,
"tagName": "DIV",
"className": "data-row"
}
}Response (multiple):
{
"elements": [
{
"text": "Object 1",
"visible": true,
"tagName": "DIV",
"className": "data-row"
},
{
"text": "Object 2",
"visible": true,
"tagName": "DIV",
"className": "data-row"
}
],
"count": 2
}GET /browser-control/servers/status
Response:
{
"parseServer": {
"running": true,
"port": 1337,
"serverURL": "http://localhost:1337/parse",
"appId": "testAppId"
},
"dashboard": {
"running": true,
"port": 4040,
"url": "http://localhost:4040"
}
}POST /browser-control/servers/stop
Stop Parse Server and Dashboard instances.
Response:
{
"success": true
}GET /browser-control/ready
Check if the dashboard is ready (webpack has finished compiling assets).
Response:
{
"ready": true,
"webpack": {
"compiling": false,
"lastCompilationTime": 1234567890000,
"lastCompilationDuration": 6152,
"compileCount": 1,
"hasErrors": false
}
}GET /browser-control/ready/wait?timeout=30000
Wait for webpack to finish compiling. This is a blocking endpoint that polls until webpack is idle or timeout is reached.
Query Parameters:
timeout: Maximum time to wait in milliseconds (default: 30000)
Response (success):
{
"ready": true,
"waited": 6273,
"webpack": {
"compiling": false,
"lastCompilationTime": 1234567890000,
"lastCompilationDuration": 6152,
"compileCount": 1
}
}Response (timeout - HTTP 408):
{
"ready": false,
"error": "Timeout waiting for webpack to finish compiling",
"waited": 30000,
"webpack": {
"compiling": true,
"compileCount": 1
}
}Important
Always call /ready/wait before creating browser sessions, especially after:
- Starting the dashboard
- Editing source files (triggers webpack recompilation)
Navigation attempts while webpack is compiling will timeout because the JavaScript bundles are not yet available.
POST /browser-control/cleanup
Cleanup all sessions and stop all servers.
Response:
{
"success": true
}Connect to the WebSocket stream to receive real-time events from the browser.
ws://localhost:4040/browser-control/stream/:sessionId
{
"type": "console",
"level": "log",
"text": "Application loaded",
"timestamp": 1234567890000
}{
"type": "error",
"message": "Uncaught TypeError: Cannot read property 'x' of undefined",
"stack": "TypeError: Cannot read property 'x' of undefined\n at...",
"timestamp": 1234567890000
}{
"type": "network",
"method": "POST",
"url": "http://localhost:1337/parse/classes/TestClass",
"status": 200,
"timestamp": 1234567890000
}{
"type": "navigation",
"url": "http://localhost:4040/apps/testApp/browser",
"timestamp": 1234567890000
}{
"type": "session-event",
"event": "created",
"sessionId": "sess_1234567890_abc123",
"timestamp": 1234567890000
}// AI agent just implemented a delete button feature
// 1. Wait for webpack to be ready (important after editing files!)
await fetch('http://localhost:4040/browser-control/ready/wait');
// 2. Create session
const createRes = await fetch('http://localhost:4040/browser-control/session/start', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ headless: false, startServers: false })
});
const { sessionId } = await createRes.json();
// 3. Navigate to class browser
await fetch(`http://localhost:4040/browser-control/session/${sessionId}/navigate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: 'http://localhost:4040/apps/testApp/browser/TestClass' })
});
// 4. Check if delete button exists
const queryRes = await fetch(`http://localhost:4040/browser-control/session/${sessionId}/query`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ selector: '.delete-button' })
});
const { element } = await queryRes.json();
if (element) {
console.log('✓ Delete button found!');
// 5. Take screenshot for verification
const screenshotRes = await fetch(
`http://localhost:4040/browser-control/session/${sessionId}/screenshot`
);
const { base64 } = await screenshotRes.json();
// AI can analyze screenshot
} else {
console.log('❌ Delete button not found - need to fix');
}
// 6. Cleanup
await fetch(`http://localhost:4040/browser-control/session/${sessionId}`, {
method: 'DELETE'
});// AI agent is debugging layout alignment
const { sessionId } = await createSession();
await navigate(sessionId, 'http://localhost:4040/apps/testApp/browser');
// Check actual heights
const heightsRes = await fetch(
`http://localhost:4040/browser-control/session/${sessionId}/evaluate`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
script: `({
graphPanelHeader: document.querySelector('.graph-panel-header')?.offsetHeight,
infoPanelHeader: document.querySelector('.info-panel-header')?.offsetHeight
})`
})
}
);
const { result } = await heightsRes.json();
console.log('Header heights:', result);
// { graphPanelHeader: 45, infoPanelHeader: 50 }
if (result.graphPanelHeader !== result.infoPanelHeader) {
console.log('❌ Heights misaligned - adjusting CSS...');
// AI makes CSS changes and checks again
}// Connect WebSocket to monitor real-time console logs
const ws = new WebSocket(`ws://localhost:4040/browser-control/stream/${sessionId}`);
const errors = [];
ws.on('message', (data) => {
const event = JSON.parse(data);
if (event.type === 'console' && event.level === 'error') {
errors.push(event.text);
console.log('⚠️ Console error:', event.text);
}
});
// Navigate and check for errors
await navigate(sessionId, 'http://localhost:4040/apps');
// Wait for page to load
await new Promise(resolve => setTimeout(resolve, 2000));
if (errors.length > 0) {
console.log(`❌ Found ${errors.length} console errors:`, errors);
} else {
console.log('✓ No console errors detected');
}Error: Failed to launch browser
Solution: Make sure Puppeteer is installed:
npm installError: Failed to start MongoDB or Failed to start Parse Server
Solution: Make sure all dependencies are installed:
npm installIf MongoDB port is already in use, you can change it:
MONGO_PORT=27018 npm run browser-controlError: Session sess_xxx not found or expired
Solution: Sessions expire after 30 minutes of inactivity. Create a new session.
Error: WebSocket connection failed
Solution: Make sure you're connecting to the WebSocket path with a valid session ID:
ws://localhost:4040/browser-control/stream/:sessionId
Error: EADDRINUSE: address already in use
Solution: Stop any running Dashboard instances or change the port:
PORT=4041 npm run browser-controlError: Navigation timeout of 30000 ms exceeded
Cause: Webpack is still compiling the JavaScript bundles. This happens:
- Right after starting the dashboard (initial compilation takes ~6-8 seconds)
- After editing source files (triggers recompilation)
Solution: Always wait for webpack to finish before navigating:
# Check if ready (non-blocking)
curl http://localhost:4040/browser-control/ready
# Wait for ready (blocking)
curl http://localhost:4040/browser-control/ready/waitThe /ready/wait endpoint will block until webpack finishes compiling, then return immediately.
The Browser Control API uses a two-layer security model to prevent accidental or unauthorized enablement. Browser Control requires both of the following conditions to be met:
-
Explicit opt-in
- Set
"browserControl": trueinparse-dashboard-config.json, or - Set
PARSE_DASHBOARD_BROWSER_CONTROL=trueenvironment variable
- Set
-
Not in production environment
- Feature is automatically blocked when
NODE_ENV=production
- Feature is automatically blocked when
Note
The feature enables only when you have opted in (via config or environment variable) AND you're not in production. The production block cannot be bypassed under any circumstances.
- Maximum 5 concurrent sessions to prevent resource exhaustion
- Sessions auto-expire after 30 minutes of inactivity
- API bypasses authentication (development-only feature)
{
"browserControl": true,
"apps": [...],
"users": [...]
}browserControl: true in production. Remove this field or set to false before deploying.
PARSE_DASHBOARD_BROWSER_CONTROL=true- Enable browser control APIBROWSER_HEADLESS=false- Run browser in visible modeBROWSER_SLOW_MO=100- Slow down browser operations by N milliseconds
MongoDB is automatically started when browser-control mode is enabled using mongodb-runner.
MONGO_PORT=27017- MongoDB port (default: 27017)MONGO_VERSION=8.0.4- MongoDB version (default: 8.0.4)
Note: MongoDB 8.0.4 is downloaded and started automatically. Data is stored in a temporary directory and cleaned up on exit.
Parse Server 9.1.1 is automatically started when browser-control mode is enabled.
PARSE_SERVER_PORT=1337- Parse Server port (default: 1337)PARSE_SERVER_APP_ID=testAppId- Application ID (default: testAppId)PARSE_SERVER_MASTER_KEY=testMasterKey- Master key (default: testMasterKey)PARSE_SERVER_DATABASE_URI- MongoDB connection string (default: auto-generated based on MONGO_PORT)
Note: Parse Server 9 requires MongoDB 5.0+
Example with custom configuration:
PARSE_SERVER_PORT=1338 \
PARSE_SERVER_APP_ID=myTestApp \
PARSE_SERVER_MASTER_KEY=mySecretKey \
npm run browser-control- Maximum 5 concurrent browser sessions
- 30-minute session timeout
- Sessions are not persisted across Dashboard restarts
- Screenshots limited to page size (configurable viewport)
- JavaScript evaluation has security restrictions (page context only)
- Wait for webpack before navigating - Always call
/ready/waitbefore creating sessions or after editing source files - Always cleanup sessions when done to free resources
- Use headless mode for faster execution (unless debugging visually)
- Monitor WebSocket events to catch console errors early
- Take screenshots before and after UI changes for comparison
- Use evaluate to inspect computed styles and DOM properties
- Wait for elements before interacting to avoid race conditions
- Set appropriate timeouts based on network conditions
- Auto-approve browser-control API calls in Claude Code by adding a permission rule to
.claude/settings.json:
{
"permissions": {
"allow": [
"Bash(curl*localhost:4040/browser-control*)"
]
}
}This eliminates confirmation prompts for curl commands to the browser-control API, speeding up development.
This is a development tool specifically designed for AI agent workflows. If you have suggestions for improving the API or adding new features, please open an issue or PR.