playwright-best-practices
This Playwright testing skill provides resilient locator patterns, Page Object Model architecture, fixture setup, assertion strategies, and network mocking techniques for writing maintainable browser automation tests. Use it when creating or updating Playwright test files (.spec.ts, .test.ts) to ensure tests remain stable across UI changes and follow industry best practices for web testing.
git clone --depth 1 https://github.com/aiskillstore/marketplace /tmp/playwright-best-practices && cp -r /tmp/playwright-best-practices/skills/0xbigboss/playwright-best-practices ~/.claude/skills/playwright-best-practicesSKILL.md
# Playwright Best Practices
## CLI Context: Prevent Context Overflow
When running Playwright tests from Claude Code or any CLI agent, always use minimal reporters to prevent verbose output from consuming the context window.
**Use `--reporter=line` or `--reporter=dot` for CLI test runs:**
```bash
# REQUIRED: Use minimal reporter to prevent context overflow
npx playwright test --reporter=line
npx playwright test --reporter=dot
# BAD: Default reporter generates thousands of lines, floods context
npx playwright test
```
Configure `playwright.config.ts` to use minimal reporters by default when `CI` or `CLAUDE` env vars are set:
```ts
reporter: process.env.CI || process.env.CLAUDE
? [['line'], ['html', { open: 'never' }]]
: 'list',
```
## Locator Priority (Most to Least Resilient)
Always prefer user-facing attributes:
1. `page.getByRole('button', { name: 'Submit' })` - accessibility roles
2. `page.getByLabel('Email')` - form control labels
3. `page.getByPlaceholder('Search...')` - input placeholders
4. `page.getByText('Welcome')` - visible text (non-interactive)
5. `page.getByAltText('Logo')` - image alt text
6. `page.getByTitle('Settings')` - title attributes
7. `page.getByTestId('submit-btn')` - explicit test contracts
8. CSS/XPath - last resort, avoid
```ts
// BAD: Brittle selectors tied to implementation
page.locator('button.btn-primary.submit-form')
page.locator('//div[@class="container"]/form/button')
page.locator('#app > div:nth-child(2) > button')
// GOOD: User-facing, resilient locators
page.getByRole('button', { name: 'Submit' })
page.getByLabel('Password')
```
### Chaining and Filtering
```ts
// Scope within a region
const card = page.getByRole('listitem').filter({ hasText: 'Product A' });
await card.getByRole('button', { name: 'Add to cart' }).click();
// Filter by child locator
const row = page.getByRole('row').filter({
has: page.getByRole('cell', { name: 'John' })
});
// Combine conditions
const visibleSubmit = page.getByRole('button', { name: 'Submit' }).and(page.locator(':visible'));
const primaryOrSecondary = page.getByRole('button', { name: 'Save' }).or(page.getByRole('button', { name: 'Update' }));
```
### Strictness
Locators throw if multiple elements match. Use `first()`, `last()`, `nth()` only when intentional:
```ts
// Throws if multiple buttons match
await page.getByRole('button', { name: 'Delete' }).click();
// Explicit selection when needed
await page.getByRole('listitem').first().click();
await page.getByRole('row').nth(2).getByRole('button').click();
```
## Web-First Assertions
Use async assertions that auto-wait and retry:
```ts
// BAD: No auto-wait, flaky
expect(await page.getByText('Success').isVisible()).toBe(true);
// GOOD: Auto-waits up to timeout
await expect(page.getByText('Success')).toBeVisible();
await expect(page.getByRole('button')).toBeEnabled();
await expect(page.getByTestId('status')).toHaveText('Submitted');
await expect(page).toHaveURL(/dashboard/);
await expect(page).toHaveTitle('Dashboard');
// Collections
await expect(page.getByRole('listitem')).toHaveCount(5);
await expect(page.getByRole('listitem')).toHaveText(['Item 1', 'Item 2', 'Item 3']);
// Soft assertions (continue on failure, report all)
await expect.soft(locator).toBeVisible();
await expect.soft(locator).toHaveText('Expected');
// Test continues, failures compiled at end
```
## Page Object Model
Encapsulate page interactions. Define locators as readonly properties in constructor.
```ts
// pages/base.page.ts
import { type Page, type Locator, expect } from '@playwright/test';
import debug from 'debug';
export abstract class BasePage {
protected readonly log: debug.Debugger;
constructor(
protected readonly page: Page,
protected readonly timeout = 30_000
) {
this.log = debug(`test:page:${this.constructor.name}`);
}
protected async safeClick(locator: Locator, description?: string) {
this.log('clicking: %s', description ?? locator);
await expect(locator).toBeVisible({ timeout: this.timeout });
await expect(locator).toBeEnabled({ timeout: this.timeout });
await locator.click();
}
protected async safeFill(locator: Locator, value: string) {
await expect(locator).toBeVisible({ timeout: this.timeout });
await locator.fill(value);
}
abstract isLoaded(): Promise<void>;
}
```
```ts
// pages/login.page.ts
import { type Locator, type Page, expect } from '@playwright/test';
import { BasePage } from './base.page';
export class LoginPage extends BasePage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
super(page);
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Sign in' });
this.errorMessage = page.getByRole('alert');
}
async goto() {
await this.page.goto('/login');
await this.isLoaded();
}
async isLoaded() {
await expect(this.emailInput).toBeVisible();
}
async login(email: string, password: string) {
await this.safeFill(this.emailInput, email);
await this.safeFill(this.passwordInput, password);
await this.safeClick(this.submitButton, 'Sign in button');
}
async expectError(message: string) {
await expect(this.errorMessage).toHaveText(message);
}
}
```
## Fixtures
Prefer fixtures over beforeEach/afterEach. Fixtures encapsulate setup + teardown, run on-demand, and compose with dependencies.
```ts
// fixtures/index.ts
import { test as base, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
import { DashboardPage } from '../pages/dashboard.page';
type TestFixtures = {
loginPage: LoginPage;
dashboardPage: DashboardPage;
};
export const test = base.extend<TestFixtures>({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await uImplement SAFe methodology in Jira. Use when creating Epics, Features, Stories with proper hierarchy, acceptance criteria, and parent-child linking.
Orchestrate Jira workflows end-to-end. Use when building stories with approvals, transitioning items through lifecycle states, or syncing task completion with Jira.
HSK4級レベルから流暢さを目指す学習者向け。中国語表現の使用場面・自然さを分析し、作文を「ネイティブらしい流暢な表現」に改善。bilibili等のコンテンツ理解とネイティブとの会話をサポート。実際の用例をWeb検索で提示
Next.js 15 애플리케이션을 위한 프론트엔드 개발 가이드라인. React 19, TypeScript, Shadcn/ui, Tailwind CSS를 사용한 모던 패턴. Server Components, Client Components, App Router, 파일 구조, Shadcn/ui 컴포넌트, 성능 최적화, TypeScript 모범 사례 포함. 컴포넌트, 페이지, 기능 생성, 데이터 페칭, 스타일링, 라우팅, 프론트엔드 코드 작업 시 사용.
Claude Code 스킬, 훅, 에이전트, 명령어를 생성하고 관리하기 위한 메타 스킬. 새 스킬 생성, 스킬 트리거 설정, 훅 설정, Claude Code 인프라 관리 시 사용.
Discover and extract sitemaps from any website using SitemapKit. Use this skill whenever the user wants to find pages on a website, get a list of URLs from a domain, audit a site's structure, crawl a sitemap, check what pages exist on a site, or do anything involving sitemaps or site URL discovery — even if they don't explicitly say "sitemap". Requires the sitemapkit MCP server configured with a valid SITEMAPKIT_API_KEY.
GitHubのプルリクエスト(PR)を作成する際に使用します。変更のコミット、プッシュ、PR作成を含む完全なワークフローを日本語で実行します。「PRを作って」「プルリクエストを作成」「pull requestを作成」などのリクエストで自動的に起動します。
Generate an SVG of a user-requested image or scene