This tutorial explains how to set up a GitHub Actions workflow that runs tests and comments on the results of pull requests (PRs), including those from forks. Using two workflows ensures security while allowing test reports to be posted on all types of PRs.
The setup consists of two workflows:
workflowA
- Runs tests and uploads the test results as artifacts.workflowB
- Retrieves test results and comments on the corresponding pull request.
This method is applicable to any project using GitHub Actions for CI/CD, ensuring a secure and efficient way to handle test reporting.
These workflows should be implemented on the default branch of the repository (either master
or main
in newer repositories) to ensure proper execution and integration. Running workflows on other branches may lead to unexpected behavior, security issues, or failure to post comments on pull requests.
GitHub restricts workflows triggered by pull_request
events from writing to the base repository when PRs originate from forks. This limitation prevents workflows from commenting on pull requests directly.
Using pull_request_target
instead of pull_request
allows commenting on forked PRs, but it introduces a significant security risk: the workflow runs with write permissions on the base repository, making it vulnerable to malicious code execution. Attackers could potentially modify workflows to exfiltrate secrets, overwrite critical repository files, or introduce malicious changes that could be merged unnoticed.
To mitigate this, we split the workflow into two:
- The first workflow (
workflowA
) runs tests and uploads the results as artifacts. This workflow is triggered usingpull_request
, ensuring it runs whenever a new pull request is opened, updated, or reopened. - The second workflow (
workflowB
) is triggered byworkflow_run
when the first workflow completes. Sinceworkflow_run
does not inherit permissions from the pull request, it eliminates security issues while allowing it to post a comment securely.
This method ensures that test results are always accessible while maintaining security.
This workflow is triggered when a pull request is opened, synchronized, or reopened on any branch. It performs the following steps:
- Check out PR code:
- name: Check out PR code
uses: actions/checkout@v4
- Run Tests:
- name: Run Tests
run: |
./run-tests.sh # Replace with your actual test command
- Convert Test Results to a Compatible Format:
- name: Convert Test Results
run: |
npx test-result-converter ./testReport.xml -o ./results/test-report.json # Modify based on your test framework
- Upload Test Report Artifact:
- name: Upload Test Report Artifact
uses: actions/upload-artifact@v4
with:
name: testReport
path: ./results/test-report.json
- Save PR Number and Upload as an Artifact:
To ensure that workflowB
can correctly comment on the corresponding pull request, we save the PR number as an artifact in workflowA
. Since workflowB
is triggered by workflowA
using workflow_run
, it does not have direct access to the PR metadata. Uploading the PR number as an artifact allows workflowB
to retrieve and use it for posting test results in the correct pull request.
- name: Save PR Number
run: echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV
- name: Upload PR Number as Artifact
run: echo $PR_NUMBER > pr_number.txt
shell: bash
- name: Upload PR Number Artifact
uses: actions/upload-artifact@v4
with:
name: pr_number
path: pr_number.txt
Since this workflow only requires read permissions, it avoids potential security risks when dealing with external contributions from forked repositories. The second workflow, which has the necessary permissions to write, is responsible for retrieving and posting the results, ensuring a secure and controlled execution process.
This workflow is triggered when workflowA
completes successfully. Since GitHub Actions does not allow direct artifact downloads across workflows using actions/download-artifact
.
- Download Test Report Artifact: Since GitHub Actions does not allow direct artifact downloads across workflows using
actions/download-artifact
, we usedawidd6/action-download-artifact@v8
instead. This repository enables downloading artifacts from a previous workflow run by specifying therun_id
, which is essential when handling artifacts between separate workflows. It follows these steps:
- name: Download Test Report Artifact
uses: dawidd6/action-download-artifact@v8
with:
name: testReport
run_id: ${{ github.event.workflow_run.id }}
path: artifacts
- Download PR Number Artifact:
- name: Download PR Number Artifact
uses: dawidd6/action-download-artifact@v8
with:
name: pr_number
run_id: ${{ github.event.workflow_run.id }}
path: pr_number
- Read PR Number:
- name: Read PR Number
id: read_pr_number
run: |
PR_NUMBER=$(cat pr_number/pr_number.txt)
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
- Publish Test Report:
- name: Publish Test Report
uses: test-reporter/[email protected]
with:
report-path: 'artifacts/test-report.json'
issue: ${{ env.PR_NUMBER }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
This final step posts the test report as a comment on the pull request, making it easy for contributors and maintainers to review test results.
By structuring the workflows this way, we achieve the following:
- Secure execution without exposing repository write access to forked pull requests.
- Successful test execution and result upload.
- Seamless commenting on pull requests with test results while mitigating security risks.
This method ensures that test results are reliably posted while maintaining a secure GitHub Actions setup. Additionally, this approach scales effectively for large repositories with many PRs, as the artifact-based workflow minimizes redundant computations and ensures efficient resource utilization. 🚀