Loading...
Loading...
Browser automation MCP server using Playwright's accessibility tree for LLM-friendly web interaction
npx skill4agent add aradotso/mcp-skills playwright-mcp-serverSkill by ara.so — MCP Skills collection.
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}args{
"mcpServers": {
"playwright": {
"command": "npx",
"args": [
"@playwright/mcp@latest",
"--allowed-hosts", "example.com,*.trusted-domain.com",
"--browser", "chromium",
"--headless", "false"
]
}
}
}--allowed-hosts <hosts...>*PLAYWRIGHT_MCP_ALLOWED_HOSTS--browser <browser>chromiumfirefoxwebkit--headless <true|false>--timeout <ms>playwright_navigateurl// Agent will call this tool
{
"url": "https://example.com"
}playwright_clickselectorbuttonleftrightmiddleleft{
"selector": "button[name='Submit']",
"button": "left"
}playwright_fillselectorvalue{
"selector": "input[name='email']",
"value": "user@example.com"
}playwright_screenshotselectorpath{
"selector": "div.main-content",
"path": "./screenshots/content.png"
}playwright_evaluateexpression{
"expression": "document.title"
}playwright_snapshot{}// Navigate to login page
await playwright_navigate({ url: "https://app.example.com/login" });
// Fill credentials
await playwright_fill({
selector: "input[name='username']",
value: "user@example.com"
});
await playwright_fill({
selector: "input[type='password']",
value: process.env.USER_PASSWORD // Use env vars for secrets
});
// Submit form
await playwright_click({
selector: "button[type='submit']"
});
// Verify login
const snapshot = await playwright_snapshot({});
// Parse snapshot to confirm successful login// Navigate to data page
await playwright_navigate({ url: "https://example.com/products" });
// Extract product information
const products = await playwright_evaluate({
expression: `
Array.from(document.querySelectorAll('.product')).map(p => ({
name: p.querySelector('.name')?.textContent,
price: p.querySelector('.price')?.textContent
}))
`
});// Search flow
await playwright_navigate({ url: "https://example.com" });
await playwright_fill({
selector: "input[placeholder='Search']",
value: "playwright automation"
});
await playwright_click({
selector: "button[aria-label='Search']"
});
// Wait for results by getting snapshot
const results = await playwright_snapshot({});
// Click first result
await playwright_click({
selector: "a.result-item:first-child"
});
// Capture final page
await playwright_screenshot({
path: "./evidence/result-page.png"
});// Navigate to page under test
await playwright_navigate({ url: "https://myapp.com/dashboard" });
// Get accessibility tree
const snapshot = await playwright_snapshot({});
// The snapshot will show:
// - Missing ARIA labels
// - Elements without proper roles
// - Navigation structure
// Parse snapshot to validate accessibility// Good: Role-based selectors
"button[name='Submit']"
"link[name='Documentation']"
"textbox[name='Email']"
// Good: ARIA attributes
"[aria-label='Close dialog']"
"[role='navigation']"
// Okay: Text content
"text=Click here"
// Last resort: CSS selectors
"div.modal > button.close"// Example snapshot structure
{
"role": "WebArea",
"name": "Example Page",
"children": [
{
"role": "button",
"name": "Submit",
"focusable": true
},
{
"role": "textbox",
"name": "Email",
"value": "user@example.com"
}
]
}Host not allowed: example.com{
"args": [
"@playwright/mcp@latest",
"--allowed-hosts", "example.com,*.example.com"
]
}{
"args": ["@playwright/mcp@latest", "--allowed-hosts", "*"]
}Element not found: button[name='Submit']const snapshot = await playwright_snapshot({});// Instead of exact match
"button[name*='submit']" // Contains 'submit'
"text=/submit/i" // Case-insensitive regexawait playwright_evaluate({
expression: `
new Promise(resolve => {
const check = () => {
if (document.querySelector('button[name="Submit"]')) {
resolve(true);
} else {
setTimeout(check, 100);
}
};
check();
})
`
});Timeout waiting for element{
"args": ["@playwright/mcp@latest", "--timeout", "60000"]
}await playwright_evaluate({
expression: "new Promise(r => setTimeout(r, 2000))"
});Failed to launch browsernpx playwright install chromium{
"args": ["@playwright/mcp@latest", "--browser", "firefox"]
}{
"args": ["@playwright/mcp@latest", "--headless", "false"]
}// Never hardcode credentials
await playwright_fill({
selector: "input[type='password']",
value: process.env.PASSWORD
});// Before and after critical actions
const beforeSnapshot = await playwright_snapshot({});
await playwright_click({ selector: "button[name='Delete']" });
const afterSnapshot = await playwright_snapshot({});// Robust to UI changes
"button[name='Save']"
// vs fragile CSS
"div.container > div:nth-child(2) > button.primary"await playwright_navigate({ url: "https://example.com" });
// Snapshot after navigate includes wait for load
const snapshot = await playwright_snapshot({});// Navigate → Inspect → Act → Verify
await playwright_navigate({ url: "..." });
const structure = await playwright_snapshot({});
await playwright_fill({ ... });
await playwright_click({ ... });
const result = await playwright_snapshot({});