Official GitHub Action for running Bruno CLI commands in CI/CD workflows with full support for collection runs and exposes machine-readable counts (exit-code, passed, failed, total, duration-ms) for downstream steps.
The following shows the minimum setup to configure the GitHub Action with a command and return counts as outputs. Learn about the supported inputs you can use to customize the GitHub Action.
name: API Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Run Bruno Collection
uses: usebruno/bruno-cli-action@v1
with:
working-directory: tests/payments
command: 'run --env prod'What you'll see: the workflow step turns red on assertion failure (green on success). Outputs are populated for downstream conditional steps.
UI rendering, artifact upload, PR comments, and soft-fail semantics are delegated to the GitHub Actions ecosystem (EnricoMi/publish-unit-test-result-action, dorny/test-reporter, actions/upload-artifact, continue-on-error). See Examples for canonical recipes.
Customize the Bruno CLI GitHub Action to suit your API project's CI/CD workflow.
| Name | Type | Description |
|---|---|---|
command |
String | Required. The Bruno CLI command to run and its options (e.g. run --env prod). The action prepends bru. |
bru-version |
String | Version of @usebruno/cli to install. (Default: latest) |
working-directory |
String | Path of the Bruno collection directory. (Default: .) |
Example using all inputs:
- name: Run Bruno collection
uses: usebruno/bruno-cli-action@v1
with:
command: 'run --env prod --reporter-junit results.xml'
bru-version: '3.5.0'
working-directory: tests/paymentsAvailable as ${{ steps.<id>.outputs.<name> }} in subsequent steps:
| Name | Description |
|---|---|
exit-code |
Exit code from the Bruno CLI command. 0 indicates success, non-zero indicates failure. |
passed |
Number of passed requests. |
failed |
Number of failed requests (assertion failures or runtime errors). |
total |
Total number of requests run. |
duration-ms |
Total run duration in milliseconds. |
| Tag | Behaviour |
|---|---|
@v1 |
Floating major. Receives every backwards-compatible release. |
@v1.0.0 |
Immutable. Pinned to a specific release. |
The v<major> tag is retagged automatically on every published release.
The following examples cover some of the reporting and artifact use case. Use --reporter-junit flag to emit clean JUnit XML; downstream actions render it for the user-visible surface needed or upload it as a workflow artifact.
- PR comment on every run (sticky)
- Checks tab UI via dorny/test-reporter
- Artifact upload with header sanitization
- Multiple report formats (JUnit + HTML + JSON)
- Slack notification on failure
- Simple non-sticky PR comment via gh CLI
The most common ask. EnricoMi/publish-unit-test-result-action posts a single comment per PR with structured results, updated on re-runs. Adds a check run with rich annotations as a side benefit.
name: API Tests
on: [pull_request]
permissions:
pull-requests: write
checks: write
contents: read
jobs:
bruno:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: usebruno/bruno-cli-action@v1
with:
working-directory: tests/payments
command: 'run --env prod --reporter-junit results.xml'
- uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: tests/payments/results.xmlWhat you'll see: a single Bruno-themed comment in the PR Conversation tab that updates in place on every re-run, plus a check run with structured per-test results in the PR Checks tab.
If you have a polyglot test stack (Jest, Pytest, Bruno) and want all results in the same Checks tab UI, dorny is the better tool than EnricoMi:
- uses: usebruno/bruno-cli-action@v1
with:
working-directory: tests/payments
command: 'run --env prod --reporter-junit results.xml'
- uses: dorny/test-reporter@v1
if: always()
with:
name: Bruno API tests
path: tests/payments/results.xml
reporter: java-junitWhat you'll see: a separate check run in the PR Checks tab labeled "Bruno API tests" with structured per-test results and expandable failure details. Visually consistent with check runs from your other JUnit-emitting test suites.
Bruno's CLI handles sensitive-header redaction; pass the flag in command. Chain actions/upload-artifact@v7 to persist the report:
- uses: usebruno/bruno-cli-action@v1
with:
working-directory: tests/payments
command: 'run --env prod --reporter-junit results.xml --reporter-skip-headers "Authorization Cookie X-Tenant-Token"'
- uses: actions/upload-artifact@v7
if: always()
with:
name: bruno-report-${{ github.run_id }}-${{ github.job }}
path: tests/payments/results.xmlWhat you'll see: an artifact named bruno-report-<run_id>-<job> on the workflow run page, downloadable for 90 days (GitHub default retention).
Pass multiple reporter flags in command. Chain actions/upload-artifact@v7 with a path list:
- uses: usebruno/bruno-cli-action@v1
with:
working-directory: tests/payments
command: 'run --env prod --reporter-junit results.xml --reporter-html report.html --reporter-json report.json'
- uses: actions/upload-artifact@v7
if: always()
with:
name: bruno-reports-${{ github.run_id }}
path: |
tests/payments/results.xml
tests/payments/report.html
tests/payments/report.jsonWhat you'll see: an artifact containing all three report files. Download to a browser to view the rich HTML report; JSON is consumable by custom dashboards or aggregators.
Use the action's failed output as a conditional. Use continue-on-error: true so the notification step still runs:
- id: bruno
uses: usebruno/bruno-cli-action@v1
continue-on-error: true
with:
working-directory: tests/payments
command: 'run --env prod'
- if: steps.bruno.outputs.failed != '0'
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Bruno tests failed: ${{ steps.bruno.outputs.failed }}/${{ steps.bruno.outputs.total }} requests failed on ${{ github.ref_name }}",
"blocks": [{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Bruno test failures on ${{ github.ref_name }}*\n${{ steps.bruno.outputs.failed }}/${{ steps.bruno.outputs.total }} requests failed in ${{ steps.bruno.outputs.duration-ms }}ms. <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>"
}
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}Prerequisites: SLACK_WEBHOOK_URL secret configured in the repository.
What you'll see: the Bruno step shows red on failure (honest signal) but the workflow continues; a Slack message lands in the channel mapped to the webhook with counts, branch, duration, and a link to the workflow run.
For users who do not want EnricoMi's full setup and only need a quick "post a comment with the counts" pattern (no stickiness, each run adds a new comment):
- id: bruno
uses: usebruno/bruno-cli-action@v1
with:
working-directory: tests/payments
command: 'run --env prod'
- if: always() && github.event_name == 'pull_request'
run: |
if [ "${{ steps.bruno.outputs.failed }}" -gt 0 ]; then
ICON="❌"
STATUS="${{ steps.bruno.outputs.passed }}/${{ steps.bruno.outputs.total }} passed, ${{ steps.bruno.outputs.failed }} failed"
else
ICON="✅"
STATUS="${{ steps.bruno.outputs.total }}/${{ steps.bruno.outputs.total }} passed"
fi
gh pr comment ${{ github.event.pull_request.number }} \
--body "${ICON} **Bruno:** ${STATUS} in ${{ steps.bruno.outputs.duration-ms }}ms · [view run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}Prerequisites: pull-requests: write permission and the workflow triggered on pull_request.
What you'll see: a new comment posted to the PR on every workflow run. Each re-run adds another comment (no in-place update). Use EnricoMi (above) if you want stickiness.
Bruno's CLI works on Jenkins, Azure DevOps, GitLab CI, and Bitbucket Pipelines via direct CLI invocation. The Bruno CLI Docker image is the recommended primitive there. See the Bruno CLI Docker docs for platform-specific examples.
MIT