Automation for testing Kivy apps
Project description
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.
Project details
Release history Release notifications | RSS feed
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 kvaut-1.0.0.tar.gz.
File metadata
- Download URL: kvaut-1.0.0.tar.gz
- Upload date:
- Size: 21.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9878331b9ae38d4ac9ea626614fdbc8a5b20a6bec717ac2f7c8333105f47400f
|
|
| MD5 |
752ce2a51e8660a5bc8da3d9ad77addc
|
|
| BLAKE2b-256 |
330e584a9f44424a26d404a3791e3eb3dfcbb2e643f3a4c86f1c622c12603082
|
Provenance
The following attestation bundles were made for kvaut-1.0.0.tar.gz:
Publisher:
publish.yml on garyjohnson/kvaut
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kvaut-1.0.0.tar.gz -
Subject digest:
9878331b9ae38d4ac9ea626614fdbc8a5b20a6bec717ac2f7c8333105f47400f - Sigstore transparency entry: 1663442420
- Sigstore integration time:
-
Permalink:
garyjohnson/kvaut@d415105ec18e192d48e60b0b746a58bb936e64af -
Branch / Tag:
refs/tags/1.0.0 - Owner: https://github.com/garyjohnson
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d415105ec18e192d48e60b0b746a58bb936e64af -
Trigger Event:
release
-
Statement type:
File details
Details for the file kvaut-1.0.0-py3-none-any.whl.
File metadata
- Download URL: kvaut-1.0.0-py3-none-any.whl
- Upload date:
- Size: 12.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2741334c4c5b92b2465b7effd0f92a002a8cabb43c86a762802ae47c8cd9898b
|
|
| MD5 |
223febafbdfa476e8ef76bf70dc7622a
|
|
| BLAKE2b-256 |
0c14df904eabd7456c36d65849f87761dc40b4ddc0afa3025f96d8bb38087f68
|
Provenance
The following attestation bundles were made for kvaut-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on garyjohnson/kvaut
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kvaut-1.0.0-py3-none-any.whl -
Subject digest:
2741334c4c5b92b2465b7effd0f92a002a8cabb43c86a762802ae47c8cd9898b - Sigstore transparency entry: 1663442598
- Sigstore integration time:
-
Permalink:
garyjohnson/kvaut@d415105ec18e192d48e60b0b746a58bb936e64af -
Branch / Tag:
refs/tags/1.0.0 - Owner: https://github.com/garyjohnson
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d415105ec18e192d48e60b0b746a58bb936e64af -
Trigger Event:
release
-
Statement type: