| name: Release Preparation |
| |
| on: |
| workflow_dispatch: |
| inputs: |
| tag: |
| description: 'Release tag (e.g., selenium-4.28.0 or selenium-4.28.1-ruby)' |
| required: true |
| chrome_channel: |
| description: 'Chrome Channel for CDP' |
| required: true |
| type: choice |
| default: "stable" |
| options: |
| - stable |
| - early-stable |
| |
| permissions: |
| contents: read |
| |
| jobs: |
| parse-tag: |
| name: Parse Tag |
| uses: ./.github/workflows/parse-release-tag.yml |
| with: |
| tag: ${{ inputs.tag }} |
| |
| generate-rust-version: |
| name: Generate Rust Version |
| needs: [parse-tag, restrict-trunk] |
| if: github.event.repository.fork == false && needs.parse-tag.outputs.language == 'all' |
| uses: ./.github/workflows/bazel.yml |
| with: |
| name: Update Rust Version |
| run: ./go rust:version ${{ needs.parse-tag.outputs.version }} |
| artifact-name: rust-version |
| |
| push-rust-version: |
| name: Push Rust Version |
| needs: [parse-tag, generate-rust-version] |
| permissions: |
| contents: write |
| actions: read |
| uses: ./.github/workflows/commit-changes.yml |
| with: |
| artifact-name: rust-version |
| commit-message: "update selenium manager version and rust changelog" |
| push-branch: rust-release-${{ needs.parse-tag.outputs.version }} |
| secrets: |
| SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }} |
| |
| selenium-manager: |
| name: Release Selenium Manager |
| needs: [parse-tag, push-rust-version] |
| uses: ./.github/workflows/ci-rust.yml |
| with: |
| release: true |
| branch: rust-release-${{ needs.parse-tag.outputs.version }} |
| secrets: |
| SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }} |
| |
| cleanup-rust-branch: |
| name: Cleanup Rust Branch |
| needs: [parse-tag, selenium-manager] |
| if: always() |
| runs-on: ubuntu-latest |
| steps: |
| - name: Checkout repo |
| uses: actions/checkout@v6 |
| with: |
| token: ${{ secrets.SELENIUM_CI_TOKEN }} |
| - name: Delete rust release branch |
| run: | |
| git push origin --delete rust-release-${{ needs.parse-tag.outputs.version }} || true |
| |
| restrict-trunk: |
| name: Restrict Trunk Branch |
| needs: parse-tag |
| uses: ./.github/workflows/restrict-trunk.yml |
| with: |
| restrict: true |
| message: | |
| Trunk branch has been restricted for release preparation (${{ inputs.tag }}). |
| To unlock due to release issues, run the "Unlock Trunk" workflow: |
| https://github.com/${{ github.repository }}/actions/workflows/unlock-trunk.yml |
| secrets: |
| SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }} |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} |
| |
| update-versions: |
| name: Bump Versions |
| needs: [parse-tag, restrict-trunk, selenium-manager] |
| if: >- |
| always() && |
| needs.restrict-trunk.result == 'success' && |
| (needs.parse-tag.outputs.language != 'all' || needs.selenium-manager.result == 'success') |
| uses: ./.github/workflows/bazel.yml |
| with: |
| name: Bump versions |
| run: ./go ${{ needs.parse-tag.outputs.language }}:version ${{ needs.parse-tag.outputs.version }}${{ needs.parse-tag.outputs.language == 'all' && format(' && ./go rust:version {0}', needs.parse-tag.outputs.version) || '' }} |
| artifact-name: patch-versions |
| |
| commit-versions: |
| name: Commit Versions |
| needs: [parse-tag, update-versions] |
| permissions: |
| contents: write |
| actions: read |
| uses: ./.github/workflows/commit-changes.yml |
| with: |
| artifact-name: patch-versions |
| commit-message: "bump versions" |
| push-branch: staging/release-${{ needs.parse-tag.outputs.tag }} |
| secrets: |
| SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }} |
| |
| update-devtools: |
| name: Update Devtools |
| needs: [parse-tag, commit-versions] |
| if: needs.parse-tag.outputs.language == 'all' |
| uses: ./.github/workflows/bazel.yml |
| with: |
| name: Update devtools |
| ref: staging/release-${{ needs.parse-tag.outputs.tag }} |
| run: ./go update_cdp ${{ inputs.chrome_channel }} |
| artifact-name: patch-devtools |
| |
| commit-devtools: |
| name: Commit Devtools |
| needs: [parse-tag, update-devtools] |
| permissions: |
| contents: write |
| actions: read |
| uses: ./.github/workflows/commit-changes.yml |
| with: |
| artifact-name: patch-devtools |
| commit-message: "update devtools versions" |
| ref: staging/release-${{ needs.parse-tag.outputs.tag }} |
| push-branch: staging/release-${{ needs.parse-tag.outputs.tag }} |
| secrets: |
| SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }} |
| |
| update-dependencies: |
| name: Run Dependency Updates |
| needs: [parse-tag, commit-versions, commit-devtools] |
| if: >- |
| always() && |
| needs.commit-versions.result == 'success' && |
| (needs.parse-tag.outputs.language != 'all' || needs.commit-devtools.result == 'success') |
| uses: ./.github/workflows/bazel.yml |
| with: |
| name: Run dependency updates |
| ref: staging/release-${{ needs.parse-tag.outputs.tag }} |
| run: ./go ${{ needs.parse-tag.outputs.language }}:update |
| artifact-name: patch-dep-updates |
| |
| calculate-changelog-depth: |
| name: Calculate Changelog Depth |
| needs: [parse-tag, commit-versions, commit-devtools, update-dependencies] |
| if: >- |
| always() && |
| needs.commit-versions.result == 'success' && |
| (needs.parse-tag.outputs.language != 'all' || needs.commit-devtools.result == 'success') |
| runs-on: ubuntu-latest |
| outputs: |
| depth: ${{ steps.calc.outputs.depth }} |
| steps: |
| - name: Calculate fetch depth from tag |
| id: calc |
| env: |
| GH_TOKEN: ${{ github.token }} |
| VERSION: ${{ needs.parse-tag.outputs.version }} |
| TAG: ${{ needs.parse-tag.outputs.tag }} |
| shell: bash |
| run: | |
| set -euo pipefail |
| if [[ "$VERSION" =~ ([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then |
| MAJOR=${BASH_REMATCH[1]} |
| MINOR=${BASH_REMATCH[2]} |
| PATCH=${BASH_REMATCH[3]} |
| |
| if [ "$PATCH" -gt 1 ]; then |
| PREV="selenium-${MAJOR}.${MINOR}.$((PATCH-1))" |
| elif [ "$PATCH" -eq 1 ]; then |
| PREV="selenium-${MAJOR}.${MINOR}.0" |
| elif [ "$MINOR" -gt 0 ]; then |
| PREV="selenium-${MAJOR}.$((MINOR-1)).0" |
| else |
| # First release of major version (X.0.0), use full history |
| echo "depth=0" >> "$GITHUB_OUTPUT" |
| exit 0 |
| fi |
| |
| COUNT=$(gh api "repos/${{ github.repository }}/compare/${PREV}...staging/release-${TAG}" --jq '.total_commits' 2>/dev/null) || { |
| echo "::warning::Failed to get commit count, using full history" |
| echo "depth=0" >> "$GITHUB_OUTPUT" |
| exit 0 |
| } |
| echo "depth=$((COUNT + 1))" >> "$GITHUB_OUTPUT" |
| else |
| echo "depth=0" >> "$GITHUB_OUTPUT" |
| fi |
| |
| update-changelogs: |
| name: Generate Changelogs |
| needs: [parse-tag, calculate-changelog-depth] |
| uses: ./.github/workflows/bazel.yml |
| with: |
| name: Update changelogs |
| ref: staging/release-${{ needs.parse-tag.outputs.tag }} |
| run: ./go ${{ needs.parse-tag.outputs.language }}:changelogs |
| artifact-name: patch-changelogs |
| fetch-depth: ${{ needs.calculate-changelog-depth.outputs.depth }} |
| |
| release-updates: |
| name: Update ${{ matrix.name }} |
| needs: [parse-tag, commit-devtools, selenium-manager, calculate-changelog-depth] |
| strategy: |
| fail-fast: false |
| matrix: |
| include: |
| - task: browsers |
| name: Pinned Browsers |
| run: ./go update_browsers |
| - task: manager |
| name: Selenium Manager Binaries |
| run: ./go update_manager |
| - task: multitool |
| name: Multitool |
| run: ./go update_multitool |
| - task: authors |
| name: Authors |
| run: ./go authors |
| - task: rust-changelogs |
| name: Rust Changelogs |
| run: ./go rust:changelogs |
| uses: ./.github/workflows/bazel.yml |
| with: |
| name: Update ${{ matrix.name }} |
| ref: staging/release-${{ needs.parse-tag.outputs.tag }} |
| run: ${{ matrix.run }}${{ matrix.task == 'browsers' && format(' {0}', inputs.chrome_channel) || '' }} |
| artifact-name: patch-${{ matrix.task }} |
| fetch-depth: ${{ needs.calculate-changelog-depth.outputs.depth }} |
| |
| create-pr: |
| name: Create Pull Request |
| needs: [parse-tag, update-dependencies, update-changelogs, release-updates] |
| if: >- |
| always() && !cancelled() && |
| needs.update-dependencies.result == 'success' && |
| needs.update-changelogs.result == 'success' && |
| needs.release-updates.result != 'failure' |
| runs-on: ubuntu-latest |
| steps: |
| - name: Checkout trunk |
| uses: actions/checkout@v6 |
| with: |
| token: ${{ secrets.SELENIUM_CI_TOKEN }} |
| - name: Download all patches |
| uses: actions/download-artifact@v8 |
| with: |
| pattern: patch-* |
| path: patches |
| - name: Prep git |
| run: | |
| git config --local user.email "selenium-ci@users.noreply.github.com" |
| git config --local user.name "Selenium CI Bot" |
| - name: Apply patches and commit |
| id: apply |
| run: | |
| apply_patch() { |
| local name="$1" |
| local msg="$2" |
| local patch="patches/patch-$name/changes.patch" |
| if [ -f "$patch" ] && [ -s "$patch" ]; then |
| git apply --index "$patch" |
| git commit -m "$msg" |
| echo "${name}=true" >> "$GITHUB_OUTPUT" |
| else |
| echo "${name}=false" >> "$GITHUB_OUTPUT" |
| fi |
| } |
| apply_patch versions "bump versions" |
| apply_patch devtools "update devtools versions" |
| apply_patch dep-updates "update dependencies" |
| apply_patch changelogs "changelogs updated" |
| apply_patch rust-changelogs "rust changelogs updated" |
| apply_patch browsers "update pinned browser versions" |
| apply_patch manager "update selenium manager versions" |
| apply_patch multitool "update multitool binaries" |
| apply_patch authors "update authors file" |
| rm -rf patches |
| - name: Create Pull Request |
| uses: peter-evans/create-pull-request@v8 |
| with: |
| token: ${{ secrets.SELENIUM_CI_TOKEN }} |
| author: Selenium CI Bot <selenium-ci@users.noreply.github.com> |
| delete-branch: true |
| branch: release-preparation-${{ needs.parse-tag.outputs.tag }} |
| base: trunk |
| title: "[build] Prepare for release of ${{ needs.parse-tag.outputs.tag }}" |
| body: | |
| |
| ### Release Info |
| | | | |
| |-----------|--------| |
| | **Tag** | ${{ needs.parse-tag.outputs.tag }} | |
| | **Version** | ${{ needs.parse-tag.outputs.version }} | |
| | **Language** | ${{ needs.parse-tag.outputs.language }} | |
| |
| ### Updates Applied |
| | Component | Status | |
| |-----------|--------| |
| | Versions | ${{ steps.apply.outputs.versions == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)' }} | |
| | CDP version | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.devtools == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} | |
| | Binding Dependencies | ${{ steps.apply.outputs.dep-updates == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)' }} | |
| | Changelogs | ${{ steps.apply.outputs.changelogs == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)' }} | |
| | Rust Changelogs | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.rust-changelogs == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} | |
| | Pinned Browsers | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.browsers == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} | |
| | Selenium Manager | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.manager == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} | |
| | Multitool binaries | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.multitool == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} | |
| | Authors | ${{ needs.parse-tag.outputs.language != 'all' && 'N/A' || (steps.apply.outputs.authors == 'true' && '✅ Updated' || '⏭️ Skipped (no changes)') }} | |
| |
| --- |
| Auto-generated by [release-preparation workflow](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) |
| labels: C-build |
| |
| cleanup-staging-branch: |
| name: Cleanup Staging Branch |
| needs: [parse-tag, create-pr] |
| if: always() |
| runs-on: ubuntu-latest |
| steps: |
| - name: Checkout repo |
| uses: actions/checkout@v6 |
| with: |
| token: ${{ secrets.SELENIUM_CI_TOKEN }} |
| - name: Delete staging branch |
| run: | |
| git push origin --delete staging/release-${{ needs.parse-tag.outputs.tag }} || true |
| |
| unlock-trunk-on-failure: |
| name: Unlock Trunk on Failure |
| needs: [parse-tag, restrict-trunk, generate-rust-version, push-rust-version, selenium-manager, update-versions, commit-versions, update-devtools, commit-devtools, update-dependencies, calculate-changelog-depth, update-changelogs, release-updates, create-pr] |
| if: always() && needs.restrict-trunk.result == 'success' && needs.create-pr.result != 'success' |
| uses: ./.github/workflows/restrict-trunk.yml |
| with: |
| restrict: false |
| skip_approval: true |
| secrets: |
| SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }} |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} |
| |
| on-prerelease-failure: |
| name: On Pre-release Failure |
| runs-on: ubuntu-latest |
| needs: [parse-tag, restrict-trunk, generate-rust-version, push-rust-version, selenium-manager, update-versions, commit-versions, update-devtools, commit-devtools, update-dependencies, calculate-changelog-depth, update-changelogs, release-updates, create-pr] |
| if: failure() || cancelled() |
| steps: |
| - name: Slack Notification |
| uses: rtCamp/action-slack-notify@v2 |
| env: |
| SLACK_ICON_EMOJI: ":rotating_light:" |
| SLACK_COLOR: failure |
| SLACK_CHANNEL: selenium-tlc |
| SLACK_USERNAME: GitHub Workflows |
| SLACK_TITLE: "Release preparation failed: ${{ needs.parse-tag.outputs.tag }}" |
| SLACK_MESSAGE: | |
| • Selenium Manager: ${{ needs.selenium-manager.result }} |
| • Versions: ${{ needs.commit-versions.result }} |
| • Devtools: ${{ needs.commit-devtools.result }} |
| • Dependencies: ${{ needs.update-dependencies.result }} |
| • Changelogs: ${{ needs.update-changelogs.result }} |
| • Release Updates: ${{ needs.release-updates.result }} |
| • Create PR: ${{ needs.create-pr.result }} |
| MSG_MINIMAL: actions url |
| SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} |