Skip to main content

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:

  1. Downloads JS files from the release (assets, zip assets, or source archive fallback).
  2. Places them at ha-config/www/dashboard/<plugin-name>/<file>.js.
  3. 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 *.png files under tests/visual/snapshots/ are gitignored in this repository. When you write visual tests for your own component, baselines are committed in your project's tests/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.

  1. Add ha-testcontainer as a dev dependency.
  2. Fetch UIX: python scripts/fetch_component.py Lint-Free-Technology/uix.
  3. Replace docker compose -f test/docker-compose.yaml up with make up.
  4. Copy examples/test_custom_component.py into UIX's tests/visual/, fill in UIX-specific TODO values, and add UIX-specific test cases.
  5. 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

ha_testcontainer-1.0.1.tar.gz (22.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ha_testcontainer-1.0.1-py3-none-any.whl (16.1 kB view details)

Uploaded Python 3

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

Hashes for ha_testcontainer-1.0.1.tar.gz
Algorithm Hash digest
SHA256 b4f276e8303177f1858bb3cc45fa36169ad2dbe999b4ddab5fcf871e193703ee
MD5 46c1c060acb50f7d03c015eabe46f7cb
BLAKE2b-256 031ecaf1da452704ceb23929fbd81261f1413f6cb7366d887277fbb3df1872a6

See more details on using hashes here.

Provenance

The following attestation bundles were made for ha_testcontainer-1.0.1.tar.gz:

Publisher: publish.yml on Lint-Free-Technology/ha-testcontainer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ha_testcontainer-1.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for ha_testcontainer-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 cee735652fbacd97df83831546651ccde6c1c40ae14c663ab20f08dc9f78c8c1
MD5 b9ee8479200328bc9c1a5b55ff183579
BLAKE2b-256 e8a77f78495d91a005bae3bb75a1548bdfa22e16e5bcdd09336c5ce87959f90e

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page