In today’s competitive landscape, delivering high-quality products quickly isn’t just an advantage—it’s a necessity. Continuous Integration and Continuous Delivery (CI/CD) have become the backbone of efficient workflows, ensuring seamless testing, faster releases, and fewer errors. But how do you implement it effectively? And why should your team care about GitHub Actions specifically?
In this post, Noveo Senior Test Engineer Elena breaks down CI/CD best practices using GitHub Actions—a tool trusted by developers worldwide. Whether you're looking to automate testing, streamline deployments, or optimize your QA processes, this guide will show you how GitHub Actions can save time, reduce costs, and keep your projects running smoothly.
CI/CD using GitHub Actions
CI/CD (continuous integration / continuous delivery) is a technology that provides software testing automatization and deployment, which has become crucial for any commercial development project nowadays. The main goal of using CI/CD is to make the delivery and deployment process fast and automated. In this article, we will analyze the features of organizing CI/CD using GitHub Actions as an example.
According to State of Developer Ecosystem Report 2024 from JetBrains , GitHub Actions is in Top-3 most popular tools for CI/CD along with Jenkins and GitLab CI.
Taking into account the fact that GitHub is also the most popular code storage tool, it is going to remain demanded and relevant.
CI/CD for QA
CI/CD pipeline is a set of steps aimed at source code transformation into the product. Usually CI/CD pipeline is a part of stage testing which can be used for unit-tests and integrated tests.
What are the benefits of CI/CD for QA?
- Automatic test launch (by trigger/event)
- Quality control (quality gates): code style, unit tests, code coverage, QA tests
- Metrics collecting, report generation
- Shift-left testing as an outcome
- Early testing status, hence low cost of error
- High speed of testing
Maturity levels of quality control systems
Basically, systems can be divided into 3 levels according to the level of automation of the assembly and testing process. On the first level launching autotests, identifying the status of test runs and decision on deployment are held completely manually. As processes develop, automatic launch of tests by trigger or schedule is usually configured, but other steps require intervention.
The third and most automated level is the automatic decision-making on deployment without the participation of engineers. So, if the pipeline has passed and the status is “green”, then the application is automatically deployed to the stands, CI/CD manages the entire testing and release cycle.
In terms of organizing pipelines in service and test repositories, there are 3 approaches:
- Separate Dev/QA repositories
- The main product repository is not connected with the test repository for autotests and separate pipelines. QA focus on building a pipeline for regular autotests running, regardless the main application code update.
- The main repository pipeline runs the autotest pipeline in one of its steps. Focus on the "link" of the application code with autotests - launch by push to the main/test repository, linking, for example, by branch number (development tasks).
- Common Dev/QA repository
- A single repository for application code and integration tests (least common case). A single pipeline with a step for tests. QA focused on the autotest code integration into the main repository (usually the backend).
Each approach has its pros and cons, they are determined by the overall architecture of the project.
How pipeline works
Pipelines are typically launched either by a trigger (preferred since the process is automatic) or by schedule.
Besides tests and deployment itself, the pipeline may contain such stages as code style checking, code coverage calculation, script launch (shell, bash, other commands), generation and saving of artifacts (logs, reports), stopping or continuing execution based on a condition, starting other processes (internal and external, for example, changing the status of tasks), notification to other systems, and other stages.
Best CI/CD practices
- Organizing the pipeline as a code in the form of yaml files - simplifies support and versioning
- Launching the application isolated from stand, identical to production. It is also important to use test data similar to real ones
- Script templating and use of ready-made images allow to avoid duplication and speed up development
- Automatic trigger between processes provides process continuity
- Following the "fail first" approach (first the most critical checks that can block the release) helps to identify problems at the earliest stage
- Adequate time to complete (important when launching on a commit)
- Transparent feedback system (easy to understand problems)
Typical CI problems
Typical problems in CI arise in the following situations:
- The integration process is implemented too late - it should start with the appearance of the first automated tests
- Often there is hard-coded logic in scripts that are not formatted as code, which complicates reuse and support
- Linking the pipeline to static environments, that leads to flexibility and scalability reduction
- The lack of decomposition into steps hinders quick diagnostics of problems
- Without centralized collection of artifacts, logs and run history, analysis and debugging are impossible
- Errors in calculating metrics and incorrectly configured quality gates (either too soft or too strict), this turns checks into a formality or slows down development
- Notifications: their absence leads to loss of control, and their excess leads to ignoring problems.
All these aspects are critically important to consider when designing CI.
GitHib Actions. Advantages
The main advantage of GitHub Actions is integration with the GitHub repository - storing code and setting up CI/CD in one place.
Flexible YAML configurations allow you to set up CI/CD as a pipeline as a code.
GitHub has a convenient Actions Marketplace, where you can select and reuse ready-made steps for custom pipelines, that option saves a lot of time.
GitHub Actions works with all programming languages and stacks, runs tasks in isolation, supports matrix builds and built-in condition logic, manages secrets, and cashes data.
GitHub Actions provides unlimited free minutes for public repositories when using standard GitHub-hosted runners, with no limit on months, OS, or runner type. This means that open-source projects can run CI/CD as much as they want.
You can read more in the Official Documentation at the link: https://docs.github.com/en/actions/get-started/quickstart
GitHub Actions Components
- Workflow is a single pipeline that contains other components.
Described in a yaml file and describes the scenario, stored in .github/workflows/ - Jobs (task) are workflow components that include a set of steps. These are independent tasks that can be executed in parallel.
- Steps - a single action inside a job.
Actions - ready-made actions that have already been written and can be reused (can be taken from the actions marketplace). You can also write custom actions.
name: Simple Workflow
on: [push]
jobs:
say-hello:
runs-on: ubuntu-latest
steps:
- name: Say hello
run: echo "Hello, GitHub Actions!"
Triggers in GitHub Actions
A trigger is an event that starts a pipeline, in GitHub Actions – a workflow.
- on: push – by pushing to the repository one can also specify on which branch to start
- on: pull_request – when operating pull request
on schedule (on: schedule), by cron
- manually through the GitHub web interface (on: workflow_dispatch)
- You can add parameters, for example, the environment.
- You can also call one workflow from another (on: workflow_call)
Full list: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
Conditions and options
You can control steps and jobs using conditions, for example:
- if: github.ref == 'refs/heads/main’ – execute only for the main branch
- if: success() – only if the previous step was successful
- if: failure() – only if the previous step failed
- if: secrets.DEPLOY_TOKEN = ‘…’ – only if there is a secret
- if: github.event_name == 'pull_request’ – only for a pull request
- if: always() – always execute, even if all previous steps failed
There are also options that complement the logic. For example, continue-on-error – continue executing the workflow, even if an error occurs. Or fail-ci-if-error – abort the workflow on error.
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: This might fail
run: exit 1
continue-on-error: true
- name: Lint Code Base
uses: github/super-linter@v5
with:
fail_ci_if_error: true
Secrets
In GitHub Actions, secrets are set via the interface: Repo → Settings → Secrets and variables → Actions → Secrets. This is a secure way to store sensitive data, such as access tokens or passwords.
When used in a workflow, secrets are not output in logs, which prevents accidental data leakage. They can be passed as environment variables: at the level of the entire workflow using the env block or locally inside a separate job via jobs → env. This provides flexibility in managing sensitive parameters in different parts of the pipeline without revealing their contents.
env:
TOKEN: ${{ secrets.MY_SECRET_TOKEN }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Use secret
run: echo "Using token: $TOKEN"
env:
TOKEN: ${{ secrets.MY_SECRET_TOKEN }}
Artifacts
Artifacts are the files that can be saved from one job and used in another or published and downloaded manually after the workflow is completed. For example: build results (.jar, .zip, .exe), reports (allure reports, logs, coverage), etc.
# Save Swagger Report to GitHub Actions Artifacts
- name: Save Swagger Coverage
uses: actions/upload-artifact@v4
if: ${{ inputs.package == 'api' }}
with:
name: swagger-coverage
path: |
swagger-coverage-report.html
swagger-coverage-results.json
Caching
Caching is a way to save and reuse intermediate data between workflow runs to speed up builds and testing
The most common thing to cache is downloading project dependencies (e.g. .m2 folder):
- name: Cache Maven packages # cache Maven dependencies (speeds up subsequent builds)
uses: actions/cache@v3
with:
path: |
~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
Implementation example
The following sequence of tasks (job) can serve as an example of a workflow for GitHub Actions:
- Deploying an application in Docker
- Building a project
- Testing (api, ui) can go simultaneously
- Generating reports, saving history
- Deployment
For this, the project can create files, for instance main.yml, backend-stage.yml, frontend-stage.yml, automation.yml, etc.
GitHub Actions + Allure reports
For storing and publishing the history of Allure Report runs, it is convenient to use a separate gh-pages branch in the repository. At the start of the workflow, the history of previous reports from this branch is loaded. Then a new report for the current run is generated and merged with the previous history (can be split into api/ui packages). After that, the updated report is published via GitHub Pages.
For the publication to work correctly, you need to grant permission to write to gh-pages via the Settings → Pages interface. Detailed setup instructions can be found here.
Additional CI/CD use cases
- Notification about test failures in the developer channel
- Automation of production data preparation for regression testing
- Cleaning the environment (Docker, utilities) via cron tasks
- Quality Gate by Definition of Done – prevents testing of unfinished features
- Selective execution of tests by tags
- Raising the necessary mocks and environments in Docker
- Automation of Release Notes via mapping with Jira, etc.
Conclusions
In this article we covered general concepts of CI/CD and revealed them using the example of GitHub Actions. In the end it can be pointed out that GitHub Actions is a modern and powerful tool that implements all current best practices. It is especially useful for QA engineers, as it allows to create flexible pipelines, automate critical stages, speed up the receipt of feedback on passed tests and increase the overall efficiency of testing and releasing products.