diff --git a/.github/workflows/fw-lite.yaml b/.github/workflows/fw-lite.yaml index e3ef4c65d9..8b898beb77 100644 --- a/.github/workflows/fw-lite.yaml +++ b/.github/workflows/fw-lite.yaml @@ -112,7 +112,7 @@ jobs: run: pnpm exec playwright install --with-deps - name: Run snapshot tests working-directory: frontend/viewer - run: task playwright-test-standalone + run: task test:snapshot-standalone - name: Build viewer working-directory: frontend/viewer run: pnpm run build @@ -405,3 +405,67 @@ jobs: sleep 10 curl -X POST https://lexbox.org/api/fwlite-release/new-release + + e2e-test: + name: E2E Tests + needs: [publish-linux] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true + + - uses: actions/download-artifact@v4 + id: download-artifact + with: + name: fw-lite-web-linux + path: fw-lite-web-linux + - name: set execute permissions + run: chmod +x ${{ steps.download-artifact.outputs.download-path }}/*/FwLiteWeb + - name: Install Task + uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611 #v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 + with: + package_json_file: 'frontend/package.json' + - uses: actions/setup-node@v4 + with: + node-version-file: './frontend/package.json' + cache: 'pnpm' + cache-dependency-path: './frontend/pnpm-lock.yaml' + - name: Prepare frontend + working-directory: frontend + run: | + pnpm install + - name: Test fw lite launcher + working-directory: frontend/viewer + env: + FW_LITE_BINARY_PATH: ${{ steps.download-artifact.outputs.download-path }}/release_linux-x64/FwLiteWeb + run: task e2e-test-helper-unit-tests + + - uses: ./.github/actions/setup-k8s + with: + lexbox-api-tag: develop + ingress-controller-port: '6579' # todo, figure out if we can use https as it's required for the tests + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Playwright dependencies + working-directory: frontend/viewer + run: pnpm exec playwright install --with-deps + + - name: Run E2E tests + working-directory: frontend/viewer + env: + FW_LITE_BINARY_PATH: ${{ steps.download-artifact.outputs.download-path }}/release_linux-x64/FwLiteWeb + TEST_SERVER_PORT: 6579 + run: task test:e2e + + - name: Upload Playwright test results and traces (on failure) + if: failure() + uses: actions/upload-artifact@v4 + with: + name: fw-lite-e2e-test-results + if-no-files-found: ignore + path: | + frontend/viewer/tests/e2e/test-results/ diff --git a/.kiro/specs/fw-lite-e2e-tests/design.md b/.kiro/specs/fw-lite-e2e-tests/design.md new file mode 100644 index 0000000000..5a1951e1bb --- /dev/null +++ b/.kiro/specs/fw-lite-e2e-tests/design.md @@ -0,0 +1,278 @@ +# Design Document + +## Overview + +The FW Lite E2E Testing Framework provides automated end-to-end testing for FW Lite integration with LexBox. The system uses Playwright for UI automation and integrates with the existing GitHub Actions CI/CD pipeline to ensure critical workflows continue to function after code changes. + +The framework follows the existing testing patterns established in the project, extending the current Playwright setup in `frontend/viewer` to include full application testing scenarios that span the entire FW Lite to LexBox integration workflow. + +## Architecture + +### High-Level Architecture + +```mermaid +graph TB + A[GitHub Actions Trigger] --> B[FW Lite Build Workflow] + B --> C[E2E Test Workflow] + C --> D[Test Environment Setup] + D --> E[FW Lite Application Launch] + E --> F[Playwright Test Execution] + F --> G[Test Results & Artifacts] + + subgraph "Test Environment" + H[FW Lite Linux Binary] + I[Test Server Configuration] + J[Test Data Setup] + end + + subgraph "Test Scenarios" + K[Project Download Test] + L[Entry Creation Test] + M[Data Persistence Test] + N[Media File Test] + O[Live Sync Test] + end + + D --> H + D --> I + D --> J + F --> K + F --> L + F --> M + F --> N + F --> O +``` + +### Workflow Integration + +The E2E tests integrate with the existing CI/CD pipeline by: + +1. **Triggering after successful FW Lite builds** - Uses the `needs` dependency in GitHub Actions +2. **Consuming build artifacts** - Downloads the Linux FW Lite binary from the previous workflow +3. **Configurable server targeting** - Can run against different environments (local, staging, production) +4. **Artifact preservation** - Uploads test results, screenshots, and logs for debugging + +### Test Environment Architecture + +```mermaid +graph LR + A[GitHub Runner] --> B[FW Lite Binary] + A --> C[Playwright Browser] + B --> D[LexBox Server] + C --> B + D --> E[Test Projects] + D --> F[Test Users] +``` + +## Components and Interfaces + +### 1. GitHub Actions Workflow (`fw-lite-e2e-tests.yaml`) + +**Purpose**: Orchestrates the E2E testing process +**Location**: `.github/workflows/fw-lite-e2e-tests.yaml` + +**Key Features**: +- Triggers after successful `publish-linux` job in fw-lite workflow +- Downloads FW Lite Linux artifacts +- Sets up test environment with configurable server endpoints +- Executes Playwright tests +- Uploads test results and failure artifacts + +**Configuration Interface**: +```yaml +env: + TEST_SERVER_HOSTNAME: ${{ vars.TEST_SERVER_HOSTNAME || 'localhost:5137' }} + TEST_PROJECT_CODE: 'sena-3' + TEST_DEFAULT_PASSWORD: ${{ secrets.TEST_USER_PASSWORD || 'pass' }} + FW_LITE_BINARY_PATH: './fw-lite-linux/linux-x64/FwLiteWeb' +``` + +### 2. Playwright Test Suite + +**Purpose**: Implements the actual E2E test scenarios +**Location**: `frontend/viewer/tests/e2e/` + +**Test Structure**: +``` +frontend/viewer/tests/e2e/ +├── fw-lite-integration.test.ts # Main integration test +├── helpers/ +│ ├── fw-lite-launcher.ts # FW Lite application management +│ ├── project-operations.ts # Project download/management helpers +│ └── test-data.ts # Test data constants and utilities +└── fixtures/ + └── test-projects.json # Expected test project configurations +``` + +### 3. FW Lite Application Manager + +**Purpose**: Manages FW Lite application lifecycle during tests +**Location**: `frontend/viewer/tests/e2e/helpers/fw-lite-launcher.ts` + +**Interface**: +```typescript +interface FwLiteManager { + launch(config: LaunchConfig): Promise + shutdown(): Promise + isRunning(): boolean + getBaseUrl(): string +} + +interface LaunchConfig { + binaryPath: string + serverUrl: string + port?: number + timeout?: number +} +``` + +### 4. Test Data Management + +**Purpose**: Provides consistent test data and expectations +**Location**: `frontend/viewer/tests/e2e/helpers/test-data.ts` + +**Interface**: +```typescript +interface TestProject { + code: string + name: string + expectedEntries: number + testUser: string +} + +interface TestEntry { + lexeme: string + definition: string + partOfSpeech: string + uniqueIdentifier: string +} +``` + +## Data Models + +### Test Configuration Model + +```typescript +interface E2ETestConfig { + server: { + hostname: string + protocol: 'http' | 'https' + port?: number + } + fwLite: { + binaryPath: string + launchTimeout: number + shutdownTimeout: number + } + testData: { + projectCode: string + testUser: string + testPassword: string + } + timeouts: { + projectDownload: number + entryCreation: number + dataSync: number + } +} +``` + +### Test Result Model + +```typescript +interface TestResult { + testName: string + status: 'passed' | 'failed' | 'skipped' + duration: number + error?: string + screenshots: string[] + logs: string[] +} +``` + +## Error Handling + +### Application Launch Failures + +- **Timeout handling**: If FW Lite fails to start within the configured timeout, the test fails with clear error messaging +- **Port conflicts**: Automatic port detection and retry logic +- **Binary validation**: Pre-flight checks to ensure the FW Lite binary exists and is executable + +### Network and Server Issues + +- **Connection retries**: Implement exponential backoff for server connection attempts +- **Server availability checks**: Validate server endpoints before starting tests +- **Graceful degradation**: Skip tests that require unavailable services with clear reporting + +### Test Data Issues + +- **Project availability**: Verify expected test projects exist before running tests +- **User authentication**: Handle authentication failures with clear error messages +- **Data conflicts**: Implement cleanup strategies for test data conflicts + +### Playwright-Specific Error Handling + +```typescript +// Example error handling pattern +try { + await page.waitForSelector('[data-testid="project-list"]', { timeout: 30000 }) +} catch (error) { + await page.screenshot({ path: 'failure-project-list.png' }) + throw new Error(`Project list failed to load: ${error.message}`) +} +``` + +## Testing Strategy + +### Test Categories + +1. **Smoke Tests**: Basic application launch and connectivity +2. **Integration Tests**: Full workflow scenarios (download → modify → sync) +3. **Data Persistence Tests**: Verify data survives application restarts +4. **Media File Tests**: Ensure media files are properly handled +5. **Live Sync Tests**: Verify real-time synchronization functionality + +### Test Data Strategy + +- **Predictable test projects**: Use known projects like 'sena-3' with expected data +- **Unique test identifiers**: Generate unique identifiers for test entries to avoid conflicts +- **Cleanup procedures**: Implement cleanup for test data after test completion +- **Isolation**: Ensure tests don't interfere with each other + +### Parallel Execution Strategy + +- **Sequential execution**: Run E2E tests sequentially to avoid resource conflicts +- **Test isolation**: Each test scenario operates on separate data sets +- **Resource management**: Proper cleanup between test scenarios + +### Retry and Flaky Test Handling + +```typescript +// Retry configuration for flaky operations +const retryConfig = { + projectDownload: { attempts: 3, delay: 5000 }, + entryCreation: { attempts: 2, delay: 2000 }, + dataSync: { attempts: 3, delay: 3000 } +} +``` + +### Test Environment Matrix + +The tests will be designed to run against multiple environments: + +- **Local Development**: `localhost:5137` +- **Staging Environment**: Configured via environment variables +- **Production Environment**: Limited test scenarios for critical path validation + +### Performance Considerations + +- **Test execution time**: Target total execution time under 15 minutes +- **Resource usage**: Monitor memory and CPU usage during test execution +- **Artifact size management**: Compress and limit screenshot/video artifacts + +### Reporting and Observability + +- **Test result aggregation**: Comprehensive test reports with pass/fail statistics +- **Failure analysis**: Detailed logs and screenshots for failed tests +- **Trend analysis**: Track test execution times and failure patterns over time +- **Integration with existing reporting**: Leverage existing GitHub Actions test reporting diff --git a/.kiro/specs/fw-lite-e2e-tests/requirements.md b/.kiro/specs/fw-lite-e2e-tests/requirements.md new file mode 100644 index 0000000000..11e3e42ec1 --- /dev/null +++ b/.kiro/specs/fw-lite-e2e-tests/requirements.md @@ -0,0 +1,67 @@ +# Requirements Document + +## Introduction + +This feature implements end-to-end testing for FW Lite integration with LexBox to ensure critical functionality like project downloads, data synchronization, and media file handling work correctly. The tests will use Playwright for UI automation and run as part of the CI/CD pipeline to catch regressions before they reach production. + +## Requirements + +### Requirement 1 + +**User Story:** As a developer, I want automated end-to-end tests for FW Lite and LexBox integration, so that I can ensure critical workflows continue to work after code changes. + +#### Acceptance Criteria + +1. WHEN the fw-lite Linux build workflow completes successfully THEN the e2e test workflow SHALL automatically trigger +2. WHEN the e2e test workflow runs THEN it SHALL use the built FW Lite application from the previous workflow +3. WHEN tests run THEN they SHALL be configurable to run against different server environments (local, staging, production) +4. WHEN tests complete THEN they SHALL report pass/fail status and detailed logs for debugging failures + +### Requirement 2 + +**User Story:** As a developer, I want to test the complete project download and modification workflow, so that I can verify data persistence and synchronization work correctly. + +#### Acceptance Criteria + +1. WHEN the test launches FW Lite THEN it SHALL successfully connect to the configured LexBox server +2. WHEN the test downloads a project (sena-3) THEN it SHALL complete without errors and the project SHALL be available locally +3. WHEN the test creates a new entry via the UI THEN the entry SHALL be saved and visible in the project +4. WHEN the test deletes the local project copy THEN all local files SHALL be removed +5. WHEN the test re-downloads the same project THEN it SHALL retrieve the updated version with the previously created entry +6. WHEN the test searches for the previously created entry THEN it SHALL be found and match the original data + +### Requirement 3 + +**User Story:** As a developer, I want the e2e tests to run in a GitHub Actions workflow, so that they integrate seamlessly with our existing CI/CD pipeline. + +#### Acceptance Criteria + +1. WHEN the fw-lite build workflow succeeds THEN the e2e test workflow SHALL have access to the built application artifacts +2. WHEN the e2e test workflow runs THEN it SHALL set up the necessary test environment including FW Lite application +3. WHEN tests execute THEN they SHALL use Playwright for UI automation +4. WHEN tests are implemented THEN they SHALL be located in the frontend/viewer folder structure +5. WHEN the workflow completes THEN it SHALL upload test results and screenshots for failed tests + +### Requirement 4 + +**User Story:** As a developer, I want the test framework to handle test data and user expectations, so that tests can run reliably against known project states. + +#### Acceptance Criteria + +1. WHEN tests run THEN they SHALL expect specific test projects (like sena-3) to be available on the target server +2. WHEN tests run THEN they SHALL expect specific test users with appropriate permissions to be available +3. WHEN tests create test data THEN they SHALL use predictable naming patterns for easy identification and cleanup +4. WHEN tests fail THEN they SHALL provide clear error messages indicating what step failed and why +5. IF test data conflicts exist THEN the test SHALL handle cleanup or use unique identifiers to avoid collisions + +### Requirement 5 + +**User Story:** As a developer, I want comprehensive test coverage for media files and live sync functionality, so that I can prevent regressions in these critical features. + +#### Acceptance Criteria + +1. WHEN tests run THEN they SHALL verify that media files are properly downloaded and accessible +2. WHEN tests create entries with media attachments THEN the media SHALL be properly synchronized +3. WHEN tests modify existing entries THEN live sync functionality SHALL be verified to work correctly +4. WHEN multiple test scenarios run THEN they SHALL not interfere with each other's data or state +5. WHEN tests complete THEN they SHALL clean up any temporary test data created during execution diff --git a/.kiro/specs/fw-lite-e2e-tests/tasks.md b/.kiro/specs/fw-lite-e2e-tests/tasks.md new file mode 100644 index 0000000000..45a4ef6649 --- /dev/null +++ b/.kiro/specs/fw-lite-e2e-tests/tasks.md @@ -0,0 +1,104 @@ +# Implementation Plan + +- [x] 1. Set up E2E test directory structure and configuration + - Create the `frontend/viewer/tests/e2e/` directory structure with subdirectories for helpers and fixtures + - Create TypeScript configuration files for E2E tests with proper type definitions + - Set up test data constants and configuration interfaces + - _Requirements: 3.4, 4.1_ + +- [x] 2. Implement FW Lite application launcher utility + - Create `fw-lite-launcher.ts` helper class to manage FW Lite application lifecycle + - Implement launch method with timeout handling and port conflict resolution + - Implement shutdown method with proper cleanup and process termination + - Add health check methods to verify application is running and responsive + - Write unit tests for the launcher utility functions + - _Requirements: 2.1, 2.2, 4.4_ + +- [x] 2.1. Set up FW Lite server binary for launcher testing + - Add build script to publish FW Lite server binary for testing + - Configure dotnet publish command to create self-contained executable + - Set up output directory structure for test binaries + - Add integration test that actually launches the FW Lite server + - Verify launcher can successfully start and stop real FW Lite instance + - _Requirements: 2.1, 2.2, 4.4_ + +- [x] 3. Create test data management system + - Implement `test-data.ts` with test project configurations and expected data structures + - Create helper functions for generating unique test identifiers to avoid data conflicts + - Implement test data cleanup utilities for removing test entries after execution + - Define TypeScript interfaces for test projects, entries, and configuration + - _Requirements: 4.1, 4.2, 4.3, 4.5_ + +- [x] 4. Implement project operations helper module + - Create `project-operations.ts` with functions for project download automation + - Implement project deletion helpers for cleaning up local project copies + - Add project verification functions to confirm successful downloads and data presence + - Create entry creation helpers for automating UI interactions to add new entries + - Write helper functions for searching and verifying entries exist in projects + - _Requirements: 2.2, 2.3, 2.4, 2.5, 2.6_ + +- [ ] 5. Create core integration test scenarios + + + + + + - Implement the main test case: download project, create entry, delete local copy, re-download, verify entry + - Add test setup and teardown functions for proper test isolation + - Implement Playwright page object patterns for FW Lite UI interactions + - Add comprehensive assertions for each step of the workflow + - Include error handling and detailed failure reporting with screenshots + - _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6_ + +- [ ] 6. Implement media file and live sync test scenarios + - Create test scenarios for media file download and accessibility verification + - Add live sync functionality tests to verify real-time data synchronization + - _Requirements: 5.1, 5.2, 5.3_ + +- [ ] 7. Create GitHub Actions workflow for E2E tests + - Create `.github/workflows/fw-lite-e2e-tests.yaml` workflow file + - Configure workflow to trigger after successful `publish-linux` job completion + - Implement artifact download step to get FW Lite Linux binary from previous workflow + - Set up test environment with configurable server endpoints using environment variables + - Add Playwright test execution step with proper timeout and retry configuration + - _Requirements: 1.1, 1.2, 3.1, 3.2, 3.3_ + +- [ ] 8. Configure test result reporting and artifact management + - Implement test result upload for pass/fail status and detailed logs + - Add screenshot and video capture for failed test scenarios + - Configure test report generation compatible with GitHub Actions + - Set up artifact retention policies for test results and debugging materials + - Add integration with existing test reporting systems + - _Requirements: 1.4, 3.5, 4.4_ + +- [ ] 9. Add error handling and retry logic + - Implement exponential backoff retry logic for network operations and server connections + - Add timeout handling for all async operations with appropriate error messages + - Create comprehensive error logging with context information for debugging + - Implement graceful failure handling that provides actionable error messages + - Add pre-flight checks for server availability and test data prerequisites + - _Requirements: 4.4, 5.4_ + +- [ ] 10. Create test configuration and environment management + - Implement configuration system for different target environments (local, staging, production) + - Add environment variable handling for server hostnames, credentials, and test settings + - Create configuration validation to ensure all required settings are present + - Implement test environment setup verification before running test scenarios + - Add support for local development testing with appropriate default configurations + - _Requirements: 1.3, 4.1, 4.2_ + +- [ ] 11. Implement test cleanup and isolation mechanisms + - Create cleanup functions that run after each test to remove temporary test data + - Implement test isolation to ensure tests don't interfere with each other's data + - Add database cleanup for test entries created during test execution + - Create unique test session identifiers to avoid conflicts between parallel test runs + - Implement proper resource cleanup for FW Lite application instances + - _Requirements: 4.5, 5.4, 5.5_ + +- [ ] 12. Add comprehensive test documentation and examples + - Create README documentation for running E2E tests locally and in CI + - Document test configuration options and environment variable requirements + - Add troubleshooting guide for common test failures and debugging steps + - Create examples of extending the test suite with additional test scenarios + - Document the test data requirements and how to set up test environments + - _Requirements: 4.4, 1.4_ diff --git a/backend/FwLite/FwLiteWeb/FwLiteWebServer.cs b/backend/FwLite/FwLiteWeb/FwLiteWebServer.cs index 99d2c87f41..17345cd801 100644 --- a/backend/FwLite/FwLiteWeb/FwLiteWebServer.cs +++ b/backend/FwLite/FwLiteWeb/FwLiteWebServer.cs @@ -69,6 +69,7 @@ public static WebApplication SetupAppServer(WebApplicationOptions options, Actio options.AddFilter(new LockedProjectFilter()); options.EnableDetailedErrors = true; }).AddJsonProtocol(); + builder.Services.AddHealthChecks(); configure?.Invoke(builder); var app = builder.Build(); @@ -123,6 +124,7 @@ public static WebApplication SetupAppServer(WebApplicationOptions options, Actio app.MapImport(); app.MapAuthRoutes(); app.MapMiniLcmRoutes("/api/mini-lcm"); + app.MapHealthChecks("/health"); app.MapStaticAssets(); app.MapRazorComponents() diff --git a/backend/FwLite/FwLiteWeb/Program.cs b/backend/FwLite/FwLiteWeb/Program.cs index e58a59753c..1545671fe0 100644 --- a/backend/FwLite/FwLiteWeb/Program.cs +++ b/backend/FwLite/FwLiteWeb/Program.cs @@ -3,7 +3,8 @@ using Microsoft.Extensions.Options; //paratext won't let us change the working directory, and if it's not set correctly then loading js files doesn't work -Directory.SetCurrentDirectory(Path.GetDirectoryName(typeof(Program).Assembly.Location)!); +if (Path.GetDirectoryName(typeof(Program).Assembly.Location) is {} directoryName) + Directory.SetCurrentDirectory(directoryName); var app = FwLiteWebServer.SetupAppServer(new() {Args = args}); await using (app) { @@ -16,5 +17,13 @@ LocalAppLauncher.LaunchBrowser(url); } + _ = Task.Run(async () => + { + // Wait for the "shutdown" command from stdin + while (await Console.In.ReadLineAsync() is not "shutdown") { } + + await app.StopAsync(); + }); + await app.WaitForShutdownAsync(); } diff --git a/backend/FwLite/FwLiteWeb/Routes/AuthRoutes.cs b/backend/FwLite/FwLiteWeb/Routes/AuthRoutes.cs index 55bc0711c4..a1cc1fad68 100644 --- a/backend/FwLite/FwLiteWeb/Routes/AuthRoutes.cs +++ b/backend/FwLite/FwLiteWeb/Routes/AuthRoutes.cs @@ -18,6 +18,9 @@ public static IEndpointConventionBuilder MapAuthRoutes(this WebApplication app) async (AuthService authService, string authority, IOptions options, [FromHeader] string referer) => { var returnUrl = new Uri(referer).PathAndQuery; + if (returnUrl.StartsWith("/api/auth/login")) { + returnUrl = "/"; + } if (options.Value.SystemWebViewLogin) { throw new NotSupportedException("System web view login is not supported for this endpoint"); diff --git a/backend/FwLite/FwLiteWeb/Routes/ProjectRoutes.cs b/backend/FwLite/FwLiteWeb/Routes/ProjectRoutes.cs index 0795351782..87967906f8 100644 --- a/backend/FwLite/FwLiteWeb/Routes/ProjectRoutes.cs +++ b/backend/FwLite/FwLiteWeb/Routes/ProjectRoutes.cs @@ -70,6 +70,12 @@ [FromQuery] UserProjectRole? role _ => Results.InternalServerError("DownloadProjectByCodeResult enum value not handled, please inform FW Lite devs") }; }); + group.MapDelete("/crdt/{code}", + async (CrdtProjectsService projectService, string code) => + { + await projectService.DeleteProject(code); + return TypedResults.Ok(); + }); return group; } } diff --git a/frontend/viewer/.gitignore b/frontend/viewer/.gitignore index 9767776119..438fd181b5 100644 --- a/frontend/viewer/.gitignore +++ b/frontend/viewer/.gitignore @@ -26,3 +26,5 @@ html-test-results *storybook.log storybook-static +screenshots +**/*-html-report diff --git a/frontend/viewer/Taskfile.yml b/frontend/viewer/Taskfile.yml index 17947292bd..2f9a5041bb 100644 --- a/frontend/viewer/Taskfile.yml +++ b/frontend/viewer/Taskfile.yml @@ -39,19 +39,27 @@ tasks: test-unit: cmd: pnpm test --project unit - playwright-test: - desc: 'runs playwright tests against already running server' - cmd: pnpm run test:playwright {{.CLI_ARGS}} - playwright-test-standalone: - desc: 'runs playwright tests and runs dev automatically, run ui mode by calling with -- --ui or use --update-snapshots' + test:snapshot: + desc: 'runs snapshot tests against already running server' + cmd: pnpm run test:snapshots {{.CLI_ARGS}} + test:snapshot-standalone: + desc: 'runs snapshot tests and runs dev automatically, run ui mode by calling with -- --ui or use --update-snapshots' env: AUTO_START_SERVER: true - cmd: pnpm run test:playwright {{.CLI_ARGS}} + cmd: pnpm run test:snapshots {{.CLI_ARGS}} generate-marketing-screenshots: desc: 'they should be in the screenshots folder' env: AUTO_START_SERVER: true MARKETING_SCREENSHOTS: true - cmd: pnpm run test:playwright {{.CLI_ARGS}} - playwright-test-report: - cmd: pnpm run test:playwright-report + cmd: pnpm run test:snapshots {{.CLI_ARGS}} + + test:e2e-setup: + deps: [build] + cmds: + - dotnet publish ../../backend/FwLite/FwLiteWeb/FwLiteWeb.csproj --configuration Release --self-contained --output ./dist/fw-lite-server + test:e2e: + cmd: pnpm run test:e2e {{.CLI_ARGS}} + e2e-test-helper-unit-tests: + desc: 'tests the fw lite launcher, run `setup-e2e-test` first' + cmd: pnpm test:unit --run fw-lite-launcher diff --git a/frontend/viewer/package.json b/frontend/viewer/package.json index 907edf8258..2d9d0d72a4 100644 --- a/frontend/viewer/package.json +++ b/frontend/viewer/package.json @@ -13,16 +13,15 @@ "build": "vite build", "build-ffmpeg-worker": "vite build --config vite.config.ffmpeg-worker.ts", "preview": "vite preview", - "pretest:playwright": "playwright install", - "test:playwright": "playwright test", - "test:playwright-report": "playwright show-report html-test-results", - "test:playwright-record": "playwright codegen", + "pretest:snapshots": "playwright install chromium", + "pretest:e2e": "playwright install chromium", + "test:snapshots": "playwright test -c ./tests/snapshots/playwright.config.ts", + "test:e2e": "playwright test -c ./tests/e2e/playwright.config.ts", "test": "vitest run", "test:ui": "vitest --ui", "test:watch": "vitest", "test:storybook": "vitest --project=storybook", "test:unit": "vitest --project=unit", - "test:browser": "vitest --project=browser", "check": "svelte-check", "lint": "eslint", "lint:report": "eslint . --output-file eslint_report.json --format json", diff --git a/frontend/viewer/src/fw-lite-launcher.test.ts b/frontend/viewer/src/fw-lite-launcher.test.ts new file mode 100644 index 0000000000..603a6f3406 --- /dev/null +++ b/frontend/viewer/src/fw-lite-launcher.test.ts @@ -0,0 +1,215 @@ +/** + * Integration tests for FW Lite Application Launcher + * + * These tests run against the real implementation without mocking + * to ensure the launcher works correctly in practice. + */ + +import {describe, it, expect, assert, beforeEach, afterEach} from 'vitest'; +import {FwLiteLauncher} from '../tests/e2e/helpers/fw-lite-launcher'; +import type {LaunchConfig} from '../tests/e2e/types'; +import {getTestConfig} from '../tests/e2e/config'; + +describe('FwLiteLauncher', () => { + let launcher: FwLiteLauncher; + + beforeEach(() => { + launcher = new FwLiteLauncher(); + }); + + afterEach(async () => { + // Ensure cleanup after each test + if (launcher.isRunning()) { + await launcher.shutdown(); + } + }); + + describe('basic functionality', () => { + it('should return false when not launched', () => { + expect(launcher.isRunning()).toBe(false); + }); + + it('should throw error when getting base URL while not running', () => { + expect(() => launcher.getBaseUrl()).toThrow('FW Lite is not running'); + }); + + it('should handle shutdown when not running', async () => { + await expect(launcher.shutdown()).resolves.not.toThrow(); + expect(launcher.isRunning()).toBe(false); + }); + + it('should throw error if binary does not exist', async () => { + const config: LaunchConfig = { + binaryPath: '/nonexistent/path/to/fw-lite', + serverUrl: 'http://localhost:5137', + port: 5000, + timeout: 1000, + }; + + await expect(launcher.launch(config)).rejects.toThrow( + 'FW Lite binary not found or not executable' + ); + }); + + it('should throw error if already running', async () => { + // Create a fake binary file for testing + const testBinaryPath = './test-fake-binary.js'; + + const fs = await import('node:fs/promises'); + await fs.writeFile(testBinaryPath, '#!/usr/bin/env node\nconsole.log("fake binary");', {mode: 0o755}); + + const config: LaunchConfig = { + binaryPath: testBinaryPath, + serverUrl: 'http://localhost:5137', + port: 5000, + timeout: 1000, + }; + + // First launch should fail because it's not a real FW Lite binary + await expect(launcher.launch(config)).rejects.toThrow(); + + // Clean up + await fs.unlink(testBinaryPath).catch(() => { }); + }, 10000); + }); + + describe('port finding functionality', () => { + it('should be able to find available ports', async () => { + const net = await import('node:net'); + + // Test the port finding logic by creating a server on a port + const server = net.createServer(); + + return new Promise((resolve, reject) => { + server.listen(0, () => { + const address = server.address(); + const port = typeof address === 'object' && address ? address.port : 0; + + expect(port).toBeGreaterThan(0); + + server.close(() => { + resolve(); + }); + }); + + server.on('error', reject); + }); + }); + }); + + describe('configuration validation', () => { + it('should validate launch configuration parameters', () => { + const validConfig: LaunchConfig = { + binaryPath: '/path/to/fw-lite', + serverUrl: 'http://localhost:5137', + port: 5000, + timeout: 10000, + }; + + // Test that config properties are accessible + expect(validConfig.binaryPath).toBe('/path/to/fw-lite'); + expect(validConfig.serverUrl).toBe('http://localhost:5137'); + expect(validConfig.port).toBe(5000); + expect(validConfig.timeout).toBe(10000); + }); + + it('should handle optional configuration parameters', () => { + const minimalConfig: LaunchConfig = { + binaryPath: '/path/to/fw-lite', + serverUrl: 'http://localhost:5137', + }; + + // Test that optional parameters can be undefined + expect(minimalConfig.port).toBeUndefined(); + expect(minimalConfig.timeout).toBeUndefined(); + }); + }); + + describe('launcher state management', () => { + it('should maintain proper state transitions', () => { + // Initial state + expect(launcher.isRunning()).toBe(false); + + // State should remain consistent + expect(launcher.isRunning()).toBe(false); + expect(launcher.isRunning()).toBe(false); + }); + }); + + describe('real FW Lite server integration', () => { + async function getFwLiteBinaryPath() { + const binaryPath = getTestConfig().fwLite.binaryPath; + const fs = await import('node:fs/promises'); + try { + await fs.access(binaryPath); + } catch { + assert.fail(`FW Lite binary not found at ${binaryPath}, skipping integration test. Run "pnpm build:fw-lite" first.`); + } + return binaryPath; + } + + + + it('should successfully launch and shutdown real FW Lite server', async () => { + // Check if the FW Lite binary exists + const binaryPath = await getFwLiteBinaryPath(); + + + const config: LaunchConfig = { + binaryPath, + serverUrl: 'http://localhost:5137', + port: 5555, // Use a specific port for testing + timeout: 30000, // 30 seconds timeout + }; + + // Launch the server + await launcher.launch(config); + + // Verify it's running + expect(launcher.isRunning()).toBe(true); + expect(launcher.getBaseUrl()).toBe('http://localhost:5555'); + + // Test that we can make a request to the server + try { + const response = await fetch(`${launcher.getBaseUrl()}/health`); + // Accept any response that indicates the server is running + expect(response.status).toBeLessThan(500); + } catch { + // If /health doesn't exist, try the root endpoint + const response = await fetch(launcher.getBaseUrl()); + expect(response.status).toBeLessThan(500); + } + + // Shutdown the server + await launcher.shutdown(); + + // Verify it's stopped + expect(launcher.isRunning()).toBe(false); + }, 60000); // 60 second timeout for this test + + it('should handle multiple launch attempts gracefully', async () => { + // Check if the FW Lite binary exists + const binaryPath = await getFwLiteBinaryPath(); + + const config: LaunchConfig = { + binaryPath, + serverUrl: 'http://localhost:5137', + port: 5556, // Use a different port + timeout: 30000, + }; + + // First launch should succeed + await launcher.launch(config); + expect(launcher.isRunning()).toBe(true); + + // Second launch should fail + await expect(launcher.launch(config)).rejects.toThrow( + 'FW Lite is already running. Call shutdown() first.' + ); + + // Cleanup + await launcher.shutdown(); + expect(launcher.isRunning()).toBe(false); + }, 60000); + }); +}); diff --git a/frontend/viewer/src/home/HomeView.svelte b/frontend/viewer/src/home/HomeView.svelte index 12167d27bf..9ac95440a7 100644 --- a/frontend/viewer/src/home/HomeView.svelte +++ b/frontend/viewer/src/home/HomeView.svelte @@ -182,7 +182,7 @@

{$t`loading...`}

{:then projects}
-
+

{$t`Local`}

diff --git a/frontend/viewer/src/home/Server.svelte b/frontend/viewer/src/home/Server.svelte index bc7f247bfa..4f75e848e8 100644 --- a/frontend/viewer/src/home/Server.svelte +++ b/frontend/viewer/src/home/Server.svelte @@ -1,4 +1,4 @@ -