Claude
Skills
Sign in
Back

playwright-test-generator

Included with Lifetime
$97 forever

Generate production-ready Playwright E2E tests from natural language specifications or requirements. Creates TypeScript test files following best practices including data-testid locators, proper async/await usage, test isolation, and the AAA (Arrange-Act-Assert) pattern.

Code Review

What this skill does


## When to Use This Skill

Use this skill when you need to:

- Create new E2E tests from user stories or requirements
- Generate test files for new features or pages
- Convert manual test cases into automated tests
- Scaffold a complete test suite for a new application
- Create tests with proper fixtures and configuration

Do NOT use this skill when:

- You need to debug existing tests (use test-debugger skill)
- You want to refactor or maintain existing tests (use test-maintainer skill)
- You need to create Page Object Models (use page-object-builder skill)

## Prerequisites

Before using this skill:

1. Playwright should be installed in the project (`npm install -D @playwright/test`)
2. Basic understanding of the application under test (URLs, main flows)
3. Knowledge of what functionality needs to be tested
4. Access to the application's UI or design documentation

## Instructions

### Step 1: Gather Test Requirements

Ask the user for:

- **Feature/functionality** to test
- **User flow** or scenario description
- **Expected outcomes** (what should happen)
- **Test data** requirements (if any)
- **Page URL(s)** involved in the test
- **Data-testid values** (or offer to suggest them based on element purpose)

### Step 2: Analyze and Plan

Review the requirements and:

- Break down the user flow into discrete steps
- Identify all page elements that need interaction
- Determine what assertions are needed
- Plan the test structure (setup, actions, verifications)
- Identify any fixtures or utilities needed

### Step 3: Generate Test File

Create a TypeScript test file with:

**File Structure:**

```typescript
import { test, expect } from "@playwright/test";

test.describe("Feature Name", () => {
  test("should <specific behavior>", async ({ page }) => {
    // Arrange: Setup
    // Act: Perform actions
    // Assert: Verify results
  });
});
```

**Required Elements:**

- Descriptive test names (what behavior is tested)
- Proper async/await usage
- data-testid locators ONLY
- Explicit waits (waitForSelector, waitForLoadState)
- Clear assertions with expect()
- Comments for AAA sections
- TypeScript types

**Locator Strategy (MANDATORY):**

```typescript
// ✅ CORRECT: Always use data-testid
await page.locator('[data-testid="submit-button"]').click();
await expect(page.locator('[data-testid="success-message"]')).toBeVisible();

// ❌ WRONG: Never use CSS selectors, XPath, or text selectors
await page.locator(".submit-btn").click(); // NO
await page.locator('//button[@type="submit"]').click(); // NO
await page.getByRole("button", { name: "Submit" }).click(); // NO
```

### Step 4: Add Configuration (if needed)

If this is the first test, generate `playwright.config.ts`:

- Base URL configuration
- Timeout settings (30s default)
- Retry logic (2 retries for flaky tests)
- Screenshot on failure
- Trace on first retry
- Parallel execution settings

### Step 5: Include Fixtures (if needed)

For complex setups, create custom fixtures:

```typescript
import { test as base } from "@playwright/test";

type MyFixtures = {
  authenticatedPage: Page;
};

export const test = base.extend<MyFixtures>({
  authenticatedPage: async ({ page }, use) => {
    // Setup: login
    await page.goto("/login");
    await page.locator('[data-testid="username"]').fill("testuser");
    await page.locator('[data-testid="password"]').fill("password");
    await page.locator('[data-testid="login-button"]').click();
    await page.waitForURL("/dashboard");

    await use(page);

    // Teardown (if needed)
  },
});
```

### Step 6: Validate Generated Test

Ensure the test includes:

- [ ] Descriptive test name
- [ ] Proper test.describe grouping
- [ ] Only data-testid locators
- [ ] No hardcoded waits (no page.waitForTimeout())
- [ ] Explicit waits for elements
- [ ] Clear AAA structure
- [ ] TypeScript types
- [ ] Meaningful assertions
- [ ] Comments for complex logic

### Step 7: Provide Usage Instructions

Tell the user:

- Where the test file was created
- How to run the test: `npx playwright test <filename>`
- How to run in debug mode: `npx playwright test --debug <filename>`
- How to view traces: `npx playwright show-trace trace.zip`
- What data-testid values need to be added to the UI (if suggesting new ones)

## Examples

### Example 1: Simple Login Test

**Input:**
"Create a test for the login flow. User enters username and password, clicks login button, and should see the dashboard."

**Output:**

```typescript
import { test, expect } from "@playwright/test";

test.describe("Authentication", () => {
  test("should successfully login with valid credentials", async ({ page }) => {
    // Arrange: Navigate to login page
    await page.goto("/login");
    await expect(page.locator('[data-testid="login-form"]')).toBeVisible();

    // Act: Enter credentials and submit
    await page
      .locator('[data-testid="username-input"]')
      .fill("[email protected]");
    await page.locator('[data-testid="password-input"]').fill("SecurePass123");
    await page.locator('[data-testid="login-button"]').click();

    // Assert: Verify successful login
    await page.waitForURL("/dashboard");
    await expect(
      page.locator('[data-testid="dashboard-header"]'),
    ).toBeVisible();
    await expect(page.locator('[data-testid="user-welcome"]')).toContainText(
      "Welcome",
    );
  });
});
```

**Required data-testid values:**

- `login-form` - The login form container
- `username-input` - Username/email input field
- `password-input` - Password input field
- `login-button` - Submit button
- `dashboard-header` - Dashboard page header
- `user-welcome` - Welcome message element

### Example 2: E-commerce Product Purchase

**Input:**
"Test the product purchase flow: search for 'laptop', select first result, add to cart, checkout, and verify order confirmation."

**Output:**

```typescript
import { test, expect } from "@playwright/test";

test.describe("E-commerce Purchase Flow", () => {
  test("should complete product purchase from search to confirmation", async ({
    page,
  }) => {
    // Arrange: Navigate to homepage
    await page.goto("/");
    await expect(page.locator('[data-testid="search-bar"]')).toBeVisible();

    // Act: Search for product
    await page.locator('[data-testid="search-input"]').fill("laptop");
    await page.locator('[data-testid="search-button"]').click();
    await page.waitForLoadState("networkidle");

    // Act: Select first product
    await expect(
      page.locator('[data-testid="product-card"]').first(),
    ).toBeVisible();
    await page.locator('[data-testid="product-card"]').first().click();

    // Act: Add to cart
    await expect(
      page.locator('[data-testid="add-to-cart-button"]'),
    ).toBeEnabled();
    await page.locator('[data-testid="add-to-cart-button"]').click();
    await expect(page.locator('[data-testid="cart-count"]')).toContainText("1");

    // Act: Proceed to checkout
    await page.locator('[data-testid="cart-icon"]').click();
    await page.locator('[data-testid="checkout-button"]').click();

    // Act: Fill checkout form
    await page.locator('[data-testid="shipping-name"]').fill("John Doe");
    await page.locator('[data-testid="shipping-address"]').fill("123 Main St");
    await page.locator('[data-testid="shipping-city"]').fill("New York");
    await page.locator('[data-testid="shipping-zip"]').fill("10001");
    await page.locator('[data-testid="payment-card"]').fill("4242424242424242");
    await page.locator('[data-testid="payment-expiry"]').fill("12/25");
    await page.locator('[data-testid="payment-cvc"]').fill("123");
    await page.locator('[data-testid="place-order-button"]').click();

    // Assert: Verify order confirmation
    await page.waitForURL(/\/order\/confirmation/);
    await expect(
      page.locator('[data-testid="order-success-message"]'),
    ).toBeVisible();
    await expect(page.locator('[data-testid="order-number"]')).toContainText(
      /ORD-\d+/,
    );
  });
});
```

### Example

Related in Code Review