diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 894cf794b..e7eaaf69e 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -29,9 +29,13 @@ permissions: jobs: e2e: - name: Playwright Core E2E + name: Playwright Core E2E (shard ${{ matrix.shard }}/4) runs-on: ubuntu-24.04 timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + shard: [1, 2, 3, 4] steps: - name: Checkout code @@ -51,8 +55,10 @@ jobs: npx playwright install --with-deps chromium - name: Build Docker images for test environment + # GO_BUILD_TAGS="" drops the release build tag so the suite can enable + # mock fixtures; release-tag gating has its own -tags release Go tests. run: | - docker build -t pulse:test --target runtime . + docker build -t pulse:test --target runtime --build-arg GO_BUILD_TAGS="" . docker build -t pulse-mock-github:test ./tests/integration/mock-github-server env: PULSE_LICENSE_PUBLIC_KEY: ${{ secrets.PULSE_LICENSE_PUBLIC_KEY }} @@ -72,7 +78,7 @@ jobs: PULSE_E2E_SKIP_DOCKER: "true" PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL: "true" PULSE_E2E_PERF: "1" - run: npm test + run: npm test -- --shard=${{ matrix.shard }}/4 - name: Collect container logs if: always() @@ -89,7 +95,7 @@ jobs: if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: playwright-report + name: playwright-report-shard-${{ matrix.shard }} path: tests/integration/playwright-report/ retention-days: 30 @@ -97,6 +103,20 @@ jobs: if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: test-failures + name: test-failures-shard-${{ matrix.shard }} path: tests/integration/test-results/ retention-days: 7 + + e2e-verdict: + name: E2E verdict + runs-on: ubuntu-24.04 + needs: e2e + if: always() + steps: + - name: Check shard results + run: | + if [ "${{ needs.e2e.result }}" != "success" ]; then + echo "E2E shards did not all pass (result: ${{ needs.e2e.result }})" + exit 1 + fi + echo "All E2E shards passed" diff --git a/Dockerfile b/Dockerfile index 85a70ac1b..5a943500e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,11 @@ ARG BUILD_AGENT ARG VERSION ARG PULSE_LICENSE_PUBLIC_KEY_SHA256 ARG PULSE_UPDATE_SIGNING_PUBLIC_KEY +# Go build tags for the server binary. Shipped images use "release", which +# fail-closes mock fixtures, admin bypass, and licensing env overrides. The +# E2E test image builds with GO_BUILD_TAGS="" so the suite can drive mock +# fixtures the same way the local dev harness does. +ARG GO_BUILD_TAGS=release WORKDIR /app # Install build dependencies @@ -84,13 +89,13 @@ RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \ if [ -n "${PULSE_UPDATE_SIGNING_PUBLIC_KEY:-}" ] && [ "${UPDATE_PUBLIC_KEYS}" != "${PULSE_UPDATE_SIGNING_PUBLIC_KEY}" ]; then echo "Error: mounted update signing key does not match PULSE_UPDATE_SIGNING_PUBLIC_KEY." >&2; echo "Expected public key: ${PULSE_UPDATE_SIGNING_PUBLIC_KEY}" >&2; echo "Actual public key: ${UPDATE_PUBLIC_KEYS}" >&2; exit 1; fi && \ SERVER_LDFLAGS="$(./scripts/release_ldflags.sh server --version "${VERSION}" --build-time "${BUILD_TIME}" --git-commit "${GIT_COMMIT}" $(if [ -n "${LICENSE_PUBLIC_KEY}" ]; then printf '%s %s' --license-public-key "${LICENSE_PUBLIC_KEY}"; fi) $(if [ -n "${UPDATE_PUBLIC_KEYS}" ]; then printf '%s %s' --update-public-keys "${UPDATE_PUBLIC_KEYS}"; fi))" && \ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ - -tags release \ + -tags "${GO_BUILD_TAGS}" \ -ldflags="${SERVER_LDFLAGS}" \ -buildvcs=false \ -trimpath \ -o pulse-linux-amd64 ./cmd/pulse && \ CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build \ - -tags release \ + -tags "${GO_BUILD_TAGS}" \ -ldflags="${SERVER_LDFLAGS}" \ -buildvcs=false \ -trimpath \ diff --git a/tests/integration/playwright.config.ts b/tests/integration/playwright.config.ts index c8d09bc1f..328ca9383 100644 --- a/tests/integration/playwright.config.ts +++ b/tests/integration/playwright.config.ts @@ -17,6 +17,11 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 2 : 0, + /* On CI, a broken test environment fails most of the suite; abort early so + the run produces a red completed verdict with a report instead of + grinding until the job timeout cancels it with no verdict at all. */ + maxFailures: process.env.CI ? 20 : 0, + /* Opt out of parallel tests on CI */ workers: 1, // Update tests modify global state diff --git a/tests/integration/scripts/run-tests.sh b/tests/integration/scripts/run-tests.sh index 09ec968c0..8519d4557 100755 --- a/tests/integration/scripts/run-tests.sh +++ b/tests/integration/scripts/run-tests.sh @@ -44,7 +44,9 @@ ensure_test_images() { if ! docker image inspect pulse:test >/dev/null 2>&1; then echo "Building missing image: pulse:test" - docker build -t pulse:test -f "$REPO_ROOT/Dockerfile" "$REPO_ROOT" + # Test image drops the release build tag so the suite can enable mock + # fixtures without a demo entitlement. + docker build -t pulse:test --build-arg GO_BUILD_TAGS="" -f "$REPO_ROOT/Dockerfile" "$REPO_ROOT" fi } diff --git a/tests/integration/scripts/setup.sh b/tests/integration/scripts/setup.sh index 995795bb7..1cb3a53a4 100755 --- a/tests/integration/scripts/setup.sh +++ b/tests/integration/scripts/setup.sh @@ -86,7 +86,9 @@ echo "✅ Mock GitHub server image built" # Build Pulse test image (from root of repo) cd "$TEST_ROOT/../.." if [ -f "Dockerfile" ]; then - docker build -t pulse:test -f Dockerfile . + # Test image drops the release build tag so the suite can enable mock + # fixtures without a demo entitlement. + docker build -t pulse:test --build-arg GO_BUILD_TAGS="" -f Dockerfile . echo "✅ Pulse test image built" else echo "⚠️ Pulse Dockerfile not found. Using published image instead."