BMAD-METHOD/docs/how-to/workflows/run-atdd.md

13 KiB
Raw Blame History

title description
How to Run ATDD with TEA Generate failing acceptance tests before implementation using TEA's ATDD workflow

How to Run ATDD with TEA

Use TEA's *atdd workflow to generate failing acceptance tests BEFORE implementation. This is the TDD (Test-Driven Development) red phase - tests fail first, guide development, then pass.

When to Use This

  • You're about to implement a NEW feature (feature doesn't exist yet)
  • You want to follow TDD workflow (red → green → refactor)
  • You want tests to guide your implementation
  • You're practicing acceptance test-driven development

Don't use this if:

  • Feature already exists (use *automate instead)
  • You want tests that pass immediately

Prerequisites

  • BMad Method installed
  • TEA agent available
  • Test framework setup complete (run *framework if needed)
  • Story or feature defined with acceptance criteria

Note: This guide uses Playwright examples. If using Cypress, commands and syntax will differ (e.g., cy.get() instead of page.locator()).

Steps

1. Load TEA Agent

Start a fresh chat and load TEA:

*tea

2. Run the ATDD Workflow

*atdd

3. Provide Context

TEA will ask for:

Story/Feature Details:

We're adding a user profile page where users can:
- View their profile information
- Edit their name and email
- Upload a profile picture
- Save changes with validation

Acceptance Criteria:

Given I'm logged in
When I navigate to /profile
Then I see my current name and email

Given I'm on the profile page
When I click "Edit Profile"
Then I can modify my name and email

Given I've edited my profile
When I click "Save"
Then my changes are persisted
And I see a success message

Given I upload an invalid file type
When I try to save
Then I see an error message
And changes are not saved

Reference Documents (optional):

  • Point to your story file
  • Reference PRD or tech spec
  • Link to test design (if you ran *test-design first)

4. Specify Test Levels

TEA will ask what test levels to generate:

Options:

  • E2E tests (browser-based, full user journey)
  • API tests (backend only, faster)
  • Component tests (UI components in isolation)
  • Mix of levels (recommended)

Component Testing by Framework

TEA generates component tests using framework-appropriate tools:

Your Framework Component Testing Tool
Cypress Cypress Component Testing (*.cy.tsx)
Playwright Vitest + React Testing Library (*.test.tsx)

Example response:

Generate:
- API tests for profile CRUD operations
- E2E tests for the complete profile editing flow
- Component tests for ProfileForm validation (if using Cypress or Vitest)
- Focus on P0 and P1 scenarios

5. Review Generated Tests

TEA generates failing tests in appropriate directories:

API Tests (tests/api/profile.spec.ts):

Vanilla Playwright:

import { test, expect } from '@playwright/test';

test.describe('Profile API', () => {
  test('should fetch user profile', async ({ request }) => {
    const response = await request.get('/api/profile');

    expect(response.status()).toBe(200);
    const profile = await response.json();
    expect(profile).toHaveProperty('name');
    expect(profile).toHaveProperty('email');
    expect(profile).toHaveProperty('avatarUrl');
  });

  test('should update user profile', async ({ request }) => {
    const response = await request.patch('/api/profile', {
      data: {
        name: 'Updated Name',
        email: 'updated@example.com'
      }
    });

    expect(response.status()).toBe(200);
    const updated = await response.json();
    expect(updated.name).toBe('Updated Name');
    expect(updated.email).toBe('updated@example.com');
  });

  test('should validate email format', async ({ request }) => {
    const response = await request.patch('/api/profile', {
      data: {
        email: 'invalid-email'
      }
    });

    expect(response.status()).toBe(400);
    const error = await response.json();
    expect(error.message).toContain('Invalid email format');
  });
});

With Playwright Utils:

import { test } from '@seontechnologies/playwright-utils/api-request/fixtures';
import { expect } from '@playwright/test';
import { z } from 'zod';

const ProfileSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  avatarUrl: z.string().url()
});

test.describe('Profile API', () => {
  test('should fetch user profile', async ({ apiRequest }) => {
    const { status, body } = await apiRequest({
      method: 'GET',
      path: '/api/profile'
    }).validateSchema(ProfileSchema);  // Chained validation

    expect(status).toBe(200);
    // Schema already validated, type-safe access
    expect(body.name).toBeDefined();
    expect(body.email).toContain('@');
  });

  test('should update user profile', async ({ apiRequest }) => {
    const { status, body } = await apiRequest({
      method: 'PATCH',
      path: '/api/profile',
      body: {  // 'body' not 'data'
        name: 'Updated Name',
        email: 'updated@example.com'
      }
    }).validateSchema(ProfileSchema);  // Chained validation

    expect(status).toBe(200);
    expect(body.name).toBe('Updated Name');
    expect(body.email).toBe('updated@example.com');
  });

  test('should validate email format', async ({ apiRequest }) => {
    const { status, body } = await apiRequest({
      method: 'PATCH',
      path: '/api/profile',
      body: { email: 'invalid-email' }  // 'body' not 'data'
    });

    expect(status).toBe(400);
    expect(body.message).toContain('Invalid email format');
  });
});

Key Benefits:

  • Returns { status, body } (cleaner than response.status() + await response.json())
  • Automatic schema validation with Zod
  • Type-safe response bodies
  • Automatic retry for 5xx errors
  • Less boilerplate

E2E Tests (tests/e2e/profile.spec.ts):

import { test, expect } from '@playwright/test';

test.describe('Profile Page', () => {
  test.beforeEach(async ({ page }) => {
    // Login first
    await page.goto('/login');
    await page.getByLabel('Email').fill('test@example.com');
    await page.getByLabel('Password').fill('password123');
    await page.getByRole('button', { name: 'Sign in' }).click();
  });

  test('should display current profile information', async ({ page }) => {
    await page.goto('/profile');

    await expect(page.getByText('test@example.com')).toBeVisible();
    await expect(page.getByText('Test User')).toBeVisible();
  });

  test('should edit and save profile', async ({ page }) => {
    await page.goto('/profile');

    // Click edit
    await page.getByRole('button', { name: 'Edit Profile' }).click();

    // Modify fields
    await page.getByLabel('Name').fill('Updated Name');
    await page.getByLabel('Email').fill('updated@example.com');

    // Save
    await page.getByRole('button', { name: 'Save' }).click();

    // Verify success
    await expect(page.getByText('Profile updated successfully')).toBeVisible();
    await expect(page.getByText('Updated Name')).toBeVisible();
  });

  test('should show validation error for invalid email', async ({ page }) => {
    await page.goto('/profile');
    await page.getByRole('button', { name: 'Edit Profile' }).click();

    await page.getByLabel('Email').fill('invalid-email');
    await page.getByRole('button', { name: 'Save' }).click();

    await expect(page.getByText('Invalid email format')).toBeVisible();
  });
});

Implementation Checklist

TEA also provides an implementation checklist:

## Implementation Checklist

### Backend
- [ ] Create `GET /api/profile` endpoint
- [ ] Create `PATCH /api/profile` endpoint
- [ ] Add email validation middleware
- [ ] Add profile picture upload handling
- [ ] Write API unit tests

### Frontend
- [ ] Create ProfilePage component
- [ ] Implement profile form with validation
- [ ] Add file upload for avatar
- [ ] Handle API errors gracefully
- [ ] Add loading states

### Tests
- [x] API tests generated (failing)
- [x] E2E tests generated (failing)
- [ ] Run tests after implementation (should pass)

6. Verify Tests Fail

This is the TDD red phase - tests MUST fail before implementation.

For Playwright:

npx playwright test

For Cypress:

npx cypress run

Expected output:

Running 6 tests using 1 worker

  ✗ tests/api/profile.spec.ts:3:3  should fetch user profile
    Error: expect(received).toBe(expected)
    Expected: 200
    Received: 404

  ✗ tests/e2e/profile.spec.ts:10:3  should display current profile information
    Error: page.goto: net::ERR_ABORTED

All tests should fail! This confirms:

  • Feature doesn't exist yet
  • Tests will guide implementation
  • You have clear success criteria

7. Implement the Feature

Now implement the feature following the test guidance:

  1. Start with API tests (backend first)
  2. Make API tests pass
  3. Move to E2E tests (frontend)
  4. Make E2E tests pass
  5. Refactor with confidence (tests protect you)

8. Verify Tests Pass

After implementation, run your test suite.

For Playwright:

npx playwright test

For Cypress:

npx cypress run

Expected output:

Running 6 tests using 1 worker

  ✓ tests/api/profile.spec.ts:3:3  should fetch user profile (850ms)
  ✓ tests/api/profile.spec.ts:15:3  should update user profile (1.2s)
  ✓ tests/api/profile.spec.ts:30:3  should validate email format (650ms)
  ✓ tests/e2e/profile.spec.ts:10:3  should display current profile (2.1s)
  ✓ tests/e2e/profile.spec.ts:18:3  should edit and save profile (3.2s)
  ✓ tests/e2e/profile.spec.ts:35:3  should show validation error (1.8s)

  6 passed (9.8s)

Green! You've completed the TDD cycle: red → green → refactor.

What You Get

Failing Tests

  • API tests for backend endpoints
  • E2E tests for user workflows
  • Component tests (if requested)
  • All tests fail initially (red phase)

Implementation Guidance

  • Clear checklist of what to build
  • Acceptance criteria translated to assertions
  • Edge cases and error scenarios identified

TDD Workflow Support

  • Tests guide implementation
  • Confidence to refactor
  • Living documentation of features

Tips

Start with Test Design

Run *test-design before *atdd for better results:

*test-design   # Risk assessment and priorities
*atdd          # Generate tests based on design

Use Recording Mode (Optional)

If MCP enhancements are enabled (tea_use_mcp_enhancements: true in config):

When prompted during *atdd, select "recording mode" to:

  • Verify selectors against actual UI with live browser
  • Capture network requests in real-time
  • Generate accurate locators from actual DOM

See Enable MCP Enhancements

Focus on P0/P1 Scenarios

Don't generate tests for everything at once:

Generate tests for:
- P0: Critical path (happy path)
- P1: High value (validation, errors)

Skip P2/P3 for now - add later with *automate

API Tests First, E2E Later

Recommended order:

  1. Generate API tests with *atdd
  2. Implement backend (make API tests pass)
  3. Generate E2E tests with *atdd (or *automate)
  4. Implement frontend (make E2E tests pass)

This "outside-in" approach is faster and more reliable.

Keep Tests Deterministic

TEA generates deterministic tests by default:

  • No hard waits (waitForTimeout)
  • Network-first patterns (wait for responses)
  • Explicit assertions (no conditionals)

Don't modify these patterns - they prevent flakiness!

Common Issues

Tests Don't Fail Initially

Problem: Tests pass on first run but feature doesn't exist.

Cause: Tests are hitting wrong endpoints or checking wrong things.

Solution: Review generated tests - ensure they match your feature requirements.

Too Many Tests Generated

Problem: TEA generated 50 tests for a simple feature.

Cause: Didn't specify priorities or scope.

Solution: Be specific:

Generate ONLY:
- P0 scenarios (2-3 tests)
- Happy path for API
- One E2E test for full flow

Selectors Are Fragile

Problem: E2E tests use brittle selectors (CSS, XPath).

Solution: Use MCP recording mode or specify accessible selectors:

Use accessible locators:
- getByRole()
- getByLabel()
- getByText()
Avoid CSS selectors

Understanding the Concepts

Reference


Generated with BMad Method - TEA (Test Architect)