Skip to content

Finding views#

Every UI test starts with finding the views to work with. By is a set of functions that return a UIMatcher; the matcher is then used to perform actions and check assertions. Matching is lazy: the view tree is searched on every call, so a matcher stays valid after the UI has changed.

By text, name and type#

By::text matches views whose text equals the string: labels, buttons, text fields, anything that converts to a string via IStringable (so AButton is matched by its caption).

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

By::name matches by ASS name, which is assigned with << in the view tree ("#username", ".container") or with addAssName. A name is the most robust selector: it does not depend on a text that may be translated or changed.

mTextField = _new<ATextField>() << "#username",
...
By::name("#username").perform(type("Steve"));

By::type<T>() matches views of type T or derived from it, and By::exactType<T>() only of exactly T.

By::type<AButton>().check(sameWidth(), "widths of the buttons are not equal");

By::value(view) matches a particular view object, which is handy for combining it with other matchers.

Note

Only visible views are matched by default. A view with Visibility::INVISIBLE or Visibility::GONE, and everything inside it, is skipped. Assertions visible(), notVisible() and gone() are exceptions; they look at invisible views, too. Call includeInvisibleViews() to find invisible views with toSet(), one(), perform or check.

Combining matchers#

| selects views matched by any of the operands, & by both:

(By::text("Common button") | By::text("Show all views...")).check(sameHeight());
(By::type<AButton>() & By::name(".primary")).perform(click());

Mind that perform is applied to every matched view, in unspecified order. When you need exactly one view, make the selector precise enough, or use one().

Getting the view itself#

one() returns the first matched view as _<AView>, or nullptr if there is none. toSet() and toVector() return all of them. Cast to the concrete type to inspect it:

auto label = _cast<ALabel>(By::name("#hello").one());
ASSERT_TRUE(label);
EXPECT_EQ(*label->text(), "Hello, Steve!");

Position and size are in getPositionInWindow(), getSize() and getCenterPointInWindow(); these are what the click action uses to aim.

Relatives#

parent() selects containers that have a direct child matched by this matcher, allChildren() selects views whose parent is matched. Together they select siblings:

// every element in the same container as the "Common button"
By::name("Common button").parent().allChildren().check(areLeftAligned());

Finding the nearest view#

Forms usually pair a label with a field. findNearestTo picks, among the matched views, the one closest to another view, measured from its bottom-right corner (the way a person scans a form). The argument must match exactly one view.

// layout: Label "Login", ATextField, Label "Password", ATextField
By::type<ATextField>().findNearestTo(By::text("Login")).perform(type("alice"));
By::type<ATextField>().findNearestTo(By::text("Password")).perform(type("secret"));

Many windows#

A matcher looks into all windows opened by the window manager, so dialogs and popups are found without extra effort. Make the selector narrow enough if the same text appears in several windows.