User input

edit

There is no window and no user: your application plays the user with BrowsingContext.Input. The library fires the trusted events that a browser fires (pointer, mouse, touch, wheel, keyboard, input) and runs their default actions: the focus, the clicks, the text typed in the fields, the form submissions, the scrolling, the drag and drop.

using var context = new BrowsingContext();
var document = context.Navigate("https://example.com/", _ => """
    <!DOCTYPE html>
    <style>body { margin: 0 } button { display: block; width: 100px; height: 40px }</style>
    <button>Save</button>
    <input id="name">
    """);

var clicks = 0;
document.QuerySelector("button")!.AddEventListener("click", _ => clicks++);

// The positions are in CSS pixels, in the viewport: the element at the position gets the events
var input = context.Input;
input.Mouse.Move(50, 20);
input.Mouse.Down();
input.Mouse.Up();
Assert.Equal(1, clicks);

// The keys go to the focused element
var name = (HtmlInputElement)document.GetElementById("name")!;
name.Focus();
input.Keyboard.Type("Ada");
input.Keyboard.Press("Backspace");
Assert.Equal("Ad", name.Value);

Pointers

The positions are in CSS pixels, in the viewport of the top-level browsing context: the element at the position (found by the hit testing of the layout) gets the events, also in the document of an iframe.

MemberEffect
Input.MouseThe mouse: Move(x, y), Down(button), Up(button). A press and a release make a click (dblclick for a second click, contextmenu for the secondary button)
Input.CreatePointer("touch"), CreatePointer("pen")A finger or a pen, with the same methods; a moving touch pans the page
Input.Wheel(x, y, deltaX, deltaY)A turn of the wheel at a position: the wheel event, then the scroll
Input.ModifiersThe modifier keys (InputModifiers.Shift, Control...) of the next events
Input.HitTest(x, y)The element at a position, without firing anything

The pen and touch events can be given to navigator.ink (the Delegated Ink Trail API): requestPresenter() and updateInkTrailStartPoint() validate their arguments as in Chromium (a trusted pointer event, a valid CSS color, a diameter greater than 0), but the engine does not draw the predicted trail: the page draws its own trail in its next frame.

To click an element, click the center of its GetBoundingClientRect(). Input.MapToTopLevel(document, x, y) converts a position of the viewport of an iframe to the top-level viewport.

Keyboard

Input.Keyboard sends the keys to the focused element, with the key values of the browsers (a character, or the name of a key such as Enter, Tab, Backspace, ArrowLeft, Shift) and the layout of a US keyboard:

MethodEffect
Type(text)Presses and releases the key of each character
Press(key)Presses and releases a key
Down(key), Up(key)Presses or releases a key: hold Shift or Control while pressing other keys
InsertText(text)Inserts a text as an input method does, without keyboard events

The default actions run: the text is inserted in the text fields and the editable content, Tab moves the focus, Enter and Space activate the focused element (Enter submits the form of a text field), the arrow keys move the caret, and Escape closes the dialogs and the popovers. Outside of the editable content, the arrow keys, PageUp, PageDown, Space, Home and End scroll the focused scroll container (or the viewport). As in the browsers, a scroll container that the user can scroll and that has no focusable descendant can be focused by Tab and by focus() (not by a click).

Pending input

A long script can call navigator.scheduling.isInputPending() to yield when the user is waiting. The library dispatches the input you send at once, so nothing is pending by default. When your application queues input before sending it (for instance from a UI thread while a script runs), answer the question with BrowsingContextOptions.PendingInput:

var pendingInput = new Queue<string>(["pointermove", "click"]);
var options = new BrowsingContextOptions
{
    PendingInput = new PendingInputOptions
    {
        // The input events that the host has not dispatched yet; the continuous events (e.g. pointermove) are
        // only considered when includeContinuous is true
        IsInputPending = (callbackContext, includeContinuous) => pendingInput.Any(type => includeContinuous || type is not ("pointermove" or "mousemove" or "wheel" or "touchmove")),
    },
};

The callback runs while the script runs, on the thread of the event loop: return quickly. Return false for the input of a cross-origin frame, and when you are not sure the input targets the window. The documents that are not fully active always get false.

Without input

HtmlElement.Click(), Focus() and DispatchEvent() act on an element directly, as the same methods of a script: the events they fire are not trusted, and no hit testing is done. Use them when you do not need to simulate a user.

To automate pages with a higher-level API (locators, auto-waiting, assertions), drive the engine with Playwright: see Playwright.