browser_execute
Execute Playwright code to automate browser interactions, inspect page accessibility, and manage web automation tasks within a controlled environment.
Instructions
Execute Playwright code with these in scope:
page- Current Playwright pagecontext- Browser context, access all pages via context.pages()state- Persistent object across calls (e.g., state.myPage = await context.newPage())$('e5')- Shorthand for page.locator('aria-ref=e5')accessibilitySnapshot()- Get current page snapshotrequire- Load Node.js modules (path, url, crypto, buffer, util, assert, os, fs)Node.js globals: setTimeout, setInterval, fetch, URL, Buffer, crypto, etc.
Rules
Multiple calls: Use multiple execute calls for complex logic - helps understand intermediate state and isolate failures
Never close: Never call browser.close() or context.close(). Only close pages you created or if user asks
No bringToFront: Never call unless user asks - it's disruptive and unnecessary
Check state after actions: Always verify page state after clicking/submitting (see next section)
Clean up listeners: Call page.removeAllListeners() at end to prevent leaks
Wait for load: Use page.waitForLoadState('domcontentloaded') not page.waitForEvent('load') - waitForEvent times out if already loaded
Avoid timeouts: Prefer proper waits over page.waitForTimeout() - there are better ways
Checking Page State
After any action (click, submit, navigate), verify what happened:
console.log('url:', page.url()); console.log(await accessibilitySnapshot().then(x => x.split('\n').slice(0, 30).join('\n')));For visually complex pages (grids, galleries, dashboards), use screenshotWithAccessibilityLabels({ page }) instead.
Accessibility Snapshots
await accessibilitySnapshot() // Full snapshot
await accessibilitySnapshot({ search: /button|submit/i }) // Filter results
await accessibilitySnapshot({ showDiffSinceLastCall: true }) // Show changesExample output:
- banner [ref=e3]:
- link "Home" [ref=e5] [cursor=pointer]:
- /url: /
- navigation [ref=e12]:
- link "Docs" [ref=e13] [cursor=pointer]Use aria-ref to interact - NO quotes around the ref value:
await page.locator('aria-ref=e13').click() // or: await $('e13').click()For pagination: (await accessibilitySnapshot()).split('\n').slice(0, 50).join('\n')
Choosing snapshot method:
Use
accessibilitySnapshotfor simple pages, text search, token efficiencyUse
screenshotWithAccessibilityLabelsfor complex visual layouts, spatial position matters
Selector Best Practices
For unknown sites: use accessibilitySnapshot() with aria-ref For development (with source access), prefer:
[data-testid="submit"]- explicit test attributesgetByRole('button', { name: 'Save' })- semanticgetByText('Sign in'),getByLabel('Email')- user-facinginput[name="email"]- semantic HTMLAvoid: classes/IDs that change frequently
If locator matches multiple elements (strict mode violation), use .first(), .last(), or .nth(n):
await page.locator('button').first().click()
await page.locator('li').nth(3).click() // 4th item (0-indexed)Working with Pages
const pages = context.pages().filter(x => x.url().includes('localhost'));
state.newPage = await context.newPage(); await state.newPage.goto('https://example.com');Navigation
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await waitForPageLoad({ page, timeout: 5000 });Common Patterns
Popups: const [popup] = await Promise.all([page.waitForEvent('popup'), page.click('a[target=_blank]')]); await popup.waitForLoadState();
Downloads: const [download] = await Promise.all([page.waitForEvent('download'), page.click('button.download')]); await download.saveAs('/tmp/' + download.suggestedFilename());
iFrames: const frame = page.frameLocator('#my-iframe'); await frame.locator('button').click();
Dialogs: page.on('dialog', async d => { await d.accept(); }); await page.click('button');
Load files: const fs = require('fs'); const content = fs.readFileSync('./data.txt', 'utf-8'); await page.locator('textarea').fill(content);
page.evaluate
Code inside page.evaluate() runs in the browser - use plain JavaScript only. console.log inside evaluate runs in browser, not visible here:
const title = await page.evaluate(() => document.title);
console.log('Title:', title); // Log outside evaluateUtility Functions
getLatestLogs({ page?, count?, search? })- Get browser console logsgetCleanHTML({ locator, search?, showDiffSinceLastCall?, includeStyles? })- Get cleaned HTMLwaitForPageLoad({ page, timeout? })- Smart load detection (ignores analytics/ads)getCDPSession()- Get CDP session for raw Chrome DevTools Protocol commandsgetLocatorStringForElement(locator)- Get stable selector from ephemeral aria-refgetReactSource({ locator })- Get React component source location (dev mode only)getStylesForLocator({ locator, cdp })- Inspect CSS styles (read styles-api resource first)createDebugger({ cdp })- Set breakpoints, step through code (read debugger-api resource first)createEditor({ cdp })- View/edit page scripts and CSS (read editor-api resource first)screenshotWithAccessibilityLabels({ page })- Screenshot with Vimium-style visual labels (yellow=links, orange=buttons, coral=inputs)
Network Interception
For scraping/reverse-engineering APIs, intercept network instead of scrolling DOM:
state.requests = []; state.responses = [];
page.on('request', req => { if (req.url().includes('/api/')) state.requests.push({ url: req.url(), method: req.method(), headers: req.headers() }); });
page.on('response', async res => { if (res.url().includes('/api/')) { try { state.responses.push({ url: res.url(), status: res.status(), body: await res.json() }); } catch {} } });Then trigger actions and analyze: console.log('Captured', state.responses.length, 'API calls');
Clean up when done: page.removeAllListeners('request'); page.removeAllListeners('response');
IMPORTANT: After navigation, refs are stale - call snapshot tool again.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| code | Yes | Playwright code with {page, context, state, $} in scope. Should be concise - use ; for multiple statements. | |
| timeout | No | Timeout in milliseconds (default: 30000) |