This skill should be used when user asks about "Playwright test", "E2E testing", "browser automation", "test login flow", "file upload test", "handle authentication in tests", or "fix flaky tests".
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
tests/
├── auth/
│ ├── login.spec.ts
│ └── signup.spec.ts
├── dashboard/
│ └── dashboard.spec.ts
├── fixtures/
│ └── test-data.ts
├── pages/
│ └── login.page.ts
└── playwright.config.ts
feature-name.spec.tstest('user can reset password via email')test('test reset password')// pages/login.page.ts
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto("/login");
}
async login(email: string, password: string) {
await this.page.getByLabel("Email").fill(email);
await this.page.getByLabel("Password").fill(password);
await this.page.getByRole("button", { name: "Sign in" }).click();
}
}
// tests/login.spec.ts
test("successful login", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login("user@example.com", "password");
await expect(page).toHaveURL("/dashboard");
});
getByRole - Accessible, resilientgetByLabel - Form inputsgetByPlaceholder - When no labelgetByText - Visible textgetByTestId - When no better option// Preferred
await page.getByRole("button", { name: "Submit" }).click();
await page.getByLabel("Email address").fill("user@example.com");
// Acceptable
await page.getByTestId("submit-button").click();
// Avoid
await page.locator("#submit-btn").click();
await page.locator('//button[@type="submit"]').click();
Save logged-in state and reuse across tests:
// global-setup.ts
async function globalSetup() {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("/login");
await page.getByLabel("Email").fill(process.env.TEST_USER_EMAIL);
await page.getByLabel("Password").fill(process.env.TEST_USER_PASSWORD);
await page.getByRole("button", { name: "Sign in" }).click();
await page.waitForURL("/dashboard");
await page.context().storageState({ path: "auth.json" });
await browser.close();
}
// playwright.config.ts
export default defineConfig({
globalSetup: "./global-setup.ts",
use: {
storageState: "auth.json",
},
});
// Create different auth states
const adminAuth = "admin-auth.json";
const userAuth = "user-auth.json";
test.describe("admin features", () => {
test.use({ storageState: adminAuth });
// Admin tests
});
test.describe("user features", () => {
test.use({ storageState: userAuth });
// User tests
});
// Single file
await page.getByLabel("Upload file").setInputFiles("path/to/file.pdf");
// Multiple files
await page
.getByLabel("Upload files")
.setInputFiles(["path/to/file1.pdf", "path/to/file2.pdf"]);
// Clear file input
await page.getByLabel("Upload file").setInputFiles([]);
// Create file from buffer
const buffer = Buffer.from("file content");
await page.getByTestId("dropzone").dispatchEvent("drop", {
dataTransfer: {
files: [{ name: "test.txt", mimeType: "text/plain", buffer }],
},
});
const downloadPromise = page.waitForEvent("download");
await page.getByRole("button", { name: "Download" }).click();
const download = await downloadPromise;
await download.saveAs("downloads/" + download.suggestedFilename());
Playwright auto-waits for elements. Use assertions:
// Auto-waits for element to be visible and stable
await page.getByRole("button", { name: "Submit" }).click();
// Auto-waits for condition
await expect(page.getByText("Success")).toBeVisible();
// Wait for navigation
await page.waitForURL("**/dashboard");
// Wait for network idle
await page.waitForLoadState("networkidle");
// Wait for specific response
await page.waitForResponse((resp) => resp.url().includes("/api/data"));
await page.route("**/api/users", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([{ id: 1, name: "Test User" }]),
});
});
// Mock error response
await page.route("**/api/users", async (route) => {
await route.fulfill({ status: 500 });
});
await page.route("**/api/data", async (route) => {
const response = await route.fetch();
const json = await response.json();
json.modified = true;
await route.fulfill({ response, json });
});
- name: Run Playwright tests
run: npx playwright test
env:
CI: true
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/
// playwright.config.ts
export default defineConfig({
workers: process.env.CI ? 2 : undefined,
fullyParallel: true,
});
# Run with UI mode
npx playwright test --ui
# Run with inspector
npx playwright test --debug
# Show browser
npx playwright test --headed
// playwright.config.ts
use: {
trace: 'on-first-retry', // Capture trace on failure
}
Race conditions:
Animation issues:
Dynamic content:
Test isolation:
// Bad: Hard sleep
await page.waitForTimeout(5000);
// Good: Wait for condition
await expect(page.getByText("Loaded")).toBeVisible();
// Bad: Flaky selector
await page.locator(".btn:nth-child(3)").click();
// Good: Semantic selector
await page.getByRole("button", { name: "Submit" }).click();