C# client
.NET 8 async client (VeriCue NuGet package). This is the reference; for a guided first test see the C# quick start.
Install
dotnet add package VeriCueConnection lifecycle
VeriCueClient implements IAsyncDisposable; ConnectAsync performs the protocol handshake before returning.
await using var client = new VeriCueClient(TimeSpan.FromSeconds(10));
await client.ConnectAsync("127.0.0.1", 4242, token: null);
Console.WriteLine(client.ServerVersion); // negotiated protocol version
Console.WriteLine(client.IsConnected); // trueInternally the client runs a background reader task: responses are demultiplexed into per-request TaskCompletionSources, pushed events into an unbounded Channel<SubscriptionEvent>. Every command method takes an optional CancellationToken.
Exceptions
All errors derive from VeriCueException:
| Exception | Raised when |
|---|---|
ConnectionException | Connect failed, response timed out, socket dropped |
ProtocolException | Malformed / unexpected message |
ServerException | Server returned an error - carries .Code (see Error codes) |
Commands
Convenience methods live in the VeriCueClient partial class; most return Task<JsonElement> (the raw result object), the most common ones are strongly typed. SendRequestAsync(method, parameters) is the raw escape hatch for anything else.
// Protocol
bool pong = await client.PingAsync();
var ver = await client.VersionAsync();
string echo = await client.EchoAsync("hi");
// Discovery
var objs = await client.ListObjectsAsync(className: "QPushButton");
var obj = await client.FindObjectAsync("MainWindow/okButton");
var props = await client.GetPropertiesAsync("MainWindow/okButton", new[] { "text" });
var kids = await client.GetChildrenAsync("MainWindow");
var parent = await client.GetParentAsync("MainWindow/okButton");
var tree = await client.GetObjectTreeAsync(root: null, depth: -1);
// Interaction
await client.MouseClickAsync("MainWindow/okButton");
await client.TypeTextAsync("MainWindow/nameEdit", "Jane");
await client.KeyPressAsync("MainWindow/nameEdit", "enter");
await client.DragAsync(path, fromX: 10, fromY: 10, toX: 200, toY: 10);
await client.ScrollAsync(path, dx: 0, dy: -3);
var shot = await client.ScreenshotAsync(); // null path = first visible window
await client.HighlightObjectAsync(path, color: "red", durationMs: 2000);
// Touch & gestures
await client.TouchTapAsync(path);
await client.TouchLongPressAsync(path, durationMs: 1000);
await client.SwipeAsync(path, fromX: 150, fromY: 400, toX: 150, toY: 100);
await client.PinchAsync(path, startDistance: 50, endDistance: 200); // zoom in
await client.MultiTouchAsync(path, touches); // each: { id, x, y, action: "press"|"move"|"release" }
// Model/view
var info = await client.GetModelInfoAsync("MainWindow/tableView");
var data = await client.GetModelDataAsync("MainWindow/tableView");
// Extended automation
await client.SetPropertyAsync(path, "text", "new value");
await client.InvokeMethodAsync(path, "clear");
await client.WaitForPropertyAsync(path, "enabled", true, timeoutMs: 5000);
await client.WaitForSignalAsync(path, "clicked()", timeoutMs: 5000);
// Performance
var ft = await client.FrameTimeAsync(durationMs: 1000);
var pc = await client.PaintCountAsync(path, durationMs: 1000);
var mem = await client.MemorySnapshotAsync();
// Mocking / helpers
await client.EmitSignalAsync(path, "timeout()");
await client.SetClipboardAsync("text");
string clip = await client.GetClipboardAsync();
// Multi-window
var windows = await client.ListWindowsAsync();
// Recording
await client.StartRecordingAsync();
var recording = await client.StopRecordingAsync();Push events
Subscribe, then consume events from the channel - as an async stream, one at a time, or via per-subscription callbacks:
string subId = await client.SubscribeSignalAsync(
"MainWindow/okButton", "clicked()",
callback: evt => Console.WriteLine($"{evt.EventName} on {evt.Path}"));
await client.SubscribePropertyAsync("MainWindow/statusLabel", "text");
await client.SubscribeDestroyedAsync("MainWindow/dialog");
// One event, with timeout:
SubscriptionEvent e = await client.NextEventAsync(TimeSpan.FromSeconds(5));
// Or stream everything:
await foreach (SubscriptionEvent evt in client.EventsAsync(ct))
Console.WriteLine($"{evt.SubscriptionId}: {evt.EventName} {evt.Data}");
await client.UnsubscribeAsync(subId);
var subs = await client.ListSubscriptionsAsync();Events always flow into the channel even when a callback is registered.
Multi-process fleets
MultiVeriCueClient manages one client per Qt process, keyed by name - mirrors the Python API:
await using var fleet = new MultiVeriCueClient(new Dictionary<string, (string, int)>
{
["main"] = ("127.0.0.1", 4242),
["worker"] = ("127.0.0.1", 4243),
});
await fleet.ConnectAllAsync();
await fleet["main"].MouseClickAsync("MainWindow/startButton");
// Same request to every process (result-or-exception per name):
var pongs = await fleet.BroadcastAsync("ping");
// Arbitrary parallel work per client:
var titles = await fleet.GatherAsync(c => c.GetPropertiesAsync("MainWindow", new[] { "windowTitle" }));xUnit usage
The client is a plain IAsyncDisposable, so a shared fixture is enough:
public class VeriCueFixture : IAsyncLifetime
{
public VeriCueClient Client { get; } = new();
public Task InitializeAsync() => Client.ConnectAsync("127.0.0.1", 4242);
public async Task DisposeAsync() => await Client.DisposeAsync();
}
public class LoginTests : IClassFixture<VeriCueFixture>
{
private readonly VeriCueClient _client;
public LoginTests(VeriCueFixture f) => _client = f.Client;
[Fact]
public async Task OkButtonLogsIn()
{
await _client.MouseClickAsync("MainWindow/okButton");
var props = await _client.GetPropertiesAsync("MainWindow/statusLabel", new[] { "text" });
Assert.Equal("Logged in", props.GetProperty("text").GetString());
}
}
