Table of Contents

UserAgentClientHints Module

The UserAgentClientHints module provides functionality for overriding user agent client hints in the browser. Client hints are a set of HTTP request headers that allow servers to request device and browser information, enabling responsive design and feature detection without relying solely on the User-Agent string.

Overview

The UserAgentClientHints module allows you to:

  • Override user agent client hints (brands, platform, architecture, mobile flag, etc.)
  • Emulate different browser brands and versions for testing
  • Scope overrides to specific browsing contexts or user contexts
  • Reset overrides to restore default browser behavior

This module complements the Emulation Module, which provides user agent string override. Client hints offer a more granular, structured way to control the information websites receive about the browser environment.

Accessing the Module

UserAgentClientHintsModule userAgentClientHints = driver.UserAgentClientHints;

Timeout and Cancellation

All commands in this module accept optional timeoutOverride and CancellationToken parameters. Use timeoutOverride to set a per-command timeout (defaults to BiDiDriver.DefaultCommandTimeout when omitted). Use CancellationToken for cooperative cancellation. See the API Design Guide for details and examples.

Setting Client Hints Override

Basic Override

SetClientHintsOverrideCommandParameters parameters =
    new SetClientHintsOverrideCommandParameters();
parameters.ClientHints = new ClientHintsMetadata
{
    Brands = new List<BrandVersion>
    {
        new BrandVersion("Chromium", "120.0"),
        new BrandVersion("Google Chrome", "120.0")
    },
    Platform = "Windows",
    PlatformVersion = "10.0",
    Architecture = "x86",
    Mobile = false
};

await driver.UserAgentClientHints.SetClientHintsOverrideAsync(parameters);
Console.WriteLine("Client hints override set");

Common Browser Brands

// Three complete profiles. Each SetClientHintsOverrideAsync call replaces the previous
// override, so pick the one you want to emulate rather than assigning all three in turn.
ClientHintsMetadata chromeOnWindows = new ClientHintsMetadata
{
    Brands = new List<BrandVersion>
    {
        new BrandVersion("Chromium", "120.0"),
        new BrandVersion("Google Chrome", "120.0")
    },
    FullVersionList = new List<BrandVersion>
    {
        new BrandVersion("Chromium", "120.0.6099.109"),
        new BrandVersion("Google Chrome", "120.0.6099.109")
    },
    Platform = "Windows",
    PlatformVersion = "10.0",
    Architecture = "x86",
    Model = "",
    Mobile = false,
    Bitness = "64"
};

ClientHintsMetadata firefoxOnMac = new ClientHintsMetadata
{
    Brands = new List<BrandVersion>
    {
        new BrandVersion("Not_A Brand", "8"),
        new BrandVersion("Firefox", "121.0")
    },
    Platform = "macOS",
    PlatformVersion = "14.0",
    Architecture = "arm",
    Mobile = false
};

ClientHintsMetadata chromeOnAndroid = new ClientHintsMetadata
{
    Brands = new List<BrandVersion>
    {
        new BrandVersion("Chromium", "120.0"),
        new BrandVersion("Google Chrome", "120.0")
    },
    Platform = "Android",
    PlatformVersion = "14.0",
    Architecture = "arm",
    Model = "Pixel 7",
    Mobile = true
};

SetClientHintsOverrideCommandParameters parameters =
    new SetClientHintsOverrideCommandParameters
    {
        ClientHints = chromeOnWindows,   // or firefoxOnMac, or chromeOnAndroid
    };

await driver.UserAgentClientHints.SetClientHintsOverrideAsync(parameters);

Client Hints Metadata Properties

The ClientHintsMetadata class supports the following properties:

Property Type Description
Brands List<BrandVersion>? Browser brand and version pairs (e.g., Chromium/120.0)
FullVersionList List<BrandVersion>? Full version strings for each brand
Platform string? Platform name (Windows, macOS, Android, Linux)
PlatformVersion string? Platform version
Architecture string? CPU architecture (x86, arm)
Model string? Device model (for mobile)
Mobile bool? Whether the device is mobile
Bitness string? Pointer size (32, 64)
Wow64 bool? Whether running under WOW64 (Windows)
FormFactors List<string>? Device form factors

All properties are optional. Omitted properties are not sent in the command and retain their default behavior.

Brands and FullVersionList are nullable and settable rather than read-only, because the emulation branches on whether the member is present: leaving one null omits it, so the browser's own value is reported, while setting it to an empty list sends [] and overrides that value with an empty one. FormFactors has the same shape for parity with them, but the emulated client hints the specification defines do not yet include form factors, so the specification gives that member no effect. Every other optional list in the library is read-only and omitted while empty; see API Design — Optional List Properties.

Resetting Client Hints

To clear the client hints override and restore default browser behavior, use the ResetClientHintsOverride static property:

await driver.UserAgentClientHints.SetClientHintsOverrideAsync(
    SetClientHintsOverrideCommandParameters.ResetClientHintsOverride);
Console.WriteLine("Client hints override cleared");

Note: This command always requires explicit parameters. You must pass either ResetClientHintsOverride to clear or a configured SetClientHintsOverrideCommandParameters instance to set overrides.

Scoping Overrides

Target Specific Browsing Contexts

SetClientHintsOverrideCommandParameters parameters =
    new SetClientHintsOverrideCommandParameters();
parameters.ClientHints = new ClientHintsMetadata
{
    Brands = new List<BrandVersion> { new BrandVersion("Chromium", "120.0") },
    Mobile = true
};
parameters.Contexts.Add(contextId);

await driver.UserAgentClientHints.SetClientHintsOverrideAsync(parameters);

Target Specific User Contexts

SetClientHintsOverrideCommandParameters parameters =
    new SetClientHintsOverrideCommandParameters();
parameters.ClientHints = new ClientHintsMetadata
{
    Platform = "Linux",
    Architecture = "x86"
};
parameters.UserContexts.Add(userContextId);

await driver.UserAgentClientHints.SetClientHintsOverrideAsync(parameters);

While Contexts and UserContexts are left empty they are omitted from the command and the override applies to all contexts. Add entries to scope the override to specific browsing contexts or user contexts. The two are mutually exclusive: a command that names both is rejected with invalid argument.

Common Patterns

Pattern: Mobile Device Testing

// Emulate mobile client hints for responsive design testing
SetClientHintsOverrideCommandParameters parameters = new SetClientHintsOverrideCommandParameters();
parameters.ClientHints = new ClientHintsMetadata
{
    Brands = new List<BrandVersion>
    {
        new BrandVersion("Chromium", "120.0"),
        new BrandVersion("Google Chrome", "120.0")
    },
    Platform = "Android",
    PlatformVersion = "14.0",
    Architecture = "arm",
    Model = "Pixel 7",
    Mobile = true,
    FormFactors = new List<string> { "Mobile" }
};

await driver.UserAgentClientHints.SetClientHintsOverrideAsync(parameters);

// Combine with Emulation module for full mobile emulation
await driver.BrowsingContext.SetViewportAsync(
    new SetViewportCommandParameters
    {
        BrowsingContextId = contextId,
        Viewport = new Viewport { Width = 412, Height = 915 },
        DevicePixelRatio = 2.625
    });

Pattern: Cross-Browser Brand Testing

// Test how a site behaves with different browser brands
Dictionary<string, ClientHintsMetadata> browserConfigs = new()
{
    ["Chrome"] = new ClientHintsMetadata
    {
        Brands = new List<BrandVersion>
        {
            new BrandVersion("Chromium", "120.0"),
            new BrandVersion("Google Chrome", "120.0")
        },
        Platform = "Windows",
        Mobile = false
    },
    ["Edge"] = new ClientHintsMetadata
    {
        Brands = new List<BrandVersion>
        {
            new BrandVersion("Chromium", "120.0"),
            new BrandVersion("Microsoft Edge", "120.0")
        },
        Platform = "Windows",
        Mobile = false
    },
    ["Safari"] = new ClientHintsMetadata
    {
        Brands = new List<BrandVersion>
        {
            new BrandVersion("Safari", "17.0")
        },
        Platform = "macOS",
        Mobile = false
    }
};

foreach (KeyValuePair<string, ClientHintsMetadata> config in browserConfigs)
{
    Console.WriteLine($"\nTesting as {config.Key}");
    SetClientHintsOverrideCommandParameters parameters =
        new SetClientHintsOverrideCommandParameters();
    parameters.ClientHints = config.Value;
    await driver.UserAgentClientHints.SetClientHintsOverrideAsync(parameters);

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

    // Verify site behavior for this browser brand
    EvaluateResult result = await driver.Script.EvaluateAsync(
        new EvaluateCommandParameters(
            "navigator.userAgentData?.brands?.map(b => b.brand).join(', ') ?? 'not supported'",
            new ContextTarget(contextId),
            true));

    if (result is EvaluateResultSuccess success &&
        success.Result is StringRemoteValue brandsValue)
    {
        string brands = brandsValue.Value;
        Console.WriteLine($"Detected brands: {brands}");
    }
}

Pattern: Verify Client Hints in Page

// Set override
SetClientHintsOverrideCommandParameters parameters =
    new SetClientHintsOverrideCommandParameters();
parameters.ClientHints = new ClientHintsMetadata
{
    Brands = new List<BrandVersion> { new BrandVersion("TestBrowser", "1.0") },
    Platform = "TestOS",
    Mobile = true
};
await driver.UserAgentClientHints.SetClientHintsOverrideAsync(parameters);

// Verify via User-Agent Client Hints API (JavaScript)
EvaluateResult result = await driver.Script.EvaluateAsync(
    new EvaluateCommandParameters(
        @"(async () => {
            const ua = navigator.userAgentData;
            if (!ua) return 'User-Agent Client Hints API not supported';
            const hints = await ua.getHighEntropyValues(['brands', 'platform', 'mobile']);
            return JSON.stringify(hints);
        })()",
        new ContextTarget(contextId),
        true));

if (result is EvaluateResultSuccess success &&
    success.Result is StringRemoteValue hintsValue)
{
    string hintsJson = hintsValue.Value;
    Console.WriteLine($"Client hints: {hintsJson}");
}

Best Practices

  1. Combine with other modules: For full device emulation, combine client hints with the Emulation module (user agent string, viewport meta behavior) and BrowsingContext.SetViewportAsync (viewport size).
  2. Reset between tests: Clear overrides between test cases to ensure isolation using ResetClientHintsOverride.
  3. Set before navigation: Apply overrides before navigating to pages that may use client hints for feature detection.
  4. Match brand and version: Ensure Brands and FullVersionList are consistent when both are used.
  5. Verify support: The User-Agent Client Hints API is not supported in all browsers; check navigator.userAgentData before relying on hints in your tests.

Limitations

  • Client hints override affects HTTP request headers and the navigator.userAgentData JavaScript API; the traditional navigator.userAgent string is controlled by the Emulation module.
  • Support varies by browser; the client-hints override command is experimental in Chromium-based browsers and not yet available in Firefox or Safari (see the support matrix).
  • Some websites may use additional detection methods beyond client hints.

Common Issues

Client Hints Not Applied

Problem: Website doesn't detect the overridden client hints.

Solution:

  • Set the override before navigating to the page
  • Ensure the page uses the User-Agent Client Hints API (navigator.userAgentData) or that the server reads the Sec-CH-* request headers
  • Verify browser support for client hints

Inconsistent with User-Agent String

Problem: Client hints and User-Agent string don't match.

Solution:

  • Use the Emulation module's SetUserAgentOverrideAsync in conjunction with SetClientHintsOverrideAsync
  • Ensure brands, platform, and mobile flag align between both overrides

Override Not Clearing

Problem: Override persists after calling reset.

Solution:

  • Use SetClientHintsOverrideCommandParameters.ResetClientHintsOverride explicitly
  • Ensure you're not passing a parameters object with ClientHints set to an empty ClientHintsMetadata (use the reset property instead)

Next Steps

Further Reading