TofuPilot Framework
Last updated on May 21, 2026
The TofuPilot Framework is a Python test framework purpose-built for hardware. A single procedure.yaml declares phases, measurements, plugs, operator UI, and execution policy, so you can ship a new procedure in minutes instead of stitching together a custom runner. Source on GitHub at tofupilot/framework, MIT licensed.
You write the YAML, push to Git, and the CLI handles bootstrapping, validation, and execution end-to-end. It is the only framework that exposes the full feature set, since the OpenHTF, Pytest, and Robot connectors each cover a subset.
Getting started
To get started with the TofuPilot Framework:
- Clone our starter template to your favorite Git provider and deploy it on TofuPilot.
- If you have a procedure directory, push it to your Git provider and import it from the New Procedure wizard.
Procedure structure
A procedure is a directory with procedure.yaml at its root. The CLI reads the file, bootstraps Python through uv, and runs every phase declared in the YAML.
name: Battery Functional Test
version: 1.0.0
description: Validates battery voltage, capacity, and charge cycles
id: 1cf7e59a-bf0a-11f0-b044-639a4e9293c8
unit:
serial_number:
pattern: "^BAT[0-9]{6}$"
part_number:
default_value: "BAT-001"
plugs:
- name: power_supply
python: plugs.psu:PowerSupply
setup:
- name: Power On
python: phases.power_on
main:
- name: Measure Voltage
python: phases.measure_voltage
teardown:
- name: Power Off
python: phases.power_offRun the procedure from its directory:
tofupilot runFields
The top-level keys map directly to procedure resources on the dashboard.
| Field | Purpose |
|---|---|
name | Display name. 1-100 characters. Required. |
version | Free-form version string. 1-50 characters. Required. |
description | Optional documentation, up to 50,000 characters. |
id | Dashboard procedure UUID. Links uploaded runs to the procedure record. |
unit | Unit identification rules and defaults. |
plugs | Long-lived resources for phases. |
execution | Worker count, slots, strategy, on-fail policy. See Execution. |
setup | Phases that run before main. Aborts if any setup phase fails. |
main | Bulk of the test. Runs after setup completes. |
teardown | Always runs, even after failure. Releases resources. |
Phases
A phase is a Python function referenced from procedure.yaml. The CLI imports the module, calls the function, and records the outcome.
def measure_voltage(test, power_supply):
voltage = power_supply.read_voltage()
test.measurements.voltage = voltageSee Phases for the full reference.
Measurements
A measurement is a typed value declared on a phase and assigned at runtime. Limits, units, and outcome are evaluated by the CLI and uploaded to the dashboard.
main:
- name: Measure Voltage
python: phases.measure_voltage
measurements:
- name: voltage
unit: V
limits:
min: 4.8
max: 5.2See Measurements for the full reference.
Plugs
A plug is a long-lived resource shared across phases. The CLI instantiates it once per slot and tears it down when the procedure ends.
class PowerSupply:
def __enter__(self):
return self
def __exit__(self, *args):
pass
def read_voltage(self) -> float:
return 5.03plugs:
- name: power_supply
python: plugs.psu:PowerSupplyOperator UI
The Operator UI block declares interactive prompts that stream to the kiosk and the dashboard. The phase pauses until the operator responds.
main:
- name: Confirm Cable
operator_ui:
type: prompt
message: "Plug in the USB cable"See Operator UI for the full reference.
Execution
The execution block controls worker count, parallel slots, scheduling strategy, and failure handling.
execution:
strategy: phase_first
workers: 16
on_first_failure: stop
slots:
- name: USB0
- name: USB1See Execution for the full reference.
Monorepos
When a repo hosts multiple procedures that share phases and plugs, the Monorepos layout keeps shared code in one place.
Offline upload
When the bench loses connectivity, the CLI queues runs locally and uploads them when the network comes back. Each queued run keeps its original timestamp, so analytics stay accurate even when uploads land hours later. See tofupilot queue.
How is this guide?
