* 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>
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:
- Log into Clerk Dashboard
- Navigate to Users section
- Create a new test user:
- Email:
test@brackt.local(or use env variable) - Password: Generate secure password
- Mark as verified
- Email:
- 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:
-
Create new PostgreSQL database:
createdb brackt_test -
Add test database URL to environment:
# .env.test DATABASE_URL=postgresql://user:password@localhost:5432/brackt_test -
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_EMAILCYPRESS_TEST_USER_PASSWORDCYPRESS_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
beforeEachhooks 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