This project uses a test-only authentication endpoint to enable Playwright E2E tests without requiring Google OAuth. This approach provides fast, reliable automated testing while maintaining production security.
The test auth system uses three layers of protection to ensure it's NEVER available in production:
- Layer 1 (Import-time):
auth_test.pychecksENVat import time and raisesRuntimeErrorifENV=production - Layer 2 (Registration):
main.pyonly includes the router whensettings.env != "production" - Layer 3 (Runtime): Each endpoint validates environment and returns 404 if accessed in production
┌─────────────────────────────────────────────────────┐
│ Playwright Test │
│ ┌───────────────────────────────────────────────┐ │
│ │ import { test } from './fixtures/auth' │ │
│ │ │ │
│ │ test('my test', async ({ authenticatedPage }) │ │
│ │ // Page is already authenticated │ │
│ │ }); │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────┐
│ Auth Fixture (tests/fixtures/auth.ts) │
│ ┌───────────────────────────────────────────────┐ │
│ │ POST /api/v1/auth/test-login │ │
│ │ { "email": "allowed@example.com" } │ │
│ │ │ │
│ │ → Stores token in localStorage │ │
│ │ → Returns authenticated page │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────┐
│ Backend (backend/app/routers/auth_test.py) │
│ ┌───────────────────────────────────────────────┐ │
│ │ 1. Validate ENV != production │ │
│ │ 2. Validate email == ALLOWED_EMAIL │ │
│ │ 3. Generate real JWT using production logic │ │
│ │ 4. Return token │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
The simplest way to write authenticated tests:
// tests/my-test.spec.ts
import { test, expect } from './fixtures/auth';
test('should access protected route', async ({ authenticatedPage }) => {
// Page is already authenticated - just navigate!
await authenticatedPage.goto('/agents');
await expect(authenticatedPage).toHaveURL('/agents');
});For more control over authentication:
import { test, expect } from '@playwright/test';
import { authenticatePage } from './fixtures/auth';
test('manual auth test', async ({ page }) => {
// Manually authenticate
await authenticatePage(page, 'test@example.com');
// Now page is authenticated
await page.goto('/agents');
});For API testing:
import { test, expect } from './fixtures/auth';
test('API test with token', async ({ testAuthToken }) => {
const response = await fetch('http://localhost:8000/api/v1/agents', {
headers: {
'Authorization': `Bearer ${testAuthToken}`
}
});
expect(response.status).toBe(200);
});# .env file
ENV=development # MUST be 'development' or 'test', NOT 'production'
ALLOWED_EMAIL=test@example.com # Email for test authentication
JWT_SECRET_KEY=your-secret-key-min-32-charsNo special configuration needed! The test utilities automatically detect non-production builds.
# Run all tests
npm run test
# Run specific test file
npx playwright test tests/agent-update-integration.spec.ts
# Run with UI (for debugging)
npx playwright test --ui
# Run in headed mode
npx playwright test --headedRun security tests to verify production safety:
npx playwright test tests/auth-test-endpoint-security.spec.tsThese tests verify:
- ✅ Endpoint works in development/test
- ✅ Email validation enforces ALLOWED_EMAIL
- ✅ Tokens are valid and match production format
- ✅ Environment checks are enforced
- ✅ Rate limiting (10 requests/minute per IP)
- ✅ Token expiration is exactly 24 hours (with clock skew tolerance)
Cause: Backend ENV is set to 'production' or test router not loaded.
Fix:
# Check backend .env file
ENV=development # NOT productionCause: Test email doesn't match ALLOWED_EMAIL.
Fix:
# Ensure ALLOWED_EMAIL matches your test email
ALLOWED_EMAIL=test@example.comCause: System clock skew or test took > 24 hours.
Fix: Tokens expire after 24 hours. Restart the test or check system time.
Cause: Token not being set in localStorage or token invalid.
Fix:
// Add debugging
test('debug auth', async ({ authenticatedPage, testAuthToken }) => {
console.log('Token:', testAuthToken);
const stored = await authenticatedPage.evaluate(() =>
localStorage.getItem('personal_q_token')
);
console.log('Stored token:', stored);
});- Import-time check: The module raises
RuntimeErrorwhen imported - Registration check: Router is never included in the FastAPI app
- Runtime check: Even if somehow accessed, returns 404
- Automated tests:
auth-test-endpoint-security.spec.tsvalidates all safety measures - Code review: Triple-layer security is documented and reviewed
- Environment validation: Pydantic validates settings at startup
- Audit logs: All test auth attempts are logged for security monitoring
- Use
authenticatedPagefixture for most tests - Set
ENV=developmentorENV=testin test environments - Keep test email in
ALLOWED_EMAILenvironment variable - Run security validation tests before deployment
- Review backend logs for test auth usage
- Don't set
ENV=productionin test environments - Don't hardcode test credentials in test files
- Don't use test auth in production
- Don't share JWT_SECRET_KEY between test and production
- Don't skip security validation tests
Backend:
backend/app/routers/auth_test.py- Test auth endpoint with security checksbackend/app/main.py- Conditionally includes test router
Frontend:
src/utils/testAuth.ts- Test auth utilities
Tests:
tests/fixtures/auth.ts- Reusable auth fixturestests/auth-test-endpoint-security.spec.ts- Security validation teststests/agent-update-integration.spec.ts- Updated to use real auth
Test tokens use the same format as production OAuth tokens:
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "test@example.com",
"email": "test@example.com",
"iat": 1234567890,
"exp": 1234654290 // +24 hours
}This ensures tests validate against the actual production token format.
| Approach | Speed | CI/CD Ready | Security | Complexity |
|---|---|---|---|---|
| Test Endpoint ✅ | ⚡ <500ms | ✅ Yes | 🔒 High | 📝 Low |
| Google OAuth | 🐌 5-10s | ❌ No | 🔒 High | 📝 High |
| Mocked OAuth | ⚡ Fast | ✅ Yes | 📝 Medium | |
| Debug Bypass | ⚡ Fast | ✅ Yes | 📝 Very Low |