From 3f982e3dc89d943a2d8d09fed2d35c2b1e874156 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 2 Sep 2026 01:19:08 +0000 Subject: [PATCH] ci(e2e): shard pull request browser tests (#41596) --- .github/CODEOWNERS | 1 + .github/workflows/web-e2e.yml | 278 ++++++++++++++++++++++++---------- e2e/AGENTS.md | 1 + e2e/package.json | 2 + e2e/scripts/setup.ts | 5 +- 5 files changed, 202 insertions(+), 85 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 070880ffc24..fd64ccdec9a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -28,6 +28,7 @@ # E2E /e2e/ @lyzno1 +/.github/workflows/web-e2e.yml @lyzno1 # Backend (default owner, more specific rules below will override) /api/ @QuantumGhost diff --git a/.github/workflows/web-e2e.yml b/.github/workflows/web-e2e.yml index e6bc08d44af..8700df8c1cc 100644 --- a/.github/workflows/web-e2e.yml +++ b/.github/workflows/web-e2e.yml @@ -12,13 +12,196 @@ permissions: contents: read jobs: - test: + core-build: + name: Prepare Core E2E Web Build + if: ${{ !inputs.run-external-runtime }} + runs-on: depot-ubuntu-24.04-4 + timeout-minutes: 30 + + steps: + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup web dependencies + uses: ./.github/actions/setup-web + + - name: Run E2E support unit tests + working-directory: ./e2e + run: vp run test:unit + + - name: Build production Web app + working-directory: ./e2e + env: + E2E_FORCE_WEB_BUILD: '1' + run: vp run e2e:web:build + + - name: Package Web build + run: >- + tar -cf e2e-web-build.tar -C web + .next/BUILD_ID + .next/e2e-web-build.sha256 + .next/standalone + .next/static + + - name: Upload Web build + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: core-e2e-web-build + path: e2e-web-build.tar + if-no-files-found: error + overwrite: true + retention-days: 7 + + chromium-full: + name: Chromium Full (${{ matrix.shard }}/3) + if: ${{ !inputs.run-external-runtime }} + needs: core-build + runs-on: ubuntu-24.04 + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + shard: [1, 2, 3] + env: + E2E_ADMIN_EMAIL: e2e-admin@example.com + E2E_ADMIN_NAME: E2E Admin + E2E_ADMIN_PASSWORD: E2eAdmin12345 + E2E_INIT_PASSWORD: E2eInit12345 + + steps: + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup web dependencies + uses: ./.github/actions/setup-web + + - name: Setup UV and Python + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + enable-cache: true + python-version: '3.12' + cache-dependency-glob: api/uv.lock + + - name: Install API dependencies + run: uv sync --project api --dev + + - name: Download Web build + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: core-e2e-web-build + + - name: Extract Web build + run: tar -xf e2e-web-build.tar -C web + + - name: Install Chromium + timeout-minutes: 15 + working-directory: ./e2e + run: vp run e2e:install:ci:chromium + + - name: Run Chromium full shard + working-directory: ./e2e + run: vp run e2e:full -- --shard ${{ matrix.shard }}/3 + + - name: Upload Cucumber report + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: cucumber-report-chromium-${{ matrix.shard }} + path: e2e/cucumber-report + if-no-files-found: ignore + overwrite: true + retention-days: 7 + + - name: Upload E2E logs + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: e2e-logs-chromium-${{ matrix.shard }} + path: e2e/.logs/*.log + if-no-files-found: ignore + include-hidden-files: true + overwrite: true + retention-days: 7 + + webkit-smoke: + name: WebKit Browser Smoke + if: ${{ !inputs.run-external-runtime }} + needs: core-build + runs-on: ubuntu-24.04 + timeout-minutes: 30 + env: + E2E_ADMIN_EMAIL: e2e-admin@example.com + E2E_ADMIN_NAME: E2E Admin + E2E_ADMIN_PASSWORD: E2eAdmin12345 + E2E_BROWSER: webkit + E2E_INIT_PASSWORD: E2eInit12345 + + steps: + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup web dependencies + uses: ./.github/actions/setup-web + + - name: Setup UV and Python + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + enable-cache: true + python-version: '3.12' + cache-dependency-glob: api/uv.lock + + - name: Install API dependencies + run: uv sync --project api --dev + + - name: Download Web build + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: core-e2e-web-build + + - name: Extract Web build + run: tar -xf e2e-web-build.tar -C web + + - name: Install WebKit + timeout-minutes: 15 + working-directory: ./e2e + run: vp run e2e:install:ci:webkit + + - name: Run WebKit browser smoke + working-directory: ./e2e + run: vp run e2e:full -- --tags '@browser-smoke' + + - name: Upload Cucumber report + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: cucumber-report-webkit + path: e2e/cucumber-report + if-no-files-found: ignore + overwrite: true + retention-days: 7 + + - name: Upload E2E logs + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: e2e-logs-webkit + path: e2e/.logs/*.log + if-no-files-found: ignore + include-hidden-files: true + overwrite: true + retention-days: 7 + + external-runtime: name: Web Full-Stack E2E + if: ${{ inputs.run-external-runtime }} runs-on: ubuntu-24.04 timeout-minutes: 120 - defaults: - run: - shell: bash steps: - name: Checkout code @@ -41,82 +224,12 @@ jobs: - name: Install API dependencies run: uv sync --project api --dev - - name: Run E2E support unit tests - if: ${{ !inputs.run-external-runtime }} - working-directory: ./e2e - run: vp run test:unit - - - name: Install Playwright browsers for core E2E - if: ${{ !inputs.run-external-runtime }} - timeout-minutes: 15 - working-directory: ./e2e - run: vp run e2e:install:ci - - - name: Install Chromium for external runtime E2E - if: ${{ inputs.run-external-runtime }} + - name: Install Chromium timeout-minutes: 15 working-directory: ./e2e run: vp run e2e:install:ci:chromium - - name: Run isolated source-api and built-web Cucumber E2E tests - if: ${{ !inputs.run-external-runtime }} - working-directory: ./e2e - env: - E2E_ADMIN_EMAIL: e2e-admin@example.com - E2E_ADMIN_NAME: E2E Admin - E2E_ADMIN_PASSWORD: E2eAdmin12345 - E2E_FORCE_WEB_BUILD: '1' - E2E_INIT_PASSWORD: E2eInit12345 - run: vp run e2e:full - - - name: Preserve Chromium E2E report and logs - if: ${{ !cancelled() && !inputs.run-external-runtime }} - run: | - if [[ -d e2e/cucumber-report ]]; then - mv e2e/cucumber-report e2e/cucumber-report-non-external - fi - if [[ -d e2e/.logs ]]; then - mv e2e/.logs e2e/.logs-non-external - fi - - - name: Run WebKit keyboard and browser smoke tests - if: ${{ !inputs.run-external-runtime }} - working-directory: ./e2e - env: - E2E_ADMIN_EMAIL: e2e-admin@example.com - E2E_ADMIN_NAME: E2E Admin - E2E_ADMIN_PASSWORD: E2eAdmin12345 - E2E_BROWSER: webkit - E2E_INIT_PASSWORD: E2eInit12345 - run: | - teardown_webkit_smoke() { - local run_status=$? - trap - EXIT - if ! vp run e2e:middleware:down; then - echo "::error title=E2E teardown failed::WebKit smoke middleware did not shut down cleanly." - if [[ "$run_status" -eq 0 ]]; then - run_status=1 - fi - fi - exit "$run_status" - } - - trap teardown_webkit_smoke EXIT - vp run e2e:middleware:up - vp run e2e -- --tags '@browser-smoke' - - - name: Preserve WebKit E2E report and logs - if: ${{ !cancelled() && !inputs.run-external-runtime }} - run: | - if [[ -d e2e/cucumber-report ]]; then - mv e2e/cucumber-report e2e/cucumber-report-webkit - fi - if [[ -d e2e/.logs ]]; then - mv e2e/.logs e2e/.logs-webkit - fi - - name: Run prepared and external runtime E2E tests - if: ${{ inputs.run-external-runtime }} working-directory: ./e2e env: E2E_ADMIN_EMAIL: e2e-admin@example.com @@ -149,10 +262,8 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: cucumber-report - path: | - e2e/cucumber-report - e2e/cucumber-report-non-external - e2e/cucumber-report-webkit + path: e2e/cucumber-report + overwrite: true retention-days: 7 - name: Upload E2E logs @@ -160,18 +271,17 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: e2e-logs - path: | - e2e/.logs/*.log - e2e/.logs-non-external/*.log - e2e/.logs-webkit/*.log + path: e2e/.logs/*.log include-hidden-files: true + overwrite: true retention-days: 7 - name: Upload E2E seed report - if: ${{ !cancelled() && inputs.run-external-runtime }} + if: ${{ !cancelled() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: e2e-seed-report path: e2e/seed-report if-no-files-found: ignore + overwrite: true retention-days: 7 diff --git a/e2e/AGENTS.md b/e2e/AGENTS.md index 240d77c1be8..73e70c710bb 100644 --- a/e2e/AGENTS.md +++ b/e2e/AGENTS.md @@ -17,6 +17,7 @@ Run commands from the repository root. Install dependencies and browsers once wi - Prepare and run external runtime scenarios: `E2E_START_AGENT_BACKEND=1 pnpm -C e2e e2e:external` - Seed against existing middleware without running Cucumber: `pnpm -C e2e seed -- --profile ` - Reset persisted E2E state: `pnpm -C e2e e2e:reset` +- Build the production Web artifact without starting services: `pnpm -C e2e e2e:web:build` - Middleware lifecycle: `pnpm -C e2e e2e:middleware:up` and `pnpm -C e2e e2e:middleware:down` - Scoped static checks: `vp check e2e` diff --git a/e2e/package.json b/e2e/package.json index 871766c9714..9c37769c1ce 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -15,6 +15,7 @@ "e2e:install": "playwright install --with-deps chromium webkit", "e2e:install:ci": "playwright install --with-deps --only-shell chromium webkit", "e2e:install:ci:chromium": "playwright install --with-deps --only-shell chromium", + "e2e:install:ci:webkit": "playwright install --with-deps --only-shell webkit", "e2e:middleware:down": "tsx ./scripts/setup.ts middleware-down", "e2e:middleware:up": "tsx ./scripts/setup.ts middleware-up", "e2e:post-merge": "tsx ./scripts/run-post-merge.ts", @@ -22,6 +23,7 @@ "e2e:prepared": "tsx ./scripts/run-prepared.ts", "e2e:prepared:prepare": "tsx ./scripts/run-cucumber.ts --seed-only --profile prepared", "e2e:reset": "tsx ./scripts/setup.ts reset", + "e2e:web:build": "tsx ./scripts/setup.ts web-build", "seed": "tsx ./scripts/run-cucumber.ts --seed-only", "test:unit": "vp test run", "type-check": "tsc" diff --git a/e2e/scripts/setup.ts b/e2e/scripts/setup.ts index 5aa99884560..6caf27435ac 100644 --- a/e2e/scripts/setup.ts +++ b/e2e/scripts/setup.ts @@ -569,7 +569,7 @@ export const startMiddleware = async () => { const printUsage = () => { console.log( - 'Usage: tsx ./scripts/setup.ts ', + 'Usage: tsx ./scripts/setup.ts ', ) } @@ -603,6 +603,9 @@ const main = async () => { case 'web': await startWeb() return + case 'web-build': + await ensureWebBuild() + return default: printUsage() process.exitCode = 1