Playwright E2E Testing
fugazi/test-automation-skills-agents
Author and maintain versioned Playwright (@playwright/test) TypeScript UI specs for browser user flows.
Plan and build comprehensive Playwright E2E test suites with Page Object Model, authentication state persistence, custom fixtures, visual regression, and CI integration.
$ npx skills add davila7/claude-code-templates --skill playwright-e2e-builder -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install davila7/claude-code-templates playwright-e2e-builder --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/davila7/claude-code-templates.git skills-src && mkdir -p .claude/skills && cp -r skills-src/cli-tool/components/skills/development/playwright-e2e-builder .claude/skills/playwright-e2e-builder && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "playwright-e2e-builder" agent skill from https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/development/playwright-e2e-builder into .claude/skills/playwright-e2e-builder/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "playwright-e2e-builder", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/development/playwright-e2e-builderType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add davila7/claude-code-templates --skill playwright-e2e-builder -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install davila7/claude-code-templates playwright-e2e-builder --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/davila7/claude-code-templates.git skills-src && mkdir -p .agents/skills && cp -r skills-src/cli-tool/components/skills/development/playwright-e2e-builder .agents/skills/playwright-e2e-builder && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "playwright-e2e-builder" agent skill from https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/development/playwright-e2e-builder into .agents/skills/playwright-e2e-builder/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "playwright-e2e-builder", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add davila7/claude-code-templates --skill playwright-e2e-builder -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install davila7/claude-code-templates playwright-e2e-builder --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/davila7/claude-code-templates.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/cli-tool/components/skills/development/playwright-e2e-builder .cursor/skills/playwright-e2e-builder && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "playwright-e2e-builder" agent skill from https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/development/playwright-e2e-builder into .cursor/skills/playwright-e2e-builder/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "playwright-e2e-builder", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/davila7/claude-code-templates.git --path cli-tool/components/skills/development/playwright-e2e-builder--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add davila7/claude-code-templates --skill playwright-e2e-builder -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install davila7/claude-code-templates playwright-e2e-builder --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/davila7/claude-code-templates.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/cli-tool/components/skills/development/playwright-e2e-builder .gemini/skills/playwright-e2e-builder && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "playwright-e2e-builder" agent skill from https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/development/playwright-e2e-builder into .gemini/skills/playwright-e2e-builder/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "playwright-e2e-builder", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install davila7/claude-code-templates playwright-e2e-builderInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add davila7/claude-code-templates --skill playwright-e2e-builder -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/davila7/claude-code-templates.git skills-src && mkdir -p .github/skills && cp -r skills-src/cli-tool/components/skills/development/playwright-e2e-builder .github/skills/playwright-e2e-builder && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "playwright-e2e-builder" agent skill from https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/development/playwright-e2e-builder into .github/skills/playwright-e2e-builder/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "playwright-e2e-builder", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add davila7/claude-code-templates --skill playwright-e2e-builder -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install davila7/claude-code-templates playwright-e2e-builder --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/davila7/claude-code-templates.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/cli-tool/components/skills/development/playwright-e2e-builder .opencode/skills/playwright-e2e-builder && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "playwright-e2e-builder" agent skill from https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/development/playwright-e2e-builder into .opencode/skills/playwright-e2e-builder/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "playwright-e2e-builder", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
playwright-e2e-builderPlan and build comprehensive Playwright E2E test suites with Page Object Model, authentication state persistence, custom fixtures, visual regression, and CI integration.
Playwright E2E Builder is an agent skill from davila7/claude-code-templates. Plan and build comprehensive Playwright E2E test suites with Page Object Model, authentication state persistence, custom fixtures, visual regression, and CI integration. Uses interview-driven planning to clarify critical user flows, auth strategy, test data approach, and parallelization before writing any tests.
Its SKILL.md is about 5.3k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Testing & QA, covering End-to-end testing, Browser testing and Test data and fixtures. It works with Playwright. The repository describes itself as: CLI tool for configuring and monitoring Claude Code. The licence is MIT.
4 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 4c82aba. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npmnpxFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use npm and npx, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
TEST_USER_PASSWORDFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Playwright E2E Builder loads about 5.3k tokens when it runs. Until then it costs about 84 tokens; SKILL.md has 533 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
- Check `.env` files for test-specific environment variablesAutomated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from davila7/claude-code-templates at commit 4c82aba, republished under its MIT licence (© davila7). 533 words, ~5,261 tokens.
.claude/skills/playwright-e2e-builder/SKILL.md (or your agent's skills folder).Use this skill when you need to:
Enter plan mode. Before writing any tests, explore the existing project:
playwright.config.ts, @playwright/test in package.json)e2e/, tests/, __tests__/)npm run dev, next dev, etc.)data-testid, data-test, data-cy attributes).env files for test-specific environment variables.github/workflows/, .gitlab-ci.yml, Jenkinsfile)Use AskUserQuestion to clarify requirements. Ask in rounds.
Question: "What are the critical user flows to test?"
Header: "Flows"
multiSelect: true
Options:
- "Authentication (signup, login, logout, password reset)" — Core auth flows
- "Core CRUD (create, read, update, delete main resources)" — Primary data operations
- "Checkout/payments (cart, billing, confirmation)" — E-commerce or payment flows
- "Dashboard/admin (data views, filters, exports)" — Admin panel interactionsQuestion: "How many pages/routes does the application have approximately?"
Header: "App size"
Options:
- "Small (< 10 routes)" — Landing page, auth, a few feature pages
- "Medium (10-30 routes)" — Multiple feature areas, settings, profiles
- "Large (30+ routes)" — Complex app with many sections and user rolesQuestion: "How does your app handle authentication?"
Header: "Auth type"
Options:
- "Cookie/session based (Recommended)" — Server sets httpOnly cookies after login
- "JWT in localStorage" — Token stored in browser localStorage
- "OAuth/SSO (Google, GitHub, etc.)" — Third-party auth provider redirect flow
- "No auth (public app)" — No login required
Question: "How should tests authenticate?"
Header: "Test auth"
Options:
- "Login via UI once, reuse state (Recommended)" — storageState pattern: login in setup, share cookies across tests
- "API login in beforeEach" — Call auth API directly before each test, skip UI login
- "Seed auth token in fixtures" — Inject pre-generated tokens, no login flow needed
- "Test login UI every time" — Actually test the login form in each test suiteQuestion: "How should test data be managed?"
Header: "Test data"
Options:
- "API seeding in fixtures (Recommended)" — Call API endpoints to create/clean test data before each test
- "Database seeding (direct SQL)" — Run SQL scripts or ORM commands to populate test database
- "Shared test environment (pre-populated)" — Tests run against a persistent staging environment with existing data
- "Mock API responses" — Intercept network requests and return mock data
Question: "What environment do E2E tests run against?"
Header: "Environment"
Options:
- "Local dev server (Recommended)" — Start dev server before tests, run against localhost
- "Preview/staging URL" — Run against a deployed preview or staging environment
- "Docker Compose stack" — Full stack in containers, tests run outside or insideQuestion: "How should tests run in CI?"
Header: "CI"
Options:
- "GitHub Actions (Recommended)" — Native Playwright support with sharding
- "GitLab CI" — Docker-based runners with Playwright image
- "Local only (no CI yet)" — Just local test runs for now
- "Other CI (Jenkins, CircleCI)" — Custom CI configuration
Question: "Do you need visual regression testing?"
Header: "Visual"
Options:
- "No — functional tests only (Recommended)" — Assert behavior, not pixels
- "Yes — screenshot comparisons" — Capture and compare page screenshots
- "Yes — component screenshots" — Capture specific components, not full pagesWrite a concrete implementation plan covering:
Present via ExitPlanMode for user approval.
After approval, implement following this order:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI
? [['html', { open: 'never' }], ['github']]
: [['html', { open: 'on-failure' }]],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'on-first-retry',
},
projects: [
// Auth setup — runs before all tests
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'e2e/.auth/user.json',
},
dependencies: ['setup'],
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
storageState: 'e2e/.auth/user.json',
},
dependencies: ['setup'],
},
{
name: 'mobile',
use: {
...devices['iPhone 14'],
storageState: 'e2e/.auth/user.json',
},
dependencies: ['setup'],
},
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});// e2e/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
const authFile = 'e2e/.auth/user.json';
setup('authenticate', async ({ page }) => {
// Navigate to login page
await page.goto('/login');
// Fill login form
await page.getByLabel('Email').fill(process.env.TEST_USER_EMAIL || 'test@example.com');
await page.getByLabel('Password').fill(process.env.TEST_USER_PASSWORD || 'testpassword');
await page.getByRole('button', { name: 'Sign in' }).click();
// Wait for auth to complete — adjust selector to your app
await page.waitForURL('/dashboard');
await expect(page.getByRole('navigation')).toBeVisible();
// Save signed-in state
await page.context().storageState({ path: authFile });
});// e2e/fixtures.ts
import { test as base, expect } from '@playwright/test';
import { LoginPage } from './pages/login-page';
import { DashboardPage } from './pages/dashboard-page';
// API client for test data seeding
class ApiClient {
constructor(private baseURL: string, private token?: string) {}
async createResource(data: Record<string, unknown>) {
const response = await fetch(`${this.baseURL}/api/resources`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
},
body: JSON.stringify(data),
});
if (!response.ok) throw new Error(`Seed failed: ${response.status}`);
return response.json();
}
async deleteResource(id: string) {
await fetch(`${this.baseURL}/api/resources/${id}`, {
method: 'DELETE',
headers: this.token ? { Authorization: `Bearer ${this.token}` } : {},
});
}
}
type Fixtures = {
loginPage: LoginPage;
dashboardPage: DashboardPage;
api: ApiClient;
};
export const test = base.extend<Fixtures>({
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
dashboardPage: async ({ page }, use) => {
await use(new DashboardPage(page));
},
api: async ({ baseURL }, use) => {
const client = new ApiClient(baseURL!);
await use(client);
},
});
export { expect };// e2e/pages/login-page.ts
import { type Page, type Locator, expect } from '@playwright/test';
export class LoginPage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(private page: 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');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async expectError(message: string) {
await expect(this.errorMessage).toContainText(message);
}
}
// e2e/pages/dashboard-page.ts
import { type Page, type Locator, expect } from '@playwright/test';
export class DashboardPage {
readonly heading: Locator;
readonly createButton: Locator;
readonly searchInput: Locator;
readonly resourceList: Locator;
constructor(private page: Page) {
this.heading = page.getByRole('heading', { level: 1 });
this.createButton = page.getByRole('button', { name: 'Create' });
this.searchInput = page.getByPlaceholder('Search');
this.resourceList = page.getByTestId('resource-list');
}
async goto() {
await this.page.goto('/dashboard');
}
async createResource(name: string) {
await this.createButton.click();
await this.page.getByLabel('Name').fill(name);
await this.page.getByRole('button', { name: 'Save' }).click();
}
async search(query: string) {
await this.searchInput.fill(query);
// Wait for debounced search to trigger
await this.page.waitForResponse(resp =>
resp.url().includes('/api/resources') && resp.status() === 200
);
}
async expectResourceVisible(name: string) {
await expect(this.resourceList.getByText(name)).toBeVisible();
}
async expectResourceCount(count: number) {
await expect(this.resourceList.getByRole('listitem')).toHaveCount(count);
}
}// e2e/auth.spec.ts
import { test, expect } from './fixtures';
test.describe('Authentication', () => {
// These tests run WITHOUT storageState (unauthenticated)
test.use({ storageState: { cookies: [], origins: [] } });
test('successful login redirects to dashboard', async ({ loginPage, page }) => {
await loginPage.goto();
await loginPage.login('test@example.com', 'testpassword');
await expect(page).toHaveURL('/dashboard');
});
test('invalid credentials shows error', async ({ loginPage }) => {
await loginPage.goto();
await loginPage.login('test@example.com', 'wrongpassword');
await loginPage.expectError('Invalid credentials');
});
test('logout clears session', async ({ page }) => {
// Login first
await page.goto('/login');
// ... login steps ...
// Logout
await page.getByRole('button', { name: 'Logout' }).click();
await expect(page).toHaveURL('/login');
// Verify can't access protected route
await page.goto('/dashboard');
await expect(page).toHaveURL('/login');
});
});
// e2e/dashboard.spec.ts
import { test, expect } from './fixtures';
test.describe('Dashboard', () => {
test('displays resource list', async ({ dashboardPage }) => {
await dashboardPage.goto();
await expect(dashboardPage.heading).toHaveText('Dashboard');
await expect(dashboardPage.resourceList).toBeVisible();
});
test('create new resource', async ({ dashboardPage, page }) => {
await dashboardPage.goto();
await dashboardPage.createResource('New E2E Resource');
// Verify resource appears in list
await dashboardPage.expectResourceVisible('New E2E Resource');
});
test('search filters results', async ({ dashboardPage, api }) => {
// Seed test data via API
await api.createResource({ name: 'Alpha Item' });
await api.createResource({ name: 'Beta Item' });
await dashboardPage.goto();
await dashboardPage.search('Alpha');
await dashboardPage.expectResourceVisible('Alpha Item');
});
test('empty state shown when no resources', async ({ dashboardPage, page }) => {
await dashboardPage.goto();
await dashboardPage.search('nonexistent-query-xyz');
await expect(page.getByText('No results found')).toBeVisible();
});
});
// e2e/crud.spec.ts
import { test, expect } from './fixtures';
test.describe('Resource CRUD', () => {
let resourceId: string;
test.beforeEach(async ({ api }) => {
// Seed a resource for tests that need one
const resource = await api.createResource({ name: 'Test Resource' });
resourceId = resource.id;
});
test.afterEach(async ({ api }) => {
// Clean up seeded data
if (resourceId) {
await api.deleteResource(resourceId).catch(() => {});
}
});
test('edit resource name', async ({ page }) => {
await page.goto(`/resources/${resourceId}`);
await page.getByRole('button', { name: 'Edit' }).click();
await page.getByLabel('Name').clear();
await page.getByLabel('Name').fill('Updated Resource');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('heading')).toHaveText('Updated Resource');
});
test('delete resource with confirmation', async ({ page }) => {
await page.goto(`/resources/${resourceId}`);
await page.getByRole('button', { name: 'Delete' }).click();
// Confirm deletion dialog
await expect(page.getByRole('dialog')).toBeVisible();
await page.getByRole('button', { name: 'Confirm' }).click();
// Should redirect to list
await expect(page).toHaveURL('/dashboard');
});
});// e2e/visual.spec.ts
import { test, expect } from './fixtures';
test.describe('Visual regression', () => {
test('dashboard matches snapshot', async ({ dashboardPage, page }) => {
await dashboardPage.goto();
// Wait for dynamic content to stabilize
await page.waitForLoadState('networkidle');
await expect(page).toHaveScreenshot('dashboard.png', {
maxDiffPixelRatio: 0.01,
});
});
test('login page matches snapshot', async ({ loginPage, page }) => {
test.use({ storageState: { cookies: [], origins: [] } });
await loginPage.goto();
await expect(page).toHaveScreenshot('login.png', {
maxDiffPixelRatio: 0.01,
});
});
// Component-level screenshots
test('navigation component matches snapshot', async ({ page }) => {
await page.goto('/dashboard');
const nav = page.getByRole('navigation');
await expect(nav).toHaveScreenshot('navigation.png');
});
});# .github/workflows/e2e.yml
name: E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
e2e:
timeout-minutes: 30
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1/4, 2/4, 3/4, 4/4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run E2E tests
run: npx playwright test --shard=${{ matrix.shard }}
env:
BASE_URL: http://localhost:3000
TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
- name: Upload test report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report-${{ strategy.job-index }}
path: playwright-report/
retention-days: 14
- name: Upload test results
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: test-results-${{ strategy.job-index }}
path: test-results/
retention-days: 7e2e/
├── .auth/
│ └── user.json # Saved auth state (gitignored)
├── fixtures.ts # Custom test fixtures and API client
├── pages/
│ ├── login-page.ts # Login page object
│ ├── dashboard-page.ts # Dashboard page object
│ └── resource-page.ts # Resource detail page object
├── auth.setup.ts # Global auth setup (runs once)
├── auth.spec.ts # Authentication tests
├── dashboard.spec.ts # Dashboard tests
├── crud.spec.ts # CRUD operation tests
└── visual.spec.ts # Visual regression tests (optional)
playwright.config.ts # Playwright configurationPrefer getByRole(), getByLabel(), getByText() over CSS selectors or test IDs. These locators mirror how users interact with the page and catch accessibility issues:
// Preferred — accessible and resilient
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Email').fill('user@test.com');
// Fallback — when role-based doesn't work
await page.getByTestId('custom-widget').click();
// Avoid — fragile, breaks on refactors
await page.locator('.btn-primary').click();
await page.locator('#email-input').fill('user@test.com');Never use page.waitForTimeout(). Wait for specific conditions:
// Wait for API response
await page.waitForResponse(resp => resp.url().includes('/api/data'));
// Wait for element state
await expect(page.getByText('Saved')).toBeVisible();
// Wait for navigation
await expect(page).toHaveURL('/dashboard');
// Wait for loading to finish
await expect(page.getByTestId('spinner')).toBeHidden();Each test should create its own data and clean up after:
test('edit resource', async ({ api, page }) => {
// Arrange — seed via API
const resource = await api.createResource({ name: 'Test' });
// Act
await page.goto(`/resources/${resource.id}`);
// ... test logic ...
// Cleanup (also runs on failure via afterEach)
});test('checkout flow @slow @checkout', async ({ page }) => {
// Long test tagged for selective execution
});
// Run only: npx playwright test --grep @checkout
// Skip slow: npx playwright test --grep-invert @slow# Playwright
e2e/.auth/
test-results/
playwright-report/
blob-report/playwright.config.ts has webServer configured to start the dev servergetByRole, getByLabel, getByText)waitForTimeout() calls — only wait for elements, URLs, or responses.auth/ directory is in .gitignorenpx playwright test passes locally before pushing© davila7, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in cli-tool/components/skills/development/playwright-e2e-builder of davila7/claude-code-templates.
Open the folder on GitHubat commit 4c82aba
Playwright E2E Builder next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Playwright E2E Builder this skilldavila7/claude-code-templates | 32k | — | ~5.3k | Automated safety check: Notes | MIT | |
| Playwright E2E Testingfugazi/test-automation-skills-agents | 247 | — | ~3.2k | Automated safety check: Pass | MIT | |
| Playwright Testingaehrc/pathling | 137 | — | ~1.5k | Automated safety check: Pass | Apache-2.0 | |
| Test CommanderEliasOulkadi/shokunin | 114 | — | ~3k | Automated safety check: Notes | MIT | |
| Write and Verify Playwright Testsappsmithorg/appsmith | 41k | — | ~2.9k | Automated safety check: Notes | Apache-2.0 | |
| Glance TestDebugBase/glance | 156 | — | ~827 | Automated safety check: Pass | MIT |
fugazi/test-automation-skills-agents
Author and maintain versioned Playwright (@playwright/test) TypeScript UI specs for browser user flows.
aehrc/pathling
Expert guidance for writing end-to-end tests with Playwright Test framework.
EliasOulkadi/shokunin
Generate unit, integration, E2E, and visual regression tests following the Testing Trophy methodology (80% integration).
appsmithorg/appsmith
Writes a Playwright end-to-end test from a prompt, runs it against a live Appsmith deployment and retries with fixes up to three times until it passes.
DebugBase/glance
Run E2E browser tests on any web application using Glance MCP.
andrewyng/context-hub
Collects reusable Playwright patterns for logging in during end-to-end tests: password forms, OAuth redirects, saved browser state and TOTP two-factor codes.
davila7/claude-code-templates
Runs web-grounded searches through Perplexity's Sonar models over OpenRouter for current events, recent literature and cited facts beyond the model's training cutoff.
davila7/claude-code-templates
Analyzes Neuropixels recordings from SpikeGLX or Open Ephys through preprocessing, drift correction, Kilosort4 spike sorting, quality metrics and curation.
davila7/claude-code-templates
Supplies LaTeX templates and formatting rules for journals, conferences, posters, and grant proposals, then can check a draft against them.
davila7/claude-code-templates
Analyzes a brand's existing writing to lock in a consistent voice, then builds SEO blog posts and platform-specific social content around it.
davila7/claude-code-templates
Guides corrective and preventive action (CAPA) work in a quality management system, from initiation and root cause analysis through effectiveness verification.
davila7/claude-code-templates
Senior FDA consultant and specialist for medical device companies including HIPAA compliance and requirement management.
Works with
Categories
Plan and build comprehensive Playwright E2E test suites with Page Object Model, authentication state persistence, custom fixtures, visual regression, and CI integration. Playwright E2E Builder is an agent skill from davila7/claude-code-templates. Plan and build comprehensive Playwright E2E test suites with Page Object Model, authentication state persistence, custom fixtures, visual regression, and CI integration.
Playwright E2E Builder fits situations like: tasks that involve End-to-end testing; tasks that involve Browser testing; tasks that involve Test data and fixtures.
Run `npx skills add davila7/claude-code-templates --skill playwright-e2e-builder -a claude-code`. Or copy the skill folder (cli-tool/components/skills/development/playwright-e2e-builder in davila7/claude-code-templates) into .claude/skills/playwright-e2e-builder in your project. Claude Code loads it when a task matches its description.
Run `npx skills add davila7/claude-code-templates --skill playwright-e2e-builder -a codex`. Or copy the skill folder (cli-tool/components/skills/development/playwright-e2e-builder in davila7/claude-code-templates) into .agents/skills/playwright-e2e-builder in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add davila7/claude-code-templates --skill playwright-e2e-builder -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/playwright-e2e-builder, .gemini/skills/playwright-e2e-builder, .github/skills/playwright-e2e-builder and .opencode/skills/playwright-e2e-builder in your project.
Going by SKILL.md and its folder, Playwright E2E Builder needs the command-line tools its instructions call (npm and npx) and credentials named TEST_USER_PASSWORD. Our summary lists: Docker.
SKILL.md contains no URLs. Its commands use npm and npx, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Playwright E2E Builder is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.3k tokens (SKILL.md is roughly 21k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Playwright E2E Builder: Playwright E2E Testing (fugazi/test-automation-skills-agents, 247 stars), Playwright Testing (aehrc/pathling, 137 stars), Test Commander (EliasOulkadi/shokunin, 114 stars) and Write and Verify Playwright Tests (appsmithorg/appsmith, 41k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
davila7 (a GitHub user) maintains it in davila7/claude-code-templates, which has 32,432 GitHub stars. The repository holds 477 skills in this directory. The repository was last updated on October 7, 2026.
Source: davila7/claude-code-templates on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.