Output Callbacks
Learn how to create structured test results with OpenHTF, covering JSON output and custom output mechanisms with detailed examples.
Last updated · Verified with OpenHTF 1.6.1
Enable automatic test report generation in JSON or other formats.
When the test ends, OpenHTF passes the finished test record to every registered output callback. The built-in JSON callback writes a file named after the device under test; a custom callback can send the record anywhere.
- Test end
- TestRecord
- OutputToJSON("./{dut_id}.json")
- custom callbackdef upload(record): ...
Output callbacks run when a test ends and write its record somewhere: a JSON file by default, or anything you write.
JSON
You can enable automatic test result export in the default JSON format.
from openhtf import Test, PhaseResult
from openhtf.output.callbacks import json_factory
def get_sn(test):
test.test_record.dut_id = 'SN1234'
return PhaseResult.CONTINUE
def main():
test = Test(get_sn)
# Exports results to JSON with pretty-printing
test.add_output_callbacks(json_factory.OutputToJSON("test_result.json", indent=2))
test.execute()
if __name__ == "__main__":
main()A test_result.json file is created after execution and saved in the execution directory. The pattern can use any field of the record, so one file per run is the usual choice:
json_factory.OutputToJSON("./records/{dut_id}.{metadata[test_name]}.{start_time_millis}.json", indent=2)filename_pattern_or_filestr, callable or fileFormat string expanded with the record's fields, a callable returning a path, or an open file object.
indentint or NonePretty-print indentation. None writes compact JSON.
inline_attachmentsboolDefault True: attachments are base64-encoded into the JSON. Set False to keep the file small when phases attach images or long logs.
The general structure of an OpenHTF JSON record is as follows (full field list in the JSON format reference):
dut_id
start_time_millis
end_time_millis
outcome # PASS/FAIL
metadata
└── test_name
└── config
└── ...
phases # Array of test phases executed
└── name
└── outcome # PASS/FAIL
└── result # Phase-specific result details (e.g., CONTINUE)
└── measurements
log_records # Array of log messages
└── message
└── timestamp_millis
station_id
code_info # Details about the test script (name, source code)Console summary
ConsoleSummary prints the outcome and, on failure, every failed measurement with its value and validator. Useful while developing a test:
import openhtf as htf
from openhtf.output.callbacks import console_summary
@htf.measures(htf.Measurement("voltage").in_range(3.0, 3.6))
def measure(test):
test.measurements.voltage = 4.1
def main():
test = htf.Test(measure)
test.add_output_callbacks(console_summary.ConsoleSummary())
test.execute(lambda: "SN1234")
if __name__ == "__main__":
main()openhtf_test:FAIL
failed phase: measure [ran for 0.00 sec]
failed_item: voltage (Outcome.FAIL)
measured_value: 4.1
validators:
validator: 3.0 <= x <= 3.6
======================= test: openhtf_test outcome: FAIL ======================Other built-ins: callbacks.OutputToFile(pattern) pickles the record, and mfg_inspector.MfgInspector converts it to Google's TestRun protobuf (save_to_disk() / upload()). Both live under openhtf.output.callbacks.
Send records to a database
Output callbacks run after every test, so that's where you send records to a database. Any REST API or database driver works with the custom callback pattern below. TofuPilot's client ships one:
from tofupilot.openhtf import upload
test.add_output_callbacks(upload()) # reads TOFUPILOT_API_KEY from the environmentupload() doesn't retry: a failed upload is logged, not resent. Keep the JSON callback too if you can't lose a record.
Custom output format
You can implement a custom output format by creating a function to handle the test record and adding it as an output callback if the built-in format doesn't meet your needs. Callbacks run in order after the test finishes.
import openhtf as htf
def custom_output_callback(test_record):
with open("./custom_output.txt", "w") as f:
f.write("Custom Output\n")
f.write(f"DUT ID: {test_record.dut_id}\n")
f.write(f"Outcome: {test_record.outcome}\n")
for phase in test_record.phases:
f.write(f"Phase: {phase.name}\n")
def get_sn(test):
test.test_record.dut_id = "SN1234"
return htf.PhaseResult.CONTINUE
def main():
test = htf.Test(get_sn)
test.add_output_callbacks(custom_output_callback)
test.execute()
if __name__ == "__main__":
main()The output file will then be:
Custom Output
DUT ID: SN1234
Outcome: Outcome.PASS
Phase: get_snRelated
Diagnoses
Use OpenHTF diagnoses to analyze results after measurement validation, classify failure modes, and drive conditional execution between phases.
Test Record
Complete reference for the OpenHTF test record structure including test outcomes, phase records, measurement data, attachments, and diagnosis results.