| name | playwright |
| description | Browser automation via the in-process Playwright/MCP-style API (the `browser` tool's `tab.*`/`page.*` helpers). Use for scripted in-session browsing, scraping, and UI testing. For general browser work prefer `dev-browser`; for shell/`npx playwright test` runs use `playwright-cli`. |
Browser Automation with Playwright
Quick start (via OMP browser tool)
browser(action: "open", url: "<url>") # Navigate to page
browser(action: "run", code: "const s = await tab.observe(); display(s);") # Get interactive elements
browser(action: "run", code: "(await tab.id(1)).click();") # Click element by observed ID
browser(action: "run", code: "await tab.fill('input[name=q]', 'text');") # Fill input
browser(action: "close") # Close browser
Core workflow
- Navigate:
tab.goto(url)
- Observe:
tab.observe() — returns elements with { id, role, name, value, states }
- Interact using IDs from the snapshot:
(await tab.id(N)).click()
- Re-observe after navigation or significant DOM changes (IDs are not durable)
Commands mapped to OMP browser tool
Navigation
| Action | OMP |
|---|
| Navigate to URL | tab.goto(url) |
| Go back | await page.goBack() |
| Go forward | await page.goForward() |
| Reload page | await page.reload() |
| Close browser | browser(action: "close") |
Snapshot (page analysis)
Use tab.observe() to discover page elements. Returns an accessibility snapshot:
const obs = await tab.observe();
Options:
tab.observe({ includeAll: true }) — include all elements (not just interactive)
tab.observe({ viewportOnly: true }) — only visible elements
Interactions (use IDs from observe)
(await tab.id(1)).click();
await tab.click('aria/Submit');
await tab.click('text/Sign In');
await tab.fill('input[name=email]', 'user@example.com');
await tab.type('input[name=search]', 'query');
await tab.press('Enter');
await page.hover('aria/Menu');
await tab.select('select[name=country]', 'US');
await tab.scroll(0, 500);
await tab.scrollIntoView('aria/Footer');
await tab.drag('aria/Item 1', 'aria/Drop Zone');
await tab.uploadFile('input[type=file]', '/path/to/file.pdf');
Get information
const text = await page.textContent('selector');
const val = await page.inputValue('selector');
const href = await page.getAttribute('a', 'href');
const title = await page.title();
const url = page.url();
const result = await tab.evaluate(() => document.title);
Screenshots & capture
await tab.screenshot();
await tab.screenshot({ save: 'tmp/shot.png' });
await tab.screenshot({ fullPage: true });
await tab.screenshot({ selector: '#main' });
const md = await tab.extract('markdown');
Wait
await tab.waitFor('aria/Dashboard');
await tab.waitForUrl('**/dashboard');
const resp = await tab.waitForResponse(r => r.url().includes('/api/'));
const data = await resp.json();
await page.waitForFunction(() => window.appReady === true);
Semantic locators (preferred over CSS)
await tab.click('aria/Submit');
await tab.click('text/Sign In');
await tab.fill('[placeholder="Search..."]', 'query');
await tab.click('[data-testid="submit-btn"]');
Cookies & Storage
const cookies = await page.context().cookies();
await page.context().addCookies([{ name: 'key', value: 'val', url: 'https://example.com' }]);
await page.context().clearCookies();
await tab.evaluate(() => localStorage.getItem('key'));
await tab.evaluate((k, v) => localStorage.setItem(k, v), 'key', 'value');
Decision rules
| Situation | Action |
|---|
| Need to know what's on the page | tab.observe(), NOT tab.screenshot() |
| Need to verify visual layout | tab.screenshot({ selector }) if possible |
| Element keeps moving / re-rendering | tab.waitFor(selector) first |
| Need network-stable state | tab.goto(url, { waitUntil: "networkidle2" }) |
| Need element off-screen | tab.scrollIntoView(selector) first, then interact |
| Form drag-and-drop | tab.drag(from, to) with selectors or { x, y } points |
| Page changes after action | re-tab.observe() (IDs are not durable across navigations) |
Anti-patterns
tab.screenshot() to "see what's there" — use tab.observe()
- Brittle CSS selectors like
.MuiButton-root.css-1234 — prefer aria/ or text/
- Polling with
tab.evaluate in a tight loop — use tab.waitFor or page.waitForFunction
- Forgetting
browser(action: "close") at the end of an automation chain
Global options on browser tool
| Option | Description |
|---|
viewport: { width, height } | Set viewport size on open |
wait_until | Navigation wait: load, domcontentloaded, networkidle0, networkidle2 |
dialogs: "accept" | "dismiss" | Auto-handle alert/confirm dialogs |
When to use this vs the others
| Skill | Use case |
|---|
playwright | One-shot scripted automation via browser tool (this skill) |
dev-browser | Long-lived dev session, persistent page state across multiple turns |
playwright-cli | Headless CLI runs (CI, screenshots, Playwright test runner) |