| name: Release Selenium |
| |
| 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: |
| 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 }} |
| |
| get-approval: |
| name: Get Approval |
| needs: parse-tag |
| uses: ./.github/workflows/get-approval.yml |
| with: |
| title: Release approval required |
| message: "Approval is required to publish ${{ needs.parse-tag.outputs.tag }}." |
| secrets: |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} |
| |
| create-language-tag: |
| name: Create Language Tag |
| needs: [parse-tag, get-approval] |
| if: needs.parse-tag.outputs.language != 'all' |
| runs-on: ubuntu-latest |
| permissions: |
| contents: write |
| steps: |
| - name: Create language-specific 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: | |
| 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, get-approval] |
| 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, get-approval] |
| 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' }} |
| run: ${{ (needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == matrix.language) && format('./go {0}:release', matrix.language) || 'echo skipping' }} |
| artifact-name: ${{ (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, get-approval] |
| 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=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 |
| |
| 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 all release packages |
| if: needs.parse-tag.outputs.language == 'all' |
| uses: actions/download-artifact@v8 |
| with: |
| pattern: release-packages-* |
| merge-multiple: true |
| path: "build/dist/" |
| - name: Download release packages |
| if: needs.parse-tag.outputs.language != 'all' |
| uses: actions/download-artifact@v8 |
| with: |
| name: release-packages-${{ needs.parse-tag.outputs.language }} |
| 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-language-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-language-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, docs] |
| uses: ./.github/workflows/bazel.yml |
| with: |
| name: Verify packages |
| run: ./go ${{ needs.parse-tag.outputs.language }}:verify |
| |
| unrestrict-trunk: |
| name: Unrestrict Trunk Branch |
| needs: verify |
| uses: ./.github/workflows/restrict-trunk.yml |
| with: |
| restrict: false |
| secrets: |
| SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }} |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} |
| |
| reset-version: |
| name: Generate Nightly Versions |
| needs: [parse-tag, docs] |
| 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 |
| |
| update-version: |
| name: Push Nightly Versions |
| needs: [parse-tag, reset-version, unrestrict-trunk] |
| 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 }} |
| |
| reset-dependencies: |
| name: Update Lockfiles for Nightly |
| needs: [parse-tag, update-version] |
| uses: ./.github/workflows/bazel.yml |
| with: |
| name: Reset Dependencies |
| run: ./go ${{ needs.parse-tag.outputs.language }}:pin${{ needs.parse-tag.outputs.language == 'all' && ' && ./go rust:pin' || '' }} |
| artifact-name: version-reset-deps |
| |
| commit-dependencies: |
| name: Push Nightly Lockfiles |
| needs: [parse-tag, reset-dependencies] |
| permissions: |
| contents: write |
| actions: read |
| uses: ./.github/workflows/commit-changes.yml |
| with: |
| artifact-name: version-reset-deps |
| commit-message: "[build] Update lockfiles for nightly versions" |
| secrets: |
| SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }} |
| |
| nightly: |
| name: Publish Nightly Packages |
| needs: [parse-tag, commit-dependencies] |
| permissions: |
| contents: write |
| packages: write |
| uses: ./.github/workflows/nightly.yml |
| with: |
| language: ${{ needs.parse-tag.outputs.language }} |
| secrets: inherit |
| |
| mirror: |
| name: Update Release Mirror |
| needs: [parse-tag, nightly] |
| if: needs.parse-tag.outputs.language == 'all' |
| uses: ./.github/workflows/mirror-selenium-releases.yml |
| secrets: inherit |
| |
| on-release-failure: |
| name: On Release Failure |
| runs-on: ubuntu-latest |
| needs: [publish, publish-python, docs, create-language-tag, github-release-draft, github-release-publish, update-version, reset-dependencies, commit-dependencies, nightly, mirror, verify] |
| if: failure() |
| steps: |
| - uses: actions/checkout@v6 |
| - 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 failed |
| SLACK_MESSAGE: | |
| • Selenium Published: ${{ needs.publish.result }} |
| • Python Published: ${{ needs.publish-python.result }} |
| • Language Tag Created: ${{ needs.create-language-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 Lockfiles Updated: ${{ needs.commit-dependencies.result }} |
| • Nightly Packages: ${{ needs.nightly.result }} |
| • Mirror Updated: ${{ needs.mirror.result }} |
| • Packages Verified: ${{ needs.verify.result }} |
| MSG_MINIMAL: actions url |
| SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} |