EarlGrey includes three main groups of APIs:
EarlGrey test cases are made up of interactions with UI elements. Each interaction consists of:
- Selecting an element to interact with,
- Performing an action on it, and/or
- Making an assertion to verify state and behavior.
To reflect this, the Interaction APIs are organized into the following:
Each of these APIs is designed with extensibility in mind, giving the user flexibility for customization, while preserving the prose-like structure of tests. Consider the following snippet:
[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")];
It shows how to start an interaction by selecting an element with ClickMe
as the Accessibility
Identifier. After you select an element, you can continue the interaction with an action or an
assertion. EarlGrey works with any element that conforms to the UIAccessibility protocol,
not just UIViews
; this allows tests to perform a richer set of interactions.
You can chain a selection and an action in one statement. For example, you can tap on the element
that has ClickMe
as its Accessibility Identifier as follows:
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]
performAction:grey_tap()];
Note: selectElementWithMatcher:
doesn't return an element; it just marks the beginning of an
interaction.
You can also chain a selection and an assertion. The following snippet shows how to select an
element that has ClickMe
as its accessibility identifier and asserts that it is displayed:
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]
assertWithMatcher:grey_sufficientlyVisible()];
Finally, you can perform actions and assertions in sequence. The following statement finds an element with
the accessibility identifier ClickMe
, taps on it, and asserts that it is not displayed.
[[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]
performAction:grey_tap()]
assertWithMatcher:grey_notVisible()];
The following is an example of a simple interaction:
[[[EarlGrey selectElementWithMatcher:<element_matcher>]
performAction:<action>]
assertWithMatcher:<assertion_matcher>];
Where <element_matcher>
and <assertion_matcher>
are matchers contained in GREYMatchers.h
and
<action>
is one of the actions in GREYActions.h
. Because each of these has a shorthand
C-function, instead of using [GREYMatchers matcherForSufficientlyVisible]
you can use
grey_sufficientlyVisible()
. Shorthand notation is enabled by default. To disable it, add
#define GREY_DISABLE_SHORTHAND 1
to the project's prefix header.
Use the Selection API to locate a UI element on the screen. This API accepts a matcher and tests it against all the elements in the UI hierarchy to locate an element to interact with. In EarlGrey, matchers support an API that is similar to that of OCHamcrest matchers. You can combine matchers using AND-OR-NOT logic, allowing you to create matching rules that can pinpoint any element in the UI hierarchy.
All EarlGrey matchers are available in the GREYMatchers
factory class. The best way to find a UI element is to use its accessibility properties. We
strongly recommend using an accessibility identifier
as it uniquely identifies an element. Use grey_accessibilityID()
as your matcher to select a UI
element by its accessibility identifier. You can also use other accessibility properties, such as using
grey_accessibilityTrait()
as the matcher for UI elements with specific accessibility traits, or by using
grey_accessibilityLabel()
as the matcher for accessibility labels.
A matcher can be ambiguous and match multiple elements: for example, grey_sufficientlyVisible()
will match all sufficiently visible UI elements. In such cases, you must narrow down the
selection until it can uniquely identify a single UI element. You can make the matchers more specific
by combining matchers with grey_allOf()
, grey_anyOf()
, grey_not()
or by supplying a root
matcher with the inRoot
method to narrow the selection.
Consider these examples for both cases.
First, with collection matchers, the following snippet finds a UI element that has an accessibility
label Send
and is displayed on the screen.
id<GREYMatcher> visibleSendButtonMatcher =
grey_allOf(grey_accessibilityLabel(@"Send"), grey_sufficientlyVisible(), nil);
[[EarlGrey selectElementWithMatcher:visibleSendButtonMatcher]
performAction:grey_tap()];
Note that with grey_allOf
the order matters. If grey_sufficientlyVisible
is used first, then every element
in the entire application will be checked for visibility. It's important to order matchers from
most selective (such as accessibility label and accessibility id) to least.
Next, with inRoot
, the following statement finds an element that has the accessibility label set
to Send
and is contained in a UI element that is an instance of the SendMessageView
class.
[[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"Send")]
inRoot:grey_kindOfClass([SendMessageView class])]
performAction:grey_tap()];
Note: For compatibility with Swift, we use grey_allOfMatchers()
and grey_anyOfMatchers()
instead of grey_allOf()
and grey_anyOf()
respectively.
To create custom matchers, use the block-based GREYElementMatcherBlock class. For example, the following code matches views that don't have any subviews:
+ (id<GREYMatcher>)matcherForViewsWithoutSubviews {
MatchesBlock matches = ^BOOL(UIView *view) {
return view.subviews.count == 0;
};
DescribeToBlock describe = ^void(id<GREYDescription> description) {
[description appendText:@"Views without subviews"];
};
return [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matches
descriptionBlock:describe];
}
MatchesBlock
can also accept type id
instead of UIView *
. You should use id
when the
matcher must also work with accessibility elements. The following matcher example works
universally for all UI element types:
+ (id<GREYMatcher>)matcherForElementWithoutChildren {
MatchesBlock matches = ^BOOL(id element) {
if ([element isKindOfClass:[UIView class]]) {
return ((UIView *)element).subviews.count == 0;
}
// Handle accessibility elements here.
return ...;
};
DescribeToBlock describe = ^void(id<GREYDescription> description) {
[description appendText:@"UI element without children"];
};
return [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matches
descriptionBlock:describe];
}
This matcher can be used in a test to select a UI element that doesn’t have any children (or
subviews) and double-tap on it (assuming that the method was declared in a class CustomMatchers
):
[[EarlGrey selectElementWithMatcher:[CustomMatchers matcherForElementWithoutChildren]]
performAction:grey_doubleTap()];
In certain situations the UI element may be hidden off-screen, and may require certain interactions
to bring it onto the screen. Common examples include scrolling to an element that is not visible in
the scrollview, zooming-in on a map to show UI elements that are shown only in street-view,
navigating through a UICollectionView
that has a custom layout, etc. You can use the [usingSearchAction:onElementWithMatcher:]
method
to provide a search action for such elements. The API allows you to specify a search action and a
matcher for the element on which the search action will be applied. EarlGrey applies the search
action repeatedly until the element you are looking for is found (or a timeout occurs).
For example, the following statement attempts to find an element matching aButtonMatcher
by
repeatedly scrolling down (by 50 points at a time) on the element matching aScrollViewMatcher
and
taps the button when it finally finds it.
[[[EarlGrey selectElementWithMatcher:aButtonMatcher]
usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 50)
onElementWithMatcher:aScrollViewMatcher]
performAction:grey_tap()];
And in this example, this statement attempts to find a table view cell matching aCellMatcher
by
repeatedly scrolling up (by 50 points at a time) on a table matching aTableViewMatcher
.
[[[EarlGrey selectElementWithMatcher:aCellMatcher]
usingSearchAction:grey_scrollInDirection(kGREYDirectionUp, 50)
onElementWithMatcher:aTableViewMatcher]
performAction:grey_tap()];
Use the Action API to specify the test actions to perform on a selected UI element.
All EarlGrey actions are available in the GREYActions factory
class. The most common action is tapping (or clicking) on a given element using the grey_tap()
method:
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"TapMe")]
performAction:grey_tap()];
If an action triggers changes in the UI hierarchy, EarlGrey synchronizes each action (including chained actions) with the UI, ensuring that it is in a stable state before the next action is performed.
Not all actions can be performed on all elements; for example, the tap action cannot be performed
on elements that are not visible. To enforce this, EarlGrey uses constraints which are
preconditions in the form of GREYMatcher that
must be met before an action is actually performed. Failure to meet these constraints results in an
exception being thrown and the test marked as failed. To avoid test failure, you can invoke
performAction:error:
to get an NSError
object containing failure details.
NSError *error;
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"Non-Existent-Ax-Id")]
performAction:grey_tap()
error:&error];
In the above case, an exception will not be thrown for the EarlGrey interaction being performed on a non-existent element. Instead, the error object passed will save the failure details and not fail the test immediately. The error details can then be perused for finding the failure details.
Custom actions can be created by conforming to the GREYAction
protocol. For convenience, you can use GREYActionBlock,
which already conforms to the GREYAction protocol and allows you to express an action in the form
of a block. The following code creates an action using a block that invokes a custom selector
(animateWindow
) to animate the selected element’s window:
- (id<GREYAction>)animateWindowAction {
return [GREYActionBlock actionWithName:@"Animate Window"
constraints:nil
performBlock:^(id element, NSError *__strong *errorOrNil) {
// First, make sure the element is attached to a window.
if ([element window] == nil) {
// Populate error.
*errorOrNil = ...
// Indicates that the action failed.
return NO;
}
// Invoke a custom selector that animates the window of the element.
[element animateWindow];
// Indicates that the action was executed successfully.
return YES;
}];
}
Use the Assertion API to verify the state and behavior of a UI element.
Use assertWithMatcher:
to perform an assertion with GREYMatcher
matchers. The selected element is run through the matcher for verification. For example,
the following snippet asserts that the element with the accessibility ID ClickMe
is visible,
and the test fails if the element is not visible on the screen.
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]
assertWithMatcher:grey_sufficientlyVisible()];
To prevent test failure, you can provide an NSError
object to the assertWithMatcher:error:
method, as in the statement shown below. Instead of failing, the EarlGrey assertion will provide
you with an NSError
object that contains details about the failure.
NSError *error;
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]
assertWithMatcher:grey_sufficientlyVisible()
error:&error];
You can also perform an assertion by using the assert
method and passing an instance of
GREYAssertion
. We recommend that you create
assertions from matchers using assertWithMatcher:
whenever possible. Matchers are lightweight and ideal for simple assertions. However, a custom
GREYAssertion
could be a better choice in the case of an assertion that needs to perform complex
logic, like manipulating the UI, to proceed with the assertion.
You can create custom assertions using the GREYAssertion
protocol or by using GREYAssertionBlock
that
accepts the assertion logic in a block. The following example uses GREYAssertionBlock
to write an
assertion that checks that the view's alpha is equal to the provided value:
+ (id<GREYAssertion>)hasAlpha:(CGFloat)alpha {
return [GREYAssertionBlock assertionWithName:@"Has Alpha"
assertionBlock:^(UIView *view, NSError *__strong *errorOrNil) {
if (view.alpha != alpha) {
NSString *reason =
[NSString stringWithFormat:@"Alpha value doesn't match for %@", view];
// Check if errorOrNil was provided, if so populate it with relevant details.
if (errorOrNil) {
*errorOrNil = ...
}
// Indicates assertion failed.
return NO;
}
// Indicates assertion passed.
return YES;
}];
}
Note: Do not assume that assertions are run against valid UI elements. You will need to perform
your own check to make sure the UI element is in a valid state before validating the assertion. For
instance, the following snippet checks that the element exists (that it isn’t nil
) before
asserting the rest of the state:
+ (id<GREYAssertion>)hasAlpha:(CGFloat)alpha {
return [GREYAssertionBlock assertionWithName:@"Has Alpha"
assertionBlock:^(UIView *view, NSError *__strong *errorOrNil) {
// Assertions can be performed on nil elements. Make sure view isn’t nil.
if (view == nil) {
// Check if errorOrNil was provided, if so populate it with relevant details.
if (errorOrNil) {
*errorOrNil = [NSError errorWithDomain:kGREYInteractionErrorDomain
code:kGREYInteractionElementNotFoundErrorCode
userInfo:nil];
}
return NO;
}
// Perform rest of the assertion logic.
...
// Indicates assertion passed.
return YES;
}];
}
Alternatively, you can create a class that conforms to GREYAssertion.
By default, EarlGrey uses a failure handler that is invoked when any exception is raised by the
framework. The default handler logs the exception, takes a screenshot, and then prints its path
along with any other useful information. You can choose to provide your own custom failure handler
and install it using the EarlGrey setFailureHandler:
API which replaces the global default
framework handler. To create a custom failure handler, write a class that conforms to the
GREYFailureHandler protocol:
@interface MyFailureHandler : NSObject <GREYFailureHandler>
@end
@implementation MyFailureHandler
- (void)handleException:(GREYFrameworkException *)exception details:(NSString *)details {
// Log the failure and state of the app if required.
// Call thru to XCTFail() with an appropriate error message.
}
- (void)setInvocationFile:(NSString *)fileName
andInvocationLine:(NSUInteger)lineNumber {
// Record the file name and line number of the statement which was executing before the
// failure occurred.
}
@end
EarlGrey provides its own macros that can be used within a testcase for assertion and verification. These macros are similar to the macros provided by XCTest and invoke the global failure handler upon assertion failure.
GREYAssert(expression, reason, ...)
— Fails if the expression evaluates to falseGREYAssertTrue(expression, reason, ...)
— Fails if the expression evaluates to false. Use for BOOL expressionsGREYAssertFalse(expression, reason, ...)
— Fails if the expression evaluates to true. Use for BOOL expressionsGREYAssertNotNil(expression, reason, ...)
— Fails if the expression evaluates to nilGREYAssertNil(expression, reason, ...)
— Fails if the expression evaluates to a non-nil valueGREYAssertEqual(left, right, reason, ...)
— Fails if left != right for scalar typesGREYAssertNotEqual(left, right, reason, ...)
— Fails if left == right for scalar typesGREYAssertEqualObjects(left, right, reason, ...)
— Fails if [left isEqual:right] returns falseGREYAssertNotEqualObjects(left, right, reason, ...)
— Fails if [left isEqual:right] returns trueGREYFail(reason, ...)
— Fails immediately with the provided reasonGREYFailWithDetails(reason, details, ...)
— Fails immediately with the provided reason and details
EarlGrey provides APIs to verify the layout of UI elements; for example, to verify that element X
is to the left of element Y. Layout assertions are modeled after NSLayoutConstraint.
To verify the layout, you need to first create a constraint that specifies the layout,
select an element constrained by it, and then assert that it matches the constraint using
grey_layout(...)
. Note that grey_layout(...)
takes an array of constraints, all of which must
be satisfied. This allows for easy specification of complex layout assertions.
For example, the following constraint specifies that the selected element is to the right of any other arbitrary element used as a reference.
GREYLayoutConstraint *rightConstraint =
[GREYLayoutConstraint layoutConstraintWithAttribute:kGREYLayoutAttributeLeft
relatedBy:kGREYLayoutRelationGreaterThanOrEqual
toReferenceAttribute:kGREYLayoutAttributeRight
multiplier:1.0
constant:0.0];
You can now select the element with the RelativeRight
accessibility ID and use
grey_layout(@[rightConstraint])
to assert if it's on the right of the reference element, which in
our example is the element with TheReference
accessibility ID.
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@”RelativeRight”)]
assertWithMatcher:grey_layout(@[rightConstraint], grey_accessibilityID(@”TheReference”))];
You can also create simple directional constraints using layoutConstraintForDirection:
. The
following code is equivalent to the previous example:
GREYLayoutConstraint *rightConstraint =
[GREYLayoutConstraint layoutConstraintForDirection:kGREYLayoutDirectionRight
andMinimumSeparation:0.0];
These APIs let you control how EarlGrey synchronizes with the app under test.
If your test case requires special handling, you can use GREYCondition
to wait or synchronize
with certain conditions. GREYCondition
takes a block that returns a BOOL
to indicate whether
the condition has been met. The framework polls this block until the condition is met before
proceeding with rest of the test case. The following snippet illustrates how to create and use a
GREYCondition
:
GREYCondition *myCondition = [GREYCondition conditionWithName:@"my condition"
block:^BOOL {
... do your condition check here ...
return yesIfMyConditionWasSatisfied;
}];
// Wait for my condition to be satisfied or timeout after 5 seconds.
BOOL success = [myCondition waitWithTimeout:5];
if (!success) {
// Handle condition timeout.
}
EarlGrey automatically waits for the app to idle by tracking the main dispatch queue, main
operation queue, network, animations and several other signals and performs interactions only when
the app is Idle. However there may be situations where the interactions must be performed in spite
of app being busy. For example in a messaging app, a photo might be uploading and corresponding animation
running but the test might still want to type and send a text message. To address such situations, you can
disable EarlGrey's synchronization using kGREYConfigKeySynchronizationEnabled
, as shown in the following snippet:
[[GREYConfiguration sharedInstance] setValue:@(NO)
forConfigKey:kGREYConfigKeySynchronizationEnabled];
Once disabled all interactions will proceed without waiting for the app to idle until synchronization is reenabled again. Note that to maximize test effectiveness, synchronization must be reenabled as soon as possible. Also instead of disabling synchronization completely you can configure the synchronization parameters to suit the needs of your app. For example:
kGREYConfigKeyNSTimerMaxTrackableInterval
can be used to specify the maximum interval of non-repeating NSTimers that EarlGrey should synchronize with.kGREYConfigKeyDispatchAfterMaxTrackableDelay
can be used to specify the maximum delay for future executions usingdispatch_after
calls that EarlGrey must synchronize with.kGREYConfigKeyURLBlacklistRegex
can be used to specify a list of URLs for which EarlGrey should not wait.
And several such configurations are available in GREYConfiguration. See below for some specific use cases in detail.
By default, EarlGrey synchronizes with all network calls, but you can customize this behavior by
providing regular expressions to skip over certain URLs. To blacklist a URL, create a regular
expression that matches the URL, add it to an NSArray
and pass it to GREYConfiguration
.
For multiple URLs, repeat the same process by creating one regular expression for each URL.
For example, to tell the framework not to wait for www.google.com and www.youtube.com,
do something like:
NSArray *blacklist = @[ @".*www\\.google\\.com", @".*www\\.youtube\\.com" ];
[[GREYConfiguration sharedInstance] setValue:blacklist
forConfigKey:kGREYConfigKeyURLBlacklistRegex];
By default, a thirty second timeout is used for any interaction. In that time, if the app under test fails to idle, a timeout exception is thrown and the test is marked as failed. You can use GREYConfiguration to change this timeout value. For example, to increase the timeout to 60 seconds (one minute):
[[GREYConfiguration sharedInstance] setValue:@(60.0)
forConfigKey:kGREYConfigKeyInteractionTimeoutDuration];
Animations that repeat indefinitely — or that run for a long time — affect synchronization. Animations that run longer than the test timeout value result in a timeout exception. To avoid such cases, EarlGrey limits the animation duration to five seconds and disables repeating animations (continuous animations run once). To instruct EarlGrey to allow animations to run longer, change the maximum allowable animation duration value. Make sure this doesn’t affect your test timeouts. The following snippet increases the maximum animation duration to 30 seconds.
[[GREYConfiguration sharedInstance] setValue:@(30.0)
forConfigKey:kGREYConfigKeyCALayerMaxAnimationDuration];
Due to limitation of dispatch queues and the way EarlGrey synchronizes with them, calling into EarlGrey
statements from a dispatch_queue
leads to a livelock. To mitigate this, we've introduced new block-based
APIs that wrap EarlGrey statements, and that can be safely called from non-main threads:
grey_execute_sync(void (^block)())
— Synchronous. Blocks until execution is complete.grey_execute_async(void (^block)())
— Asynchronous.
Outside of UI interaction, you can use EarlGrey to control the device and system in various ways.
The GREYConfiguration
class lets you configure the behavior of the framework. It provides a
means to configure synchronization, interaction timeouts, action constraints checks, logging, etc.
As soon as a configuration is changed, it is applied globally. For instance, the following code
changes the delay limit for dispatch after calls that EarlGrey tracks:
[[GREYConfiguration sharedInstance] setValue:@(5)
forConfigKey:kGREYConfigKeyDispatchAfterMaxTrackableDelay];
To rotate a device, use [EarlGrey rotateDeviceToOrientation:errorOrNil:]
to simulate a device in
a specific orientation. For instance, the following causes the system (and your app) to act as if
the device is in Landscape mode with the device held upright and the Home button on the
right side:
[EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft errorOrNil:nil];
You can choose from the following orientation modes (for more information, see UIDeviceOrientation):
UIDeviceOrientationUnknown
UIDeviceOrientationPortrait
UIDeviceOrientationPortraitUpsideDown
UIDeviceOrientationLandscapeLeft
UIDeviceOrientationLandscapeRight
UIDeviceOrientationFaceUp
UIDeviceOrientationFaceDown
You can use [EarlGrey shakeDeviceWithError:]
to simulate a shake gesture in a simulator.