Initial commit
This commit is contained in:
476
skills/web-browse/SKILL.md
Normal file
476
skills/web-browse/SKILL.md
Normal file
@@ -0,0 +1,476 @@
|
||||
# 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`
|
||||
Reference in New Issue
Block a user