Table of Contents

Input Module

The Input module provides functionality for simulating user input including mouse, keyboard, touch, and wheel events.

Overview

The Input module allows you to:

  • Simulate mouse movements and clicks
  • Send keyboard input
  • Perform touch gestures
  • Simulate wheel scrolling
  • Chain multiple actions together

Accessing the Module

InputModule input = driver.Input;

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.

Performing Actions

Mouse Click

PerformActionsCommandParameters parameters = new PerformActionsCommandParameters(contextId);

// Create a pointer (mouse) action source
PointerSourceActions mouseSource = new PointerSourceActions
{
    Parameters = new PointerParameters { PointerType = PointerType.Mouse },
};
mouseSource.Actions.Add(new PointerMoveAction { X = 100, Y = 100 });
mouseSource.Actions.Add(new PointerDownAction(0));
mouseSource.Actions.Add(new PointerUpAction(0));

parameters.Actions.Add(mouseSource);

await driver.Input.PerformActionsAsync(parameters);

Keyboard Input

PerformActionsCommandParameters parameters = new PerformActionsCommandParameters(contextId);

// Create a keyboard action source
KeySourceActions keySource = new KeySourceActions();

// Type text
keySource.Actions.Add(new KeyDownAction("H"));
keySource.Actions.Add(new KeyUpAction("H"));
keySource.Actions.Add(new KeyDownAction("i"));
keySource.Actions.Add(new KeyUpAction("i"));

parameters.Actions.Add(keySource);

await driver.Input.PerformActionsAsync(parameters);

Click on Element

// First locate the element
LocateNodesCommandResult locateResult = await driver.BrowsingContext.LocateNodesAsync(
    new LocateNodesCommandParameters(contextId, new CssLocator("button")));

NodeRemoteValue element = locateResult.Nodes[0];

// Click the element
PerformActionsCommandParameters parameters = new PerformActionsCommandParameters(contextId);

PointerSourceActions mouseSource = new PointerSourceActions
{
    Parameters = new PointerParameters { PointerType = PointerType.Mouse },
};
mouseSource.Actions.Add(new PointerMoveAction
{
    X = 0,
    Y = 0,
    Origin = Origin.Element(element.ToSharedReference()),
});
mouseSource.Actions.Add(new PointerDownAction(0));
mouseSource.Actions.Add(new PointerUpAction(0));

parameters.Actions.Add(mouseSource);

await driver.Input.PerformActionsAsync(parameters);

Send Keys to Element

// Click element first to focus it
// ... (click code from above)

// Then send keys
PerformActionsCommandParameters parameters = new PerformActionsCommandParameters(contextId);

KeySourceActions keySource = new KeySourceActions();
string text = "Hello, World!";

foreach (char c in text)
{
    keySource.Actions.Add(new KeyDownAction(c.ToString()));
    keySource.Actions.Add(new KeyUpAction(c.ToString()));
}

// Press Enter
keySource.Actions.Add(new KeyDownAction("\uE007"));
keySource.Actions.Add(new KeyUpAction("\uE007"));

parameters.Actions.Add(keySource);

await driver.Input.PerformActionsAsync(parameters);

Modifier Keys

PerformActionsCommandParameters parameters = new PerformActionsCommandParameters(contextId);

KeySourceActions keySource = new KeySourceActions();

// Ctrl+A (Select All)
keySource.Actions.Add(new KeyDownAction("\uE009"));
keySource.Actions.Add(new KeyDownAction("a"));
keySource.Actions.Add(new KeyUpAction("a"));
keySource.Actions.Add(new KeyUpAction("\uE009"));

parameters.Actions.Add(keySource);

await driver.Input.PerformActionsAsync(parameters);

Reusing an Input Source Across Calls

Every SourceActions object (KeySourceActions, PointerSourceActions, WheelSourceActions, NoneSourceActions) is identified on the remote end by its Id. The browser keeps per-source state — which keys and buttons are currently pressed, and where the pointer is — keyed by that ID, and the state persists across PerformActionsAsync calls until you call ReleaseActionsAsync. By default a new source gets a random ID, so each call starts from a fresh, released source. To continue a sequence across calls (for example, press in one call and drag-and-release in another), create the source with an explicit ID and reuse it:

// The remote end tracks the state of each input source (pressed keys and buttons,
// pointer position) by its ID, and that state persists across performActions calls
// until ReleaseActionsAsync is called. Giving the source a stable ID lets a later
// call continue where an earlier one left off.
const string MouseId = "default mouse";

// First call: move the pointer and press the primary button.
PerformActionsCommandParameters pressParameters = new PerformActionsCommandParameters(contextId);
PointerSourceActions mouseDown = new PointerSourceActions(MouseId)
{
    Parameters = new PointerParameters { PointerType = PointerType.Mouse },
};
mouseDown.Actions.Add(new PointerMoveAction { X = 100, Y = 100 });
mouseDown.Actions.Add(new PointerDownAction(0));
pressParameters.Actions.Add(mouseDown);
await driver.Input.PerformActionsAsync(pressParameters);

// Second call: the same source ID, so the button is still pressed and the
// pointer is still at (100, 100); this move performs a drag.
PerformActionsCommandParameters dragParameters = new PerformActionsCommandParameters(contextId);
PointerSourceActions mouseDrag = new PointerSourceActions(MouseId)
{
    Parameters = new PointerParameters { PointerType = PointerType.Mouse },
};
mouseDrag.Actions.Add(new PointerMoveAction { X = 300, Y = 200 });
mouseDrag.Actions.Add(new PointerUpAction(0));
dragParameters.Actions.Add(mouseDrag);
await driver.Input.PerformActionsAsync(dragParameters);

// A source created without an ID gets a new random ID each time, so it never
// shares state with sources from earlier calls.
PointerSourceActions independentMouse = new PointerSourceActions();

Two sources in the same PerformActionsAsync call must have different IDs.

Release Actions

// Release all pressed keys/buttons
ReleaseActionsCommandParameters parameters = new ReleaseActionsCommandParameters(contextId);
await driver.Input.ReleaseActionsAsync(parameters);

Set Files on File Input

Use SetFilesAsync to programmatically set files on an input type="file" element without opening the file dialog. Locate the file input element, create SetFilesCommandParameters with the browsing context and element reference, add file paths to the Files collection, and call SetFilesAsync:

LocateNodesCommandResult locateResult = await driver.BrowsingContext.LocateNodesAsync(
    new LocateNodesCommandParameters(contextId, new CssLocator("input[type='file']")));

NodeRemoteValue element = locateResult.Nodes[0];

SetFilesCommandParameters parameters = new SetFilesCommandParameters(
    contextId,
    element.ToSharedReference());

parameters.Files.Add("/path/to/file1.txt");
parameters.Files.Add("/path/to/file2.png");

await driver.Input.SetFilesAsync(parameters);

The file path format depends on the browser and driver; consult your driver documentation for supported formats.

Events

FileDialogOpened

The OnFileDialogOpened event fires when a file dialog is opened (for example, when a user clicks an input type="file" element). Subscribe to the event to handle file dialogs programmatically. When the event includes an Element reference, you can use SetFilesAsync with that element to provide files without the user selecting them:

driver.Input.OnFileDialogOpened.AddObserver(async (FileDialogOpenedEventArgs e) =>
{
    Console.WriteLine($"File dialog opened in context {e.BrowsingContextId}");
    Console.WriteLine($"Multiple files: {e.IsMultiple}");

    if (e.Element != null)
    {
        SetFilesCommandParameters parameters = new SetFilesCommandParameters(
            e.BrowsingContextId,
            e.Element.ToSharedReference());

        parameters.Files.Add("/path/to/upload.txt");
        await driver.Input.SetFilesAsync(parameters);
    }
},
ObservableEventHandlerOptions.RunHandlerAsynchronously);

SubscribeCommandParameters subscribe =
    new SubscribeCommandParameters(driver.Input.OnFileDialogOpened.EventName);
await driver.Session.SubscribeAsync(subscribe);

Remember to call Session.SubscribeAsync with the event name before the file dialog can be triggered. Use ObservableEventHandlerOptions.RunHandlerAsynchronously when the handler calls commands such as SetFilesAsync.

Common Key Constants

// Enter: \uE007, Tab: \uE004, Backspace: \uE003, Delete: \uE017
// Escape: \uE00C, Control: \uE009, Shift: \uE008, Alt: \uE00A
// ArrowUp: \uE013, ArrowDown: \uE015, ArrowLeft: \uE012, ArrowRight: \uE014

Use Unicode values for special keys: Enter \uE007, Tab \uE004, Control \uE009, etc. A partial list is in the table below.

Key Unicode Value
Enter \uE007
Tab \uE004
Backspace \uE003
Delete \uE017
Escape \uE00C
Control \uE009
Shift \uE008
Alt \uE00A
Arrow Up \uE013
Arrow Down \uE015
Arrow Left \uE012
Arrow Right \uE014

Best Practices

  1. Release actions: Call ReleaseActionsAsync between test scenarios
  2. Use delays: Add small delays between actions for reliability
  3. Focus elements: Click or tab to elements before sending keys
  4. Check element state: Ensure elements are visible and enabled

Building Action Sequences with InputBuilder

The WebDriverBiDi.Extensions package adds an InputBuilder that assembles action sequences tick by tick, helpers for clicks, typing, key chords, drag-and-drop, and scrolling, and a Keys class naming the special keys:

IReadOnlyList<SharedReference> searchBoxes = await driver.BrowsingContext.LocateNodesByCssSelectorAsync(contextId, "input[name=q]");

InputBuilder builder = new InputBuilder()
    .AddClickOnElementAction(searchBoxes[0])
    .AddSendKeysToActiveElementAction("WebDriver BiDi")
    .AddSendKeysToActiveElementAction(Keys.Enter);
await driver.Input.PerformActionsAsync(contextId, builder);

// Releases any keys or buttons an action sequence left pressed.
await driver.Input.ReleaseActionsAsync(contextId);

See WebDriverBiDi.Extensions for how ticks work and how to use several input sources at once.

Error Handling

Commands in this module throw WebDriverBiDiCommandException when the browser returns a protocol error response (for example, when an action sequence targets an invalid element or browsing context), and WebDriverBiDiTimeoutException when a command exceeds its timeout. See the Error Handling guide for full details on exception types, TransportErrorBehavior options, and recommended catch patterns.

Next Steps