docs: add E2E testing setup plan with Cypress authentication and test database configuration
This commit is contained in:
parent
0d4ce1d339
commit
8046f8b324
1 changed files with 600 additions and 0 deletions
600
plans/e2e-testing-setup.md
Normal file
600
plans/e2e-testing-setup.md
Normal file
|
|
@ -0,0 +1,600 @@
|
|||
# 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
|
||||
Loading…
Add table
Reference in a new issue