YouTip LogoYouTip

Playwright Best Practices

This chapter summarizes the most important best practices during Playwright test writing and maintenance, helping you write stable and maintainable tests. * * * ## Testing Philosophy ### Test User-Visible Behavior Automated tests should verify what end users can see and interact with, rather than verifying implementation details. For example, tests should verify that correct text is displayed on the page, buttons are clickable, and forms redirect to the correct page after submission, rather than verifying the return value of a JavaScript function or the internal structure of the DOM. ## Examples // Not recommended: Testing implementation details expect(await page.evaluate(()=> window.__store.getState().user.name)) .toBe('tutorial'); // Recommended: Testing what the user sees await expect(page.getByText('Welcome, tutorial')).toBeVisible(); * * * ## Test Isolation Each test should be completely independent from other tests and not rely on the state of the previous test. Playwright provides isolated Contexts by default, but you should also ensure you don't depend on test execution order. ## Examples // Not recommended: Tests depend on each other let createdId; test('Create resource', async ({ request })=>{ const resp = await request.post('/api/items'); createdId =(await resp.json()).id;// Shared state }); test('Use created resource', async ({ page })=>{ await page.goto(`/items/${createdId}`);// Depends on previous test result }); // Recommended: Each test is self-sufficient test('Create and use resource', async ({ request, page })=>{ const resp = await request.post('/api/items'); const id =(await resp.json()).id; await page.goto(`/items/${id}`); await expect(page.getByText('Resource Details')).toBeVisible(); }); * * * ## Locator Priority Principles Using Locators in the recommended order can improve test stability. | Priority | Method | Use Case | | --- | --- | --- | | 1 | `getByRole()` | Element has explicit ARIA role | | 2 | `getByLabel()` | Form element associated with label | | 3 | `getByPlaceholder()` | Input has placeholder | | 4 | `getByText()` | Element has explicit text | | 5 | `getByAltText()` | Image has alt attribute | | 6 | `getByTitle()` | Element has title attribute | | 7 | `getByTestId()` | Fallback solution | | 8 | `locator()` | CSS/XPath, last resort | > Try not to use CSS class names as locators. > > Class names are prone to change due to style refactoring, causing large-scale test failures. * * * ## Avoid Unnecessary Waiting Take advantage of Playwright's auto-waiting mechanism and avoid manually writing `sleep` or `waitForTimeout`. ## Examples // Not recommended: Hardcoded wait await page.waitForTimeout(3000); await page.getByText('Data loaded').click(); // Recommended: Rely on auto-waiting and assertions await expect(page.getByText('Loading data...')).toBeHidden({ timeout:10000}); await page.getByText('Data loaded').click(); * * * ## Don't Test Third-Party Services Only test what you can control, and don't rely on the availability of third-party services. ## Examples // Not recommended: Rely on external CDN or API await page.goto('https://example.com/'); // Page may load Google Analytics, external fonts, etc. // Recommended: Intercept third-party requests, mock external services await page.route('**/*analytics*', route => route.abort()); await page.route('**/external-api/**', route =>{ route.fulfill({ json:{ status:'ok'}}); }); * * * ## Use expect.soft() Soft Assertions When checking multiple independent conditions, soft assertions can report all failures at once, rather than stopping at the first failure. ## Examples // Recommended: Soft assertion checks all fields at once await expect.soft(page.getByLabel('Username')).toBeVisible(); await expect.soft(page.getByLabel('Password')).toBeVisible(); await expect.soft(page.getByLabel('Email')).toBeVisible(); await expect.soft(page.getByRole('button',{ name:'Register'})).toBeEnabled(); // If multiple fields are missing, report all at once * * * ## Organize Test Files Reasonably | Practice | Description | | --- | --- | | Split files by functional modules | `login.spec.ts`, `checkout.spec.ts`, etc. | | Use describe for grouping | Put related tests in `test.describe()` | | Use beforeEach well | Extract repeated navigation and preparation steps to `beforeEach` | | Descriptive test names | Good names explain "what was done" and "what was expected" | | Extract common code | Extract helpers used by more than 3 files to shared modules | | Don't over-DRY | 2-3 lines of duplication is more maintainable than over-abstraction | * * * ## Troubleshooting Common Issues ### Flaky Tests Causes and solutions for unstable tests: | Cause | Solution | | --- | --- | | Hardcoded sleep not long enough | Use auto-waiting mechanism instead of fixed waits | | Rely on third-party services | Use `route()` to mock external requests | | Animation causes element position changes | Set `animations: 'disabled'` in configuration | | Data residue between tests | Ensure each test creates and cleans up its own data | | Time-related logic | Use `page.clock` to fix time | ### Locator Can't Find Element Troubleshooting steps: * Confirm if element is in DOM (use `toBeAttached()` instead of `toBeVisible()`) * Confirm if element is in viewport * Confirm if it's nested in an iframe * Use Pick Locator in UI Mode to check the locator * Check if there are multiple matching elements (Locator requires strict mode by default)
← Playwright Core ApiPlaywright Parallel Sharding β†’