blob: 0ce3435511e05dccc01b63eda7e83345e9ac1896 [file]
name: Release - Finish
on:
pull_request:
types: [closed]
paths:
- 'java/version.bzl'
- 'rb/lib/selenium/webdriver/version.rb'
- 'py/selenium/__init__.py'
- 'dotnet/version.bzl'
- 'javascript/selenium-webdriver/package.json'
workflow_dispatch:
inputs:
tag:
description: 'Release tag (e.g., selenium-4.28.0 or selenium-4.28.1-ruby)'
required: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: write
packages: write
jobs:
unlock-on-close:
name: Unlock Trunk on PR Close
if: >-
github.event.repository.fork == false &&
github.event_name == 'pull_request' &&
startsWith(github.event.pull_request.head.ref, 'release-preparation-') &&
github.event.pull_request.merged == false
uses: ./.github/workflows/restrict-trunk.yml
with:
unlock: true
message: "Trunk has been unlocked: ${{ github.event.pull_request.head.ref }} was closed without merging."
secrets:
SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
extract-tag:
name: Extract Tag
runs-on: ubuntu-latest
if: >
github.event.repository.fork == false &&
((startsWith(github.event.pull_request.head.ref, 'release-preparation-') &&
github.event.pull_request.merged == true) ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.tag != ''))
outputs:
tag: ${{ steps.extract.outputs.tag }}
steps:
- name: Extract tag from input or PR branch
id: extract
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_TAG: ${{ inputs.tag }}
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
if [ "$EVENT_NAME" == "workflow_dispatch" ]; then
TAG="$INPUT_TAG"
else
# Extract tag from branch name: release-preparation-selenium-4.28.1-ruby -> selenium-4.28.1-ruby
TAG="${PR_HEAD_REF#release-preparation-}"
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
parse-tag:
name: Parse Tag
needs: extract-tag
uses: ./.github/workflows/parse-release-tag.yml
with:
tag: ${{ needs.extract-tag.outputs.tag }}
check-authorization:
name: Check Authorization
needs: extract-tag
uses: ./.github/workflows/check-authorization.yml
create-tag:
name: Create Tag
needs: [parse-tag, check-authorization]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Create release tag
env:
GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
TAG: ${{ needs.parse-tag.outputs.tag }}
SHA: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
run: |
if gh api "/repos/${{ github.repository }}/git/ref/tags/${TAG}" >/dev/null 2>&1; then
echo "Tag ${TAG} already exists — skipping creation."
exit 0
fi
gh api -X POST /repos/${{ github.repository }}/git/refs \
-f ref="refs/tags/${TAG}" \
-f sha="${SHA}"
github-release-draft:
name: GitHub Release Draft
needs: [parse-tag, check-authorization]
if: needs.parse-tag.outputs.language == 'all'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repo
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Generate release notes against previous minor release
env:
TAG: ${{ needs.parse-tag.outputs.tag }}
SHA: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
run: |
PREV=$(gh release list --exclude-drafts --json tagName \
-q 'map(.tagName | select(test("^selenium-[0-9]+\\.[0-9]+\\.0$")))[0]')
echo "Previous release tag: $PREV"
{
cat scripts/github-actions/release_header.md
echo ""
gh api -X POST "/repos/${{ github.repository }}/releases/generate-notes" \
-f tag_name="$TAG" \
-f previous_tag_name="$PREV" \
-f target_commitish="$SHA" \
--jq .body
} > release_body.md
- name: Create draft GitHub release
uses: ncipollo/release-action@v1
with:
draft: true
allowUpdates: true
bodyFile: "release_body.md"
name: "Selenium ${{ needs.parse-tag.outputs.version }}"
tag: "${{ needs.parse-tag.outputs.tag }}"
commit: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
publish:
name: Build and Publish ${{ matrix.language }}
needs: [parse-tag, check-authorization]
permissions:
contents: read
id-token: write
strategy:
fail-fast: false
matrix:
language: [java, ruby, dotnet, javascript]
uses: ./.github/workflows/bazel.yml
with:
name: Publish ${{ matrix.language }}
gpg-sign: ${{ matrix.language == 'java' }}
gem-trusted-publishing: ${{ matrix.language == 'ruby' }}
node-version: ${{ matrix.language == 'javascript' && '24.14.1' || '' }}
ruby-version: ${{ matrix.language == 'ruby' && 'jruby-10.1.0.0' || '' }}
run: |
if [ "${{ needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == matrix.language }}" = "true" ]; then
./go ${{ matrix.language }}:release rbe
else
echo skipping
fi
artifact-name: ${{ (matrix.language == 'java' || matrix.language == 'dotnet') && (needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == matrix.language) && format('release-packages-{0}', matrix.language) || '' }}
artifact-path: ${{ (matrix.language == 'java' || matrix.language == 'dotnet') && 'build/dist/*.*' || '' }}
secrets: inherit
build-python:
name: Build Python
needs: [parse-tag, check-authorization]
if: needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == 'python'
uses: ./.github/workflows/bazel.yml
with:
name: Build Python
run: ./go py:build --config=rbe_release
artifact-name: pypi-distributions
artifact-path: |
bazel-bin/py/selenium-*.whl
bazel-bin/py/selenium-[0-9]*.tar.gz
secrets: inherit
publish-python:
name: Publish Python to PyPI
needs: [parse-tag, build-python]
if: needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == 'python'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
actions: read
steps:
- name: Download Python distributions
uses: actions/download-artifact@v8
with:
name: pypi-distributions
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true
github-release-publish:
name: GitHub Release Publish
needs: [parse-tag, publish, github-release-draft]
if: >-
always() && !failure() && !cancelled() &&
(needs.parse-tag.outputs.language == 'all' ||
needs.parse-tag.outputs.language == 'java' ||
needs.parse-tag.outputs.language == 'dotnet')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repo
if: needs.parse-tag.outputs.language == 'all'
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Download release packages
uses: actions/download-artifact@v8
with:
pattern: release-packages-*
merge-multiple: true
path: "build/dist/"
- name: Delete nightly release and tag
if: needs.parse-tag.outputs.language == 'all'
env:
GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
run: |
if gh release view nightly >/dev/null 2>&1; then
gh release delete nightly --yes
fi
if git ls-remote --tags origin refs/tags/nightly | grep -q nightly; then
gh api -X DELETE /repos/${{ github.repository }}/git/refs/tags/nightly
fi
- name: Upload to GitHub release
env:
GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
VERSION: ${{ needs.parse-tag.outputs.version }}
run: |
BASE_VERSION="${VERSION%.*}.0"
gh release upload "selenium-$BASE_VERSION" build/dist/*.* --clobber
gh release edit "selenium-$BASE_VERSION" --draft=false
docs:
name: Update ${{ matrix.language }} Documentation
needs: [parse-tag, publish, publish-python, github-release-publish, create-tag]
if: >-
always() && !cancelled() &&
needs.publish.result == 'success' &&
(needs.publish-python.result == 'success' || needs.publish-python.result == 'skipped') &&
needs.github-release-publish.result != 'failure' &&
needs.create-tag.result != 'failure'
permissions:
contents: write
strategy:
fail-fast: false
matrix:
language: [java, python, ruby, dotnet, javascript]
uses: ./.github/workflows/update-documentation.yml
with:
tag: ${{ needs.parse-tag.outputs.tag }}
language: ${{ matrix.language }}
skip: ${{ needs.parse-tag.outputs.language != 'all' && needs.parse-tag.outputs.language != matrix.language }}
secrets:
SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
verify:
name: Verify Published Packages
needs: [parse-tag, publish, publish-python]
if: >-
always() && !cancelled() &&
needs.publish.result == 'success' &&
(needs.publish-python.result == 'success' || needs.publish-python.result == 'skipped')
uses: ./.github/workflows/bazel.yml
with:
name: Verify packages
run: ./go ${{ needs.parse-tag.outputs.language }}:verify
secrets: inherit
reset-version:
name: Generate Nightly Versions
needs: [parse-tag, check-authorization]
uses: ./.github/workflows/bazel.yml
with:
name: Reset Versions
run: ./go ${{ needs.parse-tag.outputs.language }}:version nightly${{ needs.parse-tag.outputs.language == 'all' && ' && ./go rust:version nightly' || '' }}
artifact-name: version-reset
# A rerun builds from trunk HEAD, so the nightly versions must not land there until every publish has succeeded.
update-version:
name: Push Nightly Versions
needs: [parse-tag, reset-version, publish, publish-python]
if: >-
always() && !cancelled() &&
needs.reset-version.result == 'success' &&
needs.publish.result == 'success' &&
(needs.publish-python.result == 'success' || needs.publish-python.result == 'skipped')
permissions:
contents: write
actions: read
uses: ./.github/workflows/commit-changes.yml
with:
artifact-name: version-reset
commit-message: "[build] Reset versions to nightly after ${{ needs.parse-tag.outputs.tag }} release"
secrets:
SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
# github-release-publish deletes the nightly tag, and mirror needs a nightly grid jar back under it.
# The build can run any time; only publish-nightly-grid has to sit between those two jobs.
nightly-grid:
name: Build Nightly Grid
needs: [parse-tag, check-authorization]
if: needs.parse-tag.outputs.language == 'all'
uses: ./.github/workflows/bazel.yml
with:
name: Build Nightly Grid
run: ./go java:version nightly && ./go java:package --config=rbe_release
artifact-name: nightly-grid
artifact-path: build/dist/*.*
publish-nightly-grid:
name: Publish Nightly Grid
needs: [parse-tag, nightly-grid, github-release-publish]
if: needs.parse-tag.outputs.language == 'all'
permissions:
contents: write
uses: ./.github/workflows/release-grid.yml
with:
artifact-name: nightly-grid
mirror:
name: Update Release Mirror
needs: [parse-tag, update-version, publish-nightly-grid]
if: needs.parse-tag.outputs.language == 'all'
uses: ./.github/workflows/mirror-selenium-releases.yml
secrets: inherit
unrestrict-trunk:
name: Unrestrict Trunk Branch
needs: [parse-tag, publish, publish-python, update-version, mirror]
if: >-
always() && !cancelled() &&
needs.publish.result == 'success' &&
(needs.publish-python.result == 'success' || needs.publish-python.result == 'skipped') &&
needs.update-version.result == 'success' &&
(needs.mirror.result == 'success' || needs.mirror.result == 'skipped')
uses: ./.github/workflows/restrict-trunk.yml
with:
unlock: true
secrets:
SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
on-release-complete:
name: On Release Complete
runs-on: ubuntu-latest
needs: [extract-tag, check-authorization, publish, publish-python, docs, create-tag, github-release-draft, github-release-publish, reset-version, update-version, publish-nightly-grid, mirror, verify, unrestrict-trunk]
if: always() && !cancelled() && needs.extract-tag.result != 'skipped' && needs.check-authorization.result != 'failure'
steps:
- uses: actions/checkout@v6
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
SLACK_ICON_EMOJI: ${{ contains(needs.*.result, 'failure') && ':rotating_light:' || (needs.publish.result == 'success' && ':white_check_mark:' || ':warning:') }}
SLACK_COLOR: ${{ contains(needs.*.result, 'failure') && 'failure' || (needs.publish.result == 'success' && 'good' || 'warning') }}
SLACK_CHANNEL: selenium-tlc
SLACK_USERNAME: GitHub Workflows
SLACK_TITLE: ${{ contains(needs.*.result, 'failure') && 'Release failed' || (needs.publish.result == 'success' && 'Release complete' || 'Release did not run') }}
SLACK_MESSAGE: |
• Selenium Published: ${{ needs.publish.result }}
• Python Published: ${{ needs.publish-python.result }}
• Tag Created: ${{ needs.create-tag.result }}
• GitHub Release Draft: ${{ needs.github-release-draft.result }}
• Docs Updated: ${{ needs.docs.result }}
• GitHub Release Published: ${{ needs.github-release-publish.result }}
• Nightly Version Updated: ${{ needs.update-version.result }}
• Nightly Grid: ${{ needs.publish-nightly-grid.result }}
• Mirror Updated: ${{ needs.mirror.result }}
• Packages Verified: ${{ needs.verify.result }}
• Trunk Unlocked: ${{ needs.unrestrict-trunk.result }}
MSG_MINIMAL: actions url
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}