Google EarlGrey学习笔记:
[Google EarlGrey] 0x00 安装及运行
[Google EarlGrey] 0x01 第一个测试用例
[Google EarlGrey] 0x02 API简介
API
EarlGrey主要包括三部分APIs
交互相关的编程接口(Interaction APIs)
同步相关的编程接口(Synchronization APIs)
其他的编程接口(Other Top Level APIs)
交互相关的编程接口(Interaction APIs)
EarlGrey的测试用例由一系列的UI交互组成。每一步交互由以下步骤构成:
选中需要交互的元素
操作该元素
断言操作后该元素的状态或者表现
针对以上三个步骤,交互相关的编程接口又可以分为:
选中相关的编程接口(Selection API)
操作相关的编程接口(Action API)
断言相关的编程接口(Assertion API)
每一个接口在设计时都考虑到了可扩展性,使得用户可以方便进行定制,保证测试用例的独立可维护。例如下面的代码
[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")];
用这段代码就可以开始对一个Accessibility Identity为ClickMe的元素进行交互了。当你选中了一个元素之后就可以进行这个元素进行操作或者断言。EarlGrey可以使用在任何遵循UIAccessibility protocol的元素,不仅仅是UIViews;这使得测试用例可以更加丰富。
你可以将元素选择和操作在一行代码中完成。例如,点击一个Accessibility Identity为ClickMe的元素可以这样写
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]
performAction:grey_tap()];
注意:selectElementWithMatcher不返回元素,只是标记交互开始
你可以将元素选择和断言在一行代码中完成。例如,选择一个Accessibility Identity为ClickMe的元素并判断它是否显示可以这么写
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]
assertWithMatcher:grey_sufficientVisible()];
当然你可以把元素操作和断言都放在一个顺序中。例如,选择一个Accessibility Identity为ClickMe的元素,点击它,然后判断它是否显示可以这么写
[[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]
performAction:grey_tap()] assertWithMatcher:grey_notVisible()];
以下是一个简单的交互模板
[[[EarlGrey selectElementWithMatcher:<element_matcher>]
performAction:<action>]
assertWithMatcher:<assertion_matcher>];
和都是GREYMatchers.h中的匹配器,为GREYAction.h中的操作方法。因为每一个匹配器或者操作都有短名称的方法,我们可以通过调用grey_sufficientlyVisible()来替代[GREYMatchers matcherForSufficientlyVisible]。用短标记方式调用是默认允许的。可以通过在项目的头文件中添加#define GREY_DISABLE_SHORTHAND 1来禁止掉。
选中相关的编程接口(Selection API)
使用Selection API来定位屏幕中的UI元素。当API接收到Matcher后会在所有UI分层中定位对应的元素来发生交互。在EarlGrey中,Matchers 支持类似OCHamcrest的Matchers。同时也可以使用与或逻辑组合Matchers,可以精准地定位到UI分层中的任意元素。
EarlGrey 匹配器(EarlGrey Matchers)
所有的EarlGrey匹配器都可以在工厂类GREYMatchers中找到。定位元素最好的方法是通过的它的accessibility properties。官方强烈推荐使用accessibility identifier因为它可以唯一标识一个元素。你也可以使用其他的accessibility properties,例如使用grey_accessibilityTrait()来匹配具有特殊的accessibility traits。再比如使用grey_accessibilityLabel()来匹配具有特定accessibility labels。
一个匹配器可以匹配多个元素。例如grey_sufficientlyVisible()会匹配出所有的可见的UI元素。这种情况下,你必须缩小选取的范围知道唯一确定单个元素。你可以通过组合多个匹配器例如使用grey_allOf(),grey_anyOf(),grey_not()来使得你的匹配更加精准。还可以使用根匹配器inRoot来缩小选取范围。
我们来看下面的例子
第一个,通过选择器的组合,下面这段代码匹配的元素,以Send作为accessibility label并且显示在当前屏幕
id<GREYMatcher> visibleSendButtonMatcher =
grey_allOf(grey_accessibilityLabel(@"Send"),grey_sufficientlyVisible(),nil);
[[EarlGrey selectElementWithMatcher:visibleSendButtonMatcher]
performAction:grey_tap()];
下一个,使用inRoot,下面这段代码匹配的元素,以Send作为accessibility label并且被包含在一个SendMessageView实例化对象的UI元素下。
[[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"Send")]
inRoot:grey_kindOfClass([SendMessageView class])]
performAction:grey_tap()];
自定义匹配器(Custom Matchers)
通过使用GREYElementMatcherBlock来自定义匹配器。例如,以下的代码用来匹配没有子视图的视图元素。
+ (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 除了接受UIView *也可以接受id类型。当匹配器用于选取元素时需要使用id。以下代码普遍适用于UI元素类型:
+ (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];
}
这个匹配器可以用来测试选取没人任何子元素或者视图的UI元素并双击它(假设这个方法命名为CustomMatchers)
[[EarlGrey selectElementWithMatcher:[CustomMatchers matcherForElementWithoutChildren]]
performAction:grey_doubleTap()];
选取隐藏的UI元素
在某些情况下UI元素是未显示在屏幕上的,需要一些操作后才会在屏幕中显示。常见的例子有,滚动到一个元素,但此元素还没显示在混动视图中;放大地图知道UI元素显示街景模式;UICollectionView中自定义的布局等。你可以使用方法[usingSearchAction:onElementWithMatcher:]来提供基于这些元素的搜索功能。API可以让你规定搜索操作并且在搜索操作下的元素进行匹配。EarlGrey可以重复执行搜索操作直到定位到需要的元素(或者超时)。
例如,下面的代码尝试通过匹配器aButtonMatcher定位元素,此元素在匹配器aScrollViewMatcher定位出的元素中,前一个匹配器会重复执行(每次向下移动50points)直到找到按钮元素并点击
[[[EarlGrey selectElementWithMatcher:aButtonMatcher]
usingSearchAction:grey_scrollInDirection(kGERYanDirectionDown,50)
onElementWithMatcher:aSrollViewMatcher]
performAction:grey_tap()];
下面的代码用来定位一个表格中的指定行,这一行满足aCellMatcher,表格满足aTableViewMatcher,未找到指定行前,会每次向下滚动50points
[[[EarlGrey selectElementWithMatcher:aCellMatcher]
usingSearchAction:grey_scrollInDirection(kGERYanDirectionDown,50)
onElementWithMatcher:aTableViewMatcher]
performAction:grey_tap()];
操作相关API(Action API)
使用Action API来描述在选中元素上的测试操作
EarlGrey Actions
所有的EarlGrey Actions都在工厂类GREYAciton中。最常用的操作就是点击,通过方法grey_tap()调用
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"TapMe")]
performAction:grey_tap()];
如果操作修改了UI布局,EarlGrey会同步对UI元素一系列操作中的每一次操作。在执行下一步操作时需要确定UI元素处于稳定的状态。
不是所有的操作都能在所有的元素上生效。例如,点击操作不能在一个不可见的元素上执行。为了避免这种情况,EarlGrey会有限制,在每次真正执行操作时需要确保GREYMatcher必须定位到元素。没有遵循这个限制会导致抛出异常并且测试用例被标记为失败。为了避免测试用例执行失败,可以通过performAction:error:这种调用方式获取NSError对象来获取错误详情。
NSError *error;
[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"TapMe")]
performAciton:grey_tap()
error:&error];
以下的例子中,应用中没有Title为”Tap me!”的button,两种调用方式的区别。
自定义操作(Custom Actions)
遵循GREYAction协议可以自定义操作。为了方便,你可以使用GREYActionBlock,此文件已经遵循GREYAction协议让你能够快速的自定义操作。下面的代码就是用来自定义一个操作,用来animate选中的元素的窗口
- (id<GREYAction>)animateWindowAction {
return [GREYActionBlock actionWithName:@"Animate Window"
constraint: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;
}];
}
断言相关API(Assertion API)
断言API用来核实UI元素的状态和行为。
使用匹配器断言(Assertions Using Matchers)
使用assertWithMatcher:通过GREYMatcher匹配器来进行断言。选中的元素能否通过匹配器来断言元素的状态。例如,下面的代码就是判断一个accessibilityID为ClickMe的元素能否可见,如果不可见则测试失败
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]
assertWithMatcher:grey_sufficientlyVisible()];
为了避免测试错误,你可以通过方法assertWithMatcher:error:来提供一个NSError对象,如下面的代码所示,相比直接测试失败,下面的代码会提供一个NSError对象来描述错误细节
NSError *error;
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]
assertWithMatcher:grey_sufficientlyVisible()
error:&error];
进行断言时,你也可以通过assert方法并传入GREYAssertion的实例。官方推荐创建断言时,只要可以的话使用assertWithMatcher:。在进行简单的断言时,匹配器更加轻量和理想。然而,自定义GREYAssertion在复杂的断言逻辑下会是更好的选择。例如操作UI再进行断言。
自定义断言(Custom Assertions)
你可以通过GREYAssertion protocol或者GREYAssertionBlock创建自定义断言。下面的代码就是使用GREYAssertionBlock来创建自定义断言来检查view的alpha值(透明度)是否等于预期值
+ (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;
}];
}
注意:不要假设断言是执行在合法的UI元素上。你需要自己检查来确保UI元素的有效性在执行断言之前。例如,下面的代码就是在执行断言前判断元素是否存在
+ (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;
}];
}
错误处理器(Failure Handlers)
默认情况下,在发生异常时,EarlGrey会使用框架自身的错误处理器。默认的处理器会记录异常,进行屏幕快照,打印其他任何有用信息的路径。你可以选择自定义自己的错误处理器通过EarlGrey setFailureHandler:来安装在API上来取代全局默认的框架错误处理器。通过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
断言宏(Assertions Macros)
EarlGrey提供了自带的宏,在测试用例中用来断言和核实。这些宏和XCTest提供的宏类似,当断言失败时会调用全局的错误处理器。
GREYAssert(expression, reason, …) 当表达式为false时失败
GREYAssertTrue(expression, reason, …) 当表达式为false时失败
GREYAssertFalse(expression, reason, …) 当表达式为true时失败
GREYAssertNotNil(expression, reason, …) 当表达式为nil时失败
GREYAssertNil(expression, reason, …) 当表达式为非nil值时失败
GREYAssertEqual(left, right, reason, …) 当left != right时失败
GREYFail(reason, …) 直接失败提供原因
GREYFailWithDetails(reason, details, …) 直接失败提供原因和细节
布局测试(Layout Testing)
EarlGrey提供了APIs来检验UI元素布局。例如,判断元素x是否在元素y的左边。布局断言是根据NSLayoutConstraint的方式构建的。为了验证布局,你需要首先创建一个constraint来描述布局,然后选中的一个在描述中的元素,最后使用grey_layout()来断言选中的元素是否符合constraint。需要注意的是grey_note()包括很多constraints,都必须要满足。这样可以简单的描述出复杂的布局断言。
例如,下面的代码描述的就是选中的元素在所有其他元素的右边
GREYLayoutConstraint *rightConstraint =
[GREYLayoutConstraint layoutConstraintWithAttribute:kGREYLayoutAttributeLeft
relatedBy:kGREYLayoutRelationGreaterThanOrEqual
toReferenceAttribute:kGREYLayoutAttributeRight
multiplier:1.0
constant:0.0];
你可以通过accessibility ID为RelativeRight来选取一个元素,然后通过grey_layout(@[rightConstraint])来判断选中的元素是否accessibility ID为TheReference元素的右侧
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@”RelativeRight”)]
assertWithMatcher:grey_layout(@[rightConstraint], grey_accessibilityID(@”TheReference”))];
你也可以使用layoutConstraintForDirection:创建一个简单的定向的constraints。下面的代码和上面的效果一样。
GREYLayoutConstraint *rightConstraint =
[GREYLayoutConstraint layoutConstraintForDirection:kGREYLayoutDirectionRight
andMinimumSeparation:0.0];
同步相关API(Synchronization APIs)
此类API用来控制EarlGrey和被测应用的同步。
GREYCondition
如果你的测试用例需要一些指定的处理,你可以使用GREYCondition来等待或者同步先决条件。GREYCondition通过代码块来返回布尔值来显示先决条件是否已经符合。在执行其余测试用例之前框架会等待满足这个测试用例的先决条件发生。下面的代码说明了如何创建和使用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通过追踪主分发队列,主运行队列,网络,绘制和一些其他的信号来自动等待应用进入空闲状态并且只有在应用处于空闲状态时才会执行交互。可是有些场景下尽管应用处于忙碌状态也需要进行一些交互。例如在一个消息应用中,图片正在上传同时页面绘制也在进行但是测试用例可能同时需要输入并发送文字信息。需要处理这种场景时,你可以使用kGREYConfigKeySynchronizationEnabled关闭EarlGrey的同步功能,代码如下
[[GREYConfiguration sharedInstance] setValue:@(NO)
forConfigKey:kGREYConfigKeySynchronizationEnabled];
关闭同步功能之后所有的交互都不会等待应用进入空闲状态下再执行直到此功能重新打开。注意为了提高测试的有效性,同步功能要紧张重新打开。另外也可以不关闭此功能,通过配置同步功能的参数来达到测试用例所需要的场景。例如
kGREYConfigKeyNSTimerMaxTrackableInterval 用来设置非重复定时器之间的最大间隔
kGREYConfigKeyDispatchAfterMaxTrackableDelay 用来设置下一步操作的最大延迟时间
kGREYConfigKeyURLBlacklistRegex 用来设置不需同步的URL黑名单正则式
网络
默认情况下,EarlGrey会同步所有的网络请求,但是你可以通过URL正则式来自定义无需同步的请求。注意尽管只有一个正则式,也可以用来将多个URL排除在外。如果需要过滤不同域名下的网络请求,可以通过逻辑操作符or(|)。例如(http://www.google.com|http://www.youtube.com)会排除http://www.google.com和http://www.youtube.com。设置方式如下
[[GREYConfiguration sharedInstance] setValue:@".*\\.foo\\.com/.*"
forConfigKey:kGREYConfigKeyURLBlacklistRegex];
交互超时
默认情况下,交互的超时时间为30s。在这种情况下,如果被测应用没有进入空闲状态,会抛出超时异常并且测试用例并置为失败。你可以使用GREYConfiguration来修改超时时间的值。例如设置超时时间为60s
[[GREYConfiguration sharedInstance] setValue:@(60.0)
forConfigKey:kGREYConfigKeyInteractionTimeoutDuration];
绘制超时
界面绘制会影响同步,因为它总是无限制的重复执行或者执行很长时间。绘制时间大于测试超时时间会抛出超时异常。为了避免这种情况,EarlGrey限制绘制时间为5s并且关闭了重复绘制(连续绘制只执行一次)。可以让EarlGrey绘制更长的时间,修改允许绘制的最大时间值。确保它不会影响到测试的超时时间。例如设置最大绘制时间为30s
[[GREYConfiguration sharedInstance] setValue:@(30.0)
forConfigKey:kGREYConfigKeyCALayerMaxAnimationDuration];
非主线程调用
由于分发队列的限制和EarlGrey与此队列同步方式,从分发队列调用EarlGrey代码会导致活锁。为了减少这种情况,可以使用基于块的APIs来包装EarlGrey语句让其被安全调用
grey_execute_sync(void (^block)()) 同步
grey_execute_async(void (^block)()) 异步
其他的编程接口(Other Top Level APIs)
除了UI的操作,你可以使用很多种方式通过EarlGrey控制设备和系统。
全局配置(Global Configuration)
GREYConfiguration可以让你配置EarlGrey框架。这提供了配置同步,交互超时,操作限制,日志等的功能。只要配置修改,就会全局生效。例如下面就是打开详细日志的方法
[[GREYConfiguration sharedInstance] setValue:@(YES)
forConfigKey:kGREYConfigKeyVerboseLogging];
控制设备朝向
使用[EarlGrey rotateDeviceToOrientation:errorOrNil:]来翻转设配让设配处于一个特定的位置。下面的代码可以使设配从竖屏模式翻转到横屏模式并且HOME键在右侧
[EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft errorOrNil:nil];
可选择的模式如下
UIDeviceOrientationUnknown
UIDeviceOrientationPortrait
UIDeviceOrientationPortraitUpsideDown
UIDeviceOrientationLandscapeLeft
UIDeviceOrientationLandscapeRight
UIDeviceOrientationFaceUp
UIDeviceOrientationFaceDown