Spaces:
Build error
Build error
| # Testing Guide | |
| This document provides comprehensive information about the testing infrastructure for the PDF-TEI-Editor project. | |
| ## Table of Contents | |
| 1. [Testing Architecture](#testing-architecture) | |
| 2. [Quick Reference](#quick-reference) | |
| 3. [Unit Tests](#unit-tests) | |
| 4. [API Integration Tests](#api-integration-tests) | |
| 5. [End-to-End Tests](#end-to-end-tests) | |
| 6. [Smart Test Runner](#smart-test-runner) | |
| 7. [Writing New Tests](#writing-new-tests) | |
| 8. [Debugging Tests](#debugging-tests) | |
| ## Testing Architecture | |
| The project uses a multi-tiered testing approach: | |
| - **Unit Tests**: JavaScript (Node.js test runner) and Python (pytest) for isolated component testing | |
| - **API Integration Tests**: Node.js-based tests against local or containerized FastAPI backend | |
| - **End-to-End Tests**: Playwright browser tests against containerized application | |
| - **Smart Test Selection**: Dependency-aware test execution based on `@testCovers` annotations | |
| ### Test Directory Structure | |
| ``` | |
| tests/ | |
| βββ unit/ # Unit tests | |
| β βββ js/ # JavaScript unit tests (*.test.js) | |
| β βββ fastapi/ # FastAPI unit tests (test_*.py) | |
| β βββ flask/ # Legacy Flask unit tests (test_*.py) | |
| βββ api/ # API integration tests | |
| β βββ v1/ # API v1 tests (*.test.js) | |
| β βββ helpers/ # Shared test utilities | |
| β βββ fixtures/ # Test data fixtures | |
| βββ e2e/ # End-to-end tests | |
| β βββ tests/ # Playwright test specs (*.spec.js) | |
| β βββ tests/helpers/ # E2E test helpers | |
| β βββ fixtures/ # E2E test fixtures | |
| βββ lib/ # Test infrastructure | |
| β βββ local-server-manager.js # Local server management | |
| β βββ container-server-manager.js # Container management | |
| β βββ ... # Other test utilities | |
| βββ backend-test-runner.js # API test runner (local/container) | |
| βββ e2e-runner.js # E2E test runner (local/container) | |
| βββ smart-test-runner.js # Intelligent test selection | |
| βββ unit-test-runner.js # JavaScript unit test runner | |
| βββ unit-test-runner.py # Python unit test runner | |
| ``` | |
| ## Quick Reference | |
| ### Common Test Commands | |
| ```bash | |
| # Run all tests | |
| npm test | |
| # Run only changed tests (smart selection) | |
| npm run test:changed | |
| # Unit Tests | |
| npm run test:unit # All unit tests (JS + Python) | |
| npm run test:unit:js # JavaScript unit tests only | |
| npm run test:unit:fastapi # FastAPI Python unit tests | |
| # API Integration Tests (FastAPI backend) | |
| npm run test:api # Local server (fastest) | |
| # End-to-End Tests (Playwright) | |
| npm run test:e2e # Local server (fastest) | |
| npm run test:e2e:headed # Show browser UI | |
| npm run test:e2e:debug # Step-through debugging | |
| npm run test:e2e:debug-failure # Capture debug artifacts on failure | |
| # Cross-browser E2E tests (real browser engines, no login required) | |
| npm run test:e2e:xmleditor-browsers # xmlTagSync tests in chromium, firefox, webkit | |
| # Container Tests (runs all tests inside container, same as CI) | |
| npm run test:container # Run with cache | |
| npm run test:container -- --no-cache # Rebuild all layers | |
| npm run test:container -- path/to/file.js # Test specific files | |
| npm run test:container -- --browser firefox # Use specific browser | |
| npm run test:container -- --browser chromium,firefox,webkit # Test multiple browsers | |
| # Run specific tests | |
| npm run test:api -- --grep "save" | |
| npm run test:e2e -- --grep "authentication" | |
| ``` | |
| ### Test Runner Options | |
| Local test runners (`test:api`, `test:e2e`) support: | |
| ```bash | |
| # Filter tests by pattern | |
| --grep <pattern> # Run matching tests | |
| --grep-invert <pattern> # Exclude matching tests | |
| # Database management | |
| --clean-db # Wipe database (default for local) | |
| --keep-db # Preserve database between runs | |
| # Browser selection (E2E only) | |
| --browser <name> # Use specific browser (chromium, firefox, webkit) | |
| # Other options | |
| --verbose # Show detailed output | |
| --no-cleanup # Keep server running after tests | |
| ``` | |
| Container test runner (`test:container`) options: | |
| ```bash | |
| --no-cache # Rebuild all Docker layers (ignore cache) | |
| --browser <browsers> # Comma-separated list for E2E tests | |
| --all # Run all tests (skip smart selection) | |
| --grep <pattern> # Filter tests by pattern | |
| # Plus any other smart-test-runner.js options | |
| ``` | |
| ## Unit Tests | |
| Unit tests validate individual components in isolation without external dependencies. | |
| ### JavaScript Unit Tests | |
| **Location**: `tests/unit/js/` | |
| **Runner**: Node.js built-in test runner | |
| **Command**: `npm run test:unit:js` | |
| Example: | |
| ```javascript | |
| import { test } from 'node:test'; | |
| import assert from 'node:assert'; | |
| import { PluginManager } from '../../app/src/modules/plugin-manager.js'; | |
| test('plugin manager registration', () => { | |
| const manager = new PluginManager(); | |
| const plugin = { name: 'test-plugin', install: () => {} }; | |
| manager.register(plugin); | |
| assert.strictEqual(manager.plugins.length, 1); | |
| }); | |
| ``` | |
| ### Python Unit Tests | |
| **Location**: `tests/unit/fastapi/` (FastAPI) and `tests/unit/flask/` (legacy) | |
| **Runner**: pytest | |
| **Command**: `npm run test:unit:fastapi` | |
| Example: | |
| ```python | |
| import pytest | |
| from fastapi_app.lib.utils.auth import verify_password, hash_password | |
| def test_password_hashing(): | |
| password = "test123" | |
| hashed = hash_password(password) | |
| assert verify_password(password, hashed) | |
| assert not verify_password("wrong", hashed) | |
| ``` | |
| ## API Integration Tests | |
| API integration tests validate backend endpoints without a browser. They run against a local FastAPI server. | |
| **Location**: `tests/api/v1/` | |
| **Naming**: `*.test.js` | |
| **Runner**: `backend-test-runner.js` | |
| **Command**: `npm run test:api` | |
| ### Key Features | |
| - **Fast Iteration**: Local mode starts/stops server automatically | |
| - **Database Management**: Auto-wipes DB between runs (configurable) | |
| - **Fixture Support**: Load test data from `tests/api/fixtures/` | |
| - **Authentication Helpers**: Built-in session management | |
| - **Lock Management**: Automatic cleanup between tests | |
| ### Writing API Tests | |
| API tests use Node.js built-in test runner with helper utilities: | |
| ```javascript | |
| /** | |
| * @testCovers fastapi_app/routers/files_save.py | |
| */ | |
| import { test, describe } from 'node:test'; | |
| import assert from 'node:assert'; | |
| import { login, authenticatedApiCall } from '../helpers/test-auth.js'; | |
| import { logger } from '../helpers/test-logger.js'; | |
| const BASE_URL = process.env.E2E_BASE_URL || 'http://localhost:8000'; | |
| describe('Files Save API', () => { | |
| let session = null; | |
| test('Setup: login as reviewer', async () => { | |
| session = await login('reviewer', 'reviewer', BASE_URL); | |
| assert.ok(session?.sessionId); | |
| }); | |
| test('should create new gold standard file', async () => { | |
| const response = await authenticatedApiCall( | |
| session.sessionId, | |
| '/files/save', | |
| 'POST', | |
| { | |
| file_id: 'test-doc', | |
| xml_string: '<TEI>...</TEI>' | |
| }, | |
| BASE_URL | |
| ); | |
| assert.strictEqual(response.status, 'new_gold'); | |
| assert.ok(response.file_id); | |
| logger.success(`Created file: ${response.file_id}`); | |
| }); | |
| }); | |
| ``` | |
| ### Authentication Helpers | |
| ```javascript | |
| import { login, authenticatedApiCall, createTestSession } from '../helpers/test-auth.js'; | |
| // Login with specific user | |
| const session = await login('reviewer', 'reviewer', BASE_URL); | |
| // Make authenticated API call | |
| const result = await authenticatedApiCall( | |
| session.sessionId, | |
| '/files/save', | |
| 'POST', | |
| { file_id: 'test', xml_string: '<TEI/>' }, | |
| BASE_URL | |
| ); | |
| // Create session (uses default 'testuser') | |
| const defaultSession = await createTestSession(BASE_URL); | |
| ``` | |
| ### Lock Cleanup | |
| Always clean up locks in test teardown: | |
| ```javascript | |
| import { clearAllLocks } from '../helpers/test-cleanup.js'; | |
| test('Cleanup: release locks', async () => { | |
| await clearAllLocks(BASE_URL); | |
| }); | |
| ``` | |
| ### Running API Tests | |
| ```bash | |
| # Local server (fast iteration) | |
| npm run test:api | |
| # With database preservation | |
| npm run test:api -- --keep-db | |
| # Specific tests | |
| npm run test:api -- --grep "save" | |
| # In container (CI environment) | |
| npm run test:container -- --grep "save" | |
| ``` | |
| ## End-to-End Tests | |
| E2E tests use Playwright to test the full application stack in a browser. | |
| **Location**: `tests/e2e/tests/` | |
| **Naming**: `*.spec.js` | |
| **Runner**: `e2e-runner.js` | |
| **Command**: `npm run test:e2e` (local) or `npm run test:container` (containerized) | |
| ### Key Features | |
| - **Full Browser Testing**: Chromium, Firefox, WebKit support | |
| - **Containerized Environment**: Isolated test instances | |
| - **UI Navigation System**: Type-safe access via `window.ui` | |
| - **Test Logging**: Structured state verification via `testLog()` | |
| - **Headed Mode**: Visual debugging with `--headed` | |
| - **Step-through Debugging**: Playwright debugger with `--debug` | |
| - **Isolated Harness Tests**: Component-level cross-browser tests without login or application state (see below) | |
| ### Isolated Component Harness Tests | |
| Some specs test individual components in isolation using a standalone HTML harness page served by the dev server. The harness loads only the component's dependencies via the importmap β no login, no fixtures, no application state. | |
| Use when the component has browser-engine-specific behavior or when you need to reproduce an editor bug without full-application overhead. | |
| | Harness HTML | Spec | Tests | | |
| |---|---|---| | |
| | `tests/e2e/harness/xmleditor-harness.html` | `tests/e2e/tests/xmleditor-cross-browser.spec.js` | `xmlTagSync` CodeMirror extension | | |
| See the [Testing Guide](../code-assistant/testing-guide.md#isolated-component-harness-tests) for instructions on running and extending harness tests. | |
| ### Writing E2E Tests | |
| ```javascript | |
| /** | |
| * @testCovers app/src/plugins/authentication.js | |
| * @testCovers fastapi_app/routers/auth.py | |
| */ | |
| /** @import { namedElementsTree } from '../../app/src/ui.js' */ | |
| import { test, expect } from '../fixtures/debug-on-failure.js'; | |
| import { performLogin, performLogout } from './helpers/login-helper.js'; | |
| test.describe('Authentication Workflow', () => { | |
| test('should login successfully', async ({ page }) => { | |
| await page.goto('http://localhost:8000'); | |
| await performLogin(page, 'testuser', 'testpass'); | |
| // Verify login using UI navigation | |
| const username = await page.evaluate(() => { | |
| /** @type {namedElementsTree} */ | |
| const ui = /** @type {any} */(window).ui; | |
| return ui.toolbar.userMenu.textContent; | |
| }); | |
| expect(username).toContain('testuser'); | |
| }); | |
| }); | |
| ``` | |
| ### UI Navigation System | |
| Access UI elements using the typed navigation system: | |
| ```javascript | |
| await page.evaluate(() => { | |
| /** @type {namedElementsTree} */ | |
| const ui = /** @type {any} */(window).ui; | |
| // Form interactions | |
| ui.loginDialog.username.value = 'testuser'; | |
| ui.loginDialog.password.value = 'testpass'; | |
| ui.loginDialog.submit.click(); | |
| // Read state | |
| return ui.loginDialog.open; // boolean | |
| }); | |
| ``` | |
| ### Using the API Client in Browser Context | |
| **CRITICAL:** Code running in browser context (inside `page.evaluate()`) should **never** use manual `fetch()` calls to the API backend. Instead, always use the `client` object exposed as a global: | |
| ```javascript | |
| /** | |
| * @import { api as Client } from '../../../app/src/plugins/client.js' | |
| */ | |
| // Inside page.evaluate(): | |
| const result = await page.evaluate(async () => { | |
| /** @type {Client} */ | |
| const client = /** @type {any} */(window).client; | |
| // Use the typed API client | |
| return await client.apiClient.sseTestProgress({ | |
| steps: 3, | |
| delay_ms: 500, | |
| label_prefix: 'E2E Test step' | |
| }); | |
| }); | |
| ``` | |
| **Why:** | |
| - The `client.apiClient` provides typed methods for all API endpoints | |
| - Handles authentication headers automatically | |
| - See `app/src/modules/api-client-v1.js` for all available API methods (auto-generated from OpenAPI schema) | |
| ### Test Logging System | |
| Use `testLog()` for state verification instead of DOM queries: | |
| ```javascript | |
| import { setupTestConsoleCapture, waitForTestMessage } from './helpers/test-logging.js'; | |
| // Set up console capture | |
| const consoleLogs = setupTestConsoleCapture(page); | |
| // Perform action... | |
| await page.evaluate(() => { | |
| window.client.saveXml(/* ... */); | |
| }); | |
| // Wait for and verify state change | |
| const saveLog = await waitForTestMessage(consoleLogs, 'FILE_SAVED'); | |
| expect(saveLog.value.file_id).toBeTruthy(); | |
| expect(saveLog.value.status).toBe('saved'); | |
| ``` | |
| ### Running E2E Tests | |
| ```bash | |
| # Local server (fastest) | |
| npm run test:e2e | |
| # Show browser UI | |
| npm run test:e2e:headed | |
| # Step-through debugging | |
| npm run test:e2e:debug | |
| # Specific browser | |
| npm run test:e2e -- --browser firefox | |
| # Specific tests | |
| npm run test:e2e -- --grep "authentication" | |
| # In container (CI environment) | |
| npm run test:container -- --browser chromium | |
| npm run test:container -- --browser chromium,firefox,webkit # Multiple browsers | |
| ``` | |
| ## Smart Test Runner | |
| The smart test runner automatically selects tests based on file dependencies, dramatically reducing test execution time. | |
| ### How It Works | |
| 1. Scans test files for `@testCovers` annotations | |
| 2. Compares changed files against test dependencies | |
| 3. Runs only affected tests plus wildcard tests | |
| ### Usage | |
| ```bash | |
| # Run tests for changed files (git diff) - local server | |
| npm run test:changed | |
| # Run tests for changed files - in container (CI environment) | |
| npm run test:container | |
| # Test specific files | |
| npm run test:container -- app/src/plugins/auth.js fastapi_app/routers/auth.py | |
| # Dry run (show which tests would run) | |
| node tests/smart-test-runner.js --changed-files app/src/ui.js --dry-run | |
| # Run all tests | |
| npm test # Local | |
| npm run test:container -- --all # Container | |
| ``` | |
| ### Test Coverage Annotations | |
| Add `@testCovers` comments to link tests to source files: | |
| ```javascript | |
| /** | |
| * @testCovers app/src/plugins/authentication.js | |
| * @testCovers fastapi_app/routers/auth.py | |
| * @testCovers app/src/modules/api-client.js | |
| */ | |
| test('authentication workflow', async ({ page }) => { | |
| // Test code... | |
| }); | |
| /** | |
| * @testCovers app/src/* | |
| */ | |
| test('frontend smoke test', async ({ page }) => { | |
| // Runs when any frontend file changes | |
| }); | |
| ``` | |
| Supported patterns: | |
| - Exact: `app/src/ui.js` | |
| - Wildcard: `app/src/*` (all files in directory) | |
| - Recursive: `app/src/**/*.js` (all JS files recursively) | |
| ## Writing New Tests | |
| ### General Guidelines | |
| 1. **Add `@testCovers` annotations** for smart test selection | |
| 2. **Clean up after tests** - Release locks, delete test files | |
| 3. **Use helper functions** - Don't duplicate authentication/setup code | |
| 4. **Sequential vs Parallel** - Use `describe.serial()` for dependent tests | |
| 5. **Meaningful assertions** - Test behavior, not implementation details | |
| ### Test Organization | |
| - **Unit tests**: Test single functions/classes in isolation | |
| - **API tests**: Test endpoint behavior and business logic | |
| - **E2E tests**: Test complete user workflows | |
| ### Naming Conventions | |
| - Unit tests: `feature.test.js` or `test_feature.py` | |
| - API tests: `resource_action.test.js` (e.g., `files_save.test.js`) | |
| - E2E tests: `workflow-description.spec.js` (e.g., `auth-workflow.spec.js`) | |
| ### Example Test Structure | |
| ```javascript | |
| describe('Feature Name', () => { | |
| // Setup | |
| test('Setup: create test data', async () => { | |
| // Initialize test state | |
| }); | |
| // Main tests | |
| test('should handle success case', async () => { | |
| // Test implementation | |
| }); | |
| test('should handle error case', async () => { | |
| // Test error handling | |
| }); | |
| // Cleanup | |
| test('Cleanup: remove test data', async () => { | |
| // Clean up resources | |
| }); | |
| }); | |
| ``` | |
| ## Debugging Tests | |
| ### API Test Debugging | |
| ```bash | |
| # Verbose output | |
| npm run test:api -- --verbose --grep "save" | |
| # Keep database for inspection | |
| npm run test:api -- --keep-db --no-cleanup | |
| # Check specific endpoint | |
| curl -X POST http://localhost:8000/api/files/save \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"file_id":"test","xml_string":"<TEI/>"}' | |
| ``` | |
| ### E2E Test Debugging | |
| ```bash | |
| # Show browser UI | |
| npm run test:e2e:headed -- --grep "auth" | |
| # Step-through debugging (Playwright debugger) | |
| npm run test:e2e:debug -- --grep "auth" | |
| # Capture debug artifacts on failure | |
| npm run test:e2e:debug-failure -- --grep "auth" | |
| # Add breakpoints in test code | |
| await page.pause(); // Pauses execution | |
| ``` | |
| **Debug-on-Failure Mode**: | |
| When using `npm run test:e2e:debug-failure`, failed tests will: | |
| - Stop on first failure | |
| - Capture console messages to `console-messages.json` | |
| - Capture page errors to `page-errors.json` | |
| - Take screenshots automatically | |
| - Record video of the test execution | |
| - Save all artifacts to `tests/e2e/test-results/<test-name>/` | |
| This is particularly useful for debugging failures where you need to understand what happened during test execution. | |
| **Implementation**: All E2E tests import from `../fixtures/debug-on-failure.js` by default, which enables this feature when the `--debug-on-failure` flag is used. During normal test runs, the fixture has no effect. | |
| ### Common Issues | |
| **Lock Conflicts**: | |
| - Use `--clean-db` to reset locks | |
| - Ensure tests call cleanup helpers | |
| **Port Conflicts**: | |
| - Test runners auto-select available ports | |
| - Check for stale server processes: `lsof -i :8000` | |
| **Container Issues**: | |
| - Rebuild image: remove `--no-rebuild` flag | |
| - Check logs: `docker logs <container-id>` | |
| **Test Timeouts**: | |
| - Increase timeout: `--timeout 180` (seconds) | |
| - Check server startup logs | |
| ### Debug Logging | |
| Enable verbose output: | |
| ```bash | |
| # API tests | |
| npm run test:api -- --verbose | |
| # E2E tests with debug messages | |
| E2E_DEBUG=true npm run test:e2e | |
| ``` | |
| ## Continuous Integration | |
| ### Pre-push Hooks | |
| Smart test runner automatically runs on `git push`: | |
| ```bash | |
| # Runs affected tests only (local server) | |
| git push | |
| ``` | |
| ### GitHub CI Testing Workflow | |
| The PR testing workflow (`.github/workflows/pr-tests.yml`) uses an optimized three-path strategy to minimize CI time while ensuring comprehensive test coverage. | |
| #### Workflow Overview | |
| ``` | |
| 1. Analyze changed files | |
| 2. Determine which tests need to run (--names-only) | |
| 3. Choose execution strategy: | |
| ββ No tests β Skip (30 seconds) | |
| ββ Unit/API only β Native execution (2-5 minutes) | |
| ββ E2E included β Container execution (10-15 minutes) | |
| ``` | |
| #### Execution Paths | |
| **Path 1: No Tests Needed** (~30 seconds) | |
| Triggers when: Changed files don't affect any tested code (documentation, configs, etc.) | |
| ```yaml | |
| Steps: | |
| 1. Checkout code | |
| 2. Install Node.js | |
| 3. Run smart test runner with --names-only | |
| 4. Detect: No test files in output | |
| 5. Skip all test execution | |
| 6. Comment PR: "β No tests needed!" | |
| ``` | |
| Example scenarios: | |
| - Documentation updates (README.md, docs/) | |
| - Configuration changes (.env.example, .gitignore) | |
| - Non-tested utility scripts | |
| **Path 2: Native Execution** (~2-5 minutes) | |
| Triggers when: Only unit/API tests detected (no E2E tests) | |
| ```yaml | |
| Steps: | |
| 1. Checkout code | |
| 2. Install Node.js, Python, uv | |
| 3. Run smart test runner with --names-only | |
| 4. Detect: Only tests/unit/ or tests/api/ in output | |
| 5. Install dependencies (npm ci, uv sync) | |
| 6. Run tests natively: npm run test:changed | |
| 7. Comment PR: "β All tests passed! (native execution)" | |
| ``` | |
| Example scenarios: | |
| - Backend-only changes (fastapi_app/routers/files.py) | |
| - JavaScript module changes (app/src/modules/state-manager.js) | |
| - Python utility updates (fastapi_app/lib/utils/auth.py) | |
| **Path 3: Container Execution** (~10-15 minutes) | |
| Triggers when: E2E tests detected in output | |
| ```yaml | |
| Steps: | |
| 1. Checkout code | |
| 2. Install Node.js, Python, uv | |
| 3. Run smart test runner with --names-only | |
| 4. Detect: tests/e2e/ in output | |
| 5. Build Docker container (with caching) | |
| 6. Run all tests in container: docker run pdf-tei-editor:ci | |
| 7. Comment PR: "β All tests passed! (containerized)" | |
| ``` | |
| Example scenarios: | |
| - Frontend UI changes (app/src/plugins/xmleditor.js) | |
| - Full-stack features affecting UI | |
| - Changes explicitly annotated with E2E test coverage | |
| #### Test Detection Logic | |
| The workflow uses the smart test runner's `--names-only` option: | |
| ```bash | |
| # Get list of test files (one per line) | |
| TEST_FILES=$(npm run test:changed -- --names-only <changed-files>) | |
| # Check if E2E tests are present | |
| if echo "$TEST_FILES" | grep -q "tests/e2e/"; then | |
| # Use container execution (Playwright browsers needed) | |
| needs_e2e=true | |
| else | |
| # Use native execution (faster) | |
| needs_e2e=false | |
| fi | |
| ``` | |
| #### Performance Comparison | |
| | PR Type | Changed Files | Tests Detected | Execution | Old Time | New Time | Saved | | |
| |---------|---------------|----------------|-----------|----------|----------|-------| | |
| | Docs | README.md | None | Skip | ~10 min | ~30 sec | ~9.5 min | | |
| | Backend | files.py | Unit + API | Native | ~10 min | ~3 min | ~7 min | | |
| | Frontend module | state-manager.js | Unit only | Native | ~10 min | ~2 min | ~8 min | | |
| | UI component | xmleditor.js | Unit + E2E | Container | ~10 min | ~10 min | 0 min | | |
| **Average time savings: 5-8 minutes per PR** (for 70% of PRs that don't need E2E tests) | |
| #### Native vs Container Execution | |
| **Native Execution** (Unit/API tests): | |
| ```bash | |
| # Setup | |
| npm ci # Install Node dependencies | |
| uv sync # Create Python virtual environment | |
| # Execution | |
| npm run test:changed # Runs smart test runner | |
| ββ JS unit tests: node tests/unit-test-runner.js | |
| ββ Python unit tests: uv run python tests/unit-test-runner.py | |
| ββ API tests: node tests/backend-test-runner.js (local FastAPI server) | |
| ``` | |
| **Container Execution** (E2E tests): | |
| ```bash | |
| # Build | |
| docker build --target ci # Build test container image | |
| # Execution | |
| docker run pdf-tei-editor:ci <changed-files> | |
| ββ All tests run inside container | |
| ββ Playwright browsers pre-installed | |
| ββ Isolated test environment | |
| ``` | |
| #### Local Equivalent | |
| Replicate the CI workflow locally: | |
| ```bash | |
| # Check which tests would run (like CI does) | |
| npm run test:changed -- --names-only | |
| # Run tests natively (like CI Path 2) | |
| npm run test:changed | |
| # Run tests in container (like CI Path 3) | |
| npm run test:container | |
| # Force all tests in container | |
| npm run test:container -- --all | |
| ``` | |
| #### Debugging CI Failures | |
| **If tests pass locally but fail in CI:** | |
| 1. **Check environment differences:** | |
| ```bash | |
| # CI uses clean install | |
| npm ci # vs npm install | |
| # CI uses specific Node/Python versions | |
| node -v # Should match workflow (20.x) | |
| python -v # Should match workflow (3.11) | |
| ``` | |
| 2. **Run in container locally:** | |
| ```bash | |
| # Exact same environment as CI | |
| npm run test:container | |
| # Force rebuild (ignore cache) | |
| npm run test:container -- --no-cache | |
| ``` | |
| 3. **Check test isolation:** | |
| ```bash | |
| # CI always starts with clean state | |
| # Verify tests clean up properly | |
| npm run test:changed -- --keep-db # Check for leaks | |
| ``` | |
| **If container build fails:** | |
| 1. **Check Dockerfile changes:** | |
| ```bash | |
| # Test build locally | |
| docker build --target ci -t pdf-tei-editor:ci . | |
| ``` | |
| 2. **Check dependency versions:** | |
| ```bash | |
| # Verify package.json and pyproject.toml | |
| npm ci | |
| uv sync | |
| ``` | |
| #### CI Architecture | |
| **Design Principles:** | |
| - **Smart test selection**: Only run affected tests | |
| - **Fail fast**: Stop on first failure | |
| - **Progressive optimization**: Fast path for common cases | |
| - **Container when needed**: E2E tests require browsers | |
| - **Real-time feedback**: Stream output, immediate PR comments | |
| - **Caching strategy**: Docker layer cache, npm/pip caching | |
| **Technical Details:** | |
| - Node.js 20.x for JavaScript execution | |
| - Python 3.11 for FastAPI backend | |
| - uv for Python dependency management | |
| - Docker Buildx for efficient builds | |
| - GitHub Actions caching for Docker layers | |
| - Concurrent test execution where possible | |
| ## Test Fixtures | |
| ### API Test Fixtures | |
| Located in `tests/api/fixtures/`: | |
| - **minimal**: Bare minimum config for smoke tests | |
| - **standard**: Full config with sample data | |
| Fixtures are automatically loaded by `backend-test-runner.js`. | |
| ### E2E Test Fixtures | |
| Located in `tests/e2e/fixtures/`: | |
| - **minimal**: Basic setup for quick tests | |
| - **standard**: Complete environment with sample files | |
| Fixtures include: | |
| - Config files (`config/`) | |
| - Sample PDF/TEI files (`files/`) | |
| - User credentials | |
| ## Additional Resources | |
| - **Test Infrastructure**: See `tests/lib/` for server management utilities | |
| - **Helper Functions**: Check `tests/*/helpers/` for shared test utilities | |
| - **Example Tests**: Review existing tests in each category for patterns | |