Skip to content

C# client

.NET 8 async client (VeriCue NuGet package). This is the reference; for a guided first test see the C# quick start.

Install

bash
dotnet add package VeriCue

Connection lifecycle

VeriCueClient implements IAsyncDisposable; ConnectAsync performs the protocol handshake before returning.

csharp
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);     // true

Internally 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:

ExceptionRaised when
ConnectionExceptionConnect failed, response timed out, socket dropped
ProtocolExceptionMalformed / unexpected message
ServerExceptionServer 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.

csharp
// 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:

csharp
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:

csharp
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:

csharp
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());
    }
}

Released under a commercial licence. Privacy · Terms