Skip to main content

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 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:

  • ElementNotFoundErrorfind() matched 0 elements, or element id is stale
  • AmbiguousMatchErrorfind() 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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kvaut-1.0.0.tar.gz (21.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kvaut-1.0.0-py3-none-any.whl (12.8 kB view details)

Uploaded Python 3

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

Hashes for kvaut-1.0.0.tar.gz
Algorithm Hash digest
SHA256 9878331b9ae38d4ac9ea626614fdbc8a5b20a6bec717ac2f7c8333105f47400f
MD5 752ce2a51e8660a5bc8da3d9ad77addc
BLAKE2b-256 330e584a9f44424a26d404a3791e3eb3dfcbb2e643f3a4c86f1c622c12603082

See more details on using hashes here.

Provenance

The following attestation bundles were made for kvaut-1.0.0.tar.gz:

Publisher: publish.yml on garyjohnson/kvaut

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for kvaut-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2741334c4c5b92b2465b7effd0f92a002a8cabb43c86a762802ae47c8cd9898b
MD5 223febafbdfa476e8ef76bf70dc7622a
BLAKE2b-256 0c14df904eabd7456c36d65849f87761dc40b4ddc0afa3025f96d8bb38087f68

See more details on using hashes here.

Provenance

The following attestation bundles were made for kvaut-1.0.0-py3-none-any.whl:

Publisher: publish.yml on garyjohnson/kvaut

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page