# Test Generation

> **When to use**: Generating Playwright test code from interactive CLI sessions — recording user flows, building test scaffolds, converting manual testing into automated tests.
> **Prerequisites**: [core-commands.md](core-commands.md) for basic CLI usage

## Quick Reference

```bash
# Every CLI action outputs the equivalent Playwright code
playwright-cli open https://example.com/login
playwright-cli snapshot
playwright-cli fill e1 "user@example.com"
# Output: await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');

playwright-cli fill e2 "password123"
# Output: await page.getByRole('textbox', { name: 'Password' }).fill('password123');

playwright-cli click e3
# Output: await page.getByRole('button', { name: 'Sign In' }).click();
```

## How It Works

Every action you perform with `playwright-cli` automatically generates the corresponding Playwright TypeScript code in the output. This code uses the same **role-based locators** that Playwright recommends for production tests.

The workflow:

1. **Open a page** → generates `await page.goto(url)`
2. **Take a snapshot** → see element refs and their accessible roles
3. **Interact** → each action generates one line of Playwright code
4. **Collect the code** → assemble generated lines into a complete test

## Recording a Flow

### Example: Login Flow

```bash
playwright-cli open https://example.com/login
# Ran Playwright code:
# await page.goto('https://example.com/login');

playwright-cli snapshot
# Output:
# e1 [textbox "Email"]
# e2 [textbox "Password"]
# e3 [button "Sign In"]
# e4 [link "Forgot password?"]

playwright-cli fill e1 "user@example.com"
# Ran Playwright code:
# await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');

playwright-cli fill e2 "password123"
# Ran Playwright code:
# await page.getByRole('textbox', { name: 'Password' }).fill('password123');

playwright-cli click e3
# Ran Playwright code:
# await page.getByRole('button', { name: 'Sign In' }).click();
```

### Assemble into a Test

Collect the generated code and wrap it in a Playwright test:

```typescript
import { test, expect } from '@playwright/test';

test('user can log in', async ({ page }) => {
  await page.goto('https://example.com/login');
  await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
  await page.getByRole('textbox', { name: 'Password' }).fill('password123');
  await page.getByRole('button', { name: 'Sign In' }).click();

  // Add assertions (not generated — you add these)
  await expect(page).toHaveURL(/.*dashboard/);
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
```

```javascript
const { test, expect } = require('@playwright/test');

test('user can log in', async ({ page }) => {
  await page.goto('https://example.com/login');
  await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
  await page.getByRole('textbox', { name: 'Password' }).fill('password123');
  await page.getByRole('button', { name: 'Sign In' }).click();

  await expect(page).toHaveURL(/.*dashboard/);
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
```

## Locator Strategies in Generated Code

The generated code uses Playwright's recommended locator hierarchy:

| Priority | Locator Type | Example | When used |
|----------|-------------|---------|-----------|
| 1 | Role-based | `getByRole('button', { name: 'Submit' })` | Elements with ARIA roles |
| 2 | Label-based | `getByLabel('Email')` | Form inputs with labels |
| 3 | Placeholder | `getByPlaceholder('Search...')` | Inputs with placeholder text |
| 4 | Text-based | `getByText('Welcome back')` | Static text content |
| 5 | Test ID | `getByTestId('submit-btn')` | Elements with `data-testid` |

These locators are **resilient to markup changes** — they mirror how users perceive the page rather than relying on CSS selectors or XPath.

## Recording Complex Flows

### E-Commerce Checkout

```bash
playwright-cli open https://shop.example.com

# Browse products
playwright-cli snapshot
playwright-cli click e5                # "Add to Cart" button
# await page.getByRole('button', { name: 'Add to Cart' }).click();

playwright-cli click e12               # Cart icon
# await page.getByRole('link', { name: 'Cart' }).click();

playwright-cli snapshot
playwright-cli click e3                # "Proceed to Checkout"
# await page.getByRole('button', { name: 'Proceed to Checkout' }).click();

# Shipping info
playwright-cli snapshot
playwright-cli fill e1 "Jane Doe"
# await page.getByRole('textbox', { name: 'Full Name' }).fill('Jane Doe');
playwright-cli fill e2 "123 Main St"
# await page.getByRole('textbox', { name: 'Address' }).fill('123 Main St');
playwright-cli fill e3 "Springfield"
# await page.getByRole('textbox', { name: 'City' }).fill('Springfield');
playwright-cli select e4 "IL"
# await page.getByRole('combobox', { name: 'State' }).selectOption('IL');
playwright-cli fill e5 "62701"
# await page.getByRole('textbox', { name: 'ZIP Code' }).fill('62701');
playwright-cli click e6               # "Continue to Payment"
```

### Multi-Step Wizard

```bash
playwright-cli open https://example.com/onboarding

# Step 1
playwright-cli snapshot
playwright-cli fill e1 "Acme Corp"
playwright-cli select e2 "technology"
playwright-cli click e3    # Next

# Step 2
playwright-cli snapshot
playwright-cli check e1    # Feature checkbox
playwright-cli check e3    # Another feature
playwright-cli click e5    # Next

# Step 3
playwright-cli snapshot
playwright-cli click e2    # "Complete Setup"
```

### Search with Dynamic Results

```bash
playwright-cli open https://example.com
playwright-cli snapshot

playwright-cli fill e1 "playwright testing"
# await page.getByRole('searchbox', { name: 'Search' }).fill('playwright testing');

playwright-cli press Enter
# await page.keyboard.press('Enter');

playwright-cli snapshot    # See search results — new refs assigned

playwright-cli click e3    # First result
# await page.getByRole('link', { name: 'Getting Started with Playwright' }).click();
```

## Adding Assertions

Generated code captures **actions** but not **assertions**. Always add assertions manually to create meaningful tests.

### Common Assertions to Add

```typescript
// URL changed after navigation
await expect(page).toHaveURL(/.*dashboard/);
await expect(page).toHaveURL('https://example.com/success');

// Element is visible
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
await expect(page.getByText('Order confirmed')).toBeVisible();

// Element contains text
await expect(page.getByTestId('total')).toHaveText('$99.99');
await expect(page.getByRole('alert')).toContainText('saved');

// Element has specific attribute
await expect(page.getByRole('button', { name: 'Submit' })).toBeDisabled();
await expect(page.getByRole('checkbox')).toBeChecked();

// Element count
await expect(page.getByRole('listitem')).toHaveCount(5);

// Page title
await expect(page).toHaveTitle(/Dashboard/);

// Screenshot comparison
await expect(page).toHaveScreenshot('checkout.png');
```

### Where to Place Assertions

```typescript
test('complete checkout flow', async ({ page }) => {
  await page.goto('https://shop.example.com/products');

  // Action: Add item to cart
  await page.getByRole('button', { name: 'Add to Cart' }).click();

  // Assertion: Cart badge updates
  await expect(page.getByTestId('cart-count')).toHaveText('1');

  // Action: Go to cart
  await page.getByRole('link', { name: 'Cart' }).click();

  // Assertion: Correct page
  await expect(page).toHaveURL(/.*cart/);
  await expect(page.getByRole('heading', { name: 'Your Cart' })).toBeVisible();

  // Action: Proceed to checkout
  await page.getByRole('button', { name: 'Checkout' }).click();

  // Action: Fill shipping
  await page.getByRole('textbox', { name: 'Full Name' }).fill('Jane Doe');
  await page.getByRole('textbox', { name: 'Address' }).fill('123 Main St');
  await page.getByRole('button', { name: 'Place Order' }).click();

  // Assertion: Order confirmed
  await expect(page.getByText('Order confirmed')).toBeVisible();
  await expect(page.getByTestId('order-number')).toBeVisible();
});
```

## Best Practices

### 1. Explore Before Recording

Take a snapshot first to understand the page structure. Don't blindly click — know what elements are available:

```bash
playwright-cli open https://example.com
playwright-cli snapshot
# Review the elements, plan your flow, then start interacting
```

### 2. Use Semantic Locators

The generated code already prefers role-based locators. If you see CSS selectors in generated output, consider filing an issue — role-based locators are more resilient:

```typescript
// Generated (good — semantic, resilient)
await page.getByRole('button', { name: 'Submit' }).click();

// Avoid writing manually (fragile — breaks if CSS changes)
await page.locator('#submit-btn').click();
await page.locator('.btn.btn-primary').click();
```

### 3. Keep Tests Focused

One test = one user behavior. Don't record an entire session into a single test:

```typescript
// Good: Focused test
test('user can add item to cart', async ({ page }) => {
  // Just the add-to-cart flow
});

test('user can complete checkout', async ({ page }) => {
  // Just the checkout flow (use auth state to skip login)
});

// Bad: Monolith test
test('user journey', async ({ page }) => {
  // Login + browse + add to cart + checkout + verify email...
});
```

### 4. Parameterize Test Data

Replace hardcoded values from the recording with variables or test data:

```typescript
// Instead of hardcoded values from recording
test('registration', async ({ page }) => {
  const user = {
    name: 'Jane Doe',
    email: `test+${Date.now()}@example.com`,
    password: 'SecurePass123!'
  };

  await page.goto('/register');
  await page.getByRole('textbox', { name: 'Name' }).fill(user.name);
  await page.getByRole('textbox', { name: 'Email' }).fill(user.email);
  await page.getByRole('textbox', { name: 'Password' }).fill(user.password);
  await page.getByRole('button', { name: 'Create Account' }).click();

  await expect(page).toHaveURL(/.*welcome/);
});
```

### 5. Add Wait Strategies for Flaky Steps

If a recorded action depends on async content loading, add explicit waits:

```typescript
// Before clicking a dynamically loaded element
await page.waitForSelector('.results-loaded');
await page.getByRole('link', { name: 'First Result' }).click();

// Or use Playwright's auto-waiting (preferred)
await expect(page.getByRole('link', { name: 'First Result' })).toBeVisible();
await page.getByRole('link', { name: 'First Result' }).click();
```

## Tips

- **Generated code is a starting point** — always review, add assertions, and parameterize before committing to your test suite
- **Re-snapshot after dynamic changes** — refs change when the DOM updates
- **Combine with `state-save`** — record a login flow once, save state, then start all other recordings from the authenticated state
- **Use `--filename` for snapshots** — save before/after snapshots for complex flows to help write assertions later
