CI/CD using GitHub Actions

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.

Continuous Integration Tools

Taking into account the fact that GitHub is also the most popular code storage tool, it is going to remain demanded and relevant.

Version control services

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.

Maturity levels of quality control systems

In terms of organizing pipelines in service and test repositories, there are 3 approaches:

  1. 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).
  1. 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.

How pipeline works

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.

Implementation example

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.