Test Record JSON Format
Field-by-field reference for the JSON file OpenHTF's OutputToJSON writes — top-level record, metadata, phases, measurements, validators, units, attachments, subtests, branches, checkpoints, diagnoses, log records — with a complete annotated sample.
Last updated · Verified with OpenHTF 1.6.1
json_factory.OutputToJSON serializes the in-memory TestRecord with the fields below (key order in the file: dut_id, timestamps, outcome, outcome_details, marginal, metadata, phases, subtests, branches, diagnosers, diagnoses, log_records, station_id, code_info). Checkpoint evaluations are kept in memory (TestRecord.checkpoints) but are not written to JSON in 1.6.1. The format has been stable since 1.5; 1.6 added nothing incompatible. Timestamps are milliseconds since the Unix epoch, enums are their string names.
Top level
dut_idstringUNKNOWN_DUT if never set.station_idstringstart_time_millis / end_time_millisintoutcomePASS | FAIL | ERROR | TIMEOUT | ABORTEDoutcome_detailsarray{code, description} entries explaining a non-PASS outcome — the exception type and message for ERROR, the failing phase for FAIL.marginalbool | nulltrue if any measurement was marginal.metadataobjectcode_infoobject{name, docstring, sourcecode} of the test module. sourcecode is empty unless capture_source is on.phasesarrayprompt_for_test_start() or a lambda) is recorded first as trigger_phase; phases skipped by run_if are absent.subtestsarray{name, outcome: PASS|FAIL|STOP, start_time_millis, end_time_millis, marginal} per subtest.branchesarrayBranchSequence: {name, diag_condition, branch_taken, evaluated_millis}.diagnosersarraydiagnosesarrayDiagnosis: {result, description, component, priority, is_failure}.log_recordsarray{level, logger_name, source, lineno, timestamp_millis, message}. level is the Python logging number (20 = INFO).Phases
namestringPhaseOptions.name after template substitution.outcomePASS | FAIL | SKIP | ERRORSKIP for PhaseResult.SKIP and for repeated attempts under the limit.resultobject{phase_result: CONTINUE|STOP|REPEAT|SKIP|FAIL_AND_CONTINUE|FAIL_SUBTEST}, or {phase_result: null, raised_exception: {...}} on error.marginalboolstart_time_millis / end_time_millisintmeasurementsobjectattachmentsobject{mimetype, sha1} plus data (base64) when inline_attachments=True. Attachments →optionsobjectPhaseOptions in effect: timeout_s, repeat_limit, run_if (as a string), requires_state, force_repeat, repeat_on_timeout, repeat_on_measurement_fail, run_under_pdb, phase_name_case, stop_on_measurement_fail.diagnosers / diagnosis_results / failure_diagnosis_resultsarraysubtest_namestring | nullcodeinfoobject{name, docstring, sourcecode} of the phase function.descriptor_idintMeasurements
namestringoutcomePASS | FAIL | UNSET | PARTIALLY_SETUNSET when declared but never assigned (fails the phase unless allow_unset_measurements). PARTIALLY_SET for dimensioned measurements with some coordinates missing.measured_valuenumber | string | bool | array[dim1, dim2, ..., value].validatorsarray of stringunitsobject{name, code, suffix} from openhtf.util.units; absent if no unit.dimensionsarray{name, code, suffix} (or description) per axis of a multi-dimensional measurement.docstringstring.doc().marginalboolset_time_millisintAnnotated sample
A two-phase functional test, trimmed to the essentials:
{
"dut_id": "SN-0001",
"station_id": "station-a",
"start_time_millis": 1737000000000,
"end_time_millis": 1737000012500,
"outcome": "PASS",
"outcome_details": [],
"marginal": false,
"metadata": {
"test_name": "TP-DEMO-1 Functional Test",
"config": {
"station_id": "station-a",
"stop_on_first_failure": false,
"allow_unset_measurements": false,
"capture_source": false
}
},
"code_info": { "name": "main", "docstring": null, "sourcecode": "" },
"phases": [
{
"name": "power_on",
"outcome": "PASS",
"result": { "phase_result": "CONTINUE" },
"marginal": false,
"start_time_millis": 1737000000200,
"end_time_millis": 1737000004000,
"measurements": {
"supply_voltage": {
"name": "supply_voltage",
"outcome": "PASS",
"validators": ["3.2 <= x <= 3.4"],
"units": { "name": "volt", "code": "VLT", "suffix": "V" },
"measured_value": 3.31
},
"boot_ok": {
"name": "boot_ok",
"outcome": "PASS",
"validators": ["'x' is equal to 'True'"],
"measured_value": true
}
},
"attachments": {},
"options": { "timeout_s": null, "repeat_limit": null, "phase_name_case": "KEEP" },
"subtest_name": null,
"codeinfo": { "name": "power_on", "docstring": null, "sourcecode": "" }
},
{
"name": "rf_check",
"outcome": "PASS",
"result": { "phase_result": "CONTINUE" },
"measurements": {
"tx_power_dbm": {
"name": "tx_power_dbm",
"outcome": "PASS",
"validators": ["17.0 <= x <= 20.0"],
"units": { "name": "Decibel-milliwatts", "code": "DBM", "suffix": "dBm" },
"measured_value": 18.7
},
"firmware_version": {
"name": "firmware_version",
"outcome": "PASS",
"validators": ["'x' matches /^1\\.4\\.2$/"],
"measured_value": "1.4.2"
}
}
}
],
"subtests": [],
"branches": [],
"diagnosers": [],
"diagnoses": [],
"log_records": [
{
"level": 20,
"logger_name": "openhtf.test_record.<uid>.phase.power_on",
"source": "main.py",
"lineno": 18,
"timestamp_millis": 1737000000210,
"message": "Rail up"
}
]
}Parsing tips
- Iterate
phases[], thenmeasurementsvalues; do not assume measurement names are unique across phases. - A multi-dimensional
measured_valueis a list of rows; the last element of each row is the value, the preceding ones are the coordinates indimensionsorder. outcome_details[0].descriptionholds the traceback summary forERRORruns.- Repeated phases appear as several entries sharing
descriptor_idandname; only the last has the final outcome. - File size is dominated by
attachments[].datawhen inlined; write attachments elsewhere withinline_attachments=Falseif you index the JSON.
TofuPilot's import and upload() callback read this exact format — phases, measurements with limits and units, attachments and logs — see Manufacturing Test Analytics.
Related
Validators
Every built-in OpenHTF measurement validator — in_range, within_percent, equals, matches_regex, all_in_range, all_equals, dimension_pivot_validate, consistent_end_dimension_pivot_validate — with marginal limits, custom validators and how they appear in the record.
Bundled plugs
The plugs that ship with OpenHTF — UserInput, ADB and Fastboot over USB (openhtf.plugs.usb), Cambrionix USB hubs, SerialCollectionPlug for serial logging, DeviceWrappingPlug — with install extras, configuration keys and usage.