fix(component): respect Home Assistant skip_pip (#2275) #942
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: E2E Tests | |
| on: | |
| push: | |
| branches: [ main, master ] | |
| paths: | |
| # Only run on code changes, not documentation | |
| - 'src/**' | |
| - 'tests/**' | |
| - 'pyproject.toml' | |
| - 'Dockerfile' | |
| - 'homeassistant-addon/**' | |
| - '.github/workflows/e2e-tests.yml' | |
| workflow_dispatch: | |
| # Limit GITHUB_TOKEN permissions to minimum required | |
| permissions: | |
| contents: read | |
| env: | |
| PYTHON_VERSION: "3.13" | |
| UV_CACHE_DIR: /tmp/.uv-cache | |
| # renovate: datasource=docker depName=ghcr.io/home-assistant/home-assistant | |
| HA_IMAGE_GHCR: "ghcr.io/home-assistant/home-assistant:2026.8.0" | |
| # Disable Testcontainers' Ryuk reaper: it has been reported to leave | |
| # zombie containers on GHA runners (see #366, Ilya0527 2026-05-18). | |
| # The E2E fixture in tests/src/e2e/conftest.py relies instead on the | |
| # enclosing ``with container:`` context manager — Python guarantees | |
| # its ``__exit__`` fires on both normal and exception flows, so the | |
| # Ryuk safety net is not needed for deterministic cleanup. Refs #366. | |
| TESTCONTAINERS_RYUK_DISABLED: "true" | |
| jobs: | |
| # Comprehensive E2E validation for main branch | |
| e2e-tests: | |
| name: E2E Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| submodules: true | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4 | |
| with: | |
| cache-binary: true | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| version: "latest" | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Install dependencies | |
| run: uv sync --all-extras --dev | |
| - name: Cache HA Docker image | |
| id: cache-ha-image | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: /tmp/ha-image.tar | |
| key: ha-image-${{ env.HA_IMAGE_GHCR }}-${{ runner.arch }} | |
| - name: Load cached HA image | |
| if: steps.cache-ha-image.outputs.cache-hit == 'true' | |
| run: docker load -i /tmp/ha-image.tar | |
| - name: Pull HA image (GHCR → Docker Hub fallback) | |
| if: steps.cache-ha-image.outputs.cache-hit != 'true' | |
| run: | | |
| HA_VERSION="${HA_IMAGE_GHCR##*:}" | |
| HA_IMAGE_DOCKERHUB="homeassistant/home-assistant:${HA_VERSION}" | |
| for registry in "$HA_IMAGE_GHCR" "$HA_IMAGE_DOCKERHUB"; do | |
| echo "Trying $registry..." | |
| if docker pull "$registry"; then | |
| if [ "$registry" != "$HA_IMAGE_GHCR" ]; then | |
| docker tag "$registry" "$HA_IMAGE_GHCR" | |
| fi | |
| docker save "$HA_IMAGE_GHCR" -o /tmp/ha-image.tar | |
| echo "Pulled and cached from $registry" | |
| exit 0 | |
| fi | |
| echo "Failed to pull from $registry, trying next..." | |
| sleep 15 | |
| done | |
| echo "All registries failed" && exit 1 | |
| - name: Run full E2E test suite | |
| run: | | |
| echo "🚀 Running full E2E test suite with 3 workers..." | |
| uv run pytest tests/src/e2e/ \ | |
| -n3 \ | |
| --dist loadscope \ | |
| --tb=short \ | |
| -v | |
| echo "✅ Full E2E test suite passed" | |
| env: | |
| HAMCP_ENV_FILE: "tests/.env.test" | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # In-process MCP server backend (#1527): the SAME full E2E suite, but the | |
| # server-under-test is the in-process MCP server (ha_mcp_tools entry) running inside | |
| # each worker's testcontainer (E2E_BACKEND=embedded), driven over its ingress | |
| # webhook. Runs in parallel with the container lane above (a separate job — it | |
| # does not extend that job's runtime). Matrixed x64 + arm64 to mirror the PR | |
| # lane (pr.yml's e2e-validation-embedded). -n2/-n3 (vs the container lane's -n3): | |
| # each worker additionally builds a ha-mcp wheel and its container preinstalls | |
| # the fastmcp dependency tree before HA boots, so a lower worker count keeps peak | |
| # memory + concurrent-pip pressure in check (arm gets +1). The larger timeout | |
| # covers that per-worker preinstall window. | |
| e2e-tests-embedded: | |
| name: E2E Tests (Embedded Server, ${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 25 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: | |
| - ubuntu-latest # Linux x64 | |
| - ubuntu-24.04-arm # Linux ARM64 | |
| include: | |
| - os: ubuntu-latest | |
| pytest_workers: 2 | |
| - os: ubuntu-24.04-arm | |
| pytest_workers: 3 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| submodules: true | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4 | |
| with: | |
| cache-binary: true | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| version: "latest" | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Install dependencies | |
| run: uv sync --all-extras --dev | |
| - name: Cache HA Docker image | |
| id: cache-ha-image | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: /tmp/ha-image.tar | |
| key: ha-image-${{ env.HA_IMAGE_GHCR }}-${{ runner.arch }} | |
| - name: Load cached HA image | |
| if: steps.cache-ha-image.outputs.cache-hit == 'true' | |
| run: docker load -i /tmp/ha-image.tar | |
| - name: Pull HA image (GHCR → Docker Hub fallback) | |
| if: steps.cache-ha-image.outputs.cache-hit != 'true' | |
| run: | | |
| HA_VERSION="${HA_IMAGE_GHCR##*:}" | |
| HA_IMAGE_DOCKERHUB="homeassistant/home-assistant:${HA_VERSION}" | |
| for registry in "$HA_IMAGE_GHCR" "$HA_IMAGE_DOCKERHUB"; do | |
| echo "Trying $registry..." | |
| if docker pull "$registry"; then | |
| if [ "$registry" != "$HA_IMAGE_GHCR" ]; then | |
| docker tag "$registry" "$HA_IMAGE_GHCR" | |
| fi | |
| docker save "$HA_IMAGE_GHCR" -o /tmp/ha-image.tar | |
| echo "Pulled and cached from $registry" | |
| exit 0 | |
| fi | |
| echo "Failed to pull from $registry, trying next..." | |
| sleep 15 | |
| done | |
| echo "All registries failed" && exit 1 | |
| - name: Run full E2E test suite (embedded backend) | |
| run: | | |
| echo "🚀 Running full E2E suite through the in-process MCP server (embedded backend) with ${{ matrix.pytest_workers }} workers..." | |
| uv run pytest tests/src/e2e/ \ | |
| -n${{ matrix.pytest_workers }} \ | |
| --dist loadscope \ | |
| --tb=short \ | |
| -v | |
| echo "✅ Full E2E test suite (embedded backend) passed" | |
| env: | |
| E2E_BACKEND: "embedded" | |
| HAMCP_ENV_FILE: "tests/.env.test" | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Update-path e2e lane (#1783/#1785): mirrors pr.yml's e2e-validation-update-path | |
| # on master push. Each run parametrizes over the component source — the released | |
| # ``stable`` git tag (proves a new server release won't break existing installs) | |
| # AND this checkout's working tree (proves the PR's own update machinery still | |
| # works) — with the ha-mcp server installed from PyPI stable in both. The test | |
| # then drives the in-process update that installs THIS commit's freshly built | |
| # wheel over the running server (the real reload/purge/restart path) and asserts | |
| # the server survives. A single ubuntu-latest lane runs both scenarios serially | |
| # (no -n), not the parallel suite. fetch-depth 0 is REQUIRED — the default | |
| # shallow checkout omits the ``stable`` tag the stable scenario checks out from. | |
| e2e-tests-update-path: | |
| name: E2E Tests (Update Path) | |
| runs-on: ubuntu-latest | |
| # 30m: each of the two scenarios (component@stable and component@working-tree) | |
| # does a first bring-up that installs ha-mcp plus the full fastmcp dependency | |
| # tree from PyPI stable before the in-process update swaps in the PR wheel. The | |
| # two containers run sequentially in this one job (observed ~50s per scenario), | |
| # so 30m still fits comfortably. | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| submodules: true | |
| # fetch-depth 0: the update-path scenario checks the custom component | |
| # out at the released ``stable`` git tag, which the default shallow | |
| # checkout does not fetch. | |
| fetch-depth: 0 | |
| - name: Verify stable tag is present | |
| run: | | |
| if ! git rev-parse --verify refs/tags/stable >/dev/null 2>&1; then | |
| echo "::error::The 'stable' git tag is missing — the update-path lane checks the released component out from it. Ensure checkout uses fetch-depth: 0." | |
| exit 1 | |
| fi | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4 | |
| with: | |
| cache-binary: true | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| version: "latest" | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Install dependencies | |
| run: uv sync --all-extras --dev | |
| - name: Cache HA Docker image | |
| id: cache-ha-image | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: /tmp/ha-image.tar | |
| key: ha-image-${{ env.HA_IMAGE_GHCR }}-${{ runner.arch }} | |
| - name: Load cached HA image | |
| if: steps.cache-ha-image.outputs.cache-hit == 'true' | |
| run: docker load -i /tmp/ha-image.tar | |
| - name: Pull HA image (GHCR → Docker Hub fallback) | |
| if: steps.cache-ha-image.outputs.cache-hit != 'true' | |
| run: | | |
| HA_VERSION="${HA_IMAGE_GHCR##*:}" | |
| HA_IMAGE_DOCKERHUB="homeassistant/home-assistant:${HA_VERSION}" | |
| for registry in "$HA_IMAGE_GHCR" "$HA_IMAGE_DOCKERHUB"; do | |
| echo "Trying $registry..." | |
| if docker pull "$registry"; then | |
| if [ "$registry" != "$HA_IMAGE_GHCR" ]; then | |
| docker tag "$registry" "$HA_IMAGE_GHCR" | |
| fi | |
| docker save "$HA_IMAGE_GHCR" -o /tmp/ha-image.tar | |
| echo "Pulled and cached from $registry" | |
| exit 0 | |
| fi | |
| echo "Failed to pull from $registry, trying next..." | |
| sleep 15 | |
| done | |
| echo "All registries failed" && exit 1 | |
| - name: Run update-path e2e test | |
| # E2E_BACKEND deliberately UNSET (container backend): the test drives its | |
| # own dedicated container end to end and never touches the session | |
| # backend, but conftest's autouse session fixture boots one regardless — | |
| # the default container backend boots it cheaply, while | |
| # E2E_BACKEND=embedded would add a wheel build + in-container preinstall | |
| # of the whole fastmcp tree that nothing in this lane uses. | |
| run: | | |
| echo "🚀 Running the update-path e2e lane (released component + PyPI server → PR wheel)..." | |
| uv run pytest tests/src/e2e/workflows/embedded/test_embedded_update_path.py \ | |
| -m update_path \ | |
| --tb=short \ | |
| -v | |
| echo "✅ Update-path e2e lane passed" | |
| env: | |
| E2E_UPDATE_PATH: "1" | |
| HAMCP_ENV_FILE: "tests/.env.test" | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |