Skip to content

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_manage_daemon the daemon is started here and, if

tap_connection

One TapConnection per pytest session (observing from the start); the server tears

tap_devices

The test's devices by role, attached before the test and detached after it.

tap_device

The test's single device: tap_devices["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 (tap_serials / TAP_SERIALS, comma-separated). Empty means every device the server lists.

artifacts Path

Where failure artifacts are written (tap_artifacts / TAP_ARTIFACTS, default tap-artifacts).

server str | None

host:port of the server (tap_server / TAP_SERVER). None finds the one tap start recorded.

acquire_timeout float

Seconds to wait for a device another session holds (tap_acquire_timeout / TAP_ACQUIRE_TIMEOUT).

manage_daemon bool

Start the server before the session and stop it afterwards if this run started it (tap_manage_daemon / TAP_MANAGE_DAEMON).

capture_on_failure bool

Save screenshot, hierarchy, device info and driver log when a test fails (tap_capture / TAP_CAPTURE: onFailure or off).

device_scope str

How long attached devices are kept: function (default), class, module or session (tap_device_scope / TAP_DEVICE_SCOPE).

Methods:

Name Description
from_pytest

Read the settings from the pytest configuration and the environment.

serials

serials: list[str]

artifacts

artifacts: Path

server

server: str | None

acquire_timeout

acquire_timeout: float

manage_daemon

manage_daemon: bool

capture_on_failure

capture_on_failure: bool

device_scope

device_scope: str

from_pytest

from_pytest(config: Config) -> TapConfig

Read the settings from the pytest configuration and the environment.

tap_config

tap_config(pytestconfig: Config) -> TapConfig

The plugin's TapConfig for the session.

tap_client

tap_client(tap_config: TapConfig) -> Generator[TapClient, None, None]

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>/.

tap_device

tap_device(tap_devices: dict[str, Device]) -> Device

The test's single device: tap_devices["device"].