Keywords: GitHub Actions | Status Check Functions | Test Result Archiving
Abstract: This article explores how to ensure subsequent steps, such as test result archiving, execute even if a previous step fails in GitHub Actions workflows, while keeping the overall job status as failed. By analyzing status check functions in if conditions (e.g., always(), success(), failure(), cancelled()), it provides configuration examples and best practices to reliably collect test data in CI/CD pipelines, enabling access to critical logs despite test failures.
Introduction
In continuous integration and continuous deployment (CI/CD) pipelines, testing is crucial for code quality assurance. However, when tests fail, GitHub Actions by default halts subsequent steps, which can prevent the collection and archiving of important test results. This article addresses real-world development scenarios, focusing on how to execute steps like test result uploads after a failure, while ensuring the job is correctly marked as failed to reflect the build status accurately.
Problem Context
In GitHub Actions, each job consists of multiple steps. By default, if a step fails, subsequent steps are skipped, and the job status is marked as failed. For instance, in a typical build and test job, if the test step fails, the test result archiving step is omitted, making it difficult for developers to access failure details. This complicates debugging, especially when analyzing test logs to identify root causes.
Consider the following workflow configuration:
name: CI
on:
pull_request:
branches:
- master
push:
branches:
- master
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Test App
run: ./gradlew test
- name: Archive Test Results
uses: actions/upload-artifact@v1
with:
name: test-results
path: app/build/reports/testsIn this setup, if the Test App step fails, the Archive Test Results step does not run, and test results are not uploaded as artifacts. While using continue-on-error: true can force continuation, it incorrectly marks the job as passed, masking the test failure.
Solution: Using if Conditional Expressions
GitHub Actions provides status check functions that allow control over execution logic at the step or job level using if conditions. These functions include always(), success(), failure(), and cancelled(), which can be combined for flexible behavior.
Core Status Check Functions
always(): Executes the step regardless of previous step status, including success, failure, or cancellation.success(): Executes only if all previous steps succeeded.failure(): Executes only if a previous step failed.cancelled(): Executes only if the job was cancelled.
By default, if no status check function is specified, GitHub Actions implicitly applies a success() condition, meaning steps run only if prior steps succeeded.
Application Example
To address the issue of uploading test results after a failure, add if: always() to the archiving step:
steps:
- name: Build App
run: ./build.sh
- name: Archive Test Results
if: always()
uses: actions/upload-artifact@v1
with:
name: test-results
path: app/buildThis ensures the Archive Test Results step runs whether the Build App step succeeds or fails. The overall job status is determined by the actual step outcomes: if tests fail, the job remains failed, but test results are uploaded.
Alternative Approaches and Fine-Grained Control
In some cases, always() may be too broad, such as when a job is manually cancelled and archiving is undesired. More precise conditions can be used:
if: success() || failure(): Executes on success or failure, excluding cancellation.if: '!cancelled()': Executes if not cancelled (note quotes to avoid YAML parsing errors).if: failure(): Executes only on failure, suitable for error handling or rollback steps.
For example, in a Kubernetes deployment scenario, if integration tests fail, a rollback might be necessary:
steps:
- name: Run Integration Tests
run: ./run-tests.sh
- name: Rollback Deployment
if: failure()
run: kubectl rollout undo deployment/my-appThis ensures the rollback step runs only if tests fail, not on success or cancellation.
In-Depth Analysis of Status Check Function Behavior
GitHub Actions documentation highlights that status check functions have special behavior in if conditions. If not explicitly used, conditions default to combining with success(). For instance, if: true is equivalent to if: success() && true, meaning the step runs only if previous steps succeeded.
This design ensures backward compatibility, but developers must be aware of implicit behaviors to avoid unexpected results. In practice, explicitly using status check functions is recommended for improved readability and maintainability.
Best Practices and Considerations
- Clarify Conditional Intent: When using functions like
always()orfailure(), ensure they align with workflow logic. For example, usealways()for critical cleanup steps andfailure()for error handling. - Avoid Overusing
always(): Whilealways()guarantees step execution, it may run unnecessarily (e.g., on job cancellation). Choose more precise conditions based on the context. - Test Workflows: Validate configurations by simulating failure and success scenarios before deployment. GitHub Actions offers logs and debugging tools to identify issues.
- Integrate with Other Features: Status check functions can be combined with environment variables, matrix strategies, and other features to implement complex workflow logic.
Conclusion
By effectively leveraging status check functions in GitHub Actions, developers can build more resilient CI/CD pipelines. Executing archiving steps after test failures provides essential debugging information while maintaining accurate job status. The methods discussed, based on real-world use cases and official documentation, help teams optimize automation processes and enhance development efficiency. As GitHub Actions evolves, staying updated with documentation is advised for accessing new features.