kvaut
Automation for testing Kivy apps. Think Playwright, but for Kivy widgets.
Requirements
- Python 3.10+
- Kivy 2.1+
Getting Started
No app modifications needed
Your Kivy app stays exactly as-is. kvaut handles the instrumentation automatically — just point it at your app's module path and it takes care of the rest.
Write a test
import kvaut
client = kvaut.Client()
client.connect("my_app.main")
# Find a button and click it
btn = client.find(by_text="Save")
client.click(btn)
# Find a TextInput and type into it
input_field = client.find(by_type="TextInput")
client.input_text(input_field, "Hello Kivy")
# Read text from a widget
text = client.get_text(input_field)
assert text == "Hello Kivy"
# Check widget attributes
attrs = client.get_attributes(btn, ["disabled", "text"])
assert attrs["disabled"] is False
client.disconnect()
With pytest
kvaut provides an optional pytest fixture for automatic lifecycle management:
import pytest
from kvaut.errors import ElementNotFoundError
@pytest.mark.app_module("my_app.main")
class TestApp:
def test_save_button(self, kvaut_client):
btn = kvaut_client.find(by_text="Save")
kvaut_client.click(btn)
text = kvaut_client.get_text(btn)
assert text == "Saved!"
def test_cancel_button(self, kvaut_client):
with pytest.raises(ElementNotFoundError):
kvaut_client.find(by_text="Nonexistent")
Finding elements
kvaut uses an RTL-style approach: find() for a single element, query() for multiple.
Both return opaque element ids that you pass to actions like click() and input_text().
find() raises an error if zero or more than one visible element matches.
query() returns a list — empty if nothing matches.
By default, only visible elements are searched. Pass hidden=True to include hidden elements
(size zero, opacity zero, or no parent).
# Single element — raises if ambiguous
save_btn = client.find(by_text="Save")
# Multiple elements — returns a list
all_buttons = client.query(by_type="Button")
for btn_id in all_buttons:
print(client.get_text(btn_id))
# Include hidden elements
hidden_btn = client.find(by_text="Hidden", hidden=True)
# Regex matching
import re
client.find(by_text=re.compile(r"^Save"))
API Reference
kvaut.Client
| Method | Description |
|---|---|
connect(module_path) |
Launch the app under test as a subprocess and wait for it to be ready |
disconnect() |
Stop the app under test |
find(*, by_text, by_type, by_id, hidden=False) |
Find a single visible element. Raises ElementNotFoundError if 0 matches, AmbiguousMatchError if >1 |
query(*, by_text, by_type, by_id, hidden=False) |
Find all matching elements. Returns a list (empty if none) |
click(element_id) |
Tap the center of an element |
input_text(element_id, text) |
Type text into a TextInput element. Raises InvalidOperationError if not a TextInput |
get_text(element_id) |
Get the text property of an element |
get_attributes(element_id, names) |
Get named widget attributes as a dict, e.g. ["disabled", "enabled"] |
tree() |
Return the full widget tree as a dict (for debugging) |
Selectors
by_text— Match by widget text. Pass a string for exact match or a compiledre.Patternfor regex.by_type— Match by widget class name, e.g."Button","TextInput".by_id— Match by the kv langidattribute.
Exceptions
All exceptions inherit from kvaut.KvautError:
ElementNotFoundError—find()matched 0 elements, or element id is staleAmbiguousMatchError—find()matched >1 elementServerNotFoundError— Server couldn't be reached or timed out on connectInvalidOperationError— Operation on wrong widget type (e.g.input_texton a Button)
How it works
kvaut uses a client/server architecture:
client.connect("my_app.main")spawns a subprocess runningpython -m kvaut.run my_app.main- kvaut.run starts an HTTP server (stdlib, no dependencies) in a background thread, imports the user's app module, finds the
Appsubclass, and callsApp().run() - The test side communicates with the server over HTTP — finding widgets, dispatching taps, reading properties
client.disconnect()kills the subprocess
Your app code never imports kvaut. There is no instrumentation step. The kvaut.run entry point
handles everything transparently.
Running tests
pip install -e ".[dev]"
pytest
For headless environments (CI), use xvfb:
xvfb-run -a pytest
Set KVAUT_LOG=DEBUG for verbose server output during debugging.
Contributing
See CONTEXT.md for the project glossary and docs/adr/ for architecture decisions.
Metadata
Release files for kvaut 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| kvaut-1.0.0.tar.gz | 21.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| kvaut-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.0 kB
Release files / kvaut-1.0.0.tar.gz
| Download URL | kvaut-1.0.0.tar.gz |
|---|---|
| Size | 21.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9878331b9ae38d4ac9ea626614fdbc8a5b20a6bec717ac2f7c8333105f47400f
|
|
BLAKE2b-256 checksum How to use checksums |
330e584a9f44424a26d404a3791e3eb3dfcbb2e643f3a4c86f1c622c12603082
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 29, 2026.
Transparency logRelease files / kvaut-1.0.0-py3-none-any.whl
| Download URL | kvaut-1.0.0-py3-none-any.whl |
|---|---|
| Size | 12.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2741334c4c5b92b2465b7effd0f92a002a8cabb43c86a762802ae47c8cd9898b
|
|
BLAKE2b-256 checksum How to use checksums |
0c14df904eabd7456c36d65849f87761dc40b4ddc0afa3025f96d8bb38087f68
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 29, 2026.
Transparency log