name: Frontend Tests on: push: branches-ignore: - 'translations**' pull_request: branches-ignore: - 'translations**' workflow_dispatch: concurrency: group: frontend-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: changes: name: Detect Frontend Changes runs-on: ubuntu-slim outputs: frontend_changed: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.frontend == 'true' }} steps: - name: Checkout uses: actions/checkout@v6.0.2 with: fetch-depth: 0 - name: Decide run mode id: force run: | if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then echo "run_all=true" >> "$GITHUB_OUTPUT" elif [[ "${{ github.event_name }}" == "push" && ( "${{ github.ref_name }}" == "main" || "${{ github.ref_name }}" == "dev" ) ]]; then echo "run_all=true" >> "$GITHUB_OUTPUT" else echo "run_all=false" >> "$GITHUB_OUTPUT" fi - name: Set diff range id: range if: steps.force.outputs.run_all != 'true' run: | if [[ "${{ github.event_name }}" == "pull_request" ]]; then echo "base=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT" elif [[ "${{ github.event.created }}" == "true" ]]; then echo "base=${{ github.event.repository.default_branch }}" >> "$GITHUB_OUTPUT" else echo "base=${{ github.event.before }}" >> "$GITHUB_OUTPUT" fi echo "ref=${{ github.sha }}" >> "$GITHUB_OUTPUT" - name: Detect changes id: filter if: steps.force.outputs.run_all != 'true' uses: dorny/paths-filter@v3.0.2 with: base: ${{ steps.range.outputs.base }} ref: ${{ steps.range.outputs.ref }} filters: | frontend: - 'src-ui/**' - '.github/workflows/ci-frontend.yml' install-dependencies: needs: changes if: needs.changes.outputs.frontend_changed == 'true' name: Install Dependencies runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v6.0.2 - name: Install pnpm uses: pnpm/action-setup@v4.2.0 with: version: 10 - name: Use Node.js 24 uses: actions/setup-node@v6.2.0 with: node-version: 24.x cache: 'pnpm' cache-dependency-path: 'src-ui/pnpm-lock.yaml' - name: Cache frontend dependencies id: cache-frontend-deps uses: actions/cache@v5.0.3 with: path: | ~/.pnpm-store ~/.cache key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }} - name: Install dependencies run: cd src-ui && pnpm install lint: name: Lint needs: [changes, install-dependencies] if: needs.changes.outputs.frontend_changed == 'true' runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v6.0.2 - name: Install pnpm uses: pnpm/action-setup@v4.2.0 with: version: 10 - name: Use Node.js 24 uses: actions/setup-node@v6.2.0 with: node-version: 24.x cache: 'pnpm' cache-dependency-path: 'src-ui/pnpm-lock.yaml' - name: Cache frontend dependencies uses: actions/cache@v5.0.3 with: path: | ~/.pnpm-store ~/.cache key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }} - name: Re-link Angular CLI run: cd src-ui && pnpm link @angular/cli - name: Run lint run: cd src-ui && pnpm run lint unit-tests: name: "Unit Tests (${{ matrix.shard-index }}/${{ matrix.shard-count }})" needs: [changes, install-dependencies] if: needs.changes.outputs.frontend_changed == 'true' runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: node-version: [24.x] shard-index: [1, 2, 3, 4] shard-count: [4] steps: - name: Checkout uses: actions/checkout@v6.0.2 - name: Install pnpm uses: pnpm/action-setup@v4.2.0 with: version: 10 - name: Use Node.js 24 uses: actions/setup-node@v6.2.0 with: node-version: 24.x cache: 'pnpm' cache-dependency-path: 'src-ui/pnpm-lock.yaml' - name: Cache frontend dependencies uses: actions/cache@v5.0.3 with: path: | ~/.pnpm-store ~/.cache key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }} - name: Re-link Angular CLI run: cd src-ui && pnpm link @angular/cli - name: Run Jest unit tests run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }} - name: Upload test results to Codecov if: always() uses: codecov/codecov-action@v5.5.2 with: flags: frontend-node-${{ matrix.node-version }} directory: src-ui/ report_type: test_results - name: Upload coverage to Codecov uses: codecov/codecov-action@v5.5.2 with: flags: frontend-node-${{ matrix.node-version }} directory: src-ui/coverage/ e2e-tests: name: "E2E Tests (${{ matrix.shard-index }}/${{ matrix.shard-count }})" needs: [changes, install-dependencies] if: needs.changes.outputs.frontend_changed == 'true' runs-on: ubuntu-24.04 container: mcr.microsoft.com/playwright:v1.58.2-noble env: PLAYWRIGHT_BROWSERS_PATH: /ms-playwright PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 strategy: fail-fast: false matrix: node-version: [24.x] shard-index: [1, 2] shard-count: [2] steps: - name: Checkout uses: actions/checkout@v6.0.2 - name: Install pnpm uses: pnpm/action-setup@v4.2.0 with: version: 10 - name: Use Node.js 24 uses: actions/setup-node@v6.2.0 with: node-version: 24.x cache: 'pnpm' cache-dependency-path: 'src-ui/pnpm-lock.yaml' - name: Cache frontend dependencies uses: actions/cache@v5.0.3 with: path: | ~/.pnpm-store ~/.cache key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }} - name: Re-link Angular CLI run: cd src-ui && pnpm link @angular/cli - name: Install dependencies run: cd src-ui && pnpm install --no-frozen-lockfile - name: Run Playwright E2E tests run: cd src-ui && pnpm exec playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }} bundle-analysis: name: Bundle Analysis needs: [changes, unit-tests, e2e-tests] if: needs.changes.outputs.frontend_changed == 'true' runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v6.0.2 with: fetch-depth: 2 - name: Install pnpm uses: pnpm/action-setup@v4.2.0 with: version: 10 - name: Use Node.js 24 uses: actions/setup-node@v6.2.0 with: node-version: 24.x cache: 'pnpm' cache-dependency-path: 'src-ui/pnpm-lock.yaml' - name: Cache frontend dependencies uses: actions/cache@v5.0.3 with: path: | ~/.pnpm-store ~/.cache key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }} - name: Re-link Angular CLI run: cd src-ui && pnpm link @angular/cli - name: Build and analyze env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} run: cd src-ui && pnpm run build --configuration=production gate: name: Frontend CI Gate needs: [changes, install-dependencies, lint, unit-tests, e2e-tests, bundle-analysis] if: always() runs-on: ubuntu-slim steps: - name: Check gate run: | if [[ "${{ needs.changes.outputs.frontend_changed }}" != "true" ]]; then echo "No frontend-relevant changes detected." exit 0 fi if [[ "${{ needs['install-dependencies'].result }}" != "success" ]]; then echo "::error::Frontend install job result: ${{ needs['install-dependencies'].result }}" exit 1 fi if [[ "${{ needs.lint.result }}" != "success" ]]; then echo "::error::Frontend lint job result: ${{ needs.lint.result }}" exit 1 fi if [[ "${{ needs['unit-tests'].result }}" != "success" ]]; then echo "::error::Frontend unit-tests job result: ${{ needs['unit-tests'].result }}" exit 1 fi if [[ "${{ needs['e2e-tests'].result }}" != "success" ]]; then echo "::error::Frontend e2e-tests job result: ${{ needs['e2e-tests'].result }}" exit 1 fi if [[ "${{ needs['bundle-analysis'].result }}" != "success" ]]; then echo "::error::Frontend bundle-analysis job result: ${{ needs['bundle-analysis'].result }}" exit 1 fi echo "Frontend checks passed."