fugazi/playwright-e2e-testing
Author and maintain versioned Playwright (@playwright/test) TypeScript UI specs for browser user flows. Use when asked to create, run, debug, or refactor E2E tests, form/navigation/auth flows, responsive checks, UI mocking, fixtures, Page Objects, or visual comparisons. Use api-testing for standalone REST/GraphQL contracts and playwright-cli for live browser sessions. Keywords: E2E spec, Playwright test, POM, fixtures, UI regression.
npx skills add https://github.com/fugazi/test-automation-skills-agents --skill playwright-e2e-testing
Comprehensive toolkit for end-to-end testing of web applications using Playwright with TypeScript. Enables robust UI testing, UI-dependent API setup, and responsive design verification following best practices.
> Activation: This skill is triggered when authoring or maintaining versioned Playwright UI specs and their test infrastructure.
request fixture or network interceptionapi-testing).playwright-cli).playwright-regression-testing).webapp-selenium-testing).| Requirement | Details |
| --------------- | --------------------------------------------------- |
| Node.js | v18+ recommended |
| Package Manager | npm, yarn, or pnpm |
| Playwright | @playwright/test package |
| TypeScript | typescript + ts-node (optional but recommended) |
| Browsers | Installed via npx playwright install |
# Initialize new project
npm init playwright@latest
# Or add to existing project
npm install -D @playwright/test
npx playwright install
Before writing tests, clarify:
Always use @playwright/test with TypeScript for type safety and better IDE support.
import { test, expect } from "@playwright/test";
test("user can login", async ({ page }) => {
await page.goto("/login");
await page.getByLabel("Email").fill("[email protected]");
await page.getByLabel("Password").fill("password123");
await page.getByRole("button", { name: "Sign in" }).click();
await expect(page).toHaveURL(/.*dashboard/);
});
Prefer role-based locators (getByRole) with accessible names, then label → placeholder → text → test ID → CSS (last resort). XPath is never used.
➡️ Full priority hierarchy, role reference, and examples: Locator Strategies: Priority — the single source of truth.
Playwright auto-waits for elements. Never use sleep() or arbitrary timeouts.
// [ok] Web-first assertions (auto-retry)
await expect(page.getByRole("alert")).toBeVisible();
await expect(page).toHaveURL(/dashboard/);
await expect(page.getByTestId("status")).toHaveText("Success!");
// [no] Avoid manual waits
await page.waitForTimeout(2000); // Bad practice
Use test.step() for readable reports and failure localization:
test("checkout flow", async ({ page }) => {
await test.step("Add item to cart", async () => {
await page.goto("/products/1");
await page.getByRole("button", { name: "Add to Cart" }).click();
});
await test.step("Complete checkout", async () => {
await page.goto("/checkout");
await page.getByRole("button", { name: "Pay Now" }).click();
});
await test.step("Verify confirmation", async () => {
await expect(page.getByRole("heading")).toContainText("Order Confirmed");
});
});
// Form submit and wait for navigation (auto-waiting)
await page.getByRole("button", { name: "Login" }).click();
await expect(page).toHaveURL(/.*dashboard/);
// Form with API response validation
const responsePromise = page.waitForResponse(
(r) => r.url().includes("/api/login") && r.status() === 200,
);
await page.getByRole("button", { name: "Login" }).click();
const response = await responsePromise;
test("API health check", async ({ request }) => {
const response = await request.get("/api/health");
expect(response.ok()).toBeTruthy();
expect(await response.json()).toMatchObject({ status: "ok" });
});
test("handles API error", async ({ page }) => {
await page.route("**/api/users", (route) =>
route.fulfill({
status: 500,
body: JSON.stringify({ error: "Server error" }),
}),
);
await page.goto("/users");
await expect(page.getByRole("alert")).toContainText("Something went wrong");
});
const viewports = [
{ width: 375, height: 667, name: "mobile" },
{ width: 768, height: 1024, name: "tablet" },
{ width: 1280, height: 720, name: "desktop" },
];
for (const vp of viewports) {
test(`navigation works on ${vp.name}`, async ({ page }) => {
await page.setViewportSize(vp);
await page.goto("/");
// Mobile: hamburger menu
if (vp.width < 768) {
await page.getByRole("button", { name: /menu/i }).click();
}
await page.getByRole("link", { name: "About" }).click();
await expect(page).toHaveURL(/about/);
});
}
Use playwright.config.ts for project-wide settings:
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
retries: process.env.CI ? 2 : 0,
reporter: [["html"], ["junit", { outputFile: "results.xml" }]],
use: {
baseURL: "http://localhost:3000",
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
},
projects: [
{ name: "chromium", use: devices["Desktop Chrome"] },
{ name: "mobile", use: devices["Pixel 5"] },
],
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
});
| Problem | Cause | Solution |
| ---------------------- | ----------------------------- | ------------------------------------------------------- |
| Element not found | Wrong locator or not rendered | Use PWDEBUG=1 to inspect, verify with getByRole |
| Timeout waiting | Element hidden or slow load | Check for overlays, increase timeout, use waitFor() |
| Flaky tests | Race conditions, animations | Add test.step(), use proper waits, disable animations |
| Strict mode violation | Multiple elements match | Use .first(), .filter(), or more specific locator |
| Screenshots differ | Dynamic content | Mask dynamic areas, use deterministic data |
| CI fails, local passes | Environment differences | Check baseURL, timeouts, webServer config |
| API mock not working | Route pattern mismatch | Use **/api/... glob, verify with page.on('request') |
| Command | Description |
| ---------------------------------------- | ----------------------------- |
| npx playwright test | Run all tests headless |
| npx playwright test --ui | Open UI mode (interactive) |
| npx playwright test --headed | Run with visible browser |
| npx playwright test --debug | Run with Playwright Inspector |
| npx playwright test -g "login" | Run tests matching pattern |
| npx playwright test --project=chromium | Run specific project |
| npx playwright show-report | Open HTML report |
| npx playwright codegen | Generate tests by recording |
| PWDEBUG=1 npx playwright test | Debug with Inspector |
| DEBUG=pw:api npx playwright test | Verbose API logging |
waitForTimeout / manual sleeps instead of web-first auto-retrying assertions.| Document | Content |
| -------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| Snippets: Setup | Config, auth setup, custom fixtures & logging |
| Snippets: Interactions | Form interactions, API testing & network interception |
| Snippets: Viewports & Auth | Responsive viewports & authentication patterns |
| Snippets: Assertions & Debug | Assertions, debug commands & utility helpers |
| Locator Strategies: Priority | Locator priority hierarchy & role-based locators |
| Locator Strategies: Text | Label, text, placeholder, alt-text & test-ID locators |
| Locator Strategies: Filtering | Filtering, chaining & complex locator patterns |
| Locator Strategies: Anti & Debug| Anti-patterns, CSS last-resort, debugging & quick reference|
| POM: Basics | POM concepts, directory structure, base page & fluent API |
| POM: Components | Page object & reusable component object implementation |
| POM: Fixtures | Custom & authenticated page-object fixtures |
| POM: Practices | Best practices, anti-patterns & a complete worked example |
| Debugging: Tools & UI | Debugging tools, UI mode, Inspector & headed mode |
| Debugging: Tracing & Logs | Trace viewer, verbose logging, screenshots & videos |
| Debugging: Errors & Network | Console/page errors & network debugging |
| Debugging: Flaky & Locators | Flaky-test fixes, locator debugging & quick commands |
new PageObject() calls in spec files; all POMs injected via fixturesgetByRole(), getByTestId(), or getByText(); no CSS selectors for interactive elementsbeforeAll with shared mutable stateTake fugazi/playwright-e2e-testing from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm, npx.
Without those the skill loads but fails at the first command.