Skip to content

Tests

The test suite lives at tests/. Run everything with uv run pytest.

Layout

tests/
+-- conftest.py # shared fixtures
+-- unit/ # fast, no subprocesses
+-- integration/ # live FastAPI server + httpx
+-- visual/ # Playwright pixel-diff (heavy)
+-- perf/ # pytest-benchmark (heavy)
+-- a11y/ # axe-core via Playwright (heavy)
+-- e2e/ # full browser flows (heavy)
+-- format/ # exporter round-trip
+-- fixtures/ # test tools (reference-tool, broken-tool, etc.)

What’s covered

Unit (tests/unit/)

Test fileWhat it covers
test_discovery.pyTool schema parsing, validation errors, optional fields, error recovery.
test_validator.pySample input generation, output-shape checking, report summarisation.
test_db.pySchema init, WAL pragmas, CRUD round-trips on all 9 tables.
test_launcher.pyFree-port allocation, env scrubbing, warm-keep bookkeeping (no real spawn).
test_artefacts.pysafe_join traversal refusal, sha256 hashing, mime inference, soft-delete.
test_workspaces.pyWorkspace CRUD + tool assignment + cascade delete.
test_secrets.py.env round-trip, masking filter behaviour, atomic writes.
test_renderer_inputs.pyEvery input partial renders without errors against a fixture spec.
test_renderer_outputs.pyEvery output partial renders given valid value shapes.
test_reference_validator.pyCheck 12 with scikit-image / librosa / pdfplumber comparators.
test_exporters.pyRound-trips for CSV, Excel, Parquet, PDF.
test_comparators.pyNumeric tolerance, structured diff, SHA-256 short-circuit.
test_proxy.pyRouting, header injection, run-id flow.

Integration (tests/integration/)

Test fileWhat it covers
test_run_flow.pyBoot server, GET /api/tools, POST /tool/<id>/run against staged example.
test_streaming.pySSE round-trip from a streaming tool.
test_cancel.pyCancel flow via the proxy.
test_settings.pySettings API + env-var overrides.
test_secrets_flow.pySecrets injection into subprocess environment.
test_library.pyArtefact gallery listing and filters.
test_reference_check.pyFull reference fixture validation against real tools.
test_tools_grid.pyDiscovery across multiple tools, sorting, filtering.
test_workspaces.pyWorkspace endpoints, membership ops.

Heavy markers

Skipped by default. Enable with PIXIE_RUN_HEAVY_TESTS=1:

MarkerCostWhat it does
visual~30sPlaywright launches Chromium, screenshots key pages, pixel-diffs.
perf~60spytest-benchmark budgets cold-start, warm run, sidebar render.
a11y~30saxe-core audits each page for WCAG violations.
e2e~90sFull browser flows against the staged example tool.

Fixtures (tests/conftest.py)

FixtureTypePurpose
tmp_pixie_rootsessionScratch repo layout with tools/ and a fresh pixie.db.
sample_tool_factoryfunctionCopies a tool from tests/fixtures/<name>/ into tmp_pixie_root.
staged_example_toolfunctionThe example-compound-interest tool with its venv pre-built.
pixie_appfunctionA fresh FastAPI app instance bound to the temp root.
pixie_serverfunctionThe above served on a real localhost port via uvicorn.
httpx_clientfunctionhttpx.AsyncClient pointed at the server.
mock_validator_reportfunctionSynthetic ValidationReport for UI tests.
db_pathfunctionInitialised pixie.db at tmp_path (workspace tests).

Adding tests

A new unit test for a function

Add a file under tests/unit/test_<topic>.py:

import pytest
from pixie.validator import _sample_for_input
from pixie.discovery import TextInput
@pytest.mark.parametrize("default,expected", [
(None, ""),
("hello", "hello"),
])
def test_text_input_sample(default, expected):
spec = TextInput(type="text", key="x", label="X", default=default, required=True)
assert _sample_for_input(spec) == expected

A new integration test against a tool

async def test_my_tool_runs(staged_example_tool, httpx_client):
response = await httpx_client.post(
"/tool/example-compound-interest/run",
data={"principal": 1000, "annual_rate": 5, "years": 10}
)
assert response.status_code == 200

A new fixture tool

Put it under tests/fixtures/<name>/. Minimal:

tests/fixtures/my-fixture/
+-- tool.json
+-- pyproject.toml
+-- main.py

Then in your test:

def test_my_fixture(sample_tool_factory):
tool_path = sample_tool_factory("my-fixture")
...

sample_tool_factory returns the path of the copied tool; the venv is NOT created (your test should not need one).

CI

GitHub Actions runs:

  • uv run pytest -m "not (perf or visual or a11y or e2e)" per push.
  • Heavy markers nightly.
  • ruff format --check and ruff check.
  • The validator over a curated subset of tools.

Failures block merge.