# ๐Ÿงช 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.