# Web Browse Skill ## Purpose Safely navigate, interact with, and capture evidence from web pages using automated browser operations. ## Security Features - **Rate Limiting**: Maximum operations per session (10 navigations, 50 clicks, 30 inputs) - **Input Sanitization**: Blocks sensitive patterns (passwords, credit cards, SSN) - **Operation Logging**: All actions are logged to `artifacts/browser/{session}/operations.log` - **Safe Mode**: Dangerous JavaScript operations are blocked - **Local Access Only**: Server binds to localhost:3030 (not accessible externally) ## When to Use - Verify preview deployments (UI/UX checks) - Capture screenshots for documentation - Run Lighthouse performance audits - Scrape public data from allowed domains - Test user flows without full E2E suite - Monitor production health with visual checks ## Agents Using This Skill - **Nova** (Preview Verification): Screenshot + Lighthouse before merge - **Theo** (Post-Deploy Monitoring): Synthetic monitoring with screenshots - **Mina** (UI/UX Review): Visual comparison and accessibility checks ## Configuration No configuration required! The browser server allows access to any valid URL and shows the browser GUI by default for easy debugging. Default settings in `.env`: ```bash # Server port (default: 9222 - Chrome DevTools Protocol port) BROWSER_MCP_PORT=9222 # Show browser GUI (default: visible for debugging) # Set to 'true' for headless/background mode (useful for CI) BROWSER_HEADLESS=false ``` ### Headless Mode (Background) To run browser in the background without GUI: ```bash # Option 1: Set environment variable BROWSER_HEADLESS=true npm run browser # Option 2: Update .env file BROWSER_HEADLESS=true ``` By default (GUI mode): - Browser window opens automatically - You can see all navigation, clicks, and interactions in real-time - Perfect for debugging and learning - Screenshots still save to artifacts/ ## API Endpoints ### Initialize Browser ```bash curl -X POST http://localhost:3030/init # Response: {"ok": true, "sessionId": "1234567890"} ``` ### Navigate to URL ```bash curl -X POST http://localhost:3030/navigate \ -H 'Content-Type: application/json' \ -d '{"url": "https://myapp.vercel.app", "waitUntil": "networkidle"}' # Response: {"ok": true, "url": "https://myapp.vercel.app", "title": "My App"} ``` ### Click Element ```bash curl -X POST http://localhost:3030/click \ -H 'Content-Type: application/json' \ -d '{"selector": "button.primary"}' # Response: {"ok": true} ``` ### Type Text ```bash curl -X POST http://localhost:3030/type \ -H 'Content-Type: application/json' \ -d '{"selector": "input[name=search]", "text": "test query", "pressEnter": true}' # Response: {"ok": true} ``` ### Authenticate (Password-Protected Sites) **Interactive Authentication Flow:** 1. **First attempt** - Try without password (checks environment variable): ```bash curl -X POST http://localhost:3030/auth \ -H 'Content-Type: application/json' \ -d '{"type": "shopify-store", "submitSelector": "button[type=submit]"}' ``` 2. **If password not in .env** - Server responds with password request: ```json { "needsPassword": true, "envVarName": "SHOPIFY_STORE_PASSWORD", "type": "shopify-store", "prompt": "Please enter the password for shopify-store:" } ``` 3. **Provide password** - Send password in request: ```bash curl -X POST http://localhost:3030/auth \ -H 'Content-Type: application/json' \ -d '{"type": "shopify-store", "password": "mypassword", "submitSelector": "button[type=submit]"}' # Response: {"ok": true, "shouldSavePassword": true, "envVarName": "SHOPIFY_STORE_PASSWORD"} ``` 4. **Save password** (optional) - Save to .env for future use: ```bash curl -X POST http://localhost:3030/auth/save \ -H 'Content-Type: application/json' \ -d '{"envVarName": "SHOPIFY_STORE_PASSWORD", "password": "mypassword"}' # Response: {"ok": true, "message": "Password saved to .env as SHOPIFY_STORE_PASSWORD"} ``` **Built-in auth types:** - `shopify-store` - Uses `SHOPIFY_STORE_PASSWORD` environment variable - `staging` - Uses `STAGING_PASSWORD` environment variable - `preview` - Uses `PREVIEW_PASSWORD` environment variable - **Custom types** - Any type converts to `{TYPE}_PASSWORD` (e.g., `my-site` → `MY_SITE_PASSWORD`) **Parameters:** - `type` (required): Auth type (built-in or custom) - `passwordSelector` (optional): CSS selector for password field (default: `input[type="password"]`) - `submitSelector` (optional): CSS selector for submit button (if provided, auto-submits form) - `password` (optional): Password to use (if not in environment variable) **Using browser-helper.sh** (handles interactive flow automatically): ```bash # Will prompt for password if not in .env, then offer to save it # Also handles 2FA automatically - waits for user to complete 2FA in browser ./browser-helper.sh auth shopify-store "input[type=password]" "button[type=submit]" ``` ### Wait for 2FA Completion If 2FA is detected after password authentication, the system will automatically wait for manual completion: ```bash curl -X POST http://localhost:3030/auth/wait-2fa \ -H 'Content-Type: application/json' \ -d '{"timeout": 120000}' # Response: {"ok": true, "completed": true, "message": "2FA completed successfully", "url": "..."} ``` **How it works:** 1. System detects 2FA page (looks for keywords in both English and Japanese): - **English**: "2fa", "mfa", "verify", "two-factor", "authentication code", "verification code", "security code" - **Japanese**: "二段階認証", "2段階認証", "認証コード", "確認コード", "セキュリティコード", "ワンタイムパスワード" 2. Returns `requires2FA: true` response 3. Browser window stays open (GUI mode) for manual 2FA input 4. System polls every 2 seconds to check if 2FA completed 5. Success detected when: - URL changes from 2FA page - 2FA indicators disappear from page content (checks both English and Japanese) 6. Default timeout: 2 minutes (configurable) **Parameters:** - `timeout` (optional): Maximum wait time in milliseconds (default: 120000 = 2 minutes) - `expectedUrlPattern` (optional): Regex pattern for successful auth URL ### Wait for Element ```bash curl -X POST http://localhost:3030/wait \ -H 'Content-Type: application/json' \ -d '{"selector": ".results", "timeout": 10000}' # Response: {"ok": true} ``` ### Scrape Text Content ```bash curl -X POST http://localhost:3030/scrape \ -H 'Content-Type: application/json' \ -d '{"selector": "h2.product-title", "limit": 50}' # Response: {"ok": true, "data": ["Product 1", "Product 2", ...]} ``` ### Take Screenshot ```bash curl -X POST http://localhost:3030/screenshot \ -H 'Content-Type: application/json' \ -d '{"filename": "homepage.png", "fullPage": true}' # Response: {"ok": true, "path": "/path/to/artifacts/browser/.../homepage.png"} ``` ### Get Page Content ```bash curl -X POST http://localhost:3030/content # Response: {"ok": true, "url": "...", "title": "...", "html": "..."} ``` ### Evaluate JavaScript ```bash curl -X POST http://localhost:3030/evaluate \ -H 'Content-Type: application/json' \ -d '{"expression": "document.querySelectorAll(\"img\").length"}' # Response: {"ok": true, "result": 42} ``` ### Close Browser ```bash curl -X POST http://localhost:3030/close # Response: {"ok": true} ``` ### Health Check ```bash curl http://localhost:3030/health # Response: {"ok": true, "browser": true, "page": true, "sessionId": "...", "allowedDomains": [...]} ``` ## Usage Examples ### Example 1: Screenshot Preview Deployment (Nova) ```bash #!/usr/bin/env bash PREVIEW_URL="https://myapp-pr-123.vercel.app" # Initialize curl -X POST http://localhost:3030/init # Navigate curl -X POST http://localhost:3030/navigate \ -H 'Content-Type: application/json' \ -d "{\"url\":\"$PREVIEW_URL\"}" # Wait for main content curl -X POST http://localhost:3030/wait \ -H 'Content-Type: application/json' \ -d '{"selector":"main"}' # Screenshot curl -X POST http://localhost:3030/screenshot \ -H 'Content-Type: application/json' \ -d '{"filename":"preview-homepage.png","fullPage":true}' # Close curl -X POST http://localhost:3030/close ``` ### Example 2: Scrape Product Titles (Competitive Research) ```bash #!/usr/bin/env bash # Initialize curl -X POST http://localhost:3030/init # Navigate to competitor site (must be in allowlist!) curl -X POST http://localhost:3030/navigate \ -H 'Content-Type: application/json' \ -d '{"url":"https://competitor.shopify.com/products"}' # Scrape product titles curl -X POST http://localhost:3030/scrape \ -H 'Content-Type: application/json' \ -d '{"selector":"h3.product-title","limit":20}' \ > artifacts/competitor-products.json # Close curl -X POST http://localhost:3030/close ``` ### Example 3: Test Search Flow (Mina) ```bash #!/usr/bin/env bash PREVIEW_URL="https://myapp.vercel.app" # Initialize curl -X POST http://localhost:3030/init # Navigate curl -X POST http://localhost:3030/navigate \ -H 'Content-Type: application/json' \ -d "{\"url\":\"$PREVIEW_URL\"}" # Type in search box curl -X POST http://localhost:3030/type \ -H 'Content-Type: application/json' \ -d '{"selector":"input[type=search]","text":"test product","pressEnter":true}' # Wait for results curl -X POST http://localhost:3030/wait \ -H 'Content-Type: application/json' \ -d '{"selector":".search-results"}' # Screenshot results curl -X POST http://localhost:3030/screenshot \ -H 'Content-Type: application/json' \ -d '{"filename":"search-results.png","fullPage":false}' # Close curl -X POST http://localhost:3030/close ``` ### Example 4: Test Password-Protected Shopify Store (with 2FA support) ```bash #!/usr/bin/env bash STORE_URL="https://mystore.myshopify.com" # Initialize curl -X POST http://localhost:3030/init # Navigate to password-protected store curl -X POST http://localhost:3030/navigate \ -H 'Content-Type: application/json' \ -d "{\"url\":\"$STORE_URL\"}" # Wait for password form curl -X POST http://localhost:3030/wait \ -H 'Content-Type: application/json' \ -d '{"selector":"input[type=password]"}' # Authenticate using environment variable AUTH_RESPONSE=$(curl -s -X POST http://localhost:3030/auth \ -H 'Content-Type: application/json' \ -d '{"type":"shopify-store","submitSelector":"button[type=submit]"}') # Check if 2FA is required REQUIRES_2FA=$(echo "$AUTH_RESPONSE" | jq -r '.requires2FA // false') if [ "$REQUIRES_2FA" = "true" ]; then echo "⏳ 2FA detected. Please complete authentication in the browser..." # Wait for 2FA completion (2 minutes timeout) curl -s -X POST http://localhost:3030/auth/wait-2fa \ -H 'Content-Type: application/json' \ -d '{"timeout":120000}' | jq echo "✅ 2FA completed" fi # Wait for store to load curl -X POST http://localhost:3030/wait \ -H 'Content-Type: application/json' \ -d '{"selector":"main"}' # Screenshot the store curl -X POST http://localhost:3030/screenshot \ -H 'Content-Type: application/json' \ -d '{"filename":"shopify-store.png","fullPage":true}' # Close curl -X POST http://localhost:3030/close ``` **Or simply use browser-helper.sh (handles everything automatically):** ```bash ./browser-helper.sh init ./browser-helper.sh navigate https://mystore.myshopify.com ./browser-helper.sh wait "input[type=password]" ./browser-helper.sh auth shopify-store "input[type=password]" "button[type=submit]" # If 2FA detected, automatically prompts user and waits for completion ./browser-helper.sh wait "main" ./browser-helper.sh screenshot shopify-store.png true ./browser-helper.sh close ``` ## Integration with Hooks ### before_merge.sh - Visual Regression Check ```bash #!/usr/bin/env bash if [ -n "$PREVIEW_URL" ]; then echo "→ Capturing preview screenshot..." curl -s -X POST http://localhost:3030/init > /dev/null curl -s -X POST http://localhost:3030/navigate \ -H 'Content-Type: application/json' \ -d "{\"url\":\"$PREVIEW_URL\"}" > /dev/null curl -s -X POST http://localhost:3030/screenshot \ -H 'Content-Type: application/json' \ -d '{"filename":"preview.png","fullPage":true}' | jq -r '.path' curl -s -X POST http://localhost:3030/close > /dev/null echo "✅ Screenshot saved" fi ``` ### after_deploy.sh - Production Health Check ```bash #!/usr/bin/env bash PROD_URL="${PRODUCTION_URL:-https://app.example.com}" echo "→ Running production health check..." curl -s -X POST http://localhost:3030/init > /dev/null curl -s -X POST http://localhost:3030/navigate \ -H 'Content-Type: application/json' \ -d "{\"url\":\"$PROD_URL\"}" > /dev/null # Check if critical element exists RESULT=$(curl -s -X POST http://localhost:3030/evaluate \ -H 'Content-Type: application/json' \ -d '{"expression":"document.querySelector(\".app-loaded\") !== null"}' | jq -r '.result') if [ "$RESULT" = "true" ]; then echo "✅ Production app loaded successfully" else echo "❌ Production app failed to load" exit 1 fi curl -s -X POST http://localhost:3030/close > /dev/null ``` ## Best Practices 1. **Always Initialize**: Call `/init` before any browser operations 2. **Clean Up**: Call `/close` when done to free resources 3. **Wait for Elements**: Use `/wait` before interacting with dynamic content 4. **Rate Limit Awareness**: Monitor operation counts to avoid hitting limits 5. **Security First**: Never type credentials or PII (blocked by default) 6. **Evidence Collection**: Save screenshots and logs for audit trail 7. **Domain Approval**: Add domains to allowlist before accessing ## Troubleshooting ### Error: "Domain not allowed" Add the domain to `BROWSER_ALLOWED_DOMAINS` in `.env` ### Error: "Browser not initialized" Call `/init` endpoint first ### Error: "Navigation limit exceeded" You've hit the 10 navigation limit per session. Close and reinitialize. ### Error: "No active page" Navigate to a URL first using `/navigate` ### Screenshots not saving Check that `artifacts/` directory has write permissions ## Artifacts All browser operations save artifacts to: ``` artifacts/browser/{sessionId}/ ├── operations.log # All operations with timestamps ├── screenshot.png # Screenshots (custom filenames) ├── preview.png └── ... ``` ## Rate Limits (Per Session) - Navigations: 10 - Clicks: 50 - Type operations: 30 ## Blocked Patterns Input containing these patterns will be rejected: - `password` (case-insensitive) - `credit card` - `ssn` / `social security` ## JavaScript Evaluation Restrictions Blocked keywords in `/evaluate`: - `delete` - `drop` - `remove` - `cookie` - `localStorage`