This tutorial demonstrates how to automate ESP-IDF firmware testing using GitHub Actions and a Raspberry Pi configured as a self-hosted runner.
An ESP32-S3 board is physically connected to the Raspberry Pi via USB, which serves as the testing host.
This tutorial continues from Stage 3 — VS Code Pytest Testing.
It uses the same ESP-IDF project and test structure, but instead of manually running the tests inside VS Code, everything is automated using GitHub Actions.
Whenever new code is pushed to the GitHub repository, the workflow automatically:
- Builds the ESP-IDF project on the Raspberry Pi.
- Flashes the ESP32-S3 over USB.
- Runs the Unity/Pytest tests on real hardware.
This setup allows true hardware-in-the-loop continuous integration (CI) for embedded firmware.
| Component | Description |
|---|---|
| Raspberry Pi | Acts as a self-hosted GitHub Actions runner. |
| ESP32-S3 | Device under test, connected via USB to the Pi. |
| ESP-IDF v5.5.1 | Installed on Raspberry Pi. |
| pytest-embedded | For running firmware tests automatically. |
| GitHub Account & Repository | To host the workflow and source code. |
| Stable Internet Connection | For Raspberry Pi to communicate with GitHub. |
GitHub Actions is GitHub’s built-in automation and CI/CD (Continuous Integration/Continuous Deployment) platform.
It allows you to define workflows that automatically run tasks whenever specific events occur in your repository — such as pushing new code, creating a pull request, or publishing a release.
- Automatically build and test ESP-IDF firmware each time code changes.
- Ensure code quality and stability before merging.
- Run real hardware tests on an ESP32-S3 connected to a Raspberry Pi, without manual flashing or monitoring.
This automation greatly improves productivity and reliability by catching bugs early — just like CI pipelines in large-scale software projects.
Reference: GitHub Actions
-
Clone the repository
To follow this tutorial, first clone this repository which contains the complete project setup, including the ESP-IDF test code and the GitHub Actions workflow file.
git clone https://github.com/ChandimaJayaneththi/esp32_github_actions_ci_rpi.git
Next, create your own GitHub repository (for example, esp32_ci_rpi_runner) and push the cloned project into it
-
Ensure the Raspberry Pi has internet access and ESP-IDF installed with Pytest:
cd ~/esp/esp-idf ./install.sh --enable-pytest
- Confirm ESP-IDF installation
cd ~/esp/esp-idf . ./export.sh idf.py --version
- Confirm ESP-IDF installation
-
Connect your ESP32-S3 to the Raspberry Pi via USB
- Check available ports:
Example output: /dev/ttyUSB0
ls /dev/tty*
- Check available ports:
-
ESP32-S3 Hardware configuration
Test Pin Connections Notes GPIO Test GPIO4 → GPIO5Use a jumper wire ADC Test GPIO6 → GPIO1(ADC1_CH0)Jumper wire required UART Test GPIO17 → GPIO16UART1 loopback connection 🔸 This wiring applies to the ESP32-S3-DevKitC-1 board.
🔸 Adjust pins if using another ESP32 variant.
esp32_github_actions_ci_rpi/
├── components/
│ ├── test_peripheral/
│ │ ├── test/test_peripheral.c
│ │ ├── CMakeLists.txt
│ │ └── include/
│ │ └── peripheral.h
│ └── peripheral/
│ ├── peripheral.c
│ └── CMakeLists.txt
├── test/
│ ├── pytest_esp32_test.py
│ ├── pytest.ini
│ └── main/
│ ├── unit_test_peripheral.c
│ └── CMakeLists.txt
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions Workflow file
├── CMakeLists.txt
└── README.md- Go to your repository on GitHub → Settings → Actions → Runners
- Click
New self-hosted runner - Select
Runner imageasLinux - Select the
Architectureaccording to your Raspberry Pi- Please confirm the architecture of your Raspberry Pi before configure it using
Output examples:
uname -a
- armv7l → 32-bit ARM (e.g. Raspberry Pi 3 running 32-bit OS)
- aarch64 → 64-bit ARM (e.g. Raspberry Pi 4/5 running 64-bit OS)
- Please confirm the architecture of your Raspberry Pi before configure it using
- Run the commands one by one listed under
DownloadandConfiguresections to install, configure and run the GitHub action runner. - Once it is successfully done, you can see the Raspberry Pi runner and the status of it on GitHub → Settings → Actions → Runners
Reference: Managing self-hosted runners
Below is the CI workflow file used to automate build, flash, and test steps:
name: ESP-IDF CI on Raspberry Pi
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:
jobs:
build:
runs-on: self-hosted
steps:
uses: actions/checkout@v4
- name: Set target
working-directory: test
run: |
. ~/esp/esp-idf/export.sh
idf.py set-target esp32s3
- name: Build the Project
working-directory: test
run: |
. ~/esp/esp-idf/export.sh
idf.py build
- name: Run Unit tests
working-directory: test
run: |
. ~/esp/esp-idf/export.sh
pytest pytest_esp32_test.py --port <PORT>Note: Replace "PORT" with the correct device (for example, /dev/ttyUSB0).
name:Defines the workflow’s display name in GitHub Actionson: Specifies when the workflow should trigger.- Runs automatically on every push or pull request to the main branch.
- Can also be started manually from the Actions tab using
workflow_dispatch.
jobs: Each job groups a set of steps to be executed on a specific runner. In this case, the job runs on a self-hosted runnersteps: Each step defines a task in the CI process. Inside eachsteps, therunkeyword executes shell commands in the runner’s environment.- Checkout repository — uses actions/checkout@v4 to download the code into the runner.
- Set target — loads the ESP-IDF environment and sets the build target to ESP32-S3.
- Build the Project — compiles the ESP-IDF project.
- Run Unit tests — runs automated unit tests on the ESP32-S3 through the connected serial port
- Trigger the workflow either commit and push your latest code or manually from the Actions tab.
- Go to your repository → Actions tab
- You’ll see a workflow named "ESP-IDF CI on Raspberry Pi" automatically start.
- The workflow will:
- Build the firmware on the Raspberry Pi
- Flash the ESP32-S3
- Run Pytest tests on hardware
You will see the following logs under Run Unit tests section
2025-11-02 04:18:59 #### Running all the registered tests #####
2025-11-02 04:18:59
2025-11-02 04:18:59 Running GPIO output/input loopback test...
2025-11-02 04:18:59 /home/chandi/actions-runner/_work/esp32_github_ci_testing/esp32_github_ci_testing/components/test_peripheral/test/test_peripheral.c:19:GPIO output/input loopback test:PASS
2025-11-02 04:18:59
2025-11-02 04:18:59 -----------------------
2025-11-02 04:18:59 1 Tests 0 Failures 0 Ignored
2025-11-02 04:18:59 OK
2025-11-02 04:19:00 Running ADC reading test...
2025-11-02 04:19:01 /home/chandi/actions-runner/_work/esp32_github_ci_testing/esp32_github_ci_testing/components/test_peripheral/test/test_peripheral.c:31:ADC reading test:PASS
2025-11-02 04:19:01
2025-11-02 04:19:01 -----------------------
2025-11-02 04:19:01 1 Tests 0 Failures 0 Ignored
2025-11-02 04:19:01 OK
2025-11-02 04:19:02 Running UART TX/RX test...
2025-11-02 04:19:02 /home/chandi/actions-runner/_work/esp32_github_ci_testing/esp32_github_ci_testing/components/test_peripheral/test/test_peripheral.c:44:UART TX/RX test:PASS
2025-11-02 04:19:02
2025-11-02 04:19:02 -----------------------
2025-11-02 04:19:02 1 Tests 0 Failures 0 Ignored
2025-11-02 04:19:02 OK
2025-11-02 04:19:02
2025-11-02 04:19:02 #### Starting interactive test menu #####
2025-11-02 04:19:02
2025-11-02 04:19:02
2025-11-02 04:19:02
2025-11-02 04:19:02 Press ENTER to see the list of tests.
============================================================
Testing GPIO Output/Input Loopback
============================================================
GPIO test PASSED
GPIO pins are functioning correctly
Digital I/O is reliable
============================================================
Testing ADC Reading
============================================================
ADC test PASSED
ADC readings are within expected range
Analog measurements are reliable
============================================================
Testing UART TX/RX
============================================================
UART test PASSED
UART communication is working
TX/RX loopback successful
============================================================
FINAL TEST SUMMARY
============================================================
Passed: 3/3 - GPIO, ADC, UART
Failed: 0/3 - None
ALL TESTS PASSED - System is fully operational
============================================================
.
============================== 1 passed in 9.28s ===============================
This project was tested on:
- Board: ESP32-S3-DevKitC-1
- ESP-IDF: v5.5.1 with Pytest
- Raspberry Pi HW: Raspberry Pi 3 Model B
- OS: Debian GNU/Linux 13 (trixie)
By the end of this tutorial, you will understand how to:
- Set up a Raspberry Pi as a self-hosted GitHub Actions runner.
- Configure ESP-IDF and Pytest to work seamlessly in an automated CI environment.
- Use GitHub Actions to build, flash, and test ESP32 firmware automatically.
- Run hardware-in-the-loop (HIL) testing directly from GitHub whenever new code is pushed.
- Extend this setup for continuous testing and integration in embedded firmware projects.
MIT License © 2025 — Chandima Jayaneththi
