TypeScript-first Playwright testing guide with locator strategies, DRY principles, and built-in feature usage for robust end-to-end testing
A comprehensive guide for writing maintainable, robust Playwright tests using TypeScript, emphasizing proper locator strategies, code reuse, and leveraging Playwright's built-in capabilities.
Always use TypeScript instead of vanilla JavaScript for type safety and better IDE support.
Follow this priority order when selecting elements:
**Priority 1: `getByTestId()`**
**Priority 2: `getByRole()`**
**Priority 3: `getByPlaceholder()`, `getByLabel()`, `getByAltText()`**
**Priority 4: `getByText()`**
**Priority 5: CSS/XPath Selectors**
Always save locators to variables before using them in actions or assertions.
**DO:**
```typescript
const signInButton = page.getByTestId("ExampleButton_signIn");
await signInButton.click();
await expect(signInButton).toBeVisible();
```
**DON'T:**
```typescript
await page.getByTestId("ExampleButton_signIn").click();
await expect(page.getByTestId("ExampleButton_signIn")).toBeVisible();
```
Extract repeated logic into reusable helper functions:
**Guidelines:**
**Example:**
```typescript
// Good helper - performs actions only
async function loginUser(page: Page, email: string, password: string) {
const emailInput = page.getByLabel('Email');
const passwordInput = page.getByLabel('Password');
const submitButton = page.getByRole('button', { name: 'Log In' });
await emailInput.fill(email);
await passwordInput.fill(password);
await submitButton.click();
}
// In test - assertions happen here
test('successful login redirects to dashboard', async ({ page }) => {
await loginUser(page, '[email protected]', 'password123');
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.getByText('Welcome back')).toBeVisible();
});
```
Leverage Playwright's native methods instead of custom JavaScript/TypeScript implementations.
**DO - Use `expect().toPass()` for retries:**
```typescript
await expect(async () => {
await radioButton.click();
await expect(radioButton).toBeChecked();
}).toPass();
```
**DON'T - Create custom retry logic:**
```typescript
// Avoid manual iteration and polling
let iterationCounter = 0;
let errorTextIsVisible = await page.getByTestId("ErrorText").isVisible();
while (errorTextIsVisible && iterationCounter < 5) {
iterationCounter++;
await radioButton.click();
// ... more manual checks
}
```
**Common Built-in Features:**
When adding new tests, follow patterns from the existing test suite in the `tests/` directory for consistency and maintainability.
**Locator Priority:**
1. `getByTestId()` - most stable
2. `getByRole()` - accessibility-focused
3. `getByLabel()` / `getByPlaceholder()` / `getByAltText()` - semantic
4. `getByText({ exact: true })` - text-based fallback
5. CSS/XPath - last resort
**Action Pattern:**
```typescript
const element = page.getByTestId('my-element');
await element.click();
await expect(element).toHaveState('checked');
```
**Helper Pattern:**
```typescript
// Helper: actions only, no assertions
async function performAction(page: Page) { /* ... */ }
// Test: assertions happen here
test('my test', async ({ page }) => {
await performAction(page);
await expect(page.getByText('Success')).toBeVisible();
});
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/playwright-testing-best-practices-24x9pe/raw