# E2E Testing Guide This directory contains End-to-End (E2E) tests for the Dify web application using [Playwright](https://playwright.dev/). ## Quick Start ### 1. Setup ```bash # Install dependencies (if not already done) pnpm install # Install Playwright browsers pnpm exec playwright install chromium ``` ### 2. Configure Environment (Optional) Add E2E test configuration to your `web/.env.local` file: ```env # E2E Test Configuration # Base URL of the frontend (optional, defaults to http://localhost:3000) E2E_BASE_URL=https://test.example.com # Skip starting dev server (use existing deployed server) E2E_SKIP_WEB_SERVER=true # API URL (optional, defaults to http://localhost:5001/console/api) NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api # Authentication Configuration # Test user credentials NEXT_PUBLIC_E2E_USER_EMAIL=test@example.com NEXT_PUBLIC_E2E_USER_PASSWORD=your-password ``` ### Authentication Methods Dify supports multiple login methods, but not all are suitable for E2E testing: | Method | E2E Support | Configuration | |--------|-------------|---------------| | **Email + Password** | ✅ Recommended | Set `NEXT_PUBLIC_E2E_USER_EMAIL` and `NEXT_PUBLIC_E2E_USER_PASSWORD` | #### Email + Password (Default) The most reliable method for E2E testing. Simply set the credentials: ```env NEXT_PUBLIC_E2E_USER_EMAIL=test@example.com NEXT_PUBLIC_E2E_USER_PASSWORD=your-password ``` ### 3. Run Tests ```bash # Run all E2E tests pnpm test:e2e # Run tests with UI (interactive mode) pnpm test:e2e:ui # Run tests with browser visible pnpm test:e2e:headed # Run tests in debug mode pnpm test:e2e:debug # View test report pnpm test:e2e:report ``` ## Project Structure ``` web/ ├── .env.local # Environment config (includes E2E variables) ├── playwright.config.ts # Playwright configuration └── e2e/ ├── fixtures/ # Test fixtures (extended test objects) │ └── index.ts # Main fixtures with page objects ├── pages/ # Page Object Models (POM) │ ├── base.page.ts # Base class for all page objects │ ├── signin.page.ts # Sign-in page interactions │ ├── apps.page.ts # Apps listing page interactions │ ├── workflow.page.ts # Workflow editor interactions │ └── index.ts # Page objects export ├── tests/ # Test files (*.spec.ts) ├── utils/ # Test utilities │ ├── index.ts # Utils export │ ├── test-helpers.ts # Common helper functions │ └── api-helpers.ts # API-level test helpers ├── .auth/ # Authentication state (gitignored) ├── global.setup.ts # Authentication setup ├── global.teardown.ts # Cleanup after tests └── README.md # This file ``` ## Writing Tests ### Using Page Objects ```typescript import { test, expect } from '../fixtures' test('create a new app', async ({ appsPage }) => { await appsPage.goto() await appsPage.createApp({ name: 'My Test App', type: 'chatbot', }) await appsPage.expectAppExists('My Test App') }) ``` ### Using Test Helpers ```typescript import { test, expect } from '../fixtures' import { generateTestId, waitForNetworkIdle } from '../utils/test-helpers' test('search functionality', async ({ appsPage }) => { const uniqueName = generateTestId('app') // ... test logic }) ``` ### Test Data Cleanup Always clean up test data to avoid polluting the database: ```typescript test('create and delete app', async ({ appsPage }) => { const appName = generateTestId('test-app') // Create await appsPage.createApp({ name: appName, type: 'chatbot' }) // Test assertions await appsPage.expectAppExists(appName) // Cleanup await appsPage.deleteApp(appName) }) ``` ### Skipping Authentication For tests that need to verify unauthenticated behavior: ```typescript test.describe('unauthenticated tests', () => { test.use({ storageState: { cookies: [], origins: [] } }) test('redirects to login', async ({ page }) => { await page.goto('/apps') await expect(page).toHaveURL(/\/signin/) }) }) ``` ## Best Practices ### 1. Use Page Object Model (POM) - Encapsulate page interactions in page objects - Makes tests more readable and maintainable - Changes to selectors only need to be updated in one place ### 2. Use Meaningful Test Names ```typescript // Good test('should display error message for invalid email format', ...) // Bad test('test1', ...) ``` ### 3. Use Data-TestId Attributes When adding elements to the application, use `data-testid` attributes: ```tsx // In React component // In test await page.getByTestId('create-app-button').click() ``` ### 4. Generate Unique Test Data ```typescript import { generateTestId } from '../utils/test-helpers' const appName = generateTestId('my-app') // e.g., "my-app-1732567890123-abc123" ``` ### 5. Handle Async Operations ```typescript // Wait for element await expect(element).toBeVisible({ timeout: 10000 }) // Wait for navigation await page.waitForURL(/\/apps/) // Wait for network await page.waitForLoadState('networkidle') ``` ## Creating New Page Objects 1. Create a new file in `e2e/pages/`: ```typescript // e2e/pages/my-feature.page.ts import type { Page, Locator } from '@playwright/test' import { BasePage } from './base.page' export class MyFeaturePage extends BasePage { readonly myElement: Locator constructor(page: Page) { super(page) this.myElement = page.getByTestId('my-element') } get path(): string { return '/my-feature' } async doSomething(): Promise { await this.myElement.click() } } ``` 2. Export from `e2e/pages/index.ts`: ```typescript export { MyFeaturePage } from './my-feature.page' ``` 3. Add to fixtures in `e2e/fixtures/index.ts`: ```typescript import { MyFeaturePage } from '../pages/my-feature.page' type DifyFixtures = { // ... existing fixtures myFeaturePage: MyFeaturePage } export const test = base.extend({ // ... existing fixtures myFeaturePage: async ({ page }, use) => { await use(new MyFeaturePage(page)) }, }) ``` ## Debugging ### Visual Debugging ```bash # Open Playwright UI pnpm test:e2e:ui # Run with visible browser pnpm test:e2e:headed # Debug mode with inspector pnpm test:e2e:debug ``` ### Traces and Screenshots Failed tests automatically capture: - Screenshots - Video recordings - Trace files View them: ```bash pnpm test:e2e:report ``` ### Manual Trace Viewing ```bash pnpm exec playwright show-trace e2e/test-results/path-to-trace.zip ``` ## Troubleshooting ### Tests timeout waiting for elements 1. Check if selectors are correct 2. Increase timeout: `{ timeout: 30000 }` 3. Add explicit waits: `await page.waitForSelector(...)` ### Authentication issues 1. Make sure global.setup.ts has completed successfully 2. For deployed environments, ensure E2E_BASE_URL matches your cookie domain 3. Clear auth state: `rm -rf e2e/.auth/` ### Flaky tests 1. Add explicit waits for async operations 2. Use `test.slow()` for inherently slow tests 3. Add retry logic for unstable operations ## Resources - [Playwright Documentation](https://playwright.dev/docs/intro) - [Page Object Model Pattern](https://playwright.dev/docs/pom) - [Best Practices](https://playwright.dev/docs/best-practices) - [Debugging Guide](https://playwright.dev/docs/debug)