Testing
wybthon.testing¶
testing
¶
Render components in plain CPython and query them like a user would.
wybthon.testing mounts views into an in-memory DOM through the real
reconciler, kernel protocol, scheduler, and event delegation. Nothing is
mocked: the commands a test applies are exactly the commands the browser
kernel applies.
from wybthon.testing import cleanup, fire, render
def test_counter():
screen = render(Counter(label="Clicks"))
fire.click(screen.get_by_role("button", name="+"))
assert screen.get_by_text("Clicks: 1")
cleanup()
Queries come in three forms, as in Testing Library:
get_by_*returns the single match and raises when there are none or several;query_by_*returns the match orNone;get_all_by_*returns every match (raising when there are none).
They search by visible text, ARIA role (with an optional accessible
name), label text, and data-testid. fire
dispatches events through Wybthon's delegated handlers and flushes the
resulting updates, so assertions see the settled DOM.
Use render as a context manager to unmount automatically, or call
cleanup at the end of each test (an
autouse pytest fixture is a convenient place).
Classes:
| Name | Description |
|---|---|
TestNode |
An in-memory DOM node: an element, a text node, or a comment. |
TestDocument |
An in-memory |
Screen |
A rendered view and the queries that find nodes in it. |
Functions:
| Name | Description |
|---|---|
render |
Mount |
cleanup |
Unmount every view rendered with |
reactive_scope |
Own test computations and dispose them deterministically at scope exit. |
tick |
Drain ready asyncio continuations and flush reactive work. |
wait_for |
Wait for an observable condition, raising TimeoutError on failure. |
Attributes:
| Name | Type | Description |
|---|---|---|
fire |
Dispatch events: |
fire
module-attribute
¶
Dispatch events: fire.click(node), fire.input(node, "text"), fire(node, "dblclick").
TestNode
¶
An in-memory DOM node: an element, a text node, or a comment.
It implements the subset of the DOM the kernel protocol uses, plus
the properties tests read (text_content, attributes, value,
checked).
Methods:
| Name | Description |
|---|---|
focus |
Make this node the document's active element (no events are dispatched). |
blur |
Clear the active element if it's this node. |
click |
Dispatch a click through Wybthon's delegated handlers (like |
Attributes:
| Name | Type | Description |
|---|---|---|
is_element |
bool
|
Whether this node is an element (not text or a comment). |
text_content |
str
|
The concatenated text of this node and its descendants (comments excluded). |
Screen
¶
A rendered view and the queries that find nodes in it.
Attributes:
| Name | Type | Description |
|---|---|---|
container |
The |
|
root |
The |
Methods:
| Name | Description |
|---|---|
unmount |
Dispose the view and release its container. |
html |
The container's inner HTML, without Wybthon's comment markers. |
text |
The container's text content, with whitespace collapsed. |
get_by_text |
Return the one element whose text matches. |
query_by_text |
Return the element whose text matches, or |
get_all_by_text |
Return every element whose text matches. |
get_by_role |
Return the one element with this ARIA role (and accessible name). |
query_by_role |
Return the element with this role (and name), or |
get_all_by_role |
Return every element with this role (and name). |
get_by_label_text |
Return the one form control labelled by |
query_by_label_text |
Return the form control labelled by |
get_by_test_id |
Return the one element with |
query_by_test_id |
Return the element with this |
get_all_by_test_id |
Return every element with this |
get_by_text
¶
Return the one element whose text matches.
query_by_text
¶
Return the element whose text matches, or None.
get_all_by_text
¶
Return every element whose text matches.
get_by_role
¶
Return the one element with this ARIA role (and accessible name).
query_by_role
¶
Return the element with this role (and name), or None.
get_all_by_role
¶
Return every element with this role (and name).
get_by_label_text
¶
Return the one form control labelled by text.
query_by_label_text
¶
Return the form control labelled by text, or None.
get_by_test_id
¶
Return the one element with data-testid equal to test_id.
query_by_test_id
¶
Return the element with this data-testid, or None.
render
¶
reactive_scope
¶
Own test computations and dispose them deterministically at scope exit.
What's in this module¶
wybthon.testing renders components in plain CPython, through the real
reconciler, kernel protocol, scheduler, and event delegation, into an
in-memory DOM. Nothing is mocked, so a passing test exercises the same
commands the browser kernel applies. The queries follow Testing
Library's conventions.
| Name | Description |
|---|---|
render |
render(view) -> Screen: mount a view (a VNode or a zero-arg callable) into a fresh in-memory document. |
Screen |
Queries plus html(), text(), and unmount(); also a context manager that unmounts on exit. |
fire |
Dispatch events through delegated handlers and flush: fire.click(node), fire.input(node, value), fire.change(node, value=None, checked=None), fire.submit(form), fire.key_down(node, key), fire.focus(node), fire.blur(node), or fire(node, "dblclick", **payload). |
cleanup |
Unmount everything render mounted; call it after each test, or from an autouse fixture. |
TestNode, TestDocument |
The in-memory DOM the screen renders into. |
reactive_scope, tick, wait_for |
Helpers for testing reactive code and async updates outside a component. |
| Query | Finds | Variants |
|---|---|---|
get_by_text(text, *, exact=True) |
Elements by visible text, or a compiled regex | query_by_text, get_all_by_text |
get_by_role(role, *, name=None) |
Elements by ARIA role and accessible name | query_by_role, get_all_by_role |
get_by_label_text(text) |
Form controls by their label | query_by_label_text |
get_by_test_id(test_id) |
Elements by data-testid |
query_by_test_id, get_all_by_test_id |
get_by_* raises unless exactly one element matches, query_by_*
returns the match or None, and get_all_by_* returns every match.
import pytest
from wybthon import Prop, Props, button, component, create_signal, div, p, prop
from wybthon.testing import cleanup, fire, render
class CounterProps(Props):
label: Prop[str] = prop(default="Count")
@component
def Counter(props: CounterProps):
count, set_count = create_signal(0)
return div(p(props.label, t": {count}"), button("+", on_click=lambda: set_count(lambda n: n + 1)))
@pytest.fixture(autouse=True)
def _cleanup():
yield
cleanup()
def test_counter():
screen = render(Counter(label="Clicks"))
fire.click(screen.get_by_role("button", name="+"))
assert screen.get_by_text("Clicks: 1")
assert screen.html() == "<div><p>Clicks: 1</p><button>+</button></div>"
See also¶
- Guides: Testing
- Kernel: the
PythonBackendthe screen renders through - Events: the delegated handlers
firedispatches to