* 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>
600 lines
14 KiB
Markdown
600 lines
14 KiB
Markdown
# 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
|