Open-source, headless, CI- and AI-friendly ONVIF conformance test tool.
onvif-tt is a Linux-native, MIT-licensed alternative to the closed
ONVIF Device Test Tool. It parses the public ONVIF Test Specification
HTML corpus into a machine-readable catalog and executes registered
test implementations against a Device Under Test, emitting both
JUnit XML (CI) and structured JSON (LLM agents).
It's built and maintained inside OpenIPC to provide an automated conformance signal for the OpenIPC camera-side ONVIF library; the tool itself is vendor-neutral and works against any ONVIF-compliant device.
- 22 ONVIF Test Specification files → 1,121 test cases parsed into
corpus/parsed.jsonwith a deterministic-parse guard. - 84 test implementations spanning:
- Device service (capabilities, GetServices, system commands incl.
SetSystemDateAndTimeandGetSystemLog, SOAP-fault handling) - Network read-only (interfaces, DNS, NTP, gateway, protocols)
- Authentication (valid creds, wrong password, anonymous-rejected)
- WS-Discovery (multicast Probe, ProbeMatch validation)
- Media v10 + Media2 (profiles, encoders, stream URI, snapshot,
cross-endpoint consistency, deep-decoder checks: resolution
matches profile, codec profile matches SPS, frame rate within
tolerance, PTS monotonicity — all via
ffprobeon the live stream) - Events — PullPoint and Basic Notification lifecycles, with a subscription tracker that auto-unsubscribes on teardown
- PTZ (nodes, AbsoluteMove / RelativeMove / ContinuousMove / Stop)
- Imaging (settings, options, MoveOptions, Status, Absolute / Relative / Continuous focus moves)
- Profile G — Recording control + capabilities, Recording Search
(
FindRecordings,GetRecordingSummary,EndSearch-fault), Replay (GetReplayUri) - Profile A — Access Control: service capabilities, GetServices consistency, AccessPointInfo / AreaInfo getters + invalid-token faults
- Profile D — Door Control: capabilities, GetDoorState / GetDoorInfo / GetDoorInfoList, eight door-actuation operations with invalid-token Fault verification + six "command-not-supported" paths (success OR ActionNotSupported Fault both conformant)
- Device service (capabilities, GetServices, system commands incl.
- Read-only by default. Tests that actuate hardware (motors, recording,
factory reset) opt in via
--allow-writes. - Device-fingerprint
xfail_onsurfaces known-buggy firmware without alarming CI; anxpassedwarning signals when a vendor fixes it. pytest-xdistparallel execution measured at ~2.7× speedup vs sequential against a typical LAN camera..github/workflows/ci.ymlruns parser + registry + catalog sanity on every push across Python 3.10–3.13. Optional integration job exercises onvif-tt against a Happytime virtual ONVIF device.- CLI:
list,show,corpus refresh|stats,run.
The official ONVIF Device Test Tool is members-only,
non-redistributable, and Windows-only. The public ONVIF Test
Specification documents (Base, Media2, PTZ, …) are not — anyone may
copy and redistribute them. onvif-tt is what you get if you take
those public specs at face value and write the executable mirror in
Python.
python -m venv .venv
. .venv/bin/activate
pip install -e .Requirements: Python 3.10+, network access to the device under test.
# 1. What's in the catalog?
onvif-tt corpus stats
# RTSS 139
# BASE 134
# ...
# TOTAL 1121
# 2. Which test IDs do we currently implement?
onvif-tt list --implemented
# 3. What does a particular test case say?
onvif-tt show DEVICE-3-1-9
# DEVICE-3-1-9 (spec BASE.html, version None)
# Title: SYSTEM COMMAND DEVICE INFORMATION
# WSDL Reference: devicemgmt.wsdl
# ...
# Procedure: ...
# 4. Run the implemented tests against a camera.
onvif-tt run \
--target 10.216.128.71:8899 \
--user admin --password admin \
--junit-xml junit.xml \
--json-report results.json
# 4 passed, 1 skipped in 3.7sonvif-tt/
├── corpus/
│ ├── html/ # 22 verbatim ONVIF Test Specification HTML files
│ └── parsed.json # generated cache (run `onvif-tt corpus refresh`)
├── src/onvif_tt/
│ ├── specs/ # corpus parser + dataclasses
│ ├── runtime/ # DUT wrapper (zeep), feature discovery, SOAP trace
│ ├── runner/ # pytest plugin + dispatch + JSON reporter
│ ├── cases/ # one .py per profile area; `@register("ID")` fns
│ └── cli.py
├── tests/ # tests *for the tool itself* (parser unit tests)
└── docs/
└── ai-readme.md # how an LLM should drive the tool
Find the spec ID:
onvif-tt list --id-glob "MEDIA2-1-*" --missingOpen src/onvif_tt/cases/<area>.py and add a function:
from onvif_tt.registry import register
from onvif_tt.runtime.dut import DUT
@register("MEDIA2-1-1-4", profiles={"T"}, mandatory=True,
requires_services={"devicemgmt", "media2"})
def test_get_profiles_media2(dut: DUT, spec) -> None:
"""Spec: MEDIA2.html#tc.MEDIA2-1-1-4 — GET PROFILES.
Media2 must return at least one profile with token + Name.
"""
profiles = dut.media2.GetProfiles()
assert profiles
assert profiles[0].token
assert profiles[0].NameThat's it — the pytest dispatch picks the test up automatically next
time onvif-tt run is invoked. The spec fixture gives you the
parsed TestCase record (procedure text, prerequisites, pass/fail
criteria) if you need it for assertion messages.
python-onvif-zeep service wrappers take positional WSDL
parameters, not keyword args:
dut.devicemgmt.GetServices(False) # ✅
dut.devicemgmt.GetServices(IncludeCapability=False) # ❌The same applies to every WSDL operation — read the spec procedure to get the parameter order.
If a test is known to fail on specific firmware, annotate it with
xfail_on matchers — the runner will mark it xfailed instead of
failing the whole CI run on that device:
@register("DEVICE-1-1-9", profiles={"S", "T"}, mandatory=True,
requires_services={"devicemgmt"},
xfail_on=[{
"Manufacturer": "H264",
"reason": "Xiongmai stock closes the TCP connection "
"instead of returning a SOAP 1.2 Fault.",
}])
def test_soap_fault_on_invalid_capability(dut, spec): ...Matchers compare against GetDeviceInformation fields (Manufacturer,
Model, FirmwareVersion, SerialNumber, HardwareId). Values can be
literals (case-sensitive equality) or callables (lambda v: ...).
Multiple matchers OR together — any one matching expectations the
failure. If a fixed-up device unexpectedly passes, the run logs an
xpassed warning so you know the bug was repaired upstream.
onvif-tt run \
--target $CAMERA_HOST:$CAMERA_PORT \
--user $USER --password $PASS \
--profile S \
--junit-xml junit.xml \
--json-report results.json- Exit code 0 on full pass / clean skips, 1 on any test failure.
junit.xmlis consumed by any CI dashboard out of the box.results.jsonis a flat record per test (id,status,duration_s,last_request,last_response,longrepr) suitable for log shipping or LLM consumption.
onvif-tt list --format json and onvif-tt show <ID> --format json
emit stable schemas suitable for tool-use prompts. See
docs/ai-readme.md for the agent-driver guide.
- The Python code in this repository is MIT (see
LICENSE). - The ONVIF Test Specification HTML files in
corpus/html/are redistributable per their own copyright notice ("Recipients of this document may copy, distribute, publish, or display this document so long as this copyright notice, license and disclaimer are retained with all copies of the document"). Seecorpus/README.md.
Our committed corpus matches ONVIF Test Specification v20.12 (Dec 2020) — the v20.12 archive is what we extracted the HTML from. ONVIF publishes new test-spec revisions twice a year (June + Dec). Anything added since v20.12 (Profile M metadata, newer Analytics events, updated TLS posture, WebRTC streaming, etc.) is not in this catalog.
To bring corpus/html/ up to a newer revision:
- Obtain the newer ONVIF Device Test Tool installer archive (an ONVIF-member colleague can hand it over — the DTT itself is members-only, but the bundled Test Specification HTML is freely redistributable under its own copyright).
- Inside the archive, the file
Docs/Specifications.exeis a 7z self-extractor.7z x Specifications.exeproduces aSpecifications/directory of HTML files identical in shape to the ones we ship. cp Specifications/*.html corpus/html/and runonvif-tt corpus refreshto regeneratecorpus/parsed.json.- The deterministic-parse unit test will fail with a diff describing the new tests — commit both the HTML and the regenerated JSON.
No code changes should be needed; the parser is content-agnostic.
The plan that drove this repo called out a few test families that turned out not to match the v20.12 corpus exactly. For transparency:
AUTH-*doesn't exist in the catalog. Authentication conformance is scattered across other tests' Pre-Requisite clauses. We provideLOCAL-AUTH-*tests (wrong-password, anonymous-rejected) covering the intent.IPCONFIG-1-1-*are write ops, not read-only as the plan said — every one of them invokesSetNetworkInterfacesandSystemReboot. We provideLOCAL-NETWORK-*read-only equivalents (GetNetworkInterfaces, GetDNS, GetNTP, GetHostname, GetDefaultGateway, GetNetworkProtocols).- The corpus doesn't carry per-test ONVIF Profile tags — there's no
way to ask "which catalog entries are Profile S?" without a separate
PROFILES.html parser.
onvif-tt list --profile-area BASEfilters by source file, not by ONVIF profile; the--profile Sfilter onrunuses the registry's own profile annotations. DISCOVERY-2-1-1 / -2in the corpus are about XML-namespace conformance on ProbeMatch envelopes — they need raw-envelope inspection thatwsdiscoverydoesn't expose. We implement a softer approximation plusLOCAL-DISCOVERY-PROBE(the actual "does multicast Probe find the DUT?" smoke).
LOCAL-* IDs are tool-author additions; everything else is the literal
corpus ID.
onvif-tt run … -n auto (or any -n N) runs through pytest-xdist.
The session-scoped DUT is constructed once per worker; SOAP envelopes
are stashed on item.user_properties so the master-side JSON reporter
can recover them after the workers send their reports back.
Verified: 66-test run goes from ~52 s sequential to ~19 s with
-n 4 (~2.7× speedup) against the dlab reference camera, with the
JSON summary identical.
onvif-tt is for development feedback, not formal ONVIF
conformance. Only the actual ONVIF Device Test Tool (members-only)
plus the ONVIF Conformance Process can produce a real Declaration of
Conformance. Use onvif-tt in CI; cross-check with the official tool
on milestone builds.