Multi-process testing
Many Qt systems are more than one process: a main GUI plus workers, a shell hosting plugin sandboxes, or several cooperating tools. veriCue handles this by embedding one Runtime per process and driving them all from a single test through MultiVeriCueClient.
Fleets run over TCP
MultiVeriCueClient addresses each process by (host, port) - it has no local-endpoint form in either the Python or the C# client. So a fleet is the one common case where TCP on loopback is the right answer even for a single-host run. A single process you drive on its own can still use local IPC via connect_local().
1. Embed a Runtime in each process
Each process gets its own VeriCueServer on its own port:
// main GUI process
vericue::VeriCueServer server(&mainWindow);
server.start(4242);// worker process
vericue::VeriCueServer server(&workerWindow);
server.start(4243);Fixed ports are fine for a stable test rig. For CI, prefer start(0) (OS-assigned) and have each process print its port - printf("VERICUE_PORT=%d\n", server.serverPort()) - so the harness can collect the endpoints.
If your processes set auth tokens, each process can have its own.
2. Drive them with MultiVeriCueClient
MultiVeriCueClient (Python) manages one VeriCueClient per process and exposes them by name:
from vericue import MultiVeriCueClient
async with MultiVeriCueClient({
"main": ("127.0.0.1", 4242),
"worker": ("127.0.0.1", 4243),
}) as fleet:
# Drive the GUI...
await fleet["main"].mouse_click("MainWindow/startWorkerButton")
# ...then assert on the worker process it spawned.
await fleet["worker"].wait_for_property(
"WorkerWindow/statusLabel", "text", "Ready"
)connect_all() runs in parallel and the async with block guarantees all connections close on exit - including when an assertion fails.
Per-process tokens
An endpoint is (host, port) or (host, port, token):
fleet = MultiVeriCueClient({
"main": ("127.0.0.1", 4242, os.environ["MAIN_TOKEN"]),
"worker": ("127.0.0.1", 4243, os.environ["WORKER_TOKEN"]),
})3. Bulk helpers
fleet.names() # ["main", "worker"]
"worker" in fleet # True
for name, client in fleet: ... # iterate name -> client pairs
# Same raw request to every process; returns {name: response},
# with an Exception object as the value for processes that failed.
results = await fleet.broadcast("ping")
# Arbitrary parallel map across all processes:
windows = await fleet.gather(lambda c: c.list_windows())
# -> {"main": [...], "worker": [...]}broadcast never raises for a single failing process - check the values
- which makes it useful for health sweeps across a fleet.
A realistic flow: GUI spawns a worker
The subtlety in multi-process tests is that the worker process (and its veriCue port) usually doesn't exist until the GUI spawns it. Connect in two stages:
import asyncio
from vericue import VeriCueClient
from vericue.errors import ConnectionError as VeriCueConnectionError
async def test_gui_spawns_worker():
# Stage 1: the GUI is already running.
async with VeriCueClient() as gui:
await gui.connect("127.0.0.1", 4242)
await gui.mouse_click("MainWindow/startWorkerButton")
# Stage 2: poll until the worker's veriCue port accepts connections.
worker = VeriCueClient()
for _ in range(50): # ~5 s budget
try:
await worker.connect("127.0.0.1", 4243)
break
except VeriCueConnectionError: # not a builtin OSError
await asyncio.sleep(0.1)
else:
raise AssertionError("worker never opened its veriCue port")
try:
await worker.wait_for_property(
"WorkerWindow/statusLabel", "text", "Ready", timeout=10000
)
finally:
await worker.disconnect()Concurrent automation sessions
Each process runs its own server, so licensing counts per process. With an organization key file, every server enforces its own session limit against the clients connected to it. With floating licensing, each process checks out one lease from the shared pool at startup - a GUI + two workers holds three leases while running. See Floating licences for sizing guidance.

