diff --git a/.github/actions/ensure-playwright-image/action.yml b/.github/actions/ensure-playwright-image/action.yml new file mode 100644 index 000000000000..fab5bb8b5d4c --- /dev/null +++ b/.github/actions/ensure-playwright-image/action.yml @@ -0,0 +1,106 @@ +name: 'Ensure Playwright Docker Image' +description: 'Checks if the custom Playwright Docker image exists in GHCR. Builds and pushes it if missing.' + +inputs: + github_token: + description: 'GitHub token with packages:write permission' + required: true + +outputs: + image: + description: 'The full Docker image reference (e.g. ghcr.io/owner/repo/playwright:v1.56.0)' + value: ${{ steps.image.outputs.ref }} + +runs: + using: 'composite' + steps: + - name: Get Playwright version + id: playwright + run: | + VERSION=$(node -p " + const pkg = require('./dev-packages/browser-integration-tests/package.json'); + const v = (pkg.dependencies || {})['@playwright/test'] || (pkg.devDependencies || {})['@playwright/test']; + v.replace(/[~^]/, '') + ") + echo "version=$VERSION" >> $GITHUB_OUTPUT + shell: bash + + - name: Verify @playwright/test version consistency + shell: bash + run: | + CANONICAL=$(node -p " + const pkg = require('./dev-packages/browser-integration-tests/package.json'); + (pkg.dependencies || {})['@playwright/test'] || (pkg.devDependencies || {})['@playwright/test'] + ") + echo "Canonical @playwright/test version: $CANONICAL" + MISMATCHES=0 + + check_version() { + local file="$1" + local version + version=$(node -p " + const pkg = require('./$file'); + (pkg.dependencies || {})['@playwright/test'] || + (pkg.devDependencies || {})['@playwright/test'] || + (pkg.peerDependencies || {})['@playwright/test'] || + 'not found' + ") + if [ "$version" != "not found" ] && [ "$version" != "$CANONICAL" ]; then + echo "::error file=$file::@playwright/test version mismatch: $version (expected $CANONICAL)" + MISMATCHES=$((MISMATCHES + 1)) + fi + } + + check_version "dev-packages/test-utils/package.json" + + for pkg in dev-packages/e2e-tests/test-applications/*/package.json; do + check_version "$pkg" + done + + if [ "$MISMATCHES" -gt 0 ]; then + echo "Found $MISMATCHES package(s) with mismatched @playwright/test versions." + echo "All packages must use the same version as dev-packages/browser-integration-tests ($CANONICAL)." + exit 1 + fi + echo "All @playwright/test versions are consistent ($CANONICAL)" + + - name: Set image reference + id: image + shell: bash + run: | + DOCKERFILE_HASH=$(sha256sum .github/docker/playwright.Dockerfile | cut -c1-8) + TAG="v${{ steps.playwright.outputs.version }}-${DOCKERFILE_HASH}" + echo "ref=ghcr.io/${{ github.repository }}/playwright:${TAG}" >> $GITHUB_OUTPUT + + - name: Check if image already exists + id: check + shell: bash + run: | + if docker manifest inspect "${{ steps.image.outputs.ref }}" > /dev/null 2>&1; then + echo "exists=true" >> $GITHUB_OUTPUT + echo "Image ${{ steps.image.outputs.ref }} already exists, skipping build." + else + echo "exists=false" >> $GITHUB_OUTPUT + echo "Image ${{ steps.image.outputs.ref }} not found, will build." + fi + env: + DOCKER_CLI_EXPERIMENTAL: enabled + + - name: Log in to GHCR + if: steps.check.outputs.exists == 'false' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ inputs.github_token }} + + - name: Build and push + if: steps.check.outputs.exists == 'false' + uses: docker/build-push-action@v6 + with: + file: .github/docker/playwright.Dockerfile + push: true + build-args: | + PLAYWRIGHT_VERSION=${{ steps.playwright.outputs.version }} + tags: | + ${{ steps.image.outputs.ref }} diff --git a/.github/actions/install-playwright/action.yml b/.github/actions/install-playwright/action.yml deleted file mode 100644 index 8ca47ce04081..000000000000 --- a/.github/actions/install-playwright/action.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: 'Install Playwright dependencies' -description: 'Installs Playwright dependencies and caches them.' -inputs: - browsers: - description: 'What browsers to install.' - default: 'chromium webkit firefox' - cwd: - description: 'The working directory to run Playwright in.' - default: '.' - -runs: - using: 'composite' - steps: - - name: Get Playwright version - id: playwright-version - run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT - shell: bash - working-directory: ${{ inputs.cwd }} - - - name: Restore cached playwright binaries - uses: actions/cache/restore@v4 - id: playwright-cache - with: - path: | - ~/.cache/ms-playwright - # Bump the iteration when bumping runner images to use a new cache - key: playwright-${{ runner.os }}-iteration-1-${{ steps.playwright-version.outputs.version }} - - # We always install all browsers, if uncached - - name: Install Playwright dependencies (uncached) - run: npx playwright install chromium webkit firefox --with-deps - if: steps.playwright-cache.outputs.cache-hit != 'true' - shell: bash - working-directory: ${{ inputs.cwd }} - - - name: Install Playwright system dependencies only (cached) - env: - PLAYWRIGHT_BROWSERS: ${{ inputs.browsers || 'chromium webkit firefox' }} - run: npx playwright install-deps "$PLAYWRIGHT_BROWSERS" - if: steps.playwright-cache.outputs.cache-hit == 'true' - shell: bash - working-directory: ${{ inputs.cwd }} - - # Only store cache on develop branch - - name: Store cached playwright binaries - uses: actions/cache/save@v4 - if: github.event_name == 'push' && github.ref == 'refs/heads/develop' - with: - path: | - ~/.cache/ms-playwright - # Bump the iteration when bumping runner images to use a new cache - key: playwright-${{ runner.os }}-iteration-1-${{ steps.playwright-version.outputs.version }} diff --git a/.github/docker/playwright.Dockerfile b/.github/docker/playwright.Dockerfile new file mode 100644 index 000000000000..e08df49898e7 --- /dev/null +++ b/.github/docker/playwright.Dockerfile @@ -0,0 +1,7 @@ +# PLAYWRIGHT_VERSION is passed as a build arg by the ensure-playwright-image action. +# The canonical source is dev-packages/browser-integration-tests/package.json. +ARG PLAYWRIGHT_VERSION +FROM mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-noble + +# Install yarn (v1) for the monorepo +RUN npm install -g yarn@1.22.22 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6032cee30fdc..638923a88087 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,6 +71,24 @@ jobs: permissions: pull-requests: read + job_playwright_image: + name: Ensure Playwright Image + runs-on: ubuntu-24.04 + permissions: + packages: write + outputs: + image: ${{ steps.ensure.outputs.image }} + steps: + - name: Check out current commit + uses: actions/checkout@v6 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Ensure Playwright image + id: ensure + uses: ./.github/actions/ensure-playwright-image + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + job_build: name: Build needs: job_get_metadata @@ -475,9 +493,15 @@ jobs: name: Playwright ${{ matrix.bundle }}${{ matrix.project && matrix.project != 'chromium' && format(' {0}', matrix.project) || ''}}${{ matrix.shard && format(' ({0}/{1})', matrix.shard, matrix.shards) || ''}} Tests - needs: [job_get_metadata, job_build] + needs: [job_get_metadata, job_build, job_playwright_image] if: needs.job_build.outputs.changed_browser_integration == 'true' || github.event_name != 'pull_request' runs-on: ubuntu-24.04-large-js + container: + image: ${{ needs.job_playwright_image.outputs.image }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + options: --ipc=host -e HOME=/root timeout-minutes: 25 strategy: fail-fast: false @@ -540,11 +564,6 @@ jobs: with: dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} - - name: Install Playwright - uses: ./.github/actions/install-playwright - with: - browsers: ${{ matrix.project }} - - name: Run Playwright tests env: PW_BUNDLE: ${{ matrix.bundle }} @@ -578,9 +597,15 @@ jobs: job_browser_loader_tests: name: PW ${{ matrix.bundle }} Tests - needs: [job_get_metadata, job_build] + needs: [job_get_metadata, job_build, job_playwright_image] if: needs.job_build.outputs.changed_browser_integration == 'true' || github.event_name != 'pull_request' runs-on: ubuntu-24.04 + container: + image: ${{ needs.job_playwright_image.outputs.image }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + options: --ipc=host -e HOME=/root timeout-minutes: 15 strategy: fail-fast: false @@ -608,11 +633,6 @@ jobs: with: dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} - - name: Install Playwright - uses: ./.github/actions/install-playwright - with: - browsers: chromium - - name: Run Playwright Loader tests env: PW_BUNDLE: ${{ matrix.bundle }} @@ -771,9 +791,15 @@ jobs: job_remix_integration_tests: name: Remix (Node ${{ matrix.node }}) Tests - needs: [job_get_metadata, job_build] + needs: [job_get_metadata, job_build, job_playwright_image] if: needs.job_build.outputs.changed_remix == 'true' || github.event_name != 'pull_request' runs-on: ubuntu-24.04 + container: + image: ${{ needs.job_playwright_image.outputs.image }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + options: --ipc=host -e HOME=/root timeout-minutes: 15 strategy: fail-fast: false @@ -793,11 +819,6 @@ jobs: with: dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} - - name: Install Playwright - uses: ./.github/actions/install-playwright - with: - browsers: chromium - - name: Run integration tests env: NODE_VERSION: ${{ matrix.node }} @@ -873,8 +894,15 @@ jobs: # See: https://github.com/actions/runner/issues/2205 if: always() && needs.job_e2e_prepare.result == 'success' && needs.job_e2e_prepare.outputs.matrix != '{"include":[]}' - needs: [job_get_metadata, job_build, job_e2e_prepare] + && needs.job_playwright_image.result == 'success' + needs: [job_get_metadata, job_build, job_e2e_prepare, job_playwright_image] runs-on: ubuntu-24.04 + container: + image: ${{ needs.job_playwright_image.outputs.image }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + options: --ipc=host -e HOME=/root timeout-minutes: 15 env: # We just use a dummy DSN here, only send to the tunnel anyhow @@ -956,12 +984,6 @@ jobs: env: SENTRY_E2E_WORKSPACE_ROOT: ${{ github.workspace }} - - name: Install Playwright - uses: ./.github/actions/install-playwright - with: - browsers: chromium - cwd: ${{ runner.temp }}/test-application - - name: Run E2E test working-directory: ${{ runner.temp }}/test-application timeout-minutes: 10 @@ -1002,9 +1024,16 @@ jobs: if: always() && needs.job_get_metadata.outputs.is_release != 'true' && needs.job_e2e_prepare.result == 'success' && needs.job_e2e_prepare.outputs.matrix-optional != '{"include":[]}' && (github.event_name != 'pull_request' || - github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' - needs: [job_get_metadata, job_build, job_e2e_prepare] + github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' && + needs.job_playwright_image.result == 'success' + needs: [job_get_metadata, job_build, job_e2e_prepare, job_playwright_image] runs-on: ubuntu-24.04 + container: + image: ${{ needs.job_playwright_image.outputs.image }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + options: --ipc=host -e HOME=/root timeout-minutes: 15 env: E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} @@ -1071,12 +1100,6 @@ jobs: timeout-minutes: 7 run: ${{ matrix.build-command || 'pnpm test:build' }} - - name: Install Playwright - uses: ./.github/actions/install-playwright - with: - browsers: chromium - cwd: ${{ runner.temp }}/test-application - - name: Run E2E test working-directory: ${{ runner.temp }}/test-application timeout-minutes: 10 diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index f9c74b5f344f..364f56cf6c6b 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -23,8 +23,24 @@ env: permissions: contents: read issues: write + packages: write jobs: + job_playwright_image: + name: Ensure Playwright Image + runs-on: ubuntu-24.04 + outputs: + image: ${{ steps.ensure.outputs.image }} + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Ensure Playwright image + id: ensure + uses: ./.github/actions/ensure-playwright-image + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + job_e2e_prepare: name: Prepare E2E Canary tests runs-on: ubuntu-24.04 @@ -53,8 +69,14 @@ jobs: job_e2e_tests: name: E2E ${{ matrix.label }} Test - needs: [job_e2e_prepare] + needs: [job_e2e_prepare, job_playwright_image] runs-on: ubuntu-24.04 + container: + image: ${{ needs.job_playwright_image.outputs.image }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + options: --ipc=host -e HOME=/root timeout-minutes: 20 env: # We just use a dummy DSN here, only send to the tunnel anyhow @@ -164,12 +186,6 @@ jobs: timeout-minutes: 7 run: yarn ${{ matrix.build-command }} - - name: Install Playwright - uses: ./.github/actions/install-playwright - with: - browsers: chromium - cwd: ${{ runner.temp }}/test-application - - name: Run E2E test working-directory: ${{ runner.temp }}/test-application timeout-minutes: 15 diff --git a/.github/workflows/flaky-test-detector.yml b/.github/workflows/flaky-test-detector.yml index c0a8f1f720b1..6737ccea13b5 100644 --- a/.github/workflows/flaky-test-detector.yml +++ b/.github/workflows/flaky-test-detector.yml @@ -22,8 +22,30 @@ concurrency: cancel-in-progress: true jobs: + job_playwright_image: + name: Ensure Playwright Image + runs-on: ubuntu-24.04 + permissions: + packages: write + outputs: + image: ${{ steps.ensure.outputs.image }} + steps: + - uses: actions/checkout@v6 + - name: Ensure Playwright image + id: ensure + uses: ./.github/actions/ensure-playwright-image + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + flaky-detector: + needs: [job_playwright_image] runs-on: ubuntu-24.04 + container: + image: ${{ needs.job_playwright_image.outputs.image }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + options: --ipc=host -e HOME=/root timeout-minutes: 60 name: 'Check tests for flakiness' # Also skip if PR is from master -> develop @@ -49,11 +71,6 @@ jobs: - name: Build packages run: yarn build - - name: Install Playwright - uses: ./.github/actions/install-playwright - with: - browsers: 'chromium' - - name: Determine changed tests uses: dorny/paths-filter@v4.0.1 id: changed