Loading...
Loading...
Playwright automation best practices for web scraping and testing. Use when writing or reviewing Playwright code, especially for element operations (click, fill, select), waiting strategies (waitForSelector, waitForURL, waitForLoadState), navigation patterns, and locator usage. Apply when encountering unstable tests, race conditions, or needing guidance on avoiding deprecated patterns like waitForNavigation or networkidle.
npx skill4agent add suntory-n-water/sui-blog playwright-best-practices// ✅ Recommended: Locator with auto-waiting
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Username').fill('john');
// ❌ Avoid: Direct DOM manipulation
await page.evaluate(() => document.querySelector('button').click());// Priority order:
// 1. Role-based (best for accessibility)
await page.getByRole('button', { name: 'Sign in' });
// 2. Label-based (best for forms)
await page.getByLabel('Email address');
// 3. Text-based
await page.getByText('Submit');
// 4. Test ID
await page.getByTestId('submit-button');
// 5. CSS/XPath (last resort)
await page.locator('.btn-primary');// ✅ Recommended: Action includes waiting
await page.getByRole('button').click();
// ⚠️ Usually unnecessary
await page.waitForSelector('button');
await page.locator('button').click();await page.getByRole('button', { name: 'Next' }).click();await page.getByLabel('Email').fill('user@example.com');await page.getByLabel('Country').selectOption({ label: 'Japan' });await page.getByLabel('I agree').check();// ✅ Recommended: Click and wait for next page element
await page.getByRole('link', { name: 'Next' }).click();
await page.waitForSelector('#next-page-element', { timeout: 30 * 1000 }); // example wait time
// Or use conditional selector for different destination pages
const waitSelector = condition === 'A'
? '#page-a-element'
: '#page-b-element';
await page.waitForSelector(waitSelector, { timeout: 30 * 1000 }); // example wait time
// Or use waitForURL if URL pattern is predictable
await page.click('button');
await page.waitForURL('**/next-page');const [newPage] = await Promise.all([
page.context().waitForEvent('page'),
page.getByRole('link', { name: 'Open in new tab' }).click()
]);
await newPage.waitForLoadState();// Complete form workflow
await page.getByLabel('Username').fill('john');
await page.getByLabel('Password').fill('secret');
await page.getByRole('checkbox', { name: 'Remember me' }).check();
await page.getByLabel('Country').selectOption('Japan');
await page.getByRole('button', { name: 'Submit' }).click();
// Verify submission
await expect(page.getByText('Success')).toBeVisible();// ❌ Avoid: Deprecated API
const navigationPromise = page.waitForNavigation();
await page.click('button');
await navigationPromise;
// ✅ Use instead: waitForURL or waitForSelector
await page.click('button');
await page.waitForURL('**/next-page');
// or
await page.waitForSelector('#next-page-element');// ❌ Avoid: Can complete before page is ready
await page.waitForLoadState('networkidle');
// ✅ Use instead: Wait for specific elements
await page.waitForSelector('#content-loaded');
// or
await page.waitForLoadState('load');// ❌ Avoid: Bypasses actionability checks
await page.evaluate(() => document.querySelector('button').click());
// ✅ Use instead: Locator click
await page.locator('button').click();// ❌ Unnecessary: Playwright auto-waits for navigation
await Promise.all([
page.waitForNavigation(),
page.click('button')
]);
// ✅ Simpler: Just click
await page.click('button');| Action | Visible | Stable | Receives Events | Enabled | Editable |
|---|---|---|---|---|---|
| click() | ✓ | ✓ | ✓ | ✓ | - |
| fill() | ✓ | - | - | ✓ | ✓ |
| check() | ✓ | ✓ | ✓ | ✓ | - |
| selectOption() | ✓ | - | - | ✓ | - |
| hover() | ✓ | ✓ | ✓ | - | - |
visibility:hiddendisabledreadonly// 1. Before using locator.all() (doesn't auto-wait)
await page.getByRole('listitem').first().waitFor();
const items = await page.getByRole('listitem').all();
// 2. Waiting for element to disappear
await page.locator('.loading').waitFor({ state: 'hidden' });
// 3. Waiting for element to detach
await page.locator('.modal').waitFor({ state: 'detached' });
// 4. Conditional page destinations
const waitSelector = condition ? '#page-a' : '#page-b';
await page.waitForSelector(waitSelector, { timeout: 30 * 1000 }); // example wait time