diff --git a/.github/workflows/build-adhoc.yml b/.github/workflows/build-adhoc.yml new file mode 100644 index 0000000000..327c449fc4 --- /dev/null +++ b/.github/workflows/build-adhoc.yml @@ -0,0 +1,44 @@ +name: _ADHOC + +run-name: >- + _ADHOC (${{ inputs.gh_ref }}${{ inputs.nitrate_ref != '' && format(' / nitrate:{0}', inputs.nitrate_ref) || '' }}) + +on: + workflow_dispatch: + inputs: + gh_ref: + description: 'Branch/ref to build in penpot/penpot' + type: string + required: true + nitrate_ref: + description: 'Branch/ref to build admin-console in penpot/penpot-nitrate (defaults to gh_ref)' + type: string + required: false + force: + description: 'Rebuild and overwrite even if already built/promoted' + type: boolean + required: false + default: false + +jobs: + build-bundle: + uses: ./.github/workflows/build-bundle.yml + secrets: inherit + with: + gh_ref: ${{ inputs.gh_ref }} + force: ${{ inputs.force }} + + build-docker: + needs: build-bundle + uses: ./.github/workflows/build-docker.yml + secrets: inherit + with: + gh_ref: ${{ inputs.gh_ref }} + force: ${{ inputs.force }} + + build-docker-admin-console: + uses: ./.github/workflows/build-docker-admin-console.yml + secrets: inherit + with: + gh_ref: ${{ inputs.nitrate_ref || inputs.gh_ref }} + force: ${{ inputs.force }} diff --git a/.github/workflows/build-bundle.yml b/.github/workflows/build-bundle.yml index e785f2c84e..493a3e44e8 100644 --- a/.github/workflows/build-bundle.yml +++ b/.github/workflows/build-bundle.yml @@ -9,6 +9,11 @@ on: type: string required: true default: 'develop' + force: + description: 'Rebuild and overwrite even if this version already exists in S3' + type: boolean + required: false + default: false workflow_call: inputs: gh_ref: @@ -16,20 +21,31 @@ on: type: string required: true default: 'develop' + force: + description: 'Rebuild and overwrite even if this version already exists in S3' + type: boolean + required: false + default: false +# Literal group name: under `workflow_call`, `github.workflow` resolves to the +# caller's workflow, which put this workflow and the other reusable one called +# by the same caller into a single shared group, and left a manual dispatch of +# the same ref in a group of its own, free to race on the same artifacts. concurrency: - group: ${{ github.workflow }}-${{ inputs.gh_ref }} + group: build-bundle-${{ inputs.gh_ref }} cancel-in-progress: true jobs: # ── 1. Decide whether there is anything to build ─────────────────────── check: name: Check current bundle - runs-on: penpot-runner-01 + runs-on: penpot-standar-runner timeout-minutes: 10 outputs: gh_ref: ${{ steps.vars.outputs.gh_ref }} bundle_version: ${{ steps.vars.outputs.bundle_version }} + sha: ${{ steps.vars.outputs.sha }} + commit_title: ${{ steps.vars.outputs.commit_title }} exists: ${{ steps.check.outputs.exists }} steps: @@ -44,10 +60,12 @@ jobs: run: | echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT echo "bundle_version=$(git describe --tags --always)" >> $GITHUB_OUTPUT + echo "sha=$(git rev-parse --short=12 HEAD)" >> $GITHUB_OUTPUT + echo "commit_title=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT # The uploaded zip carries its version as S3 metadata. If the # existing object was already built from this same commit, the - # whole build job is skipped. + # whole build job is skipped. `force` bypasses this check entirely. - name: Check if this bundle is already built id: check env: @@ -55,6 +73,16 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }} run: | + if [ "${{ inputs.force }}" = "true" ]; then + echo "exists=false" >> $GITHUB_OUTPUT + { + echo "### 🔁 Bundle build forced" + echo "" + echo "\`force: true\` — skipping the S3 version check." + } >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + EXISTING_VERSION=$(aws s3api head-object \ --bucket ${{ secrets.S3_BUCKET }} \ --key "penpot-${{ steps.vars.outputs.gh_ref }}.zip" \ @@ -75,7 +103,7 @@ jobs: # ── 2. Build and upload, only when needed ────────────────────────────── build: name: Build and Upload Penpot Bundle - runs-on: penpot-runner-01 + runs-on: penpot-standar-runner timeout-minutes: 90 needs: check if: needs.check.outputs.exists == 'false' @@ -113,10 +141,20 @@ jobs: s3://${{ secrets.S3_BUCKET }}/penpot-${{ needs.check.outputs.gh_ref }}.zip \ --metadata bundle-version=${{ needs.check.outputs.bundle_version }} + - name: Write step summary + run: | + { + echo "### ✅ Bundle built" + echo "" + echo "- Version: \`${{ needs.check.outputs.bundle_version }}\` (\`git describe --tags --always\`)" + echo "- Commit: [\`${{ needs.check.outputs.sha }}\`](https://github.com/${{ github.repository }}/commit/${{ needs.check.outputs.sha }}) — ${{ needs.check.outputs.commit_title }}" + echo "- Built at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" + } >> "$GITHUB_STEP_SUMMARY" + # ── 3. Single failure notification for the whole workflow ───────────── notify: name: Notify failure - runs-on: penpot-runner-01 + runs-on: penpot-standar-runner timeout-minutes: 5 needs: [check, build] if: failure() diff --git a/.github/workflows/build-develop.yml b/.github/workflows/build-develop.yml index 2da1ab2a31..7346450831 100644 --- a/.github/workflows/build-develop.yml +++ b/.github/workflows/build-develop.yml @@ -1,16 +1,30 @@ name: _DEVELOP +run-name: >- + _DEVELOP (develop @ ${{ github.sha }}) + on: workflow_dispatch: + inputs: + force: + description: 'Rebuild and overwrite even if already built/promoted' + type: boolean + required: false + default: false schedule: - cron: '16 5-20 * * 1-5' +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + jobs: build-bundle: uses: ./.github/workflows/build-bundle.yml secrets: inherit with: gh_ref: "develop" + force: ${{ inputs.force || false }} build-docker: needs: build-bundle @@ -18,9 +32,11 @@ jobs: secrets: inherit with: gh_ref: "develop" + force: ${{ inputs.force || false }} - build-admin-console-docker: + build-docker-admin-console: uses: ./.github/workflows/build-docker-admin-console.yml secrets: inherit with: gh_ref: "develop" + force: ${{ inputs.force || false }} diff --git a/.github/workflows/build-docker-admin-console.yml b/.github/workflows/build-docker-admin-console.yml index b3f8384636..dbc28387b6 100644 --- a/.github/workflows/build-docker-admin-console.yml +++ b/.github/workflows/build-docker-admin-console.yml @@ -13,6 +13,11 @@ on: type: string required: false default: 'develop' + force: + description: 'Rebuild and overwrite even if already built' + type: boolean + required: false + default: false workflow_call: inputs: gh_ref: @@ -24,6 +29,11 @@ on: type: string required: false default: 'develop' + force: + description: 'Rebuild and overwrite even if already built' + type: boolean + required: false + default: false secrets: ORG_WORKFLOW_TOKEN: description: 'Token with Actions write access on penpot-nitrate' @@ -47,6 +57,7 @@ jobs: gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$DISPATCH_REF" \ -f gh_ref="$GH_REF" \ + -f force="${{ inputs.force }}" \ -f caller_run_id="$DISTINCT_ID" \ -f caller_run_url="$CALLER_URL" diff --git a/.github/workflows/build-docker-devenv.yml b/.github/workflows/build-docker-devenv.yml index b0340d329b..53de6376ce 100644 --- a/.github/workflows/build-docker-devenv.yml +++ b/.github/workflows/build-docker-devenv.yml @@ -6,7 +6,7 @@ on: jobs: build-and-push: name: Build and push DevEnv Docker image - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner steps: - name: Set common environment variables diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index ac900455e0..ade22b78d1 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -8,6 +8,11 @@ on: type: string required: true default: 'develop' + force: + description: 'Rebuild and overwrite even if this sha is already promoted' + type: boolean + required: false + default: false workflow_call: inputs: gh_ref: @@ -15,9 +20,18 @@ on: type: string required: true default: 'develop' + force: + description: 'Rebuild and overwrite even if this sha is already promoted' + type: boolean + required: false + default: false +# Literal group name: under `workflow_call`, `github.workflow` resolves to the +# caller's workflow, which put this workflow and the other reusable one called +# by the same caller into a single shared group, and left a manual dispatch of +# the same ref in a group of its own, free to race on the same artifacts. concurrency: - group: ${{ github.workflow }}-${{ inputs.gh_ref }} + group: build-docker-${{ inputs.gh_ref }} cancel-in-progress: true env: @@ -32,12 +46,13 @@ jobs: # ── 1. Resolve the build key and check the whole set at once ─────────── prepare: name: Prepare - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner timeout-minutes: 15 outputs: gh_ref: ${{ steps.vars.outputs.gh_ref }} bundle_version: ${{ steps.vars.outputs.bundle_version }} sha: ${{ steps.vars.outputs.sha }} + commit_title: ${{ steps.vars.outputs.commit_title }} exists: ${{ steps.check.outputs.exists }} steps: @@ -56,6 +71,7 @@ jobs: GH_REF="${{ inputs.gh_ref || github.ref_name }}" echo "gh_ref=$GH_REF" >> $GITHUB_OUTPUT echo "sha=$(git rev-parse --short=12 HEAD)" >> $GITHUB_OUTPUT + echo "commit_title=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT BUNDLE_VERSION=$(aws s3api head-object \ --bucket ${{ secrets.S3_BUCKET }} \ @@ -67,7 +83,8 @@ jobs: # The image set is a single block, so a single set-level check is # enough: `promote` drops a marker object in S3 only after every # image was built AND every branch tag was moved. Marker present - # means there is nothing at all to do for this commit. + # means there is nothing at all to do for this commit. `force` + # bypasses this check entirely. - name: Check if this image set is already built id: check env: @@ -75,6 +92,21 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }} run: | + if [ "${{ inputs.force }}" = "true" ]; then + echo "exists=false" >> $GITHUB_OUTPUT + mkdir -p "$BUNDLE_CACHE" + find "$BUNDLE_CACHE" -type f -mtime +1 -delete || true + ZIP="$BUNDLE_CACHE/penpot-${{ steps.vars.outputs.bundle_version }}.zip" + aws s3 cp "s3://${{ secrets.S3_BUCKET }}/penpot-${{ steps.vars.outputs.gh_ref }}.zip" "$ZIP.$$.tmp" + mv "$ZIP.$$.tmp" "$ZIP" + { + echo "### 🔁 Image set build forced" + echo "" + echo "\`force: true\` — skipping the S3 marker check." + } >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + if aws s3api head-object \ --bucket ${{ secrets.S3_BUCKET }} \ --key "markers/images-sha-${{ steps.vars.outputs.sha }}" \ @@ -103,7 +135,7 @@ jobs: # ── 2. One build per image, in parallel, only when needed ────────────── build: name: Build ${{ matrix.image }} - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner timeout-minutes: 60 needs: prepare if: needs.prepare.outputs.exists == 'false' @@ -134,7 +166,7 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - # To avoid the “429 Too Many Requests” error when downloading + # To avoid the "429 Too Many Requests" error when downloading # images from DockerHub for unregistered users. # https://docs.docker.com/docker-hub/usage/ - name: Login to DockerHub Registry @@ -216,7 +248,7 @@ jobs: # the S3 marker guarantees the branch tags were already moved. promote: name: Promote image set - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner timeout-minutes: 10 needs: [prepare, build] @@ -254,16 +286,23 @@ jobs: run: | echo "${{ github.run_id }}" | aws s3 cp - \ "s3://${{ secrets.S3_BUCKET }}/markers/images-sha-${{ needs.prepare.outputs.sha }}" + + - name: Write step summary + run: | { echo "### ✅ Image set promoted" echo "" + echo "- Version: \`${{ needs.prepare.outputs.bundle_version }}\` (\`git describe --tags --always\`)" + echo "- Commit: [\`${{ needs.prepare.outputs.sha }}\`](https://github.com/${{ github.repository }}/commit/${{ needs.prepare.outputs.sha }}) — ${{ needs.prepare.outputs.commit_title }}" + echo "- Built at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" + echo "" echo "All \`:${{ needs.prepare.outputs.gh_ref }}\` tags now point to \`sha-${{ needs.prepare.outputs.sha }}\`." } >> "$GITHUB_STEP_SUMMARY" # ── 4. Single failure notification for the whole workflow ───────────── notify: name: Notify failure - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner timeout-minutes: 5 needs: [prepare, build, promote] if: failure() diff --git a/.github/workflows/build-staging.yml b/.github/workflows/build-staging.yml index 2ae5ee13b0..c2e3dad823 100644 --- a/.github/workflows/build-staging.yml +++ b/.github/workflows/build-staging.yml @@ -1,16 +1,30 @@ name: _STAGING +run-name: >- + _STAGING (staging) + on: workflow_dispatch: + inputs: + force: + description: 'Rebuild and overwrite even if already built/promoted' + type: boolean + required: false + default: false schedule: - cron: '36 5-20 * * 1-5' +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + jobs: build-bundle: uses: ./.github/workflows/build-bundle.yml secrets: inherit with: gh_ref: "staging" + force: ${{ inputs.force || false }} build-docker: needs: build-bundle @@ -18,9 +32,11 @@ jobs: secrets: inherit with: gh_ref: "staging" + force: ${{ inputs.force || false }} - build-admin-console-docker: + build-docker-admin-console: uses: ./.github/workflows/build-docker-admin-console.yml secrets: inherit with: gh_ref: "staging" + force: ${{ inputs.force || false }} diff --git a/.github/workflows/build-tag.yml b/.github/workflows/build-tag.yml index f488c911a7..bfa30fb67b 100644 --- a/.github/workflows/build-tag.yml +++ b/.github/workflows/build-tag.yml @@ -1,17 +1,33 @@ name: _TAG +run-name: >- + _TAG (${{ github.ref_name }} @ ${{ github.sha }}) + on: workflow_dispatch: + inputs: + force: + description: 'Rebuild and overwrite even if already built/promoted (manual re-releases only)' + type: boolean + required: false + default: false push: tags: - '*' +# Keyed by ref and never cancelling: pushing 2.17.2 shortly after 2.17.2-RC1 +# must not abort the release already in flight. +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: false + jobs: build-bundle: uses: ./.github/workflows/build-bundle.yml secrets: inherit with: gh_ref: ${{ github.ref_name }} + force: ${{ inputs.force || false }} build-docker: needs: build-bundle @@ -19,11 +35,21 @@ jobs: secrets: inherit with: gh_ref: ${{ github.ref_name }} + force: ${{ inputs.force || false }} + + build-docker-admin-console: + uses: ./.github/workflows/build-docker-admin-console.yml + secrets: inherit + with: + gh_ref: ${{ github.ref_name }} + force: ${{ inputs.force || false }} notify: name: Notifications runs-on: ubuntu-24.04 - needs: build-docker + needs: + - build-docker + - build-docker-admin-console steps: - name: Notify Mattermost uses: mattermost/action-mattermost-notify@ae31bb6f9e26a54336e79696f108a2c91cf55b4e # v2.1.0 @@ -37,7 +63,9 @@ jobs: publish-final-tag: if: ${{ !contains(github.ref_name, '-RC') && !contains(github.ref_name, '-alpha') && !contains(github.ref_name, '-beta') && contains(github.ref_name, '.') }} - needs: build-docker + needs: + - build-docker + - build-docker-admin-console uses: ./.github/workflows/release.yml secrets: inherit with: diff --git a/.github/workflows/build-tmp-tokens.yml b/.github/workflows/build-tmp-tokens.yml new file mode 100644 index 0000000000..838a97a617 --- /dev/null +++ b/.github/workflows/build-tmp-tokens.yml @@ -0,0 +1,24 @@ +name: _TMP TOKENS + +on: + workflow_dispatch: + schedule: + - cron: '46 5-20 * * 1-5' + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +jobs: + build-bundle: + uses: ./.github/workflows/build-bundle.yml + secrets: inherit + with: + gh_ref: "hiru-tokens-in-libs" + + build-docker: + needs: build-bundle + uses: ./.github/workflows/build-docker.yml + secrets: inherit + with: + gh_ref: "hiru-tokens-in-libs" diff --git a/.github/workflows/plugins-deploy-package.yml b/.github/workflows/plugins-deploy-package.yml index cbc8e109cd..2666957893 100644 --- a/.github/workflows/plugins-deploy-package.yml +++ b/.github/workflows/plugins-deploy-package.yml @@ -34,7 +34,7 @@ permissions: jobs: deploy: - runs-on: penpot-runner-01 + runs-on: penpot-standar-runner steps: - name: Checkout uses: actions/checkout@v6 diff --git a/.github/workflows/tests-backend.yml b/.github/workflows/tests-backend.yml index d7a3377772..02f839c062 100644 --- a/.github/workflows/tests-backend.yml +++ b/.github/workflows/tests-backend.yml @@ -32,7 +32,7 @@ jobs: test-backend: if: ${{ !github.event.pull_request.draft }} name: "Backend Tests" - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner container: image: penpotapp/devenv:latest volumes: diff --git a/.github/workflows/tests-common.yml b/.github/workflows/tests-common.yml index 5996c82742..3164fe5089 100644 --- a/.github/workflows/tests-common.yml +++ b/.github/workflows/tests-common.yml @@ -30,7 +30,7 @@ jobs: test-common: if: ${{ !github.event.pull_request.draft }} name: "Common Tests" - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner container: image: penpotapp/devenv:latest volumes: diff --git a/.github/workflows/tests-composable-suite.yml b/.github/workflows/tests-composable-suite.yml index 1ad48c0edf..3b1d68be21 100644 --- a/.github/workflows/tests-composable-suite.yml +++ b/.github/workflows/tests-composable-suite.yml @@ -38,7 +38,7 @@ jobs: composable-test-suite: if: ${{ !github.event.pull_request.draft }} name: "Run composable test suite (mocked backend)" - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner container: image: penpotapp/devenv:latest volumes: diff --git a/.github/workflows/tests-frontend.yml b/.github/workflows/tests-frontend.yml index 14011a5110..58ba335e38 100644 --- a/.github/workflows/tests-frontend.yml +++ b/.github/workflows/tests-frontend.yml @@ -34,7 +34,7 @@ jobs: test-frontend: if: ${{ !github.event.pull_request.draft }} name: "Frontend Tests" - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner container: image: penpotapp/devenv:latest volumes: diff --git a/.github/workflows/tests-integration.yml b/.github/workflows/tests-integration.yml index 61e9c6932e..343e0dcef6 100644 --- a/.github/workflows/tests-integration.yml +++ b/.github/workflows/tests-integration.yml @@ -5,11 +5,37 @@ defaults: shell: bash on: + workflow_dispatch: + inputs: + gh_ref: + description: 'Name of the branch or ref' + type: string + required: true + default: 'develop' + + shards: + description: 'Shard layout (JSON array)' + type: choice + required: true + default: '[1, 2, 3, 4]' + options: + - '[1, 2, 3, 4]' + - '[1, 2, 3, 4, 5, 6]' + - '[1, 2]' + - '[1]' + + workers: + description: 'Playwright workers per shard' + type: string + required: true + default: '2' + pull_request: paths: - 'frontend/**' - 'common/**' - 'render-wasm/**' + - '.github/workflows/tests-integration.yml' types: - opened @@ -25,25 +51,41 @@ on: - 'frontend/**' - 'common/**' - 'render-wasm/**' + - '.github/workflows/tests-integration.yml' concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.gh_ref || github.ref }} cancel-in-progress: true jobs: build-integration: if: ${{ !github.event.pull_request.draft }} name: "Build Integration Bundle" - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner + timeout-minutes: 30 container: image: penpotapp/devenv:latest volumes: - /var/cache/github-runner/m2:/root/.m2 - /var/cache/github-runner/gitlib:/root/.gitlibs + outputs: + bundle_key: ${{ steps.vars.outputs.bundle_key }} + steps: + # An empty `ref` makes checkout fall back to its default (the PR merge + # ref on pull_request, the pushed ref on push). - name: Checkout repository uses: actions/checkout@v6 + with: + ref: ${{ inputs.gh_ref }} + + # The cache key must come from the SHA actually checked out: on a manual + # run `github.sha` points at the dispatching ref, not at `gh_ref`. + - name: Extract cache key + id: vars + run: | + echo "bundle_key=integration-bundle-$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - name: Build Bundle working-directory: ./frontend @@ -53,58 +95,142 @@ jobs: - name: Store Bundle Cache uses: actions/cache@v5 with: - key: "integration-bundle-${{ github.sha }}" + key: ${{ steps.vars.outputs.bundle_key }} path: frontend/resources/public test-integration: if: ${{ !github.event.pull_request.draft }} - name: "Integration Tests" - runs-on: penpot-runner-02 + name: "Integration Tests (${{ matrix.shard }})" + runs-on: penpot-extended-runner + timeout-minutes: 40 + + needs: build-integration + + strategy: + fail-fast: false + matrix: + shard: ${{ fromJSON(inputs.shards || '[1, 2, 3, 4]') }} + + container: + image: penpotapp/devenv:latest + volumes: + - /var/cache/github-runner/m2:/root/.m2 + - /var/cache/github-runner/gitlib:/root/.gitlibs + - /var/cache/github-runner/ms-playwright:/ms-playwright + env: + PLAYWRIGHT_BROWSERS_PATH: /ms-playwright + + steps: + - name: Checkout Repository + uses: actions/checkout@v6 + with: + ref: ${{ inputs.gh_ref }} + + - name: Restore Cache + uses: actions/cache/restore@v5 + with: + key: ${{ needs.build-integration.outputs.bundle_key }} + path: frontend/resources/public + + - name: Install deps + working-directory: ./frontend + run: | + corepack enable; + corepack install; + pnpm install --frozen-lockfile; + + # No-op once the shared volume is warm; keeps the first run working. + - name: Install Playwright Chromium + working-directory: ./frontend + run: pnpm exec playwright install chromium + + # `strategy.job-total` is the matrix size, so the shard denominator + # follows the `shards` input without being hardcoded. + - name: Run Tests + working-directory: ./frontend + env: + WORKERS: ${{ inputs.workers }} + run: | + WORKERS=${WORKERS:-2} + echo "Running shard ${{ matrix.shard }}/${{ strategy.job-total }} with $WORKERS workers" + pnpm exec playwright test --project default \ + --workers="$WORKERS" \ + --shard=${{ matrix.shard }}/${{ strategy.job-total }} \ + --reporter=blob + + - name: Upload blob report + uses: actions/upload-artifact@v7 + if: always() + with: + name: integration-blob-report-${{ matrix.shard }} + path: frontend/blob-report/ + overwrite: true + retention-days: 3 + + - name: Upload test result + uses: actions/upload-artifact@v7 + if: always() + with: + name: integration-tests-result-${{ matrix.shard }} + path: frontend/test-results/ + overwrite: true + if-no-files-found: ignore + retention-days: 3 + + merge-reports: + if: ${{ always() && !github.event.pull_request.draft && needs.test-integration.result != 'skipped' }} + name: "Merge Integration Reports" + runs-on: penpot-extended-runner + timeout-minutes: 15 + + needs: test-integration + container: image: penpotapp/devenv:latest volumes: - /var/cache/github-runner/m2:/root/.m2 - /var/cache/github-runner/gitlib:/root/.gitlibs - needs: build-integration - steps: - name: Checkout Repository uses: actions/checkout@v6 - - - name: Restore Cache - uses: actions/cache/restore@v5 with: - key: "integration-bundle-${{ github.sha }}" - path: frontend/resources/public + ref: ${{ inputs.gh_ref }} - - name: Run Tests + - name: Install deps + working-directory: ./frontend + run: | + corepack enable; + corepack install; + pnpm install --frozen-lockfile; + + - name: Download blob reports + uses: actions/download-artifact@v7 + with: + path: frontend/all-blob-reports + pattern: integration-blob-report-* + merge-multiple: true + + - name: Merge into HTML report working-directory: ./frontend env: - PLAYWRIGHT_REPORTER: list,json PLAYWRIGHT_JSON_OUTPUT_NAME: report.json run: | - ./scripts/test-e2e + pnpm exec playwright merge-reports \ + --reporter=html,json,list ./all-blob-reports - - name: Flaky summary + - name: Test summary if: always() working-directory: ./frontend run: | if [ ! -f report.json ]; then - echo "No report.json produced (the run failed early)." >> "$GITHUB_STEP_SUMMARY" + echo "No report produced (all shards failed early)." >> "$GITHUB_STEP_SUMMARY" exit 0 fi + jq -r -f ../.github/scripts/playwright-summary.jq report.json >> "$GITHUB_STEP_SUMMARY" - jq -r ' - [ .. | objects - | select(has("tests") and has("file")) - | select(any(.tests[]; .status == "flaky")) - | "- `\(.file):\(.line)` — \(.title)" - ] as $f - | "## Flaky tests: \($f | length)\n" - + (if ($f | length) == 0 then "_none_" else ($f | join("\n")) end) - ' report.json >> "$GITHUB_STEP_SUMMARY" - + # Kept for 30 days so flakiness rates can be aggregated across runs + # without scraping job logs. - name: Upload JSON report uses: actions/upload-artifact@v7 if: always() @@ -112,13 +238,13 @@ jobs: name: integration-json-report path: frontend/report.json overwrite: true + if-no-files-found: ignore retention-days: 30 - - name: Upload test result + - name: Upload HTML report uses: actions/upload-artifact@v7 - if: always() with: - name: integration-tests-result - path: frontend/test-results/ + name: integration-html-report + path: frontend/playwright-report/ overwrite: true - retention-days: 3 + retention-days: 7 diff --git a/.github/workflows/tests-library.yml b/.github/workflows/tests-library.yml index 4c84965f4c..a5b565a892 100644 --- a/.github/workflows/tests-library.yml +++ b/.github/workflows/tests-library.yml @@ -32,7 +32,7 @@ jobs: test-library: if: ${{ !github.event.pull_request.draft }} name: "Library Tests" - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner container: image: penpotapp/devenv:latest volumes: diff --git a/.github/workflows/tests-mcp.yml b/.github/workflows/tests-mcp.yml index 317d82dca2..f51c56b8a3 100644 --- a/.github/workflows/tests-mcp.yml +++ b/.github/workflows/tests-mcp.yml @@ -28,7 +28,7 @@ jobs: test-mcp: if: ${{ !github.event.pull_request.draft }} name: "Test MCP" - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner container: penpotapp/devenv:latest steps: diff --git a/.github/workflows/tests-plugin-api-suite.yml b/.github/workflows/tests-plugin-api-suite.yml index e589c5414c..de6e2080bb 100644 --- a/.github/workflows/tests-plugin-api-suite.yml +++ b/.github/workflows/tests-plugin-api-suite.yml @@ -53,7 +53,7 @@ jobs: api-test-suite-mocked: if: ${{ github.event_name != 'workflow_dispatch' && !github.event.pull_request.draft }} name: "Run Plugin API Test Suite (mocked)" - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner container: image: penpotapp/devenv:latest volumes: @@ -95,7 +95,7 @@ jobs: # api-test-suite-live: # if: ${{ github.event_name == 'workflow_dispatch' }} # name: Run Plugin API Test Suite (live) - # runs-on: penpot-runner-02 + # runs-on: penpot-extended-runner # container: # image: penpotapp/devenv:latest # diff --git a/.github/workflows/tests-plugins.yml b/.github/workflows/tests-plugins.yml index 1415c73db7..b1bbdd0992 100644 --- a/.github/workflows/tests-plugins.yml +++ b/.github/workflows/tests-plugins.yml @@ -30,7 +30,7 @@ jobs: test-plugins: if: ${{ !github.event.pull_request.draft }} name: Plugins Runtime Linter & Tests - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner container: image: penpotapp/devenv:latest volumes: diff --git a/.github/workflows/tests-wasm.yml b/.github/workflows/tests-wasm.yml index 424d4f908f..9f26175554 100644 --- a/.github/workflows/tests-wasm.yml +++ b/.github/workflows/tests-wasm.yml @@ -30,7 +30,7 @@ jobs: test-render-wasm: if: ${{ !github.event.pull_request.draft }} name: "Render WASM Tests" - runs-on: penpot-runner-02 + runs-on: penpot-extended-runner container: image: penpotapp/devenv:latest volumes: