brackt/plans/completed/e2e-testing-setup.md

601 lines
14 KiB
Markdown
Raw Normal View History

# 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