test(goldens): render goldens with the real app theme #898
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| id-token: write | |
| pull-requests: write | |
| deployments: write | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| jobs: | |
| # Detect which files have changed | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| app: ${{ steps.filter.outputs.app }} | |
| functions: ${{ steps.filter.outputs.functions }} | |
| proto: ${{ steps.filter.outputs.proto }} | |
| formattable: ${{ steps.filter.outputs.formattable }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Check for file changes | |
| uses: dorny/paths-filter@v4 | |
| id: filter | |
| with: | |
| filters: | | |
| app: | |
| - 'lib/**' | |
| - 'web/**' | |
| - 'pubspec.yaml' | |
| - 'test/**' | |
| - 'test-e2e/**' | |
| - 'package.json' | |
| - 'playwright.config.ts' | |
| - 'android/**' | |
| - '.github/workflows/ci.yml' | |
| - 'mise.toml' | |
| functions: | |
| - 'functions/**' | |
| proto: | |
| - 'proto/**' | |
| # Any file the fmt job checks (dart format, prettier, shfmt), | |
| # anywhere in the repo — so a formattable file added under | |
| # docs/** or .claude/** is format-checked on its own PR instead | |
| # of failing a later, unrelated PR when the repo-wide fmt globs | |
| # first run. Keep in sync with the fmt job's checks. | |
| formattable: | |
| - '**/*.dart' | |
| - '**/*.js' | |
| - '**/*.ts' | |
| - '**/*.mjs' | |
| - '**/*.sh' | |
| # Check formatting before expensive jobs | |
| fmt: | |
| needs: changes | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| needs.changes.outputs.app == 'true' || | |
| needs.changes.outputs.functions == 'true' || | |
| needs.changes.outputs.formattable == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: '3.44.0' | |
| channel: 'stable' | |
| cache: true | |
| - name: Cache pub dependencies | |
| uses: actions/cache@v6 | |
| with: | |
| path: ~/.pub-cache | |
| key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pub- | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| - name: Get dependencies | |
| run: flutter pub get | |
| - name: Install shfmt | |
| run: | | |
| curl -sSL "https://github.com/mvdan/sh/releases/download/v3.8.0/shfmt_v3.8.0_linux_amd64" -o /usr/local/bin/shfmt | |
| chmod +x /usr/local/bin/shfmt | |
| - name: Check formatting | |
| run: | | |
| dart format --output=none --set-exit-if-changed . | |
| npm ci | |
| npx prettier --check "**/*.{js,ts,mjs}" | |
| mapfile -t SH_FILES < <(find . -name '*.sh' -not -path './.git/*' -not -path './.mise/*' -not -path '*/node_modules/*' | sort) | |
| [[ ${#SH_FILES[@]} -gt 0 ]] && shfmt -d -i 0 -ci "${SH_FILES[@]}" | |
| # Lint proto files and check for breaking changes against main. | |
| # buf breaking runs on PRs only (bufbuild/buf-action skips it on push to main | |
| # where the change is already merged). FILE stability level (configured in | |
| # proto/buf.yaml) is appropriate for v1alpha; switch to WIRE_JSON_COMPATIBLE | |
| # when the API graduates to v1. | |
| proto: | |
| needs: changes | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| needs.changes.outputs.proto == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 # buf breaking needs full history to compare against main | |
| - uses: bufbuild/buf-action@v1 | |
| with: | |
| input: proto | |
| push: false | |
| pr_comment: false | |
| breaking_against: "https://github.com/${{ github.repository }}.git#branch=main,subdir=proto" | |
| # Analyze code in parallel with tests so builds can start sooner | |
| analyze: | |
| needs: changes | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| needs.changes.outputs.app == 'true' || | |
| needs.changes.outputs.functions == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Setup Flutter App | |
| uses: ./.github/actions/setup-flutter-app | |
| with: | |
| google-services-json: ${{ secrets.GOOGLE_SERVICES_JSON }} | |
| generate-mocks: 'true' | |
| - name: Analyze code | |
| run: flutter analyze --no-fatal-infos | |
| # Run tests once before building | |
| test: | |
| needs: changes | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| needs.changes.outputs.app == 'true' || | |
| needs.changes.outputs.functions == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Setup Flutter App | |
| uses: ./.github/actions/setup-flutter-app | |
| with: | |
| google-services-json: ${{ secrets.GOOGLE_SERVICES_JSON }} | |
| generate-mocks: 'true' | |
| - name: Run tests with coverage | |
| run: flutter test --coverage | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v7 | |
| with: | |
| files: coverage/lcov.info | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| - name: Test Pages Functions | |
| run: | | |
| cd functions | |
| npm ci | |
| npm test | |
| # Build web after analysis passes (tests run in parallel; deploy gates on test) | |
| build-web: | |
| needs: [changes, analyze, fmt] | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| needs.changes.outputs.app == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Setup Flutter App | |
| uses: ./.github/actions/setup-flutter-app | |
| with: | |
| google-services-json: ${{ secrets.GOOGLE_SERVICES_JSON }} | |
| - name: Get git version info | |
| id: git_version | |
| run: scripts/get_version_info.sh github >> $GITHUB_OUTPUT | |
| - name: Build web | |
| run: | | |
| flutter build web --release --base-href "/" --source-maps \ | |
| --dart-define=GIT_TAG=${{ steps.git_version.outputs.git_tag }} \ | |
| --dart-define=GIT_COMMIT=${{ steps.git_version.outputs.git_commit }} \ | |
| --dart-define=GIT_BRANCH=${{ steps.git_version.outputs.git_branch }} \ | |
| --dart-define=BUILD_VERSION=${{ steps.git_version.outputs.version }} \ | |
| --dart-define=BUILD_TIME=${{ steps.git_version.outputs.build_time }} | |
| - name: Upload source maps artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: source-maps | |
| path: build/web/main.dart.js.map | |
| retention-days: 7 | |
| - name: Strip source maps from deployable artifact | |
| run: rm -f build/web/main.dart.js.map | |
| - name: Upload build artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: web-build | |
| path: build/web | |
| # Run E2E tests on web build | |
| test-e2e-web: | |
| needs: build-web | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| - name: Download web build artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: web-build | |
| path: build/web | |
| - name: Install npm dependencies | |
| run: npm ci | |
| - name: Cache Playwright browsers | |
| uses: actions/cache@v6 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-chromium-${{ hashFiles('package-lock.json') }} | |
| restore-keys: playwright-chromium- | |
| - name: Install Playwright browsers | |
| run: npx playwright install --with-deps chromium | |
| - name: Start http-server in background | |
| run: | | |
| npx http-server build/web -p 8080 -c-1 -a 127.0.0.1 --proxy http://127.0.0.1:8080? > /dev/null 2>&1 & | |
| echo $! > .http-server.pid | |
| - name: Wait for server to be ready | |
| run: | | |
| timeout 30 bash -c 'until curl -s http://127.0.0.1:8080 > /dev/null; do sleep 1; done' || exit 1 | |
| # Additional wait to ensure Flutter app fully initializes | |
| sleep 3 | |
| # Verify server responds to festival route (tests SPA routing) | |
| curl -f -s http://127.0.0.1:8080/cbf2025 > /dev/null || (echo "SPA routing not working" && exit 1) | |
| - name: Run Playwright tests | |
| run: npx playwright test | |
| - name: Upload Playwright report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: playwright-report | |
| path: playwright-report/ | |
| retention-days: 7 | |
| - name: Stop http-server | |
| if: always() | |
| run: | | |
| if [ -f .http-server.pid ]; then | |
| kill $(cat .http-server.pid) || true | |
| rm .http-server.pid | |
| fi | |
| # Build Android after analysis passes (tests run in parallel) | |
| build-android: | |
| needs: [changes, analyze, fmt] | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.actor != 'dependabot[bot]' && ( | |
| github.event_name == 'workflow_dispatch' || | |
| needs.changes.outputs.app == 'true' | |
| ) | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: '17' | |
| distribution: 'temurin' | |
| - name: Cache Gradle dependencies | |
| uses: actions/cache@v6 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-gradle- | |
| - name: Setup Flutter App | |
| uses: ./.github/actions/setup-flutter-app | |
| with: | |
| google-services-json: ${{ secrets.GOOGLE_SERVICES_JSON }} | |
| - name: Get git version info | |
| id: git_version | |
| run: scripts/get_version_info.sh github >> $GITHUB_OUTPUT | |
| - name: Build release APK (debug-signed) | |
| # Build with release config (enabling R8 minification) so ProGuard/R8 | |
| # issues are caught on every PR. key.properties is absent here, so | |
| # build.gradle automatically falls back to the debug signing config. | |
| run: | | |
| flutter build apk --release \ | |
| --dart-define=GIT_TAG=${{ steps.git_version.outputs.git_tag }} \ | |
| --dart-define=GIT_COMMIT=${{ steps.git_version.outputs.git_commit }} \ | |
| --dart-define=GIT_BRANCH=${{ steps.git_version.outputs.git_branch }} \ | |
| --dart-define=BUILD_VERSION=${{ steps.git_version.outputs.version }} \ | |
| --dart-define=BUILD_TIME=${{ steps.git_version.outputs.build_time }} | |
| - name: Upload release APK | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: app-release-apk | |
| path: build/app/outputs/flutter-apk/app-release.apk | |
| if-no-files-found: error | |
| deploy-web-preview: | |
| needs: [changes, build-web, test-e2e-web, test] | |
| runs-on: ubuntu-latest | |
| outputs: | |
| preview-url: ${{ steps.set-url.outputs.url }} | |
| if: | | |
| github.actor != 'dependabot[bot]' && ( | |
| github.event_name == 'workflow_dispatch' || | |
| needs.changes.outputs.app == 'true' || | |
| needs.changes.outputs.functions == 'true' | |
| ) | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Download build artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: web-build | |
| path: build/web | |
| - name: Deploy to Cloudflare Pages (Staging/PR Previews) | |
| id: deploy | |
| uses: cloudflare/wrangler-action@v4 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| command: pages deploy build/web --project-name=staging-cambeerfestival --branch=${{ github.head_ref || github.ref_name }} | |
| - name: Set preview URL output | |
| id: set-url | |
| run: | | |
| URL="${{ steps.deploy.outputs.pages-deployment-alias-url }}" | |
| [ -z "$URL" ] && URL="${{ steps.deploy.outputs.deployment-url }}" | |
| echo "url=$URL" >> "$GITHUB_OUTPUT" | |
| - name: Comment PR with Preview URL | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const aliasUrl = '${{ steps.deploy.outputs.pages-deployment-alias-url }}'; | |
| const deployUrl = '${{ steps.deploy.outputs.deployment-url }}'; | |
| const previewUrl = aliasUrl || deployUrl; | |
| const comment = `## 🚀 Cloudflare Pages Preview | |
| Your preview deployment is ready! | |
| **Preview URL**: ${previewUrl} | |
| This preview will be automatically updated when you push new commits to this PR.`; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: comment | |
| }); | |
| # Run CSP smoke test against the live Cloudflare Pages preview. | |
| # The normal test-e2e-web job runs against a plain local server where | |
| # web/_headers is never applied — CSP violations are invisible there. | |
| # This job hits the real deployment so any missing CSP allowances fail fast. | |
| smoke-test-preview: | |
| needs: [deploy-web-preview] | |
| runs-on: ubuntu-latest | |
| if: needs.deploy-web-preview.result == 'success' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| - name: Install npm dependencies | |
| run: npm ci | |
| - name: Cache Playwright browsers | |
| uses: actions/cache@v6 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-chromium-${{ hashFiles('package-lock.json') }} | |
| restore-keys: playwright-chromium- | |
| - name: Install Playwright browsers | |
| run: npx playwright install --with-deps chromium | |
| - name: Run CSP smoke test against deployed preview | |
| run: npx playwright test test-e2e/csp-smoke.spec.ts | |
| env: | |
| BASE_URL: ${{ needs.deploy-web-preview.outputs.preview-url }} | |
| - name: Upload smoke test report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: csp-smoke-report | |
| path: playwright-report/ | |
| retention-days: 7 |