| name | browser-automator |
| description | Browser automation expertise covering Playwright and Puppeteer patterns, element selection strategies, wait mechanisms, network interception, file download and upload handling, multi-tab management, authentication flows, headless vs headed modes, screenshot and PDF generation, and CI integration.
Use when the user asks about browser automator, browser automator best practices, or needs guidance on browser automator implementation.
Do NOT use when the user needs a different specialized skill or is asking about an unrelated technology domain.
|
| license | Apache-2.0 |
| metadata | {"author":"foundry-skills","version":"1.0.0","tags":"automation shell-scripting web-development","category":"software-engineering","subcategory":"developer-tools","depends":"","disclaimer":"none","difficulty":"intermediate"} |
Browser Automator
Core Philosophy
Browser automation programmatically controls a web browser to perform tasks that would otherwise require manual interaction: form filling, data extraction, testing, screenshot generation, and workflow automation. The key to reliable browser automation is understanding the asynchronous nature of web pages and building robust wait strategies that adapt to real-world page load variability.
Framework Selection
Playwright vs Puppeteer
| Feature | Playwright | Puppeteer |
|---|
| Languages | JS/TS, Python, Java, .NET | JS/TS only |
| Browsers | Chromium, Firefox, WebKit | Chrome/Chromium (Firefox experimental) |
| Auto-wait | Built-in for all actions | Manual waits needed |
| Selectors | Role, text, CSS, XPath, test ID | CSS, XPath |
| Network interception | Yes, full API | Yes |
| Downloads/Uploads | First-class support | Supported |
| Parallel execution | Browser contexts (lightweight) | Incognito pages |
| Debugging | Trace viewer, codegen | DevTools Protocol |
Recommendation: Use Playwright for new projects. It has better auto-waiting, cross-browser support, and a richer API.
Element Selection Strategies
Priority Order
await page.getByRole('button', { name: 'Submit' });
await page.getByRole('textbox', { name: 'Email' });
await page.getByRole('link', { name: 'Sign Up' });
await page.getByRole('heading', { name: 'Dashboard', level: 1 });
await page.getByRole('checkbox', { name: 'Accept terms' });
await page.getByText('Welcome back');
await page.getByLabel('Email address');
await page.getByPlaceholder('Enter your email');
await page.getByTitle('Close dialog');
await page.getByAltText('Company logo');
await page.getByTestId('submit-form');
await page.();
page.();
page.();
page.()
.({ : })
.(, { : });
page.().();
page.().();
page.().();
Wait Mechanisms
Playwright Auto-Wait
await page.getByRole('button').click();
await page.getByLabel('Name').fill('Alice');
await page.getByRole('checkbox').check();
await page.getByRole('option').selectOption('premium');
Explicit Waits
await page.getByTestId('loading').waitFor({ state: 'hidden' });
await page.getByTestId('results').waitFor({ state: 'visible', timeout: 10000 });
await page.getByTestId('content').waitFor({ state: 'attached' });
await page.waitForURL('**/dashboard');
await page.waitForURL(/\/orders\/\d+/);
const responsePromise = page.waitForResponse(
resp => resp.url().includes('/api/data') && resp.status() === 200
);
await page.getByRole('button', { name: 'Load' }).click();
const response = await responsePromise;
await page.waitForLoadState('networkidle');
page.( {
.(). >= ;
}, { : });
page.();
page.();
Network Interception
await page.route('**/api/users', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ users: [{ id: 1, name: 'Alice' }] }),
});
});
await page.route('**/api/**', async (route) => {
const headers = { ...route.request().headers(), 'X-Custom-Header': 'test' };
await route.continue({ headers });
});
await page.route('**/*.{png,jpg,jpeg,gif,svg,ico}', route => route.abort());
await page.route('**/analytics/**', route => route.abort());
await page.route('**/ads/**', route => route.abort());
: [] = [];
page.(, {
(response.().()) {
responses.(response);
}
});
[response] = .([
page.( resp.().()),
page.(, { : }).(),
]);
data = response.();
File Download and Upload
File Download
const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', { name: 'Download Report' }).click();
const download = await downloadPromise;
await download.saveAs('/tmp/report.pdf');
const buffer = await download.createReadStream();
console.log(download.suggestedFilename());
File Upload
await page.getByLabel('Upload file').setInputFiles('/path/to/file.pdf');
await page.getByLabel('Upload files').setInputFiles([
'/path/to/file1.pdf',
'/path/to/file2.pdf',
]);
await page.getByLabel('Upload file').setInputFiles([]);
const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByRole('button', { name: 'Upload' }).click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles('/path/to/file.pdf');
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
await page.dispatchEvent('.dropzone', 'drop', { dataTransfer });
Multi-Tab Management
const pagePromise = context.waitForEvent('page');
await page.getByRole('link', { name: 'Open in new tab' }).click();
const newPage = await pagePromise;
await newPage.waitForLoadState();
const newTab = await context.newPage();
await newTab.goto('[reference URL]');
const pages = context.pages();
await pages[0].bringToFront();
await pages[1].bringToFront();
const popupPromise = page.waitForEvent('popup');
await page.getByRole('button', { name: 'Open Settings' }).click();
const popup = await popupPromise;
await popup.getByRole('button', { name: 'Save' }).();
popup.();
Authentication Flows
Session Storage
async function authenticate(page: Page): Promise<void> {
await page.goto('[reference URL]');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Sign In' }).click();
await page.waitForURL('**/dashboard');
await page.context().storageState({ path: '.auth/state.json' });
}
const context = await browser.newContext({
storageState: '.auth/state.json',
});
const page = await context.newPage();
await page.goto('[reference URL]');
OAuth Flow
async function handleOAuth(page: Page): Promise<void> {
await page.goto('[reference URL]');
await page.getByRole('button', { name: 'Sign in with Google' }).click();
if (await page.url().includes('accounts.google.com')) {
await page.getByLabel('Email').fill('user@gmail.com');
await page.getByRole('button', { name: 'Next' }).click();
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Next' }).click();
const allowButton = page.getByRole('button', { name: 'Allow' });
( allowButton.()) {
allowButton.();
}
}
page.();
}
Headless vs Headed
const browser = await chromium.launch({ headless: true });
const browser = await chromium.launch({
headless: false,
slowMo: 100,
});
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
deviceScaleFactor: 2,
userAgent: 'Custom User Agent',
locale: 'en-US',
timezoneId: 'America/New_York',
geolocation: { longitude: -73.935242, latitude: 40.730610 },
permissions: ['geolocation'],
});
Screenshot and PDF Generation
Screenshots
await page.screenshot({
path: 'fullpage.png',
fullPage: true,
});
const element = page.getByTestId('chart');
await element.screenshot({ path: 'chart.png' });
await page.screenshot({
path: 'region.png',
clip: { x: 0, y: 0, width: 800, height: 600 },
});
await page.screenshot({
path: 'hd.png',
type: 'png',
scale: 'device',
});
await page.screenshot({
path: 'photo.jpg',
type: 'jpeg',
quality: 90,
});
const buffer = await page.screenshot({ type: });
PDF Generation
await page.pdf({
path: 'document.pdf',
format: 'A4',
printBackground: true,
margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' },
displayHeaderFooter: true,
headerTemplate: '<div style="font-size:10px; text-align:center; width:100%">Report</div>',
footerTemplate: '<div style="font-size:10px; text-align:center; width:100%">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
});
await page.setContent(`
<html>
<body>
<h1>Invoice #12345</h1>
<table>...</table>
</body>
</html>
`);
await page.pdf({ path: 'invoice.pdf', format: 'Letter' });
CI Integration
name: Browser Automation
on:
schedule:
- cron: '0 6 * * *'
jobs:
automate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: node dist/automation.js
env:
HEADLESS: 'true'
TARGET_URL: ${{ secrets.TARGET_URL }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: automation-results
path: |
screenshots/
reports/
downloads/
Debugging
const context = await browser.newContext();
await context.tracing.start({ screenshots: true, snapshots: true, sources: true });
await context.tracing.stop({ path: 'trace.zip' });
page.on('console', msg => console.log(`BROWSER: ${msg.type()}: ${msg.text()}`));
page.on('pageerror', error => console.error(`BROWSER ERROR: ${error.message}`));
Best Practices
- Use auto-wait over explicit waits: Let Playwright handle timing
- Prefer semantic selectors: Role > text > test ID > CSS
- Block unnecessary resources: Images, analytics, ads slow down automation
- Save authentication state: Avoid re-logging in for every run
- Use browser contexts for isolation: Lighter than separate browsers
- Record traces for debugging: Replay exact browser state
- Handle popups and dialogs: Register handlers before triggering them
- Set appropriate timeouts: Default 30s, increase for slow pages
- Run in CI with container: Consistent browser version and dependencies
- Use Codegen for discovery: Generate selectors interactively
When to Use
Use this skill when:
- Designing or implementing browser automator solutions
- Reviewing or improving existing browser automator approaches
- Making architectural or implementation decisions about browser automator
- Learning browser automator patterns and best practices
- Troubleshooting browser automator-related issues
Do NOT use this skill when:
- The question is about a fundamentally different technology domain
- A more specific sibling skill covers the exact topic needed
- The user needs a complete hands-on tutorial rather than expert guidance
Output Format
# Browser Automator Analysis
## Context Assessment
[Situation summary and constraints]
## Recommended Approach
[Primary recommendation with rationale]
## Implementation Steps
1. [Step with specific details]
2. [Step with specific details]
3. [Step with specific details]
## Trade-offs and Considerations
- [Key trade-off 1]
- [Key trade-off 2]
## Next Steps
- [Immediate action item]
- [Follow-up action item]
Example
Input: "Help me implement browser automator for a medium-scale production application"
Output: A structured analysis covering current state assessment, recommended browser automator approach with specific patterns, implementation roadmap with milestones, and risk mitigation strategies tailored to the application scale and constraints.
Edge Cases
- Legacy system integration: When browser automator must coexist with legacy approaches, provide a gradual migration path rather than a complete rewrite
- Scale mismatch: When the solution complexity exceeds the project scale, recommend a simpler approach and note when to revisit
- Team skill gaps: When the team lacks experience with the recommended approach, include learning resources and simpler alternatives
- Conflicting requirements: When constraints conflict (e.g., performance vs. maintainability), explicitly state the trade-off and recommend based on stated priorities