# 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= CYPRESS_TEST_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; } } } ``` ### 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; cleanDatabase(): Chainable; } } } ``` --- ## 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