Table of Contents

Common Scenarios

This guide demonstrates common browser automation scenarios using WebDriverBiDi.NET.

Basic Page Navigation

BiDiDriver driver = new BiDiDriver(TimeSpan.FromSeconds(10));
await driver.StartAsync("ws://localhost:9515/session/YOUR-SESSION-ID");

try
{
    // Get the active context
    GetTreeCommandResult tree = await driver.BrowsingContext.GetTreeAsync(
        new GetTreeCommandParameters());
    string contextId = tree.ContextTree[0].BrowsingContextId;

    // Navigate and wait for page load
    NavigateCommandParameters navParams = new NavigateCommandParameters(
        contextId,
        "https://example.com")
    {
        Wait = ReadinessState.Complete
    };

    NavigateCommandResult result = await driver.BrowsingContext.NavigateAsync(navParams);
    Console.WriteLine($"Navigated to: {result.Url}");
}
catch (WebDriverBiDiTimeoutException ex)
{
    Console.WriteLine($"Navigation timeout: {ex.Message}");
    // Handle timeout - maybe retry or fail gracefully
}
catch (WebDriverBiDiException ex)
{
    Console.WriteLine($"Navigation failed: {ex.Message}");
    // Handle other protocol errors
}
finally
{
    await driver.StopAsync();
}

Form Submission

BiDiDriver driver = new BiDiDriver(TimeSpan.FromSeconds(10));
await driver.StartAsync("ws://localhost:9515/session/YOUR-SESSION-ID");

try
{
    GetTreeCommandResult tree = await driver.BrowsingContext.GetTreeAsync(
        new GetTreeCommandParameters());
    string contextId = tree.ContextTree[0].BrowsingContextId;

    // Navigate to form page
    await driver.BrowsingContext.NavigateAsync(
        new NavigateCommandParameters(contextId, "https://example.com/form")
        { Wait = ReadinessState.Complete });

    // Find the input field
    string findInputScript = "document.querySelector('input[name=\"username\"]')";
    EvaluateResult inputResult = await driver.Script.EvaluateAsync(
        new EvaluateCommandParameters(findInputScript, new ContextTarget(contextId), true));

    if (inputResult is EvaluateResultSuccess inputSuccess &&
        inputSuccess.Result is NodeRemoteValue inputElement)
    {
        // Click the input to focus it
        PerformActionsCommandParameters clickParams = new PerformActionsCommandParameters(contextId);
        PointerSourceActions clickParamsMouse = new PointerSourceActions
        {
            Parameters = new PointerParameters { PointerType = PointerType.Mouse },
        };
        clickParamsMouse.Actions.Add(new PointerMoveAction
        {
            X = 0,
            Y = 0,
            Origin = Origin.Element(inputElement.ToSharedReference()),
        });
        clickParamsMouse.Actions.Add(new PointerDownAction(0));
        clickParamsMouse.Actions.Add(new PointerUpAction(0));
        clickParams.Actions.Add(clickParamsMouse);
        await driver.Input.PerformActionsAsync(clickParams);

        // Type into the field
        string username = "testuser";
        PerformActionsCommandParameters typeParams = new PerformActionsCommandParameters(contextId);
        KeySourceActions typeParamsKeyboard = new KeySourceActions();
        foreach (char c in username)
        {
            typeParamsKeyboard.Actions.Add(new KeyDownAction(c.ToString()));
            typeParamsKeyboard.Actions.Add(new KeyUpAction(c.ToString()));
        }
        typeParams.Actions.Add(typeParamsKeyboard);
        await driver.Input.PerformActionsAsync(typeParams);

        // Submit the form by pressing Enter (WebDriver key code \uE007)
        PerformActionsCommandParameters submitParams = new PerformActionsCommandParameters(contextId);
        KeySourceActions submitKey = new KeySourceActions();
        submitKey.Actions.Add(new KeyDownAction("\uE007"));
        submitKey.Actions.Add(new KeyUpAction("\uE007"));
        submitParams.Actions.Add(submitKey);
        await driver.Input.PerformActionsAsync(submitParams);
    }
}
finally
{
    await driver.StopAsync();
}

Monitoring Console Logs

BiDiDriver driver = new BiDiDriver(TimeSpan.FromSeconds(10));
await driver.StartAsync("ws://localhost:9515/session/YOUR-SESSION-ID");

try
{
    // Set up log monitoring with error handling inside the handler
    List<string> consoleMessages = new List<string>();

    driver.Log.OnEntryAdded.AddObserver((EntryAddedEventArgs e) =>
    {
        try
        {
            string message = $"[{e.Timestamp:HH:mm:ss}] {e.Level}: {e.Text}";
            consoleMessages.Add(message);
            Console.WriteLine(message);
        }
        catch (Exception ex)
        {
            // Handle errors within the event handler
            Console.WriteLine($"Error processing log entry: {ex.Message}");
        }
    });

    // Subscribe to log events
    SubscribeCommandParameters subscribe =
        new SubscribeCommandParameters(driver.Log.OnEntryAdded.EventName);
    await driver.Session.SubscribeAsync(subscribe);

    // Navigate to page
    GetTreeCommandResult tree = await driver.BrowsingContext.GetTreeAsync(
        new GetTreeCommandParameters());
    string contextId = tree.ContextTree[0].BrowsingContextId;

    await driver.BrowsingContext.NavigateAsync(
        new NavigateCommandParameters(contextId, "https://example.com")
        { Wait = ReadinessState.Complete });

    // Wait for logs to arrive
    await Task.Delay(2000);

    Console.WriteLine($"\nTotal console messages: {consoleMessages.Count}");
}
catch (WebDriverBiDiException ex)
{
    Console.WriteLine($"WebDriver BiDi error: {ex.Message}");
}
finally
{
    await driver.StopAsync();
}

Capturing Screenshots

BiDiDriver driver = new BiDiDriver(TimeSpan.FromSeconds(10));
await driver.StartAsync("ws://localhost:9515/session/YOUR-SESSION-ID");

try
{
    GetTreeCommandResult tree = await driver.BrowsingContext.GetTreeAsync(
        new GetTreeCommandParameters());
    string contextId = tree.ContextTree[0].BrowsingContextId;

    // Navigate to page
    await driver.BrowsingContext.NavigateAsync(
        new NavigateCommandParameters(contextId, "https://example.com")
        { Wait = ReadinessState.Complete });

    // Capture full page screenshot
    CaptureScreenshotCommandParameters screenshotParams =
        new CaptureScreenshotCommandParameters(contextId);

    CaptureScreenshotCommandResult result =
        await driver.BrowsingContext.CaptureScreenshotAsync(screenshotParams);

    // Save to file
    byte[] imageBytes = Convert.FromBase64String(result.Data);
    await File.WriteAllBytesAsync("screenshot.png", imageBytes);

    Console.WriteLine("Screenshot saved to screenshot.png");
}
finally
{
    await driver.StopAsync();
}

Network Traffic Monitoring

BiDiDriver driver = new BiDiDriver(TimeSpan.FromSeconds(10));
await driver.StartAsync("ws://localhost:9515/session/YOUR-SESSION-ID");

try
{
    // Track all network requests
    List<RequestData> requests = new List<RequestData>();

    driver.Network.OnResponseCompleted.AddObserver((ResponseCompletedEventArgs e) =>
    {
        requests.Add(e.Request);
        Console.WriteLine($"{e.Response.Status} - {e.Request.Method} {e.Request.Url}");
    });

    // Subscribe to network events
    SubscribeCommandParameters subscribe =
        new SubscribeCommandParameters(driver.Network.OnResponseCompleted.EventName);
    await driver.Session.SubscribeAsync(subscribe);

    // Navigate
    GetTreeCommandResult tree = await driver.BrowsingContext.GetTreeAsync(
        new GetTreeCommandParameters());
    string contextId = tree.ContextTree[0].BrowsingContextId;

    await driver.BrowsingContext.NavigateAsync(
        new NavigateCommandParameters(contextId, "https://example.com")
        { Wait = ReadinessState.Complete });

    // Wait for all resources to load
    await Task.Delay(2000);

    Console.WriteLine($"\nTotal requests: {requests.Count}");

    // Analyze requests
    int htmlCount = requests.Count(r => r.Url.Contains(".html") || r.Url.EndsWith("/"));
    int cssCount = requests.Count(r => r.Url.Contains(".css"));
    int jsCount = requests.Count(r => r.Url.Contains(".js"));
    int imageCount = requests.Count(r => r.Url.Contains(".jpg") || r.Url.Contains(".png") || r.Url.Contains(".gif"));

    Console.WriteLine($"HTML: {htmlCount}, CSS: {cssCount}, JS: {jsCount}, Images: {imageCount}");
}
finally
{
    await driver.StopAsync();
}

To capture each request together with its response and bodies, or to save the traffic as a HAR file, see NetworkTrafficMonitor in Dramaturge.

Multi-Tab Management

BiDiDriver driver = new BiDiDriver(TimeSpan.FromSeconds(10));
await driver.StartAsync("ws://localhost:9515/session/YOUR-SESSION-ID");

try
{
    // Create multiple tabs
    List<string> tabIds = new List<string>();

    for (int i = 0; i < 3; i++)
    {
        CreateCommandResult result = await driver.BrowsingContext.CreateAsync(
            new CreateCommandParameters(CreateType.Tab));
        tabIds.Add(result.BrowsingContextId);
        Console.WriteLine($"Created tab {i + 1}: {result.BrowsingContextId}");
    }

    // Navigate each tab to different URL
    string[] urls =
    {
        "https://example.com",
        "https://example.org",
        "https://example.net"
    };

    for (int i = 0; i < tabIds.Count; i++)
    {
        await driver.BrowsingContext.NavigateAsync(
            new NavigateCommandParameters(tabIds[i], urls[i])
            { Wait = ReadinessState.Complete });
        Console.WriteLine($"Tab {i + 1} navigated to {urls[i]}");
    }

    // Get page titles from all tabs
    for (int i = 0; i < tabIds.Count; i++)
    {
        EvaluateResult result = await driver.Script.EvaluateAsync(
            new EvaluateCommandParameters(
                "document.title",
                new ContextTarget(tabIds[i]),
                true));

        if (result is EvaluateResultSuccess success &&
            success.Result is StringRemoteValue stringValue)
        {
            string title = stringValue.Value ?? "No title";
            Console.WriteLine($"Tab {i + 1} title: {title}");
        }
    }

    // Close all tabs
    foreach (string tabId in tabIds)
    {
        await driver.BrowsingContext.CloseAsync(new CloseCommandParameters(tabId));
    }

    Console.WriteLine("All tabs closed");
}
finally
{
    await driver.StopAsync();
}

Waiting for Elements

BiDiDriver driver = new BiDiDriver(TimeSpan.FromSeconds(10));
await driver.StartAsync("ws://localhost:9515/session/YOUR-SESSION-ID");

try
{
    GetTreeCommandResult tree = await driver.BrowsingContext.GetTreeAsync(
        new GetTreeCommandParameters());
    string contextId = tree.ContextTree[0].BrowsingContextId;

    await driver.BrowsingContext.NavigateAsync(
        new NavigateCommandParameters(contextId, "https://example.com")
        { Wait = ReadinessState.Complete });

    // Wait for element to appear (with polling)
    bool elementFound = false;
    int maxAttempts = 30;
    int attempt = 0;

    while (!elementFound && attempt < maxAttempts)
    {
        EvaluateResult result = await driver.Script.EvaluateAsync(
            new EvaluateCommandParameters(
                "document.querySelector('.dynamic-content') !== null",
                new ContextTarget(contextId),
                true));

        if (result is EvaluateResultSuccess success &&
            success.Result is BooleanRemoteValue boolValue)
        {
            elementFound = boolValue.Value;
            if (elementFound)
            {
                Console.WriteLine($"Element found after {attempt * 100}ms");
                break;
            }
        }

        await Task.Delay(100);
        attempt++;
    }

    if (!elementFound)
    {
        Console.WriteLine("Element not found after timeout");
    }
}
finally
{
    await driver.StopAsync();
}
BiDiDriver driver = new BiDiDriver(TimeSpan.FromSeconds(10));
await driver.StartAsync("ws://localhost:9515/session/YOUR-SESSION-ID");

try
{
    GetTreeCommandResult tree = await driver.BrowsingContext.GetTreeAsync(
        new GetTreeCommandParameters());
    string contextId = tree.ContextTree[0].BrowsingContextId;

    // Set a cookie before navigating
    PartialCookie cookie = new PartialCookie(
        "userPreference",
        BytesValue.FromString("darkMode"),
        "example.com")
    {
        Path = "/",
        Secure = false,
        HttpOnly = false
    };

    await driver.Storage.SetCookieAsync(new SetCookieCommandParameters(cookie));
    Console.WriteLine("Cookie set");

    // Navigate
    await driver.BrowsingContext.NavigateAsync(
        new NavigateCommandParameters(contextId, "https://example.com")
        { Wait = ReadinessState.Complete });

    // Retrieve cookies
    GetCookiesCommandParameters getCookiesParams = new GetCookiesCommandParameters();
    getCookiesParams.Partition = new BrowsingContextPartitionDescriptor(contextId);

    GetCookiesCommandResult cookies = await driver.Storage.GetCookiesAsync(getCookiesParams);

    Console.WriteLine($"\nAll cookies ({cookies.Cookies.Count}):");
    foreach (Cookie c in cookies.Cookies)
    {
        Console.WriteLine($"  {c.Name} = {c.Value.Value}");
    }

    // Delete specific cookie
    DeleteCookiesCommandParameters deleteParams = new DeleteCookiesCommandParameters();
    deleteParams.Filter = new CookieFilter { Name = "userPreference" };
    await driver.Storage.DeleteCookiesAsync(deleteParams);

    Console.WriteLine("\nCookie deleted");
}
finally
{
    await driver.StopAsync();
}

JavaScript Execution

BiDiDriver driver = new BiDiDriver(TimeSpan.FromSeconds(10));
await driver.StartAsync("ws://localhost:9515/session/YOUR-SESSION-ID");

try
{
    GetTreeCommandResult tree = await driver.BrowsingContext.GetTreeAsync(
        new GetTreeCommandParameters());
    string contextId = tree.ContextTree[0].BrowsingContextId;

    await driver.BrowsingContext.NavigateAsync(
        new NavigateCommandParameters(contextId, "https://example.com")
        { Wait = ReadinessState.Complete });

    // Execute simple expression
    EvaluateResult titleResult = await driver.Script.EvaluateAsync(
        new EvaluateCommandParameters("document.title", new ContextTarget(contextId), true));

    if (titleResult is EvaluateResultSuccess titleSuccess &&
        titleSuccess.Result is StringRemoteValue titleValue)
    {
        Console.WriteLine($"Page title: {titleValue.Value}");
    }

    // Call function with arguments
    string addFunction = "(a, b) => a + b";
    CallFunctionCommandParameters funcParams = new CallFunctionCommandParameters(
        addFunction,
        new ContextTarget(contextId),
        true);
    funcParams.Arguments.Add(LocalValue.Number(10));
    funcParams.Arguments.Add(LocalValue.Number(20));

    EvaluateResult sumResult = await driver.Script.CallFunctionAsync(funcParams);
    if (sumResult is EvaluateResultSuccess sumSuccess &&
        sumSuccess.Result is NumberRemoteValue sumValue)
    {
        Console.WriteLine($"10 + 20 = {sumValue.Value}");
    }

    // Execute complex script
    // Parenthesized: a script beginning with a brace parses as a block, not an object literal.
    string complexScript = @"
    ({
        url: window.location.href,
        linkCount: document.querySelectorAll('a').length,
        imageCount: document.querySelectorAll('img').length,
        hasTitle: !!document.title
    })";

    EvaluateResult complexResult = await driver.Script.EvaluateAsync(
        new EvaluateCommandParameters(complexScript, new ContextTarget(contextId), true));

    if (complexResult is EvaluateResultSuccess complexSuccess &&
        complexSuccess.Result.As<KeyValuePairCollectionRemoteValue>().Value is RemoteValueDictionary data)
    {
        Console.WriteLine($"\nPage analysis:");
        Console.WriteLine($"  URL: {data["url"].As<StringRemoteValue>().Value}");
        Console.WriteLine($"  Links: {data["linkCount"].As<NumberRemoteValue>().Value}");
        Console.WriteLine($"  Images: {data["imageCount"].As<NumberRemoteValue>().Value}");
        Console.WriteLine($"  Has title: {data["hasTitle"].As<BooleanRemoteValue>().Value}");
    }
}
finally
{
    await driver.StopAsync();
}

Next Steps