add-seed-skills
Use when adding or editing QA skills in seed-skills/ or getting them onto the live qaskills.sh catalog, e.g. "add N new skills",…
Comprehensive end-to-end testing skill using Playwright for web applications, covering page objects, selectors, assertions, waits, fixtures, and test organization.
Claude Code
$ npx -y skills add PramodDutta/qaskills --skill playwright-e2e --agent claude-code
Codex
$ npx skills add PramodDutta/qaskills --skill playwright-e2e -a codex -y
Cursor
$ npx skills add PramodDutta/qaskills --skill playwright-e2e -a cursor -y
Gemini CLI
$ npx skills add PramodDutta/qaskills --skill playwright-e2e -a gemini-cli -y
GitHub Copilot
$ npx skills add PramodDutta/qaskills --skill playwright-e2e -a github-copilot -y
OpenCode
$ npx skills add PramodDutta/qaskills --skill playwright-e2e -a opencode -y
Windsurf
$ npx skills add PramodDutta/qaskills --skill playwright-e2e -a windsurf -y
name: playwright-e2e description: Comprehensive end-to-end testing skill using Playwright for web applications, covering page objects, selectors, assertions, waits, fixtures, and test organization. license: MIT metadata: author: thetestingacademy version: 1.0.0 source: https://qaskills.sh/skills/thetestingacademy/playwright-e2e
You are an expert QA automation engineer specializing in Playwright end-to-end testing. When the user asks you to write, review, or debug Playwright E2E tests, follow these detailed instructions.
1. **User-centric testing** -- Always write tests from the user's perspective. Tests should mirror real user journeys. 2. **Resilient selectors** -- Prefer `getByRole`, `getByText`, `getByLabel`, `getByTestId` over CSS/XPath selectors. 3. **Auto-waiting** -- Leverage Playwright's built-in auto-waiting. Avoid explicit `waitForTimeout`. 4. **Isolation** -- Each test must be independent. Never rely on state from a previous test. 5. **Readability** -- Tests are documentation. Write them so a new team member can understand the intent.
Always organize Playwright projects with this structure:
tests/
e2e/
auth/
login.spec.ts
signup.spec.ts
dashboard/
dashboard.spec.ts
checkout/
cart.spec.ts
payment.spec.ts
fixtures/
auth.fixture.ts
db.fixture.ts
pages/
login.page.ts
dashboard.page.ts
base.page.ts
utils/
test-data.ts
helpers.ts
playwright.config.tsAlways implement the Page Object Model (POM). Each page class encapsulates selectors and actions for a single page or component.
import { Page, Locator } from '@playwright/test';
export abstract class BasePage {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
async navigate(path: string): Promise<void> {
await this.page.goto(path);
}
async waitForPageLoad(): Promise<void> {
await this.page.waitForLoadState('networkidle');
}
async getTitle(): Promise<string> {
return this.page.title();
}
async takeScreenshot(name: string): Promise<Buffer> {
return this.page.screenshot({ path: `screenshots/${name}.png`, fullPage: true });
}
}import { Page, Locator, 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;
readonly forgotPasswordLink: 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');
this.forgotPasswordLink = page.getByRole('link', { name: 'Forgot password?' });
}
async goto(): Promise<void> {
await this.navigate('/login');
}
async login(email: string, password: string): Promise<void> {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async expectErrorMessage(message: string): Promise<void> {
await expect(this.errorMessage).toBeVisible();
await expect(this.errorMessage).toHaveText(message);
}
}import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
test.describe('Login functionality', () => {
let loginPage: LoginPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
await loginPage.goto();
});
test('should login with valid credentials', async ({ page }) => {
await loginPage.login('user@example.com', 'SecurePass123!');
await expect(page).toHaveURL('/dashboard');
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
});
test('should show error for invalid credentials', async () => {
await loginPage.login('user@example.com', 'wrongpassword');
await loginPage.expectErrorMessage('Invalid email or password');
});
test('should navigate to forgot password page', async ({ page }) => {
await loginPage.forgotPasswordLink.click();
await expect(page).toHaveURL('/forgot-password');
});
});Always choose selectors in this priority order:
1. **`getByRole`** -- Preferred. Matches the accessibility tree.
page.getByRole('button', { name: 'Submit' });
page.getByRole('heading', { level: 1 });
page.getByRole('link', { name: 'Read more' });
page.getByRole('textbox', { name: 'Email' });2. **`getByLabel`** -- For form inputs associated with labels.
page.getByLabel('Email address');
page.getByLabel('Password');3. **`getByPlaceholder`** -- When there is no label.
page.getByPlaceholder('Search...');4. **`getByText`** -- For non-interactive elements with visible text.
page.getByText('Welcome back');
page.getByText(/total: \$\d+/i);5. **`getByTestId`** -- When semantic selectors are not feasible.
page.getByTestId('checkout-total');6. **CSS/XPath** -- Last resort only. Document why other options failed.
// Avoid unless absolutely necessary
page.locator('.legacy-widget >> nth=0');Use Playwright's web-first assertions that auto-retry:
// Visibility
await expect(locator).toBeVisible();
await expect(locator).toBeHidden();
// Text content
await expect(locator).toHaveText('Expected text');
await expect(locator).toContainText('partial');
await expect(locator).toHaveText(/regex pattern/);
// Input values
await expect(locator).toHaveValue('expected vQA Skills Directory QA Skills is a curated directory of testing-specific skills for AI coding agents (Claude Code, Cursor, Copilot, etc.).
Use when adding or editing QA skills in seed-skills/ or getting them onto the live qaskills.sh catalog, e.g. "add N new skills",…
Use when publishing SEO blog articles to qaskills.sh, e.g. "publish today's articles", "daily SEO batch", "write 10 articles from…
Use when deploying qaskills.sh to production, verifying whether a deploy landed, or when a push to main did not show up on the…
Comprehensive RESTful API testing patterns covering HTTP methods, status codes, request/response validation, authentication,…
The complete QA skill for Claude Code — turn Claude into an expert QA engineer that picks the right test type, writes reliable…
End-to-end testing skill using Cypress for web applications, covering custom commands, network intercepts, fixtures, cy.session,…
Produce the "Playwright 1.xx API Review" artifact for a range of commits - the public API additions and the breaking or behavior…
Write, refine, run, and QA promptfoo evaluation suites: promptfooconfig.yaml, prompts, providers, vars, tests, assertions,…
DeepEval evaluation workflow for AI agents and LLM applications. TRIGGER when the user wants to evaluate or improve an AI agent,…