name: Automated Tests on: pull_request: push: branches: - master workflow_dispatch: schedule: - cron: "0 2 * * *" permissions: contents: read concurrency: group: frontend-tests-${{ github.workflow }}-${{ github.head_ref || github.ref_name }} cancel-in-progress: true jobs: format-tests: # CI runs on the repository's self-hosted runner pool. runs-on: [self-hosted, Linux, X64, pleno, frontend] timeout-minutes: 15 steps: - name: Checkout repository uses: actions/checkout@v5 - name: Setup Node.js uses: actions/setup-node@v5 with: node-version: 22 - name: Check AI workflow sync run: node scripts/sync-ai-workflow.mjs --check - name: Install dependencies run: npm ci --legacy-peer-deps - name: Check frontend test formatting run: npm run format:tests:check build-and-unit: needs: format-tests runs-on: [self-hosted, Linux, X64, pleno, frontend] timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@v5 - name: Setup Node.js uses: actions/setup-node@v5 with: node-version: 22 - name: Install dependencies run: npm ci --legacy-peer-deps - name: Lint run: npm run lint - name: Check i18n source consistency run: npm run i18n:v2:check - name: Build sanity check run: npm run build - name: Unit tests run: npm run test:unit env: VITEST_BATCH_SIZE: 5 e2e-pr: if: github.event_name != 'schedule' needs: build-and-unit name: E2E-pr-${{ matrix.suite }}-${{ matrix.project }} runs-on: [self-hosted, Linux, X64, pleno, frontend] timeout-minutes: 30 strategy: fail-fast: false max-parallel: 4 matrix: suite: [core, changed] project: [chromium-desktop, chromium-mobile] env: PLAYWRIGHT_ARTIFACT_NAMESPACE: e2e-pr-${{ matrix.suite }}-${{ matrix.project }} PLAYWRIGHT_REPORTER_MODE: line-html steps: - name: Checkout repository uses: actions/checkout@v5 with: fetch-depth: 0 - name: Resolve Playwright diff refs id: playwright-diff shell: bash env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} EVENT_NAME: ${{ github.event_name }} HEAD_SHA: ${{ github.sha }} PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} PUSH_BEFORE_SHA: ${{ github.event.before }} run: | set -euo pipefail zero_sha="0000000000000000000000000000000000000000" if [[ "$EVENT_NAME" == "pull_request" && -n "$PR_BASE_SHA" ]]; then base_ref="$PR_BASE_SHA" elif [[ -z "$PUSH_BEFORE_SHA" || "$PUSH_BEFORE_SHA" == "$zero_sha" ]]; then git fetch --no-tags --prune origin "$DEFAULT_BRANCH" base_ref="origin/$DEFAULT_BRANCH" else base_ref="$PUSH_BEFORE_SHA" fi echo "base=$base_ref" >> "$GITHUB_OUTPUT" echo "head=$HEAD_SHA" >> "$GITHUB_OUTPUT" - name: Setup Node.js uses: actions/setup-node@v5 with: node-version: 22 - name: Install dependencies run: npm ci --legacy-peer-deps - name: Install Playwright browsers run: node scripts/install-playwright-browsers.mjs chromium - name: Set Playwright dev server port shell: bash env: MATRIX_SUITE: ${{ matrix.suite }} MATRIX_PROJECT: ${{ matrix.project }} RUN_ID: ${{ github.run_id }} run: | set -euo pipefail workflow_offset=$(( (RUN_ID % 90) * 600 )) case "$MATRIX_SUITE" in core) suite_offset=0 ;; changed) suite_offset=10 ;; *) echo "Unsupported Playwright PR suite: $MATRIX_SUITE" >&2; exit 1 ;; esac case "$MATRIX_PROJECT" in chromium-desktop) project_offset=1 ;; chromium-mobile) project_offset=2 ;; *) echo "Unsupported Playwright PR project: $MATRIX_PROJECT" >&2; exit 1 ;; esac echo "PLAYWRIGHT_DEV_PORT=$((10000 + workflow_offset + suite_offset + project_offset))" >> "$GITHUB_ENV" - name: Run Playwright smoke tests if: matrix.suite == 'core' run: | ulimit -n 16384 || true npx playwright test --grep @smoke --project="${{ matrix.project }}" - name: Run Playwright PR core tests if: matrix.suite == 'core' run: | ulimit -n 16384 || true npm run test:e2e:pr -- --core-only --project="${{ matrix.project }}" - name: Run Playwright changed-area tests if: matrix.suite == 'changed' run: | ulimit -n 16384 || true npm run test:e2e:pr -- --changed-only --project="${{ matrix.project }}" --base="${{ steps.playwright-diff.outputs.base }}" --head="${{ steps.playwright-diff.outputs.head }}" - name: Upload Playwright report if: failure() || cancelled() continue-on-error: true uses: actions/upload-artifact@v4 with: name: playwright-report-pr-${{ matrix.suite }}-${{ matrix.project }} path: | output/playwright/${{ env.PLAYWRIGHT_ARTIFACT_NAMESPACE }}-* output/playwright/${{ env.PLAYWRIGHT_ARTIFACT_NAMESPACE }} if-no-files-found: ignore retention-days: 3 e2e-full: if: > always() && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.ref_name == github.event.repository.default_branch) && needs.build-and-unit.result == 'success' && (github.event_name == 'schedule' || needs.e2e-pr.result == 'success') needs: [build-and-unit, e2e-pr] name: E2E-full-${{ matrix.role }}-${{ matrix.browser_label }}-${{ matrix.device }} runs-on: [self-hosted, Linux, X64, pleno, frontend] timeout-minutes: 60 strategy: fail-fast: false max-parallel: 4 matrix: role: [customer, subuser, admin, superuser] browser: [chromium, firefox, webkit] device: [mobile, tablet, desktop] include: - browser: chromium browser_label: Chromium browser_install: chromium - browser: firefox browser_label: Firefox browser_install: firefox - browser: webkit browser_label: WebKit browser_install: webkit env: PLAYWRIGHT_ARTIFACT_NAMESPACE: e2e-full-${{ matrix.role }}-${{ matrix.browser }}-${{ matrix.device }} PLAYWRIGHT_REPORTER_MODE: line-html steps: - name: Checkout repository uses: actions/checkout@v5 - name: Setup Node.js uses: actions/setup-node@v5 with: node-version: 22 - name: Install dependencies run: npm ci --legacy-peer-deps - name: Install Playwright browsers run: node scripts/install-playwright-browsers.mjs ${{ matrix.browser_install }} - name: Set Playwright dev server port shell: bash env: MATRIX_ROLE: ${{ matrix.role }} MATRIX_BROWSER: ${{ matrix.browser }} MATRIX_DEVICE: ${{ matrix.device }} RUN_ID: ${{ github.run_id }} run: | set -euo pipefail workflow_offset=$(( (RUN_ID % 90) * 600 )) case "$MATRIX_ROLE" in customer) role_offset=0 ;; subuser) role_offset=100 ;; admin) role_offset=200 ;; superuser) role_offset=300 ;; *) echo "Unsupported Playwright role: $MATRIX_ROLE" >&2; exit 1 ;; esac case "$MATRIX_BROWSER" in chromium) browser_offset=0 ;; firefox) browser_offset=30 ;; webkit) browser_offset=60 ;; *) echo "Unsupported Playwright browser: $MATRIX_BROWSER" >&2; exit 1 ;; esac case "$MATRIX_DEVICE" in mobile) device_offset=1 ;; tablet) device_offset=2 ;; desktop) device_offset=3 ;; *) echo "Unsupported Playwright device: $MATRIX_DEVICE" >&2; exit 1 ;; esac echo "PLAYWRIGHT_DEV_PORT=$((10000 + workflow_offset + role_offset + browser_offset + device_offset))" >> "$GITHUB_ENV" - name: Run full Playwright slice run: | ulimit -n 16384 || true npm run test:e2e:full:slice -- --role="${{ matrix.role }}" --project="${{ matrix.browser }}-${{ matrix.device }}" - name: Upload Playwright report if: failure() || cancelled() continue-on-error: true uses: actions/upload-artifact@v4 with: name: playwright-report-full-${{ matrix.role }}-${{ matrix.browser }}-${{ matrix.device }} path: | output/playwright/${{ env.PLAYWRIGHT_ARTIFACT_NAMESPACE }}/report output/playwright/${{ env.PLAYWRIGHT_ARTIFACT_NAMESPACE }}/test-results output/playwright/test-lists/${{ matrix.role }}-${{ matrix.browser }}-${{ matrix.device }}.txt if-no-files-found: ignore retention-days: 3