Skip to main content

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 compiled re.Pattern for regex.
  • by_type — Match by widget class name, e.g. "Button", "TextInput".
  • by_id — Match by the kv lang id attribute.

Exceptions

All exceptions inherit from kvaut.KvautError:

  • ElementNotFoundError — find() matched 0 elements, or element id is stale
  • AmbiguousMatchError — find() matched >1 element
  • ServerNotFoundError — Server couldn't be reached or timed out on connect
  • InvalidOperationError — Operation on wrong widget type (e.g. input_text on a Button)

How it works

kvaut uses a client/server architecture:

  1. client.connect("my_app.main") spawns a subprocess running python -m kvaut.run my_app.main
  2. kvaut.run starts an HTTP server (stdlib, no dependencies) in a background thread, imports the user's app module, finds the App subclass, and calls App().run()
  3. The test side communicates with the server over HTTP — finding widgets, dispatching taps, reading properties
  4. 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)

Source distribution for kvaut 1.0.0
File Size Uploaded
kvaut-1.0.0.tar.gz 21.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for kvaut 1.0.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.0.18

1 release file

0.0.17

1 release file

0.0.16

1 release file

0.0.15

1 release file

0.0.14

1 release file

0.0.13

1 release file

0.0.10

1 release file

0.0.9

1 release file

0.0.7

1 release file

0.0.6

1 release file

0.0.5

1 release file

0.0.1

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page