better-chatbot / docs /tips-guides /e2e-testing-guide.md
Bot
Initial commit for HF Spaces
05c5ed5
|
Raw
History Blame Contribute Delete
9.69 kB
# 🧪 End-to-End Testing Guide
Comprehensive guide for running and developing end-to-end tests for better-chatbot using Playwright.
## Quick Start
```bash
# Install dependencies (if not already done)
pnpm install
# Install Playwright browsers
pnpm playwright:install
# Run all e2e tests
pnpm test:e2e
# Run tests with UI (interactive mode)
pnpm test:e2e:ui
# Run specific test file
pnpm test:e2e -- tests/agents/agent-creation.spec.ts
# Run tests in debug mode
pnpm test:e2e:debug
```
## 🏗️ Test Architecture
Our e2e tests are designed for **reliability, speed, and maintainability**:
### Test Structure (Will be expanded over time, this is just an example)
```
tests/
├── lifecycle/ # Setup and teardown for tests
│ ├── auth.setup.ts # User registration & authentication
│ └── teardown.global.ts # Test data cleanup
├── core/ # Core tests for landing page, auth flows, etc.
│ └── unauthenticated.spec.ts # Landing page & auth flows
├── agents/ # Agent tests
│ ├── agent-creation.spec.ts # Agent CRUD operations
│ ├── agent-visibility.spec.ts # Multi-user sharing & permissions
│ └── agents.spec.ts # Basic agent functionality
└── models/ # Model selection & persistence
└── model-selection.spec.ts # Model selection & persistence
```
### Key Features
-**Automated user registration** with unique test accounts
-**Multi-user testing** for sharing & permissions
-**Automatic cleanup** of test data
-**Parallel execution** for speed
-**Robust selectors** using data-testid attributes
## 🔧 Configuration
### Environment Setup
Tests require these environment variables:
```bash
# Database (required)
POSTGRES_URL=postgres://user:password@localhost:5432/database
# Authentication (required)
BETTER_AUTH_SECRET=your-secret-here
# At least one LLM provider (required)
OPENAI_API_KEY=your-openai-key
# OR
ANTHROPIC_API_KEY=your-anthropic-key
# OR
GOOGLE_GENERATIVE_AI_API_KEY=your-google-key
# Optional: Set default model for tests - will need to be corelated with API keys
E2E_DEFAULT_MODEL=openai/gpt-4o-mini
```
### VSCode Extension
We recommend using the [Playwright](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright) extension for VSCode. It provides a lot of helpful features for writing and debugging tests.
### Test Database
```bash
pnpm docker:pg
```
## 🎯 Authentication Strategy
### Authentication Setup
Tests authenticate 4 users 1 admin, 1 editor, 1 editor2, and 1 regular by default on setup. - This is to test multi-user functionality like agent or workspace sharing. These users are defined in `tests/constants/test-users.ts`.
To test as an authenticated user (nearly all tests), you can use the `test.use({ storageState: TEST_USERS.editor.authFile });` or `test.use({ storageState: TEST_USERS.editor2.authFile });` or `test.use({ storageState: TEST_USERS.regular.authFile });` or `test.use({ storageState: TEST_USERS.admin.authFile });` in the test file. Without this, the test will run as an unauthenticated user. This can go in the describe block or the test block.
### Multi-User Testing
Playwright is designed to run tests in parallel. This means that each test will run in its own browser instance. For tests that need to test multi-user functionality, you can set the tests to run sequentially by using the `test.describe.configure({ mode: 'serial' });` decorator. See `tests/agents/agent-visibility.spec.ts` for an example.
**Example:**
#### User 1 Only
```typescript
// Most tests use single user authentication
import { TEST_USERS } from '../constants/test-users';
test.describe('Agent Creation', () => {
test.use({ storageState: TEST_USERS.editor.authFile });
test('should create agent', async ({ page }) => {
// Test logic here
});
});
```
#### User 2 Only
```typescript
import { TEST_USERS } from '../constants/test-users';
test.describe('Agent Creation', () => {
test.use({ storageState: TEST_USERS.editor2.authFile });
test('should create agent', async ({ page }) => {
// Test logic here
});
});
```
#### User 1 and User 2
This is the most common use case for multi-user testing.
```typescript
import { TEST_USERS } from '../constants/test-users';
test.describe('Agent Sharing', () => {
test('user sharing workflow', async ({ browser }) => {
// User1 creates agent
const user1Context = await browser.newContext({
storageState: TEST_USERS.editor.authFile,
});
const user1Page = await user1Context.newPage();
// User2 interacts with shared agent
const user2Context = await browser.newContext({
storageState: TEST_USERS.editor2.authFile,
});
const user2Page = await user2Context.newPage();
});
});
```
### Benefits
- **No duplicate test runs** - Regular tests run once with user1
- **Efficient multi-user testing** - Only when needed for sharing features
- **Clean isolation** - Each test gets fresh authentication state
## 🔍 Best Practices
### Reliable Selectors
Always use `data-testid` attributes for stable selectors:
```typescript
// ✅ Good - stable and semantic
await page.getByTestId('agent-name-input').fill('My Agent');
await page.getByTestId('agent-save-button').click();
// ❌ Avoid - fragile and language-dependent
await page.locator('input[placeholder="Enter agent name"]').fill('My Agent');
await page.getByText('Save').click();
```
### Waiting Strategies
Use appropriate waiting strategies for reliability:
```typescript
// Wait for network activity to settle
await page.waitForLoadState('networkidle');
// Wait for specific API responses
const responsePromise = page.waitForResponse(
(response) => response.url().includes('/api/agent/') && response.request().method() === 'PUT'
);
await page.getByTestId('save-button').click();
await responsePromise;
// Wait for navigation
await page.waitForURL('**/agents', { timeout: 10000 });
```
### Unique Test Data
Generate unique data to avoid conflicts:
```typescript
const testSuffix = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
const agentName = `Test Agent ${testSuffix}`;
```
## 🐛 Debugging
### Debug Commands
```bash
# Run specific test with browser visible
pnpm test:e2e -- tests/agents/agent-creation.spec.ts --headed
# Debug mode with breakpoints
pnpm test:e2e:debug
# Run single test
npx playwright test -g "should create agent"
# Generate test report
npx playwright show-report
```
### Debug Helpers
Add debug information to tests:
```typescript
// Take screenshots for debugging
await page.screenshot({ path: 'debug-agent-creation.png', fullPage: true });
// Log page content
console.log('Current URL:', page.url());
const agents = await page.locator('[data-testid="agent-card-name"]').all();
console.log(`Found ${agents.length} agents`);
```
### Common Issues
**Tests timing out:**
- Ensure `E2E_DEFAULT_MODEL` is set to a fast model
- Check database connection and API keys
- Increase timeout for slow operations
**Authentication failures:**
- Verify `BETTER_AUTH_SECRET` is set
- Check PostgreSQL connection
- Ensure auth setup completes successfully
**Element not found:**
- Verify data-testid exists in component
- Check for loading states
- Use proper waiting strategies
## 🚀 CI/CD Integration
Tests run automatically on GitHub Actions with:
- **PostgreSQL 17** test database
- **Parallel execution** across multiple workers
- **Automatic artifact upload** for debugging
- **Clean test environment** isolated from production
## 📝 Writing New Tests
### Test Template
```typescript
import { test, expect } from '@playwright/test';
import { TEST_USERS } from '../constants/test-users';
test.describe('Your Feature', () => {
test.use({ storageState: TEST_USERS.editor.authFile });
test('should perform action', async ({ page }) => {
// Navigate to page
await page.goto('/your-feature');
// Perform actions
await page.getByTestId('input-field').fill('test value');
await page.getByTestId('submit-button').click();
// Wait for response
await page.waitForURL('**/success', { timeout: 10000 });
// Verify results
await expect(page.getByTestId('success-message')).toBeVisible();
});
});
```
### Multi-User Test Template
```typescript
import { TEST_USERS } from '../constants/test-users';
test('multi-user workflow', async ({ browser }) => {
const testId = Date.now().toString(36);
// User1 setup
const user1Context = await browser.newContext({
storageState: TEST_USERS.editor.authFile,
});
const user1Page = await user1Context.newPage();
try {
// User1 actions
await user1Page.goto('/create');
// ... user1 workflow
} finally {
await user1Context.close();
}
// User2 verification
const user2Context = await browser.newContext({
storageState: 'tests/.auth/user2.json',
});
const user2Page = await user2Context.newPage();
try {
// User2 actions
await user2Page.goto('/shared');
// ... user2 workflow
} finally {
await user2Context.close();
}
});
```
## 🧹 Data Cleanup
Tests automatically clean up after themselves:
1. **User identification** by email patterns (`playwright.*@example.com`)
2. **Cascade deletion** respecting foreign key constraints
3. **Complete cleanup** of test users and related data
No manual cleanup required - the system handles it automatically!
---
For more examples, see the existing test files in the `tests/` directory. Each test demonstrates different patterns and best practices for reliable e2e testing.