pytest plugin¶
The fixtures and options the plugin registers. Guide: Python + pytest.
pytest_plugin
¶
pytest integration: per-test attached devices through the Tap server.
Configuration (ini option, or environment variable):
tap_serials / TAP_SERIALS comma-separated serials to use; roles map to them in
order, starting one device further on for each test
(wrapping). Unset = whatever the server's device list offers.
tap_artifacts / TAP_ARTIFACTS failure artifact directory (default tap-artifacts)
tap_capture / TAP_CAPTURE onFailure (default) = Device.capture() every device of a
failed test into that directory; off = capture nothing
tap_device_scope / TAP_DEVICE_SCOPE
function (default) = attach before each test, detach after it;
class / module / session = keep the devices for the next test of
the same class / module / session (see below)
tap_server / TAP_SERVER host:port of a running server (default: the one `tap start` recorded)
TAP_TOKEN bearer token for an explicit TAP_SERVER (default: read from
daemon.json in the state dir)
tap_manage_daemon / TAP_MANAGE_DAEMON
true = run `tap start` before the first test and `tap stop`
after the last one if that start created the server
(default false: a server must already be running)
tap_acquire_timeout seconds to wait for a device another session holds (default 300)
Fixtures: tap_device (role "device") and tap_devices (dict role → Device).
Markers: @pytest.mark.tap_devices("left", "right") declares roles. A test needing more roles
than available devices is skipped. The starting device rotates from test to test, and a
single-role test moves on to the next device when one is held by another session (waiting only
when all are). Before each test one device per role is attached in sorted
serial order so concurrent multi-device tests cannot deadlock; after it, a failed test's
devices are captured (Device.capture(): screenshot, hierarchy, device info, driver log)
while they remain attached, then they are detached. Attachments never outlive a test, unless tap_device_scope says
otherwise: then consecutive tests of one class / module / the session that declare the same roles
reuse the devices, saving the driver start of each attach (about 1 s on an emulator, several on a
slow phone). Nothing is reset between them — the app keeps the previous test's state, so each test
brings it where it needs it (cold_launch(), clear_data()). A reused device is probed
before each test; one that stopped working is detached and a fresh one attached. Failure artifacts
are still captured per test; the devices are detached when the scope ends.
Functions:
| Name | Description |
|---|---|
tap_config |
The plugin's TapConfig for the session. |
tap_client |
The server channel. With |
tap_connection |
One |
tap_devices |
The test's devices by role, attached before the test and detached after it. |
tap_device |
The test's single device: |
TapConfig
¶
The plugin's settings, read from pytest.ini (tap_* keys) or TAP_* environment
variables; the environment wins.
Attributes:
| Name | Type | Description |
|---|---|---|
serials |
list[str]
|
Devices to use, in order ( |
artifacts |
Path
|
Where failure artifacts are written ( |
server |
str | None
|
|
acquire_timeout |
float
|
Seconds to wait for a device another session holds
( |
manage_daemon |
bool
|
Start the server before the session and stop it afterwards if this run
started it ( |
capture_on_failure |
bool
|
Save screenshot, hierarchy, device info and driver log when a test
fails ( |
device_scope |
str
|
How long attached devices are kept: |
Methods:
| Name | Description |
|---|---|
from_pytest |
Read the settings from the pytest configuration and the environment. |
tap_client
¶
The server channel. With tap_manage_daemon the daemon is started here and, if
that start created it, stopped after the session; one already running is left alone.
tap_connection
¶
tap_connection(tap_client: TapClient, request: FixtureRequest) -> Generator[TapConnection, None, None]
One TapConnection per pytest session (observing from the start); the server tears
everything down if this process dies.
tap_devices
¶
tap_devices(request: FixtureRequest, tap_connection: TapConnection, tap_config: TapConfig) -> Generator[dict[str, Device], None, None]
The test's devices by role, attached before the test and detached after it.
Roles come from @pytest.mark.tap_devices("sender", "receiver"); without the marker
there is one role, "device". Each role gets a different serial, and the test is skipped
when there are fewer devices than roles. If the test fails, failure artifacts are saved for
every device under tap_artifacts/<test id>/.