OpenHTF and the TofuPilot Framework are the two open-source Python frameworks purpose-built for hardware manufacturing test. OpenHTF was built by Google in 2016 and is the established, widely deployed option. The TofuPilot Framework is newer, built on a Rust execution engine for parallel execution. Both model a test as ordered phases with structured measurements, both connect instruments through reusable plugs, and both run natively on the TofuPilot CLI and stream to the same dashboard. This guide compares them side by side with real code so you can pick the right one.
Framework Overview
| TofuPilot Framework | OpenHTF | |
|---|---|---|
| Language | Python (Rust engine) | Python |
| License | MIT | Apache 2.0 |
| Maintainer | TofuPilot | |
| First release | 2025 | 2016 |
| Authoring model | Declarative YAML procedure plus Python phases and plugs | Code-first Python with decorators |
| Execution | Parallel phases and slots | Sequential phases |
| Community | Newer, smaller | Established, larger install base |
Feature Comparison
| Feature | TofuPilot Framework | OpenHTF |
|---|---|---|
| Phases | Yes | Yes |
| Numeric, string, boolean measurements | Yes | Yes |
| Logs and attachments | Yes | Yes |
| Unit identification by serial number | Yes | Yes |
| Sub-units (child components) | Yes | Yes |
| Plugs (shared instrument connections) | Yes | Yes |
| Multi-dimensional measurements (waveforms, sweeps) | Yes | Yes |
| Aggregations (mean, min, max, std over arrays) | Yes | No |
| Per-axis validators (validate curve shape) | Yes | No |
| Operator prompts | Yes | Yes |
| Custom forms and display components | Yes | No |
| Retry, timeout, setup/teardown | Yes | Yes |
| Conditional execution | Yes | Yes |
| Parallel phases | Yes | No |
| Multi-slot execution (multiple boards on one fixture) | Yes | No |
On the shared data model the two are close to parity. The gaps fall in three areas: advanced measurement analysis, the operator UI, and execution.
The Same Test in Both Frameworks
A functional test that verifies a board's 3.3V rail and firmware version, written in each framework.
TofuPilot Framework Version
The procedure YAML declares the sequence and limits. The phases and plug are plain Python that the procedure references by name.
name: Functional Testversion: 1.0.0unit: serial_number: default_value: "PCBA000001" part_number: default_value: "PCBA-100"plugs: - name: dut python: plugs.dut:Dutmain: - name: Measure Voltage python: phases.measure_voltage measurements: - name: rail_3v3 unit: V validators: - operator: ">=" expected_value: 3.2 - operator: "<=" expected_value: 3.4 - name: Check Firmware python: phases.check_firmware measurements: - name: firmware_version validators: - operator: "==" expected_value: "2.1.0"# Parameters are injected by name: measurements, plus the dut plug.def measure_voltage(measurements, dut): measurements.rail_3v3 = dut.read_voltage()def check_firmware(measurements, dut): measurements.firmware_version = dut.query_firmware()class Dut: """Manage the connection to the unit under test.""" def read_voltage(self) -> float: return 3.31 # Replace with an instrument read def query_firmware(self) -> str: return "2.1.0" # Replace with a DUT queryRun it from the procedure directory with tofupilot run, then deploy to a station with a Git push.
OpenHTF Version
OpenHTF attaches measurements and plugs to phase functions with decorators, then runs them in sequence.
import openhtf as htffrom openhtf.plugs import BasePlugfrom openhtf.util import unitsclass DutPlug(BasePlug): """Manage the connection to the unit under test.""" def read_voltage(self) -> float: return 3.31 # Replace with an instrument read def query_firmware(self) -> str: return "2.1.0" # Replace with a DUT query@htf.measures( htf.Measurement("rail_3v3").in_range(3.2, 3.4).with_units(units.VOLT))@htf.plug(dut=DutPlug)def measure_voltage(test, dut): test.measurements.rail_3v3 = dut.read_voltage()@htf.measures(htf.Measurement("firmware_version").equals("2.1.0"))@htf.plug(dut=DutPlug)def check_firmware(test, dut): test.measurements.firmware_version = dut.query_firmware()def main(): test = htf.Test(measure_voltage, check_firmware, part_number="PCBA-100") test.execute(test_start=lambda: input("Scan serial number: "))if __name__ == "__main__": main()The TofuPilot CLI runs both frameworks natively and streams phases, measurements, logs, attachments, and the operator UI to the dashboard, with no extra code on either side.
Key Differences in the Code
| Aspect | TofuPilot Framework | OpenHTF |
|---|---|---|
| Sequence definition | Declared in procedure.yaml, separate from logic | Function order in htf.Test(...), in code |
| Limits | validators in YAML, stored as data | .in_range(3.2, 3.4) chained in code |
| Plug injection | By parameter name (dut) | By @htf.plug(dut=...) decorator |
| Measurement access | measurements.rail_3v3 = ... | test.measurements.rail_3v3 = ... |
| Plug class | Plain Python class | Subclass of BasePlug |
Execution and Parallelism
This is the clearest architectural difference.
OpenHTF runs phases sequentially against one unit under test. To test multiple boards or run independent steps concurrently, you orchestrate that yourself outside the framework.
The TofuPilot Framework runs on a Rust engine that executes independent phases in parallel and serializes only where you declare a depends_on. It also supports multi-slot execution, testing several boards in parallel on one fixture, which directly reduces cycle time on high-volume lines. Your phase and plug code stays plain Python; the engine handles the scheduling.
If your bottleneck is throughput on a fixture that holds multiple DUTs, this is the deciding factor. If you test one unit at a time, it matters less.
Measurements and Operator UI
Both frameworks capture typed measurements with limits, units, and validators, and both support multi-dimensional measurements for waveforms and sweeps. The TofuPilot Framework adds two things OpenHTF does not: aggregations (mean, min, max, std computed over an array) and per-axis validators that check the shape of a curve rather than only its endpoints. For RF sweeps, thermal profiles, or any test where the waveform itself is the spec, that removes glue code.
Both ship a built-in operator UI. OpenHTF provides web prompts for operator input such as scanning a serial number. The TofuPilot Framework adds declarative form and display components (text, number, switch, radio, select, multiselect, image choice, sliders, progress) that you define in the procedure without writing any frontend.
When to Use the TofuPilot Framework
The TofuPilot Framework is the better choice when:
- Your line is throughput-bound. Parallel phases and multi-slot execution test several boards at once on one fixture and cut cycle time. OpenHTF executes sequentially.
- You validate waveforms or sweeps. Built-in aggregations and per-axis validators check curve shape, not only endpoints, with no glue code.
- Operators need rich guided workflows. Declarative forms and display components render on the station with no frontend work.
- You want the test plan separate from the logic. The YAML procedure is readable and diffable independently of the Python phases.
- You are setting up new stations. Deploy-on-push, real-time streaming, and offline queueing are built into the CLI.
When to Use OpenHTF
OpenHTF is the better choice when:
- You already run it. Existing OpenHTF suites run natively on TofuPilot with no rewrite. The migration cost is not worth it.
- Maturity and community matter most. Years of production use at scale and a larger body of public examples and patterns.
- You want a single pure-Python paradigm. Everything is in Python, with no separate YAML layer to learn.
- Apache 2.0 fits your legal review. Its explicit patent grant can matter in some procurement contexts.
Using Both Together
You do not have to choose the framework to choose the platform. A common path is to keep an existing OpenHTF suite running as is, and reach for the TofuPilot Framework on new stations that need parallel slots or richer measurements. Both stream phases, measurements, logs, attachments, and the operator UI to the same dashboard, and both are open-source, so neither choice locks the test code itself.
Decision Matrix
| Question | Better fit |
|---|---|
| Testing multiple boards on one fixture, throughput-bound? | TofuPilot Framework |
| Validating waveform or sweep shape, not just endpoints? | TofuPilot Framework |
| Need rich operator forms on the station? | TofuPilot Framework |
| Already running OpenHTF suites in production? | OpenHTF |
| Want a single pure-Python paradigm, no YAML? | OpenHTF |
| Maximum maturity and community matter most? | OpenHTF |
Both run on the TofuPilot CLI and upload to the same dashboard, so you can start with either and mix them per station.