pytest-embedded-idf Integration for ESP-IDF
pytest-embedded-idf is a pytest plugin that extends pytest-embedded to provide a robust testing framework specifically designed for ESP-IDF based firmware. It streamlines the process of building, flashing, and testing applications on Espressif hardware, integrating directly with `idf.py` tools. The current version is 2.7.0, and it follows the release cadence of `pytest-embedded` and ESP-IDF updates.
Common errors
-
ModuleNotFoundError: No module named 'pytest_embedded_idf'
cause The `pytest-embedded-idf` library is not installed in the current Python environment.fixInstall the library: `pip install pytest-embedded-idf`. -
FileNotFoundError: [Errno 2] No such file or directory: 'idf.py'
cause The `idf.py` command, which is part of the ESP-IDF build system, cannot be found in the system's PATH. This usually means the ESP-IDF environment has not been sourced or `IDF_PATH` is incorrect.fixEnsure `IDF_PATH` is set correctly (e.g., `export IDF_PATH=/path/to/esp-idf`) and that the ESP-IDF tools are in your PATH (often done by sourcing the `export.sh` or `export.bat` script from your ESP-IDF installation). -
Permission denied: '/dev/ttyUSB0' (or similar serial port device path)
cause The current user does not have sufficient permissions to open and write to the serial port device connected to the ESP board.fixOn Linux, add your user to the `dialout` group: `sudo usermod -a -G dialout $USER`. Log out and log back in for changes to apply. On Windows, ensure drivers are installed and no other process is holding the port. -
Failed to flash DUT: flash command exited with non-zero exit code
cause The flashing process (invoked by `idf.py flash`) failed. This can be due to a variety of reasons including incorrect board connection, wrong baud rate, corrupted firmware, or issues with the esptool utility.fixCheck the detailed error messages preceding this output for clues from `esptool.py`. Ensure the board is properly connected, drivers are installed, and the correct serial port is being detected (or specified with `--port`). Try flashing manually using `idf.py flash` to debug the underlying issue.
Warnings
- breaking pytest-embedded-idf versions 2.x.x require pytest-embedded versions 2.x.x. Using incompatible major versions (e.g., pytest-embedded-idf 2.x.x with pytest-embedded 1.x.x) will lead to API mismatches and runtime errors.
- gotcha The `IDF_PATH` environment variable must be correctly set and point to your ESP-IDF installation directory for `idf.py` commands (used for building and flashing) to function correctly. Without it, tests involving building or flashing will fail.
- gotcha On Linux, serial port access often requires the user to be a member of the `dialout` group. If not, `pytest-embedded-idf` will encounter 'Permission denied' errors when trying to open serial ports.
- gotcha If your ESP-IDF project fails to build or flash using `idf.py build` or `idf.py flash` directly, it will also fail when `pytest-embedded-idf` attempts these actions. Common causes include incorrect CMakeLists.txt, missing components, or build environment issues.
Install
-
pip install pytest-embedded-idf
Imports
- IdfDut
from pytest_embedded.dut import Dut
from pytest_embedded_idf.dut import IdfDut
Quickstart
# Save this as 'test_hello.py' in your ESP-IDF project directory (e.g., esp-idf/examples/get-started/hello_world)
# Requirements:
# 1. An ESP-IDF project directory.
# 2. ESP-IDF environment variable IDF_PATH correctly set.
# 3. A compatible Espressif development board connected via USB.
# 4. pip install pytest pytest-embedded pytest-embedded-idf
import pytest
from pytest_embedded_idf.dut import IdfDut
import os
# Example: Mark the test to run on an ESP32 target.
# You can change this to esp32s2, esp32s3, esp32c3, esp32c6, etc.,
# depending on your target and --target option if specified.
@pytest.mark.esp32
def test_hello_world_output(dut: IdfDut):
"""
Tests that the ESP-IDF 'hello_world' example prints the expected output.
The 'dut' fixture automatically handles building, flashing, and connecting.
"""
print(f"\nConnected DUT target: {dut.target}")
print(f"DUT COM Port: {dut.serial.port}")
print(f"Application path: {dut.app.path}")
# Expect 'Hello world!' from the device's serial output
dut.expect(r'Hello world!')
print("Successfully received 'Hello world!' from DUT.")
# Example: Send a command (if your application supports it)
# dut.write('get_heap')
# dut.expect(r'Free heap: \d+ bytes')