Skip to content

Simulating user input#

Actions are objects that are passed to UIMatcher::perform. Each action is applied to every matched view, and the UI is processed (uitest::frame()) after each step, so a handler's reaction is visible immediately. Input goes through the same window entry points the platform uses (onPointerPressed, onCharEntered, onKeyDown and so on), so the views see what they would see from a real user.

By::text("Say hello").perform(click());

Clicks#

click() presses and releases the left button at the center of the view. clickRight() does the same with the right button.

1
2
3
4
5
6
7
TEST_F(UIClick, RightClick) {
    // check clicked right or long pressed signal emitted
    EXPECT_CALL(*mTestWindow, onButtonClickedRightOrLongPressed).Times(1);

    // press the button
    By::text("Say hello").perform(clickRight());
}

Pass a position relative to the view's top-left corner to click somewhere else. This is how the text field tests place a cursor:

1
2
3
4
TEST_F(UITextField, CursorAppearsAtTheEnd) {
    By::type<ATextField>().perform(click({299_dp, 0_dp})) // click at the right border
                          .check(selectionMatches(12));
}

doubleClick() takes an optional position, too:

1
2
3
4
TEST_F(UITextField, DoubleClickWordSelection1) {
    By::type<ATextField>().perform(doubleClick({20_dp, 0_dp})) // click at the first word
                          .check(selectionMatches(0, 5));
}

gestureLongPress() emits a long press gesture, the touch counterpart of the right click:

1
2
3
4
5
6
7
TEST_F(UIClick, LongPress) {
    // check clicked right or long pressed signal is emitted
    EXPECT_CALL(*mTestWindow, onButtonClickedRightOrLongPressed).Times(1);

    // press the button
    By::text("Say hello").perform(gestureLongPress());
}

For pointer sequences, use the separate steps: mousePress(), mouseRelease() and mouseMove() (and mousePressRight(), mouseReleaseRight()). pointerPress, pointerRelease and pointerMove are there for the cases when a pointer index has to be specified.

If you need a pointer event that the actions do not cover, call the window directly, as UIClickTest does:

mTestWindow->onPointerPressed({ .position = view->getCenterPointInWindow(), .asButton = AInput::LBUTTON });

Typing#

type("text") focuses the view and enters the string character by character, as if typed on the keyboard. It does not clear the field; select all first if you want to replace the contents.

TEST_F(UIType, HelloAppearsAfterClick) {
    // type "Steve" to the text field
    By::name("#username").perform(type("Steve"));

    // press the button
    By::text("Say hello").perform(click());

    // check label is appeared and contains text "Hello, Steve!"
    By::name("#hello").check(visible(), "label is not appeared")
                      .check(text("Hello, Steve!"), "invalid text");
}

Key presses#

keyDownAndUp(key) focuses the view and presses and releases a key. The argument is an AShortcut, so combinations are formed with +:

1
2
3
4
5
6
TEST_F(UITextField, CtrlA) {
    By::type<ATextField>()
            .perform(keyDownAndUp(AInput::LCONTROL + AInput::A))
            .perform(type("replace"))
            .check(text("replace"));
}

Actions can be chained on the same matcher, interleaved with checks. The following test moves the cursor with the keyboard and checks its position after each step (selectionMatches is a custom assertion, see Checking views):

TEST_F(UITextField, HomeEnd) {
    By::type<ATextField>()
            .perform(click({0, 0}))
            .check(selectionMatches(0))
            .perform(keyDownAndUp(AInput::END))
            .check(selectionMatches("hello world!"sv.length()))
            .perform(keyDownAndUp(AInput::HOME))
            .check(selectionMatches(0))
            ;
}

Scrolling and hovering#

scroll({ dx, dy }) delivers a scroll event to the view. mouseMove() moves the pointer to the view (with an optional offset), which updates hover state.

1
2
3
4
TEST_F(UIScrollTest, ContainedViewExpanded) {
    By::type<AScrollArea>().perform(scroll({0, 500}));
    (By::name(".container") | By::type<AScrollArea>()).check(areBottomAligned());
}

To scroll a particular child into view, call AScrollArea::scrollTo(view) directly; actions are only needed when the scroll itself is what you test.

Summary#

Action What it does
click(), clickRight() Press and release the left or right button
doubleClick() Two clicks and a double click event
gestureLongPress() Long press gesture
mousePress(), mouseRelease() Press or release the left button
mouseMove() Move the pointer
type("...") Enter characters into the focused view
keyDownAndUp(key) Press and release a key or a shortcut
scroll({ dx, dy }) Mouse wheel

Position arguments are relative to the view's top-left corner and accept _dp literals, as in click({ 23_dp, 0_dp }).