From cc8583f6b54ca2b50404aebe9761c0cd146bb43a Mon Sep 17 00:00:00 2001 From: Jared Lockhart <119884+jaredlockhart@users.noreply.github.com> Date: Wed, 18 Mar 2026 17:21:04 -0400 Subject: [PATCH 01/11] chore(nimbus): add GHA workflows for desktop integration tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because * The desktop integration tests currently only run on CircleCI * We are migrating all CI jobs from CircleCI to GitHub Actions (EXP-6320) This commit * Adds GHA workflows for all 5 desktop integration test suites: - `integration-nimbus-ui.yml` — Nimbus UI tests (`-m nimbus_ui`) - `integration-remote-settings-launch.yml` — Remote Settings launch test - `integration-remote-settings-all.yml` — Remote Settings all workflows - `integration-desktop-enrollment.yml` — Desktop enrollment tests - `integration-desktop-targeting.yml` — Desktop targeting (Release + Beta + Nightly matrix) * All workflows use the existing `setup-cached-build` composite action for Docker layer caching via Google Artifact Registry * All run on `ubuntu-24.04` with the same make targets and Docker Compose setup as CircleCI * All upload HTML test reports as GitHub Actions artifacts * Targeting tests use a matrix strategy for Release/Beta/Nightly channels Fixes #14583 Fixes #14584 Fixes #14585 Fixes #14586 Fixes #14587 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../integration-desktop-enrollment.yml | 48 +++++++++++++++ .../integration-desktop-targeting.yml | 58 +++++++++++++++++++ .github/workflows/integration-nimbus-ui.yml | 48 +++++++++++++++ .../integration-remote-settings-all.yml | 48 +++++++++++++++ .../integration-remote-settings-launch.yml | 48 +++++++++++++++ Makefile | 4 +- 6 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/integration-desktop-enrollment.yml create mode 100644 .github/workflows/integration-desktop-targeting.yml create mode 100644 .github/workflows/integration-nimbus-ui.yml create mode 100644 .github/workflows/integration-remote-settings-all.yml create mode 100644 .github/workflows/integration-remote-settings-launch.yml diff --git a/.github/workflows/integration-desktop-enrollment.yml b/.github/workflows/integration-desktop-enrollment.yml new file mode 100644 index 0000000000..3317969f14 --- /dev/null +++ b/.github/workflows/integration-desktop-enrollment.yml @@ -0,0 +1,48 @@ +name: Desktop Enrollment Integration Tests + +on: + push: + branches: + - main + paths: + - "experimenter/**" + - "application-services/**" + - ".github/workflows/integration-desktop-enrollment.yml" + pull_request: + paths: + - "experimenter/**" + - "application-services/**" + - ".github/workflows/integration-desktop-enrollment.yml" + merge_group: + types: [checks_requested] + +jobs: + test: + name: "Desktop Enrollment (Release Firefox)" + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + env: + FIREFOX_CHANNEL: release + PYTEST_ARGS: -k FIREFOX_DESKTOP -m desktop_enrollment --reruns 1 --base-url https://nginx/nimbus/ + PYTEST_BASE_URL: https://nginx/nimbus/ + steps: + - uses: actions/checkout@v6 + + - uses: ./.github/actions/setup-cached-build + with: + arch: x86_64 + gcp-project-number: ${{ vars.GCPV2_WORKLOAD_IDENTITY_POOL_PROJECT_NUMBER }} + + - name: Run integration tests + run: | + cp .env.integration-tests .env + make refresh SKIP_DUMMY=1 up_prod_detached integration_test_nimbus_desktop PYTEST_ARGS="$PYTEST_ARGS" + + - name: Upload test report + if: always() + uses: actions/upload-artifact@v4 + with: + name: desktop-enrollment-test-report + path: experimenter/tests/integration/test-reports/report.htm diff --git a/.github/workflows/integration-desktop-targeting.yml b/.github/workflows/integration-desktop-targeting.yml new file mode 100644 index 0000000000..c485fc0791 --- /dev/null +++ b/.github/workflows/integration-desktop-targeting.yml @@ -0,0 +1,58 @@ +name: Desktop Targeting Integration Tests + +on: + push: + branches: + - main + paths: + - "experimenter/**" + - "application-services/**" + - ".github/workflows/integration-desktop-targeting.yml" + pull_request: + paths: + - "experimenter/**" + - "application-services/**" + - ".github/workflows/integration-desktop-targeting.yml" + merge_group: + types: [checks_requested] + +jobs: + test: + strategy: + fail-fast: false + matrix: + include: + - channel: release + name: Release + - channel: beta + name: Beta + - channel: nightly + name: Nightly + name: "Desktop Targeting (${{ matrix.name }} Firefox)" + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + env: + FIREFOX_CHANNEL: ${{ matrix.channel }} + PYTEST_ARGS: -k FIREFOX_DESKTOP -m run_targeting -n 4 --reruns 1 --base-url https://nginx/nimbus/ + PYTEST_BASE_URL: https://nginx/nimbus/ + steps: + - uses: actions/checkout@v6 + + - uses: ./.github/actions/setup-cached-build + with: + arch: x86_64 + gcp-project-number: ${{ vars.GCPV2_WORKLOAD_IDENTITY_POOL_PROJECT_NUMBER }} + + - name: Run integration tests + run: | + cp .env.integration-tests .env + make refresh SKIP_DUMMY=1 FIREFOX_CHANNEL=${{ matrix.channel }} up_prod_detached integration_test_nimbus_desktop PYTEST_ARGS="$PYTEST_ARGS" + + - name: Upload test report + if: always() + uses: actions/upload-artifact@v4 + with: + name: desktop-targeting-${{ matrix.channel }}-test-report + path: experimenter/tests/integration/test-reports/report.htm diff --git a/.github/workflows/integration-nimbus-ui.yml b/.github/workflows/integration-nimbus-ui.yml new file mode 100644 index 0000000000..00f2ba1afb --- /dev/null +++ b/.github/workflows/integration-nimbus-ui.yml @@ -0,0 +1,48 @@ +name: Nimbus UI Integration Tests + +on: + push: + branches: + - main + paths: + - "experimenter/**" + - "application-services/**" + - ".github/workflows/integration-nimbus-ui.yml" + pull_request: + paths: + - "experimenter/**" + - "application-services/**" + - ".github/workflows/integration-nimbus-ui.yml" + merge_group: + types: [checks_requested] + +jobs: + test: + name: "Desktop Nimbus UI (Release Firefox)" + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + env: + FIREFOX_CHANNEL: release + PYTEST_ARGS: -m nimbus_ui -n 4 --reruns 1 --base-url https://nginx/nimbus/ + PYTEST_BASE_URL: https://nginx/nimbus/ + steps: + - uses: actions/checkout@v6 + + - uses: ./.github/actions/setup-cached-build + with: + arch: x86_64 + gcp-project-number: ${{ vars.GCPV2_WORKLOAD_IDENTITY_POOL_PROJECT_NUMBER }} + + - name: Run integration tests + run: | + cp .env.integration-tests .env + make refresh SKIP_DUMMY=1 up_prod_detached integration_test_nimbus_desktop PYTEST_ARGS="$PYTEST_ARGS" + + - name: Upload test report + if: always() + uses: actions/upload-artifact@v4 + with: + name: nimbus-ui-test-report + path: experimenter/tests/integration/test-reports/report.htm diff --git a/.github/workflows/integration-remote-settings-all.yml b/.github/workflows/integration-remote-settings-all.yml new file mode 100644 index 0000000000..4ce76f9a61 --- /dev/null +++ b/.github/workflows/integration-remote-settings-all.yml @@ -0,0 +1,48 @@ +name: Remote Settings All Workflows Integration Tests + +on: + push: + branches: + - main + paths: + - "experimenter/**" + - "application-services/**" + - ".github/workflows/integration-remote-settings-all.yml" + pull_request: + paths: + - "experimenter/**" + - "application-services/**" + - ".github/workflows/integration-remote-settings-all.yml" + merge_group: + types: [checks_requested] + +jobs: + test: + name: "Remote Settings All Workflows (Release Firefox Desktop)" + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + env: + FIREFOX_CHANNEL: release + PYTEST_ARGS: -k FIREFOX_DESKTOP --reruns 1 --base-url https://nginx/nimbus/ + PYTEST_BASE_URL: https://nginx/nimbus/ + steps: + - uses: actions/checkout@v6 + + - uses: ./.github/actions/setup-cached-build + with: + arch: x86_64 + gcp-project-number: ${{ vars.GCPV2_WORKLOAD_IDENTITY_POOL_PROJECT_NUMBER }} + + - name: Run integration tests + run: | + cp .env.integration-tests .env + make refresh SKIP_DUMMY=1 up_prod_detached integration_test_nimbus_desktop PYTEST_ARGS="$PYTEST_ARGS" + + - name: Upload test report + if: always() + uses: actions/upload-artifact@v4 + with: + name: remote-settings-all-test-report + path: experimenter/tests/integration/test-reports/report.htm diff --git a/.github/workflows/integration-remote-settings-launch.yml b/.github/workflows/integration-remote-settings-launch.yml new file mode 100644 index 0000000000..8f85e53165 --- /dev/null +++ b/.github/workflows/integration-remote-settings-launch.yml @@ -0,0 +1,48 @@ +name: Remote Settings Launch Integration Tests + +on: + push: + branches: + - main + paths: + - "experimenter/**" + - "application-services/**" + - ".github/workflows/integration-remote-settings-launch.yml" + pull_request: + paths: + - "experimenter/**" + - "application-services/**" + - ".github/workflows/integration-remote-settings-launch.yml" + merge_group: + types: [checks_requested] + +jobs: + test: + name: "Remote Settings Launch (All Applications)" + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + env: + FIREFOX_CHANNEL: release + PYTEST_ARGS: -m remote_settings_launch --reruns 1 --base-url https://nginx/nimbus/ + PYTEST_BASE_URL: https://nginx/nimbus/ + steps: + - uses: actions/checkout@v6 + + - uses: ./.github/actions/setup-cached-build + with: + arch: x86_64 + gcp-project-number: ${{ vars.GCPV2_WORKLOAD_IDENTITY_POOL_PROJECT_NUMBER }} + + - name: Run integration tests + run: | + cp .env.integration-tests .env + make refresh SKIP_DUMMY=1 up_prod_detached integration_test_nimbus_desktop PYTEST_ARGS="$PYTEST_ARGS" + + - name: Upload test report + if: always() + uses: actions/upload-artifact@v4 + with: + name: remote-settings-launch-test-report + path: experimenter/tests/integration/test-reports/report.htm diff --git a/Makefile b/Makefile index 1de8b4888c..15c472b92d 100644 --- a/Makefile +++ b/Makefile @@ -127,7 +127,7 @@ build_ui: ssl $(DOCKER_BUILD) --target ui -f experimenter/Dockerfile -t experimenter:ui experimenter/ build_prod: ssl build_megazords - $(DOCKER_BUILD) --target deploy -f experimenter/Dockerfile -t experimenter:deploy experimenter/ + $(DOCKER_BUILD) $(EXPERIMENTER_BUILD_FLAGS) --target deploy -f experimenter/Dockerfile -t experimenter:deploy experimenter/ compose_stop: $(CIRRUS_ENABLE) $(COMPOSE) kill || true @@ -265,7 +265,7 @@ CIRRUS_PYTHON_TYPECHECK_CREATESTUB = pyright -p . --createstub cirrus CIRRUS_GENERATE_DOCS = python cirrus/generate_docs.py cirrus_build: build_megazords - $(CIRRUS_ENABLE) $(DOCKER_BUILD) --target deploy -f cirrus/server/Dockerfile -t cirrus:deploy --build-context=fml=experimenter/experimenter/features/manifests/ cirrus/server/ + $(CIRRUS_ENABLE) $(DOCKER_BUILD) $(CIRRUS_BUILD_FLAGS) --target deploy -f cirrus/server/Dockerfile -t cirrus:deploy --build-context=fml=experimenter/experimenter/features/manifests/ cirrus/server/ cirrus_build_dev: build_megazords $(CIRRUS_ENABLE) $(DOCKER_BUILD) --target dev -f cirrus/server/Dockerfile -t cirrus:dev --build-context=fml=experimenter/experimenter/features/manifests/ cirrus/server/ From 3fd32e2f8d4f543b4023d726bcf1404c12a10a0f Mon Sep 17 00:00:00 2001 From: Jared Lockhart <119884+jaredlockhart@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:12:06 -0400 Subject: [PATCH 02/11] fix(nimbus): split remote-settings-all GHA workflow into 3 matrix jobs Because * The previous `-k FIREFOX_DESKTOP` filter selected cirrus_enrollment tests that require the Cirrus container, which isn't started by up_prod_detached without CIRRUS=1 * CircleCI splits this job into 3 parallel nodes by marker: remote_settings_experiments, remote_settings_rollouts, remote_settings_live_updates This commit * Converts the single job into a matrix strategy with 3 marker-based splits matching CircleCI's parallelism * Gives each matrix job a unique artifact name Co-Authored-By: Claude Opus 4.6 (1M context) --- .../integration-remote-settings-all.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration-remote-settings-all.yml b/.github/workflows/integration-remote-settings-all.yml index 4ce76f9a61..3a270042cf 100644 --- a/.github/workflows/integration-remote-settings-all.yml +++ b/.github/workflows/integration-remote-settings-all.yml @@ -18,14 +18,24 @@ on: jobs: test: - name: "Remote Settings All Workflows (Release Firefox Desktop)" + name: "Remote Settings All Workflows (${{ matrix.name }})" runs-on: ubuntu-24.04 permissions: contents: read id-token: write + strategy: + fail-fast: false + matrix: + include: + - name: Experiments + marker: remote_settings_experiments + - name: Rollouts + marker: remote_settings_rollouts + - name: Live Updates + marker: remote_settings_live_updates env: FIREFOX_CHANNEL: release - PYTEST_ARGS: -k FIREFOX_DESKTOP --reruns 1 --base-url https://nginx/nimbus/ + PYTEST_ARGS: -k FIREFOX_DESKTOP -m ${{ matrix.marker }} --reruns 1 --base-url https://nginx/nimbus/ PYTEST_BASE_URL: https://nginx/nimbus/ steps: - uses: actions/checkout@v6 @@ -44,5 +54,5 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: remote-settings-all-test-report + name: remote-settings-all-${{ matrix.marker }}-test-report path: experimenter/tests/integration/test-reports/report.htm From 85ee655acd004fd9ce4a8746555978c56189c4f3 Mon Sep 17 00:00:00 2001 From: Jared Lockhart <119884+jaredlockhart@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:47:31 -0400 Subject: [PATCH 03/11] chore(nimbus): use xl runners for GHA integration tests Because * GHA integration tests on ubuntu-24.04 (4 vCPU) run 40-80% slower than equivalent CircleCI jobs on large/xlarge instances * Target is ~10min per job to match CircleCI performance This commit * Bumps all 5 integration test workflows from ubuntu-24.04 (4 vCPU) to ubuntu-24.04-xl (8 vCPU, 32GB RAM) Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/integration-desktop-enrollment.yml | 2 +- .github/workflows/integration-desktop-targeting.yml | 2 +- .github/workflows/integration-nimbus-ui.yml | 2 +- .github/workflows/integration-remote-settings-all.yml | 2 +- .github/workflows/integration-remote-settings-launch.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/integration-desktop-enrollment.yml b/.github/workflows/integration-desktop-enrollment.yml index 3317969f14..2df9bf7464 100644 --- a/.github/workflows/integration-desktop-enrollment.yml +++ b/.github/workflows/integration-desktop-enrollment.yml @@ -19,7 +19,7 @@ on: jobs: test: name: "Desktop Enrollment (Release Firefox)" - runs-on: ubuntu-24.04 + runs-on: ubuntu-24.04-xl permissions: contents: read id-token: write diff --git a/.github/workflows/integration-desktop-targeting.yml b/.github/workflows/integration-desktop-targeting.yml index c485fc0791..f0af15443e 100644 --- a/.github/workflows/integration-desktop-targeting.yml +++ b/.github/workflows/integration-desktop-targeting.yml @@ -29,7 +29,7 @@ jobs: - channel: nightly name: Nightly name: "Desktop Targeting (${{ matrix.name }} Firefox)" - runs-on: ubuntu-24.04 + runs-on: ubuntu-24.04-xl permissions: contents: read id-token: write diff --git a/.github/workflows/integration-nimbus-ui.yml b/.github/workflows/integration-nimbus-ui.yml index 00f2ba1afb..5a412b287c 100644 --- a/.github/workflows/integration-nimbus-ui.yml +++ b/.github/workflows/integration-nimbus-ui.yml @@ -19,7 +19,7 @@ on: jobs: test: name: "Desktop Nimbus UI (Release Firefox)" - runs-on: ubuntu-24.04 + runs-on: ubuntu-24.04-xl permissions: contents: read id-token: write diff --git a/.github/workflows/integration-remote-settings-all.yml b/.github/workflows/integration-remote-settings-all.yml index 3a270042cf..e2aa1a021e 100644 --- a/.github/workflows/integration-remote-settings-all.yml +++ b/.github/workflows/integration-remote-settings-all.yml @@ -19,7 +19,7 @@ on: jobs: test: name: "Remote Settings All Workflows (${{ matrix.name }})" - runs-on: ubuntu-24.04 + runs-on: ubuntu-24.04-xl permissions: contents: read id-token: write diff --git a/.github/workflows/integration-remote-settings-launch.yml b/.github/workflows/integration-remote-settings-launch.yml index 8f85e53165..553627b85d 100644 --- a/.github/workflows/integration-remote-settings-launch.yml +++ b/.github/workflows/integration-remote-settings-launch.yml @@ -19,7 +19,7 @@ on: jobs: test: name: "Remote Settings Launch (All Applications)" - runs-on: ubuntu-24.04 + runs-on: ubuntu-24.04-xl permissions: contents: read id-token: write From db6a97cd06035cf985170833609131d7c2056821 Mon Sep 17 00:00:00 2001 From: Jared Lockhart <119884+jaredlockhart@users.noreply.github.com> Date: Thu, 19 Mar 2026 13:44:03 -0400 Subject: [PATCH 04/11] fix(nimbus): revert to ubuntu-24.04 runners for integration tests Because * ubuntu-24.04-xl runners are not available in the mozilla org, causing jobs to queue indefinitely waiting for a runner This commit * Reverts all integration test workflows back to ubuntu-24.04 Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/integration-desktop-enrollment.yml | 2 +- .github/workflows/integration-desktop-targeting.yml | 2 +- .github/workflows/integration-nimbus-ui.yml | 2 +- .github/workflows/integration-remote-settings-all.yml | 2 +- .github/workflows/integration-remote-settings-launch.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/integration-desktop-enrollment.yml b/.github/workflows/integration-desktop-enrollment.yml index 2df9bf7464..3317969f14 100644 --- a/.github/workflows/integration-desktop-enrollment.yml +++ b/.github/workflows/integration-desktop-enrollment.yml @@ -19,7 +19,7 @@ on: jobs: test: name: "Desktop Enrollment (Release Firefox)" - runs-on: ubuntu-24.04-xl + runs-on: ubuntu-24.04 permissions: contents: read id-token: write diff --git a/.github/workflows/integration-desktop-targeting.yml b/.github/workflows/integration-desktop-targeting.yml index f0af15443e..c485fc0791 100644 --- a/.github/workflows/integration-desktop-targeting.yml +++ b/.github/workflows/integration-desktop-targeting.yml @@ -29,7 +29,7 @@ jobs: - channel: nightly name: Nightly name: "Desktop Targeting (${{ matrix.name }} Firefox)" - runs-on: ubuntu-24.04-xl + runs-on: ubuntu-24.04 permissions: contents: read id-token: write diff --git a/.github/workflows/integration-nimbus-ui.yml b/.github/workflows/integration-nimbus-ui.yml index 5a412b287c..00f2ba1afb 100644 --- a/.github/workflows/integration-nimbus-ui.yml +++ b/.github/workflows/integration-nimbus-ui.yml @@ -19,7 +19,7 @@ on: jobs: test: name: "Desktop Nimbus UI (Release Firefox)" - runs-on: ubuntu-24.04-xl + runs-on: ubuntu-24.04 permissions: contents: read id-token: write diff --git a/.github/workflows/integration-remote-settings-all.yml b/.github/workflows/integration-remote-settings-all.yml index e2aa1a021e..3a270042cf 100644 --- a/.github/workflows/integration-remote-settings-all.yml +++ b/.github/workflows/integration-remote-settings-all.yml @@ -19,7 +19,7 @@ on: jobs: test: name: "Remote Settings All Workflows (${{ matrix.name }})" - runs-on: ubuntu-24.04-xl + runs-on: ubuntu-24.04 permissions: contents: read id-token: write diff --git a/.github/workflows/integration-remote-settings-launch.yml b/.github/workflows/integration-remote-settings-launch.yml index 553627b85d..8f85e53165 100644 --- a/.github/workflows/integration-remote-settings-launch.yml +++ b/.github/workflows/integration-remote-settings-launch.yml @@ -19,7 +19,7 @@ on: jobs: test: name: "Remote Settings Launch (All Applications)" - runs-on: ubuntu-24.04-xl + runs-on: ubuntu-24.04 permissions: contents: read id-token: write From 293cb797a61cce579c66a885c92f41d9e5fada37 Mon Sep 17 00:00:00 2001 From: Jared Lockhart <119884+jaredlockhart@users.noreply.github.com> Date: Thu, 19 Mar 2026 14:24:32 -0400 Subject: [PATCH 05/11] chore(nimbus): add run-integration-test composite action with retry Because * Transient infra failures (DNS resolution, upstream 500s) cause integration test jobs to fail before pytest even starts * The retry logic was duplicated across all 5 integration workflows This commit * Adds .github/actions/run-integration-test composite action that wraps the make command with configurable retry attempts and handles test report artifact upload * Migrates all 5 integration test workflows to use the new action Co-Authored-By: Claude Opus 4.6 (1M context) --- .../actions/run-integration-test/action.yml | 42 +++++++++++++++++++ .../integration-desktop-enrollment.yml | 12 +----- .../integration-desktop-targeting.yml | 13 ++---- .github/workflows/integration-nimbus-ui.yml | 12 +----- .../integration-remote-settings-all.yml | 12 +----- .../integration-remote-settings-launch.yml | 12 +----- 6 files changed, 53 insertions(+), 50 deletions(-) create mode 100644 .github/actions/run-integration-test/action.yml diff --git a/.github/actions/run-integration-test/action.yml b/.github/actions/run-integration-test/action.yml new file mode 100644 index 0000000000..e5403d9b71 --- /dev/null +++ b/.github/actions/run-integration-test/action.yml @@ -0,0 +1,42 @@ +name: "Run Integration Test" +description: "Run integration tests with retry on infra failures" + +inputs: + make-args: + description: "Extra arguments to pass to make (e.g. FIREFOX_CHANNEL=beta)" + default: "" + retries: + description: "Number of retry attempts after the first failure" + default: "1" + artifact-name: + description: "Name for the uploaded test report artifact" + required: true + +runs: + using: composite + steps: + - name: Run integration tests + shell: bash + run: | + cp .env.integration-tests .env + attempts=$(( ${{ inputs.retries }} + 1 )) + for attempt in $(seq 1 "$attempts"); do + echo "::group::Attempt $attempt of $attempts" + if make refresh SKIP_DUMMY=1 ${{ inputs.make-args }} up_prod_detached integration_test_nimbus_desktop PYTEST_ARGS="$PYTEST_ARGS"; then + echo "::endgroup::" + exit 0 + fi + echo "::endgroup::" + if [ "$attempt" -eq "$attempts" ]; then + echo "::error::All $attempts attempts failed" + exit 1 + fi + echo "::warning::Attempt $attempt failed, retrying..." + done + + - name: Upload test report + if: always() + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: experimenter/tests/integration/test-reports/report.htm diff --git a/.github/workflows/integration-desktop-enrollment.yml b/.github/workflows/integration-desktop-enrollment.yml index 3317969f14..1b29a970b7 100644 --- a/.github/workflows/integration-desktop-enrollment.yml +++ b/.github/workflows/integration-desktop-enrollment.yml @@ -35,14 +35,6 @@ jobs: arch: x86_64 gcp-project-number: ${{ vars.GCPV2_WORKLOAD_IDENTITY_POOL_PROJECT_NUMBER }} - - name: Run integration tests - run: | - cp .env.integration-tests .env - make refresh SKIP_DUMMY=1 up_prod_detached integration_test_nimbus_desktop PYTEST_ARGS="$PYTEST_ARGS" - - - name: Upload test report - if: always() - uses: actions/upload-artifact@v4 + - uses: ./.github/actions/run-integration-test with: - name: desktop-enrollment-test-report - path: experimenter/tests/integration/test-reports/report.htm + artifact-name: desktop-enrollment-test-report diff --git a/.github/workflows/integration-desktop-targeting.yml b/.github/workflows/integration-desktop-targeting.yml index c485fc0791..3d11b8f3f7 100644 --- a/.github/workflows/integration-desktop-targeting.yml +++ b/.github/workflows/integration-desktop-targeting.yml @@ -45,14 +45,7 @@ jobs: arch: x86_64 gcp-project-number: ${{ vars.GCPV2_WORKLOAD_IDENTITY_POOL_PROJECT_NUMBER }} - - name: Run integration tests - run: | - cp .env.integration-tests .env - make refresh SKIP_DUMMY=1 FIREFOX_CHANNEL=${{ matrix.channel }} up_prod_detached integration_test_nimbus_desktop PYTEST_ARGS="$PYTEST_ARGS" - - - name: Upload test report - if: always() - uses: actions/upload-artifact@v4 + - uses: ./.github/actions/run-integration-test with: - name: desktop-targeting-${{ matrix.channel }}-test-report - path: experimenter/tests/integration/test-reports/report.htm + make-args: FIREFOX_CHANNEL=${{ matrix.channel }} + artifact-name: desktop-targeting-${{ matrix.channel }}-test-report diff --git a/.github/workflows/integration-nimbus-ui.yml b/.github/workflows/integration-nimbus-ui.yml index 00f2ba1afb..92acdd5eec 100644 --- a/.github/workflows/integration-nimbus-ui.yml +++ b/.github/workflows/integration-nimbus-ui.yml @@ -35,14 +35,6 @@ jobs: arch: x86_64 gcp-project-number: ${{ vars.GCPV2_WORKLOAD_IDENTITY_POOL_PROJECT_NUMBER }} - - name: Run integration tests - run: | - cp .env.integration-tests .env - make refresh SKIP_DUMMY=1 up_prod_detached integration_test_nimbus_desktop PYTEST_ARGS="$PYTEST_ARGS" - - - name: Upload test report - if: always() - uses: actions/upload-artifact@v4 + - uses: ./.github/actions/run-integration-test with: - name: nimbus-ui-test-report - path: experimenter/tests/integration/test-reports/report.htm + artifact-name: nimbus-ui-test-report diff --git a/.github/workflows/integration-remote-settings-all.yml b/.github/workflows/integration-remote-settings-all.yml index 3a270042cf..7a90e4812b 100644 --- a/.github/workflows/integration-remote-settings-all.yml +++ b/.github/workflows/integration-remote-settings-all.yml @@ -45,14 +45,6 @@ jobs: arch: x86_64 gcp-project-number: ${{ vars.GCPV2_WORKLOAD_IDENTITY_POOL_PROJECT_NUMBER }} - - name: Run integration tests - run: | - cp .env.integration-tests .env - make refresh SKIP_DUMMY=1 up_prod_detached integration_test_nimbus_desktop PYTEST_ARGS="$PYTEST_ARGS" - - - name: Upload test report - if: always() - uses: actions/upload-artifact@v4 + - uses: ./.github/actions/run-integration-test with: - name: remote-settings-all-${{ matrix.marker }}-test-report - path: experimenter/tests/integration/test-reports/report.htm + artifact-name: remote-settings-all-${{ matrix.marker }}-test-report diff --git a/.github/workflows/integration-remote-settings-launch.yml b/.github/workflows/integration-remote-settings-launch.yml index 8f85e53165..8546ff4a78 100644 --- a/.github/workflows/integration-remote-settings-launch.yml +++ b/.github/workflows/integration-remote-settings-launch.yml @@ -35,14 +35,6 @@ jobs: arch: x86_64 gcp-project-number: ${{ vars.GCPV2_WORKLOAD_IDENTITY_POOL_PROJECT_NUMBER }} - - name: Run integration tests - run: | - cp .env.integration-tests .env - make refresh SKIP_DUMMY=1 up_prod_detached integration_test_nimbus_desktop PYTEST_ARGS="$PYTEST_ARGS" - - - name: Upload test report - if: always() - uses: actions/upload-artifact@v4 + - uses: ./.github/actions/run-integration-test with: - name: remote-settings-launch-test-report - path: experimenter/tests/integration/test-reports/report.htm + artifact-name: remote-settings-launch-test-report From c6d8043a60721bd6c4de8b21dbdb87a5e9618f4e Mon Sep 17 00:00:00 2001 From: Jared Lockhart <119884+jaredlockhart@users.noreply.github.com> Date: Thu, 19 Mar 2026 14:35:13 -0400 Subject: [PATCH 06/11] chore(nimbus): add test striping for parallel GHA integration jobs Because * Desktop targeting tests (222 cases) take 19-27min per channel * Desktop enrollment tests (6 cases) take 19min * GHA runners are 4 vCPU so we can't increase xdist parallelism This commit * Adds --split/--splits pytest options via conftest.py hook that deterministically stripes tests by modulo across GHA matrix nodes * Splits targeting into 2 nodes per channel (6 total, was 3) * Splits enrollment into 2 nodes (was 1) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../integration-desktop-enrollment.yml | 10 ++++++--- .../integration-desktop-targeting.yml | 15 +++++-------- .../tests/integration/nimbus/conftest.py | 22 +++++++++++++++++++ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/.github/workflows/integration-desktop-enrollment.yml b/.github/workflows/integration-desktop-enrollment.yml index 1b29a970b7..282ff443e5 100644 --- a/.github/workflows/integration-desktop-enrollment.yml +++ b/.github/workflows/integration-desktop-enrollment.yml @@ -18,14 +18,18 @@ on: jobs: test: - name: "Desktop Enrollment (Release Firefox)" + strategy: + fail-fast: false + matrix: + split: [0, 1] + name: "Desktop Enrollment (${{ matrix.split }})" runs-on: ubuntu-24.04 permissions: contents: read id-token: write env: FIREFOX_CHANNEL: release - PYTEST_ARGS: -k FIREFOX_DESKTOP -m desktop_enrollment --reruns 1 --base-url https://nginx/nimbus/ + PYTEST_ARGS: -k FIREFOX_DESKTOP -m desktop_enrollment --reruns 1 --splits 2 --split ${{ matrix.split }} --base-url https://nginx/nimbus/ PYTEST_BASE_URL: https://nginx/nimbus/ steps: - uses: actions/checkout@v6 @@ -37,4 +41,4 @@ jobs: - uses: ./.github/actions/run-integration-test with: - artifact-name: desktop-enrollment-test-report + artifact-name: desktop-enrollment-${{ matrix.split }}-test-report diff --git a/.github/workflows/integration-desktop-targeting.yml b/.github/workflows/integration-desktop-targeting.yml index 3d11b8f3f7..4c2c5a86a8 100644 --- a/.github/workflows/integration-desktop-targeting.yml +++ b/.github/workflows/integration-desktop-targeting.yml @@ -21,21 +21,16 @@ jobs: strategy: fail-fast: false matrix: - include: - - channel: release - name: Release - - channel: beta - name: Beta - - channel: nightly - name: Nightly - name: "Desktop Targeting (${{ matrix.name }} Firefox)" + channel: [release, beta, nightly] + split: [0, 1] + name: "Desktop Targeting (${{ matrix.channel }} ${{ matrix.split }})" runs-on: ubuntu-24.04 permissions: contents: read id-token: write env: FIREFOX_CHANNEL: ${{ matrix.channel }} - PYTEST_ARGS: -k FIREFOX_DESKTOP -m run_targeting -n 4 --reruns 1 --base-url https://nginx/nimbus/ + PYTEST_ARGS: -k FIREFOX_DESKTOP -m run_targeting -n 4 --reruns 1 --splits 2 --split ${{ matrix.split }} --base-url https://nginx/nimbus/ PYTEST_BASE_URL: https://nginx/nimbus/ steps: - uses: actions/checkout@v6 @@ -48,4 +43,4 @@ jobs: - uses: ./.github/actions/run-integration-test with: make-args: FIREFOX_CHANNEL=${{ matrix.channel }} - artifact-name: desktop-targeting-${{ matrix.channel }}-test-report + artifact-name: desktop-targeting-${{ matrix.channel }}-${{ matrix.split }}-test-report diff --git a/experimenter/tests/integration/nimbus/conftest.py b/experimenter/tests/integration/nimbus/conftest.py index dd808be2f8..61ab103e3f 100755 --- a/experimenter/tests/integration/nimbus/conftest.py +++ b/experimenter/tests/integration/nimbus/conftest.py @@ -30,6 +30,28 @@ from nimbus.pages.experimenter.home import HomePage from nimbus.utils import helpers +def pytest_addoption(parser): + parser.addoption( + "--split", + type=int, + default=None, + help="This node's split index (0-based)", + ) + parser.addoption( + "--splits", + type=int, + default=None, + help="Total number of split nodes", + ) + + +def pytest_collection_modifyitems(config, items): + split = config.getoption("--split") + splits = config.getoption("--splits") + if split is not None and splits is not None: + items[:] = [item for i, item in enumerate(items) if i % splits == split] + + APPLICATION_KINTO_REVIEW_PATH = { BaseExperimentApplications.FIREFOX_DESKTOP.value: ( "#/buckets/main-workspace/collections/nimbus-desktop-experiments/simple-review" From c7b41a07e2fee7c2c95bb2227c4bef6a1e259712 Mon Sep 17 00:00:00 2001 From: Jared Lockhart <119884+jaredlockhart@users.noreply.github.com> Date: Thu, 19 Mar 2026 14:45:45 -0400 Subject: [PATCH 07/11] style(nimbus): add missing blank line in conftest.py Co-Authored-By: Claude Opus 4.6 (1M context) --- experimenter/tests/integration/nimbus/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/experimenter/tests/integration/nimbus/conftest.py b/experimenter/tests/integration/nimbus/conftest.py index 61ab103e3f..28d4d5eb59 100755 --- a/experimenter/tests/integration/nimbus/conftest.py +++ b/experimenter/tests/integration/nimbus/conftest.py @@ -30,6 +30,7 @@ from nimbus.pages.experimenter.home import HomePage from nimbus.utils import helpers + def pytest_addoption(parser): parser.addoption( "--split", From fab5aaf1e545a902c95a53c60eff68788de6a5d2 Mon Sep 17 00:00:00 2001 From: Jared Lockhart <119884+jaredlockhart@users.noreply.github.com> Date: Thu, 19 Mar 2026 14:54:04 -0400 Subject: [PATCH 08/11] fix(nimbus): sort test items by nodeid before splitting Because * pytest doesn't guarantee stable collection order across runs * Without deterministic ordering, the modulo split could assign different tests to different nodes on each run This commit * Sorts items by nodeid before applying the modulo split Co-Authored-By: Claude Opus 4.6 (1M context) --- experimenter/tests/integration/nimbus/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/experimenter/tests/integration/nimbus/conftest.py b/experimenter/tests/integration/nimbus/conftest.py index 28d4d5eb59..796b0e2923 100755 --- a/experimenter/tests/integration/nimbus/conftest.py +++ b/experimenter/tests/integration/nimbus/conftest.py @@ -50,6 +50,7 @@ def pytest_collection_modifyitems(config, items): split = config.getoption("--split") splits = config.getoption("--splits") if split is not None and splits is not None: + items.sort(key=lambda item: item.nodeid) items[:] = [item for i, item in enumerate(items) if i % splits == split] From 931d339b1064272703091ba3284df19ef4d52629 Mon Sep 17 00:00:00 2001 From: Jared Lockhart <119884+jaredlockhart@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:35:55 -0400 Subject: [PATCH 09/11] chore(nimbus): add composite action path to integration workflow triggers Because * Changes to .github/actions/run-integration-test/ should trigger integration test workflows to verify the action still works This commit * Adds .github/actions/run-integration-test/** to path filters in all 5 integration test workflows Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/integration-desktop-enrollment.yml | 2 ++ .github/workflows/integration-desktop-targeting.yml | 2 ++ .github/workflows/integration-nimbus-ui.yml | 2 ++ .github/workflows/integration-remote-settings-all.yml | 2 ++ .github/workflows/integration-remote-settings-launch.yml | 2 ++ 5 files changed, 10 insertions(+) diff --git a/.github/workflows/integration-desktop-enrollment.yml b/.github/workflows/integration-desktop-enrollment.yml index 282ff443e5..5a9bc4821d 100644 --- a/.github/workflows/integration-desktop-enrollment.yml +++ b/.github/workflows/integration-desktop-enrollment.yml @@ -7,11 +7,13 @@ on: paths: - "experimenter/**" - "application-services/**" + - ".github/actions/run-integration-test/**" - ".github/workflows/integration-desktop-enrollment.yml" pull_request: paths: - "experimenter/**" - "application-services/**" + - ".github/actions/run-integration-test/**" - ".github/workflows/integration-desktop-enrollment.yml" merge_group: types: [checks_requested] diff --git a/.github/workflows/integration-desktop-targeting.yml b/.github/workflows/integration-desktop-targeting.yml index 4c2c5a86a8..646bef105b 100644 --- a/.github/workflows/integration-desktop-targeting.yml +++ b/.github/workflows/integration-desktop-targeting.yml @@ -7,11 +7,13 @@ on: paths: - "experimenter/**" - "application-services/**" + - ".github/actions/run-integration-test/**" - ".github/workflows/integration-desktop-targeting.yml" pull_request: paths: - "experimenter/**" - "application-services/**" + - ".github/actions/run-integration-test/**" - ".github/workflows/integration-desktop-targeting.yml" merge_group: types: [checks_requested] diff --git a/.github/workflows/integration-nimbus-ui.yml b/.github/workflows/integration-nimbus-ui.yml index 92acdd5eec..e7ccf56f2b 100644 --- a/.github/workflows/integration-nimbus-ui.yml +++ b/.github/workflows/integration-nimbus-ui.yml @@ -7,11 +7,13 @@ on: paths: - "experimenter/**" - "application-services/**" + - ".github/actions/run-integration-test/**" - ".github/workflows/integration-nimbus-ui.yml" pull_request: paths: - "experimenter/**" - "application-services/**" + - ".github/actions/run-integration-test/**" - ".github/workflows/integration-nimbus-ui.yml" merge_group: types: [checks_requested] diff --git a/.github/workflows/integration-remote-settings-all.yml b/.github/workflows/integration-remote-settings-all.yml index 7a90e4812b..749020725e 100644 --- a/.github/workflows/integration-remote-settings-all.yml +++ b/.github/workflows/integration-remote-settings-all.yml @@ -7,11 +7,13 @@ on: paths: - "experimenter/**" - "application-services/**" + - ".github/actions/run-integration-test/**" - ".github/workflows/integration-remote-settings-all.yml" pull_request: paths: - "experimenter/**" - "application-services/**" + - ".github/actions/run-integration-test/**" - ".github/workflows/integration-remote-settings-all.yml" merge_group: types: [checks_requested] diff --git a/.github/workflows/integration-remote-settings-launch.yml b/.github/workflows/integration-remote-settings-launch.yml index 8546ff4a78..1b8bd0acf4 100644 --- a/.github/workflows/integration-remote-settings-launch.yml +++ b/.github/workflows/integration-remote-settings-launch.yml @@ -7,11 +7,13 @@ on: paths: - "experimenter/**" - "application-services/**" + - ".github/actions/run-integration-test/**" - ".github/workflows/integration-remote-settings-launch.yml" pull_request: paths: - "experimenter/**" - "application-services/**" + - ".github/actions/run-integration-test/**" - ".github/workflows/integration-remote-settings-launch.yml" merge_group: types: [checks_requested] From a08f608c3fda4442f0f1d836044dde549f161d7d Mon Sep 17 00:00:00 2001 From: Jared Lockhart <119884+jaredlockhart@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:00:09 -0400 Subject: [PATCH 10/11] chore(nimbus): set BUILDKIT_PROGRESS=plain for CI-friendly Docker output Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/actions/run-integration-test/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/run-integration-test/action.yml b/.github/actions/run-integration-test/action.yml index e5403d9b71..204c7369fb 100644 --- a/.github/actions/run-integration-test/action.yml +++ b/.github/actions/run-integration-test/action.yml @@ -17,6 +17,8 @@ runs: steps: - name: Run integration tests shell: bash + env: + BUILDKIT_PROGRESS: plain run: | cp .env.integration-tests .env attempts=$(( ${{ inputs.retries }} + 1 )) From 8f3b624c00e107646d08dd8737cee88090bb559b Mon Sep 17 00:00:00 2001 From: Jared Lockhart <119884+jaredlockhart@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:08:08 -0400 Subject: [PATCH 11/11] chore(nimbus): set COMPOSE_ANSI=never to suppress pull progress bars in CI Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/actions/run-integration-test/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/run-integration-test/action.yml b/.github/actions/run-integration-test/action.yml index 204c7369fb..07f63b39a4 100644 --- a/.github/actions/run-integration-test/action.yml +++ b/.github/actions/run-integration-test/action.yml @@ -19,6 +19,7 @@ runs: shell: bash env: BUILDKIT_PROGRESS: plain + COMPOSE_ANSI: never run: | cp .env.integration-tests .env attempts=$(( ${{ inputs.retries }} + 1 ))