brackt/plans/completed/e2e-testing-setup.md
Chris Parsons 35fe84a1dd
Update claude.md to push more tests. (#84)
* Update claude.md to push more tests.

* Archiving old plans.

* fix: run DB migrations programmatically on server startup

Replace unreliable drizzle-kit CLI migration with drizzle-orm's built-in
migrator running in the server process before accepting connections. This
ensures migrations are always applied on deploy and fails fast if they error.

- Add runMigrations() to server.ts using drizzle-orm/postgres-js/migrator
- Skip migrations in development (handled manually via db:migrate)
- Add DATABASE_URL guard with a clear error message
- Remove start:production script (now identical to start)
- Update Dockerfile CMD to use npm run start

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 22:31:04 -08:00

14 KiB

E2E Testing Setup Plan

Created: October 21, 2025
Status: Planned
Goal: Enable end-to-end testing with Cypress for authenticated user flows


Overview

Currently, 23 of 26 Cypress tests are skipped because they require authentication. This plan outlines the steps to enable full E2E testing with proper test user authentication and database setup.


Phase 1: Test User Authentication Setup

1.1 Create Test User in Clerk

Goal: Set up a dedicated test user account for Cypress tests

Steps:

  1. Log into Clerk Dashboard
  2. Navigate to Users section
  3. Create a new test user:
    • Email: test@brackt.local (or use env variable)
    • Password: Generate secure password
    • Mark as verified
  4. Save user credentials securely

Environment Variables:

# Add to .env.test or Cypress env
CYPRESS_TEST_USER_EMAIL=test@brackt.local
CYPRESS_TEST_USER_PASSWORD=<secure-password>
CYPRESS_TEST_USER_ID=<clerk-user-id>

1.2 Implement Cypress Login Command

File: cypress/support/commands.ts

Implementation:

Cypress.Commands.add('login', (email?: string, password?: string) => {
  const testEmail = email || Cypress.env('TEST_USER_EMAIL');
  const testPassword = password || Cypress.env('TEST_USER_PASSWORD');

  cy.session([testEmail, testPassword], () => {
    cy.visit('/sign-in');
    
    // Wait for Clerk to load
    cy.get('[name="identifier"]', { timeout: 10000 }).should('be.visible');
    
    // Enter email
    cy.get('[name="identifier"]').type(testEmail);
    cy.get('button[type="submit"]').click();
    
    // Enter password
    cy.get('[name="password"]', { timeout: 10000 }).should('be.visible');
    cy.get('[name="password"]').type(testPassword);
    cy.get('button[type="submit"]').click();
    
    // Wait for redirect after successful login
    cy.url().should('not.include', '/sign-in');
    cy.url().should('not.include', '/sign-up');
  }, {
    validate() {
      // Verify session is still valid
      cy.getCookie('__session').should('exist');
    },
  });
});

// Add TypeScript declaration
declare global {
  namespace Cypress {
    interface Chainable {
      login(email?: string, password?: string): Chainable<void>;
    }
  }
}

1.3 Update Cypress Configuration

File: cypress.config.js

Add:

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    setupNodeEvents(_on, _config) {
      // implement node event listeners here
    },
    supportFile: 'cypress/support/e2e.ts',
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
    video: true,
    screenshotOnRunFailure: true,
    experimentalSessionAndOrigin: true, // Enable session support
  },
  env: {
    TEST_USER_EMAIL: process.env.CYPRESS_TEST_USER_EMAIL,
    TEST_USER_PASSWORD: process.env.CYPRESS_TEST_USER_PASSWORD,
  },
});

Phase 2: Test Database Setup

2.1 Create Test Database

Goal: Separate test database to avoid polluting production/dev data

Steps:

  1. Create new PostgreSQL database:

    createdb brackt_test
    
  2. Add test database URL to environment:

    # .env.test
    DATABASE_URL=postgresql://user:password@localhost:5432/brackt_test
    
  3. Run migrations on test database:

    DATABASE_URL=postgresql://user:password@localhost:5432/brackt_test npm run db:migrate
    

2.2 Create Database Seed Script

File: cypress/support/database.ts

Purpose: Seed test data before E2E tests

Implementation:

import { database } from '~/database/context';
import * as schema from '~/database/schema';

export async function seedTestDatabase() {
  const db = database();
  
  // Clear existing data
  await db.delete(schema.picks);
  await db.delete(schema.draftSlots);
  await db.delete(schema.teams);
  await db.delete(schema.seasonSports);
  await db.delete(schema.seasons);
  await db.delete(schema.commissioners);
  await db.delete(schema.leagues);
  
  // Create test league
  const [league] = await db.insert(schema.leagues).values({
    id: 'test-league-1',
    name: 'Test League',
    createdBy: Cypress.env('TEST_USER_ID'),
    isPublicDraftBoard: false,
  }).returning();
  
  // Create test season
  const [season] = await db.insert(schema.seasons).values({
    id: 'test-season-1',
    leagueId: league.id,
    year: new Date().getFullYear(),
    status: 'pre_draft',
    draftRounds: 20,
    draftInitialTime: 120,
    draftIncrementTime: 15,
    currentPickNumber: 1,
  }).returning();
  
  // Create commissioner
  await db.insert(schema.commissioners).values({
    leagueId: league.id,
    userId: Cypress.env('TEST_USER_ID'),
  });
  
  // Create teams
  const teams = Array.from({ length: 12 }, (_, i) => ({
    id: `test-team-${i + 1}`,
    seasonId: season.id,
    name: `Team ${i + 1}`,
    ownerId: i === 0 ? Cypress.env('TEST_USER_ID') : null,
  }));
  
  await db.insert(schema.teams).values(teams);
  
  return { league, season, teams };
}

export async function cleanTestDatabase() {
  const db = database();
  
  await db.delete(schema.picks);
  await db.delete(schema.draftSlots);
  await db.delete(schema.teams);
  await db.delete(schema.seasonSports);
  await db.delete(schema.seasons);
  await db.delete(schema.commissioners);
  await db.delete(schema.leagues);
}

2.3 Add Cypress Tasks for Database Operations

File: cypress.config.js

Update:

import { seedTestDatabase, cleanTestDatabase } from './cypress/support/database';

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      on('task', {
        async 'db:seed'() {
          await seedTestDatabase();
          return null;
        },
        async 'db:clean'() {
          await cleanTestDatabase();
          return null;
        },
      });
      
      return config;
    },
    // ... rest of config
  },
});

2.4 Update Cypress Commands

File: cypress/support/commands.ts

Add:

Cypress.Commands.add('seedDatabase', () => {
  cy.task('db:seed');
});

Cypress.Commands.add('cleanDatabase', () => {
  cy.task('db:clean');
});

declare global {
  namespace Cypress {
    interface Chainable {
      seedDatabase(): Chainable<void>;
      cleanDatabase(): Chainable<void>;
    }
  }
}

Phase 3: Update Test Files

3.1 Enable League Creation Tests

File: cypress/e2e/league-creation.cy.ts

Changes:

describe('League Creation Flow', () => {
  beforeEach(() => {
    cy.cleanDatabase();
    cy.login();
  });

  it('should display the league creation form', () => {
    cy.visit('/leagues/new');
    
    cy.get('input[name="name"]').should('exist');
    cy.get('#teamCount').should('exist');
    cy.get('#draftRounds').should('exist');
    cy.get('select[name="draftSpeed"]').should('exist');
  });

  it('should create a new league', () => {
    cy.visit('/leagues/new');

    cy.get('input[name="name"]').type('My Test League');
    
    cy.get('#teamCount').click();
    cy.contains('[role="option"]', '12 Teams').click();
    
    cy.get('#draftRounds').click();
    cy.contains('[role="option"]', '20').click();

    cy.get('select[name="draftSpeed"]').select('standard');

    cy.get('button[type="submit"]').click();

    cy.url().should('include', '/leagues/');
    cy.contains('My Test League').should('exist');
  });
});

3.2 Enable League Settings Tests

File: cypress/e2e/league-settings.cy.ts

Changes:

describe('League Settings', () => {
  beforeEach(() => {
    cy.cleanDatabase();
    cy.seedDatabase();
    cy.login();
  });

  it('should display league settings page', () => {
    cy.visit('/leagues/test-league-1/settings');

    cy.get('input[name="name"]').should('have.value', 'Test League');
  });

  it('should update league name', () => {
    cy.visit('/leagues/test-league-1/settings');

    cy.get('input[name="name"]').clear().type('Updated League Name');
    cy.contains('button', /save/i).click();

    cy.contains('Updated League Name').should('exist');
  });
});

3.3 Enable Draft Room Tests

File: cypress/e2e/draft-room.cy.ts

Changes:

describe('Draft Room', () => {
  beforeEach(() => {
    cy.cleanDatabase();
    cy.seedDatabase();
    cy.login();
  });

  it('should display draft grid', () => {
    cy.visit('/leagues/test-league-1/draft/test-season-1');

    cy.contains('Team 1').should('exist');
    cy.contains('Team 2').should('exist');
  });

  it('should start draft as commissioner', () => {
    cy.visit('/leagues/test-league-1/draft/test-season-1');

    cy.contains('button', /start draft/i).click();

    // Verify timers appear
    cy.get('[class*="font-mono"]').should('exist');
  });
});

Phase 4: CI/CD Integration

4.1 Update GitHub Actions Workflow

File: .github/workflows/test.yml

Add E2E testing job:

jobs:
  test:
    # ... existing unit test job

  e2e:
    name: 🎭 E2E Tests
    runs-on: ubuntu-latest
    needs: test
    
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: brackt_test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run database migrations
        run: npm run db:migrate
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/brackt_test

      - name: Start dev server
        run: npm run dev &
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/brackt_test
          PORT: 3000

      - name: Wait for server
        run: npx wait-on http://localhost:3000 --timeout 60000

      - name: Run E2E tests
        run: npm run test:e2e:headless
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/brackt_test
          CYPRESS_TEST_USER_EMAIL: ${{ secrets.CYPRESS_TEST_USER_EMAIL }}
          CYPRESS_TEST_USER_PASSWORD: ${{ secrets.CYPRESS_TEST_USER_PASSWORD }}
          CYPRESS_TEST_USER_ID: ${{ secrets.CYPRESS_TEST_USER_ID }}

      - name: Upload Cypress videos
        uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: cypress-videos
          path: cypress/videos

      - name: Upload Cypress screenshots
        uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: cypress-screenshots
          path: cypress/screenshots

4.2 Add GitHub Secrets

Required secrets in GitHub repository settings:

  • CYPRESS_TEST_USER_EMAIL
  • CYPRESS_TEST_USER_PASSWORD
  • CYPRESS_TEST_USER_ID

Phase 5: Documentation & Scripts

5.1 Update TESTING.md

Add section:

## Running E2E Tests Locally

### Prerequisites
1. Test database created and migrated
2. Test user created in Clerk
3. Environment variables set

### Setup
```bash
# Create test database
createdb brackt_test

# Run migrations
DATABASE_URL=postgresql://user:password@localhost:5432/brackt_test npm run db:migrate

# Set environment variables
export CYPRESS_TEST_USER_EMAIL=test@brackt.local
export CYPRESS_TEST_USER_PASSWORD=your-password
export CYPRESS_TEST_USER_ID=clerk-user-id

Running Tests

# Start dev server in one terminal
npm run dev

# Run E2E tests in another terminal
npm run test:e2e

# Or run headless
npm run test:e2e:headless

Troubleshooting

  • Login fails: Check Clerk credentials and user exists
  • Database errors: Ensure test database is migrated
  • Timeout errors: Increase timeout in cypress.config.js

### 5.2 Add Helper Scripts

**File:** `package.json`

**Add scripts:**
```json
{
  "scripts": {
    "test:e2e:setup": "createdb brackt_test && DATABASE_URL=postgresql://user:password@localhost:5432/brackt_test npm run db:migrate",
    "test:e2e:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:3000 && npm run test:e2e\"",
    "test:e2e:ci": "npm run test:e2e:headless"
  }
}

Implementation Checklist

Phase 1: Authentication

  • Create test user in Clerk Dashboard
  • Save credentials to environment variables
  • Implement cy.login() command
  • Update Cypress config with env variables
  • Test login flow manually

Phase 2: Database

  • Create test database
  • Run migrations on test database
  • Create seed script
  • Add Cypress tasks for database operations
  • Test seeding and cleaning

Phase 3: Tests

  • Remove .skip() from league-creation tests
  • Remove .skip() from league-settings tests
  • Remove .skip() from draft-room tests
  • Add beforeEach hooks with login and seed
  • Run tests locally to verify

Phase 4: CI/CD

  • Add E2E job to GitHub Actions
  • Add GitHub secrets for test user
  • Test CI pipeline with a PR
  • Verify artifacts upload on failure

Phase 5: Documentation

  • Update TESTING.md with E2E instructions
  • Add troubleshooting section
  • Document environment setup
  • Add helper scripts to package.json

Success Criteria

All 26 Cypress tests passing (3 smoke + 23 E2E)
Tests run successfully in CI/CD
Test database properly seeded and cleaned
Login flow working reliably
Screenshots/videos captured on failure
Documentation complete and accurate


Estimated Timeline

  • Phase 1: 2-3 hours (authentication setup)
  • Phase 2: 3-4 hours (database setup and seeding)
  • Phase 3: 2-3 hours (updating test files)
  • Phase 4: 1-2 hours (CI/CD integration)
  • Phase 5: 1 hour (documentation)

Total: 9-13 hours


Notes

  • Start with Phase 1 and test thoroughly before moving on
  • Keep test user credentials secure (use GitHub secrets, not .env files in repo)
  • Consider using a separate Clerk environment for testing
  • Test database should be reset between test runs
  • Monitor CI/CD costs if using paid runners

Future Enhancements

  • Visual regression testing with Percy or Chromatic
  • Parallel test execution to speed up CI
  • Test data factories for more flexible seeding
  • API mocking for external services
  • Performance testing with Lighthouse CI