Multi-device tests¶
Examples are in Python
In Kotlin, roles are @TapDevices("sender", "receiver") with a Devices parameter, and
concurrent steps use coroutines. Kotlin + JUnit 5
shows this page's example in Kotlin.
The server sees every device ADB can see and knows nothing but serials; which of them a run
uses is decided by the client. A device is in use exactly while a session holds its per-serial
lock (~/.tap/sessions/<serial>.lock), taken when the session opens and released when it
closes or its process dies. So two processes, a Gradle test JVM and a pytest run say, never
share a device, and a crashed process's devices come back on their own.
Naming devices, with roles such as sender and receiver, is the client's job: the pytest
plugin and the JUnit extension map roles to serials and then open one session per role.
Declaring roles¶
import pytest
from tap_e2e import res, text
@pytest.mark.tap_devices("sender", "receiver")
def test_delivers_a_message(tap_devices):
sender = tap_devices["sender"].app("com.example.chat")
receiver = tap_devices["receiver"].app("com.example.chat")
sender.cold_launch()
receiver.cold_launch()
sender.element(res("compose")).set_text("hi")
sender.element(text("Send")).tap()
receiver.wait(text("hi"), timeout=20).visible()
Sessions are opened one at a time in sorted serial order, waiting up to the acquire timeout
(tap_acquire_timeout, 300 s) for a device another session holds. Every process takes device
locks in the same order, so two tests that both want the same two devices cannot deadlock: the
second simply waits for the first to finish. A test that needs more devices than the server
lists is skipped.
Which serial plays which role¶
Decided by the client, in this order:
- A role pinned to a serial (JUnit:
tap.device.<role>=<serial>). TAP_SERIALS=a,b(JUnit:tap.serials) limits the process to those devices; roles take them in declaration order.- With nothing configured, the client asks the server for its device list and takes devices that are online and not quarantined: free ones first, then ones another session holds.
The unpinned devices are a rotating list: each test starts one device further on (wrapping), so tests spread over the devices instead of all queueing on the first. A single-role test also moves on to the next device when one is held by another session right now, and waits only when every candidate is busy. Multi-role tests rotate the same way but open their assignment in sorted serial order, waiting for each device, so they cannot deadlock.
Without the test runner¶
From the client you do the same by hand: connection.available_serials() to look, then
connection.attach_device(serial, wait_for_device=60) to open. Without a wait, a busy device
fails at once with DeviceBusyError. The device list carries only serials and states; anything
richer (API level, model) comes from device.info() once a session is open, or from
adb -s <serial> shell getprop before.
Waiting across devices¶
The receiver's UI is a normal app.wait(...) on that device. For conditions that span devices
or systems, use device.await_until(...) (Your own condition).
The server's device list shows which device is held by whom.