Full test container for Home Assistant with demo config, custom components, and visual testing
Project description
ha-testcontainer
A full, reusable test container for Home Assistant.
ha-testcontainer is a Python library that wraps the official Home Assistant
Docker image in a Testcontainers-based class.
It handles all the plumbing — startup, programmatic onboarding, long-lived
token creation, and custom component mounting — so your tests can focus on
what matters.
UIX (UI eXtension) is the primary consumer and uses this library as a replacement for its legacy local test stack.
Features
| Capability | Detail |
|---|---|
| Version flexibility | stable, beta, dev, or any pinned tag (2024.6.0) |
| Automatic onboarding | Creates an admin user and mints a long-lived API token with no manual interaction |
| Demo entities | Built-in HA demo integration gives you lights, sensors, weather objects immediately |
| Custom config | Mount any configuration.yaml tree via config_path= |
| Custom components | Mount any custom_components/ directory via custom_components_path= |
| Fetch any component | scripts/fetch_component.py owner/repo — downloads any GitHub-hosted HA component |
| Fetch any frontend plugin | scripts/fetch_plugin.py owner/repo — downloads any JS dashboard plugin; registers it as a Lovelace resource |
| Storage-mode dashboard | Default Lovelace dashboard in storage mode; REST-pushable for tests |
| REST API helper | ha.api("GET", "states") — authenticated calls with zero boilerplate |
| Integration setup | ha.setup_integration("my_domain") — drives config-flow programmatically |
| Playwright visual tests | Session-scoped browser context, token injection, pixel-diff snapshot comparison |
| Boilerplate example | examples/test_custom_component.py — copy, fill in TODO values, done |
Quick start
1 — Install
pip install -e ".[test]"
playwright install chromium
2 — Fetch a custom component
# Any GitHub-hosted HA custom component (Python, goes into custom_components/):
python scripts/fetch_component.py Lint-Free-Technology/uix
python scripts/fetch_component.py Lint-Free-Technology/uix 5.3.1 # pinned version
# Or via make (COMPONENT is required):
make setup COMPONENT=Lint-Free-Technology/uix
make setup COMPONENT=Lint-Free-Technology/uix VERSION=5.3.1
2b — Fetch a frontend plugin (optional)
Frontend plugins are JavaScript dashboard modules (Lovelace cards, etc.),
distinct from Python custom components. They are downloaded into
ha-config/www/dashboard/ and automatically served by HA at /local/….
# Download and register a dashboard plugin:
python scripts/fetch_plugin.py custom-cards/button-card
python scripts/fetch_plugin.py thomasloven/lovelace-card-mod 3.4.4
# Or via make:
make fetch-plugin PLUGIN=custom-cards/button-card
make fetch-plugin PLUGIN=thomasloven/lovelace-card-mod VERSION=3.4.4
3 — Run tests
make test # unit + integration tests (no browser)
make test-visual # Playwright visual tests (requires Docker + Playwright)
See Testing below for a full breakdown of each tier.
4 — Explore locally with docker compose
make up # starts HA at http://localhost:8123
make down
Usage in your own project
from ha_testcontainer import HATestContainer, HAVersion
with HATestContainer(
version=HAVersion.STABLE, # or "beta", "dev", "2024.6.0"
config_path="ha-config",
custom_components_path="custom_components",
) as ha:
# Set up any custom component via config-flow
ha.setup_integration("uix")
# REST API — authenticated, zero boilerplate
states = ha.api("GET", "states").json()
print(ha.get_url()) # http://localhost:<random-port>
print(ha.get_token()) # long-lived access token
pytest fixture example
# conftest.py
import pytest
from ha_testcontainer import HATestContainer
@pytest.fixture(scope="session")
def ha():
with HATestContainer(config_path="ha-config", custom_components_path="custom_components") as c:
c.setup_integration("uix")
yield c
# test_my_component.py
def test_api(ha):
resp = ha.api("GET", "states")
assert resp.status_code == 200
Playwright visual test example
See examples/test_custom_component.py for a
fully annotated boilerplate that any component author can copy.
from ha_testcontainer.visual import PAGE_LOAD_TIMEOUT, assert_snapshot
def test_my_card(ha_page, ha_url):
ha_page.goto(f"{ha_url}/lovelace/0", wait_until="networkidle",
timeout=PAGE_LOAD_TIMEOUT)
assert_snapshot(ha_page, "my_card_baseline")
Run SNAPSHOT_UPDATE=1 pytest tests/visual/ to create or update baselines.
Fetching custom components
scripts/fetch_component.py works with any GitHub repository that follows
the standard HA custom-component layout (a custom_components/<name>/ directory
in the repository root):
python scripts/fetch_component.py owner/repo # latest release
python scripts/fetch_component.py owner/repo 5.3.1 # specific version
python scripts/fetch_component.py owner/repo --list # list releases
python scripts/fetch_component.py owner/repo --target-dir /other/path
The script auto-discovers all custom_components/ sub-directories in the
release archive, so multi-component repositories are handled correctly.
Fetching frontend plugins (dashboard cards)
scripts/fetch_plugin.py downloads JavaScript dashboard modules — Lovelace
cards and similar frontend-only plugins — from GitHub releases. These are
distinct from Python custom components: they live in www/ rather than
custom_components/, and are loaded by the HA frontend via Lovelace resources.
python scripts/fetch_plugin.py owner/repo # latest release
python scripts/fetch_plugin.py owner/repo 1.2.3 # specific version
python scripts/fetch_plugin.py owner/repo --list # list releases
python scripts/fetch_plugin.py owner/repo --plugin-name custom-name
python scripts/fetch_plugin.py owner/repo --resource-type js # legacy (default: module)
The script:
- Downloads JS files from the release (assets, zip assets, or source archive fallback).
- Places them at
ha-config/www/dashboard/<plugin-name>/<file>.js. - Registers each file as a Lovelace resource in
ha-config/lovelace_resources.yaml.
HA serves ha-config/www/ at /local/, so the plugin is immediately available
to Lovelace dashboards as /local/dashboard/<plugin-name>/<file>.js.
Configuration
HA version
| Value | Image tag pulled |
|---|---|
HAVersion.STABLE (default) |
ghcr.io/home-assistant/home-assistant:stable |
HAVersion.BETA |
ghcr.io/home-assistant/home-assistant:beta |
HAVersion.DEV |
ghcr.io/home-assistant/home-assistant:dev |
"2024.6.0" |
ghcr.io/home-assistant/home-assistant:2024.6.0 |
Override at runtime:
HA_VERSION=beta pytest tests/
HA_VERSION=2024.6.0 make test
Config & custom components
| Environment variable | Default | Purpose |
|---|---|---|
HA_VERSION |
stable |
Image tag |
HA_CONFIG_PATH |
ha-config/ |
Host dir mounted as /config |
HA_CUSTOM_COMPONENTS_PATH |
custom_components/ |
Host dir mounted as /config/custom_components |
Repository layout
ha-testcontainer/
├── ha_testcontainer/
│ ├── __init__.py # public API: HATestContainer, HAVersion, visual helpers
│ ├── container.py # HATestContainer implementation
│ └── visual.py # PAGE_LOAD_TIMEOUT, assert_snapshot, inject_ha_token
├── ha-config/
│ ├── configuration.yaml # demo HA config (default_config + demo integration)
│ ├── lovelace_resources.yaml # Lovelace resources list (managed by fetch_plugin.py)
│ ├── www/ # served at /local/ by HA; plugins downloaded here
│ └── themes/ # theme YAML files (auto-loaded)
├── custom_components/
│ └── README.md # populated by scripts/fetch_component.py (gitignored)
├── scripts/
│ ├── fetch_component.py # download any HA Python component from GitHub releases
│ └── fetch_plugin.py # download any JS frontend plugin; register as resource
├── examples/
│ └── test_custom_component.py # boilerplate visual test — copy & customise
├── tests/
│ ├── conftest.py # session-scoped ha / ha_url / ha_token fixtures
│ ├── test_container_unit.py # unit tests — no Docker needed (14 tests)
│ ├── test_container.py # integration tests — requires Docker (10 tests)
│ └── visual/
│ ├── conftest.py # Playwright fixtures (ha_page, ha_browser_context)
│ └── snapshots/ # gitignored — no baselines committed here (see below)
├── docker-compose.yml # local dev: docker compose up
├── Makefile # setup / test / update-snapshots targets
└── pyproject.toml
Snapshot-based visual testing
Snapshot tests follow a two-file convention:
| File | Description |
|---|---|
snapshots/<name>.png |
Committed baseline — the ground truth, lives in the consumer's repo |
snapshots/<name>.actual.png |
Generated on every run — gitignored |
On the first run (or when SNAPSHOT_UPDATE=1 is set), the actual
screenshot becomes the baseline. Subsequent runs diff the two; any pixel
difference causes the test to fail.
Baselines are placed next to the calling test file automatically — no configuration needed.
Important — baselines are stored in the consumer's repo, not here. ha-testcontainer is a reusable library; it does not commit any snapshot PNG files. All
*.pngfiles undertests/visual/snapshots/are gitignored in this repository. When you write visual tests for your own component, baselines are committed in your project'stests/visual/snapshots/directory. They will never appear in ha-testcontainer's history.
Testing
The test suite has three tiers:
Tier 1 — Unit tests (no Docker required)
These run in milliseconds and cover the Python logic of HATestContainer
in isolation (URL construction, token handling, API path normalisation):
pip install -e ".[test]" # install once
pytest tests/test_container_unit.py -v
Or via make:
make install
make test
make test runs all non-browser tests, including both unit and
integration tests, skipping the visual tier.
Tier 2 — Integration tests (Docker required)
These start a real Home Assistant container and exercise the full lifecycle (onboarding, REST API, demo entities, Lovelace push):
# Prerequisites: Docker daemon running, HA image available
pytest tests/test_container.py -v
The container is started once per pytest session (session-scoped fixture) and reused across all tests in the file. Startup takes ~60 s the first time while HA initialises.
Environment variables let you control which image and config directory are used:
| Variable | Default | Purpose |
|---|---|---|
HA_VERSION |
stable |
Image tag (stable, beta, dev, 2024.6.0, …) |
HA_CONFIG_PATH |
ha-config/ |
Host dir mounted as /config |
HA_CUSTOM_COMPONENTS_PATH |
custom_components/ |
Host dir mounted as /config/custom_components |
HA_VERSION=beta pytest tests/test_container.py -v
Tier 3 — Visual (Playwright) tests (Docker + Playwright required)
pip install -e ".[test]"
playwright install chromium
pytest tests/visual/ -v
Or via make:
make install
make test-visual
Visual tests open a Chromium browser, log in to the running HA instance, and compare screenshots against committed baselines. Baselines are stored in the consumer's own repository — see Snapshot-based visual testing.
Version smoke tests (slow, optional)
These pull the stable, beta, and dev images in sequence and verify that
the container starts for each:
pytest tests/test_container.py -v -m version_smoke
# or:
make test-smoke
They are skipped by default to avoid pulling large images on every run.
UIX's test/docker-compose.yaml + test/configuration.yaml + test/lovelace.yaml
are replaced by make up and UIX writing its own tests/ using HATestContainer.
- Add
ha-testcontaineras a dev dependency. - Fetch UIX:
python scripts/fetch_component.py Lint-Free-Technology/uix. - Replace
docker compose -f test/docker-compose.yaml upwithmake up. - Copy
examples/test_custom_component.pyinto UIX'stests/visual/, fill in UIX-specific TODO values, and add UIX-specific test cases. - Delete UIX's
test/directory.
License
MIT — see LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ha_testcontainer-1.0.1.tar.gz.
File metadata
- Download URL: ha_testcontainer-1.0.1.tar.gz
- Upload date:
- Size: 22.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b4f276e8303177f1858bb3cc45fa36169ad2dbe999b4ddab5fcf871e193703ee
|
|
| MD5 |
46c1c060acb50f7d03c015eabe46f7cb
|
|
| BLAKE2b-256 |
031ecaf1da452704ceb23929fbd81261f1413f6cb7366d887277fbb3df1872a6
|
Provenance
The following attestation bundles were made for ha_testcontainer-1.0.1.tar.gz:
Publisher:
publish.yml on Lint-Free-Technology/ha-testcontainer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ha_testcontainer-1.0.1.tar.gz -
Subject digest:
b4f276e8303177f1858bb3cc45fa36169ad2dbe999b4ddab5fcf871e193703ee - Sigstore transparency entry: 1280797238
- Sigstore integration time:
-
Permalink:
Lint-Free-Technology/ha-testcontainer@853267879f714e4a3e897acfca6fad1d620c0725 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Lint-Free-Technology
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@853267879f714e4a3e897acfca6fad1d620c0725 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file ha_testcontainer-1.0.1-py3-none-any.whl.
File metadata
- Download URL: ha_testcontainer-1.0.1-py3-none-any.whl
- Upload date:
- Size: 16.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cee735652fbacd97df83831546651ccde6c1c40ae14c663ab20f08dc9f78c8c1
|
|
| MD5 |
b9ee8479200328bc9c1a5b55ff183579
|
|
| BLAKE2b-256 |
e8a77f78495d91a005bae3bb75a1548bdfa22e16e5bcdd09336c5ce87959f90e
|
Provenance
The following attestation bundles were made for ha_testcontainer-1.0.1-py3-none-any.whl:
Publisher:
publish.yml on Lint-Free-Technology/ha-testcontainer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ha_testcontainer-1.0.1-py3-none-any.whl -
Subject digest:
cee735652fbacd97df83831546651ccde6c1c40ae14c663ab20f08dc9f78c8c1 - Sigstore transparency entry: 1280797242
- Sigstore integration time:
-
Permalink:
Lint-Free-Technology/ha-testcontainer@853267879f714e4a3e897acfca6fad1d620c0725 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Lint-Free-Technology
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@853267879f714e4a3e897acfca6fad1d620c0725 -
Trigger Event:
workflow_dispatch
-
Statement type: