# 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:**
```bash
# 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:**
```typescript
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:**
```javascript
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:
   ```bash
   createdb brackt_test
   ```

2. Add test database URL to environment:
   ```bash
   # .env.test
   DATABASE_URL=postgresql://user:password@localhost:5432/brackt_test
   ```

3. Run migrations on test database:
   ```bash
   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:**
```typescript
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:**
```javascript
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:**
```typescript
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:**
```typescript
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:**
```typescript
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:**
```typescript
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:**
```yaml
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:**
```markdown
## 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
```bash
# 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
