EarlGrey-API的翻译

493 阅读13分钟

API

EarlGrey包含三组主要的API:

  • 交互API
  • 同步API
  • 其他API

交互API

EarlGrey的测试用例由一系列的UI元素交互组成。 每个交互包括:

  • 选择一个元素进行交互
  • 对其执行操作,与(&&) /或(||)
  • 做出断言来验证元素的状态和行为

为了体现上述行为,交互API由以下内容组成:

  • 选中元素相关API
  • 操作相关API
  • 断言相关API

这些API中的每一个接口都考虑到了可扩展性,让用户可以进行灵活的定制,同时保留了测试代码的可读性。阅读下面的代码片段:

[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")];

它显示了如何通过Accessibility Identifier为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_sufficientlyVisible()];

最后,你可以按顺序执行操作和断言。 下面的代码展示了查找Accessibility Identity为ClickMe的元素,点击它并判断其是否显示。

[[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]
    performAction:grey_tap()]
    assertWithMatcher:grey_notVisible()];

以下是一个简单的交互模板:

[[[EarlGrey selectElementWithMatcher:<element_matcher>]
    performAction:<action>]
    assertWithMatcher:<assertion_matcher>];

其中<element_matcher><assertion_matcher>是包含在GREYMatchers.h中的匹配器,<action>为GREYAction.h中的操作方法。因为每个匹配器或操作都有一个简写的方法,所以可以使用grey_sufficientlyVisible(),来替代[GREYMatchers matcherForSufficientlyVisible]。用简写方法的方式默认是允许的。 要禁用它,请将#define GREY_DISABLE_SHORTHAND 1添加到项目的头文件。

选中元素API

使用Selection API来定位屏幕中的UI元素。当API接收到Matcher后会在所有UI分层中定位对应的元素来发生交互。在EarlGrey中,matchers支持一种类似于OCHamcrest matchers的API。你可以使用与或逻辑组合matchers,允许你创建匹配规则,以确定UI分层中的任何元素。

EarlGrey Matchers

所有EarlGrey Matchers都可以在GREYMatchers工厂类中找到。 查找UI元素的最佳方法是使用它的accessibility properties。我们强烈建议使用accessibility identifier,因为它是一个元素的唯一标识。 使用grey_accessibilityID()作为matcher,通过其accessibility traits来定位UI元素。或者使用grey_accessibilityLabel()作为 accessibility labels的匹配器。

Matcher可能会匹配多个元素:例如,grey_sufficientlyVisible()将匹配所有可见的UI元素。 在这种情况下,你必须缩小选择范围,直到它可以唯一标识一个UI元素。 你可以通过将matchers与grey_allOf()grey_anyOf()grey_not()结合使用或通过向root matchers提供inRoot方法来缩小选择范围,从而使matchers更加准确。

来看下面的例子。

第一个,使用组合的matchers,以下这段代码将查找accessibility label为Send的元素并且显示在当前屏幕。

id<GREYMatcher> visibleSendButtonMatcher =
    grey_allOf(grey_accessibilityLabel(@"Send"), grey_sufficientlyVisible(), nil);

[[EarlGrey selectElementWithMatcher:visibleSendButtonMatcher]
    performAction:grey_tap()];

请注意,使用grey_allOf命令很重要。 如果先使用grey_sufficientlyVisible,则将检查整个应用程序中的每个元素的可见性。 从大多数选项中(比如accessibility label和accessibility id)中选择matchers至关重要。

接下来,使用inRoot,以下这段代码将查找accessibility label为Send且被包含在SendMessageView类实例化对象中的UI元素。

[[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"Send")]
    inRoot:grey_kindOfClass([SendMessageView class])]
    performAction:grey_tap()];

注意:为了与Swift兼容,我们分别使用grey_allOfMatchers()grey_anyOfMatchers()来代替grey_allOf()grey_anyOf()

自定义 Matchers

要创建自定义matchers,请使用基于block的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类型。当matcher用于选取元素时,需要使用id类型。以下matcher示例通用于所有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];
}

这个matcher可以在测试中用于选取没有任何子元素(或子视图)的元素并且双击它(假设该方法是在CustomMatchers类中声明的):

[[EarlGrey selectElementWithMatcher:[CustomMatchers matcherForElementWithoutChildren]]
    performAction:grey_doubleTap()];
选取未显示的UI元素

在某些情况下,UI元素可能被隐藏或在屏幕外,需要某些交互才能将其展示在屏幕上。 常见示例包括滚动视图中元素未被滚动至可见,放大地图至街景模式可见的UI元素,浏览自定义布局的UICollectionView等。

例如,以下代码通过aButtonMarcher匹配一个元素,此元素在aScrollViewMatcher匹配的滚动视图中,经过重复向下滚动(每次减少50points)找到该元素并点击

[[[EarlGrey selectElementWithMatcher:aButtonMatcher]
    usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 50)
 onElementWithMatcher:aScrollViewMatcher]
    performAction:grey_tap()];

以下代码用来定位一个UITableView视图中的指定Cell,aTableViewMatcher匹配了该表格视图,通过重复向下滚动(每次减少50points)找到aCellMatcher定位的指定Cell。

[[[EarlGrey selectElementWithMatcher:aCellMatcher]
    usingSearchAction:grey_scrollInDirection(kGREYDirectionUp, 50)
 onElementWithMatcher:aTableViewMatcher]
    performAction:grey_tap()];

操作API

使用Action API来描述在选定UI元素上的测试操作。

EarlGrey Actions

所有EarlGrey actions都在GREYActions工厂类中。 最常见的操作是使用grey_tap()方法点击指定元素:

[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"TapMe")]
    performAction:grey_tap()];

如果某个操作触发UI层次结构中的更改,则EarlGrey会将每个操作(包括链式操作)与UI同步,以确保在执行下一个操作之前它处于稳定状态。

并非所有操作都可以在所有元素上执行,例如,无法对不可见的元素执行点按操作。为了确保操作可执行,每次执行之前要确保GREYMatcher定位到该元素。未满足这些条件会导致抛出异常并将测试标记为失败。 为了避免测试失败,可以调用performAction:error:获取包含错误详情的NSError对象。

NSError *error;
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"Non-Existent-Ax-Id")]
    performAction:grey_tap()
            error:&error];

在上面的例子中,在不存在的元素上执行EarlGrey交互时不会抛出异常。 相反,传递的错误对象将保存故障详细信息,并且不会立即使测试失败。 然后可以查看错误详细信息以查找故障详细信息。

自定义操作

自定义操作可以通过遵循GREYAction协议的方式来创建。为了方便起见,你可以使用已遵循GREYAction协议的GREYActionBlock,并允许你以block的形式自定义操作。 以下代码用block自定义一个操作(animateWindow)用来设置选中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;
  }];
}

断言API

使用断言API来验证UI元素的状态和行为。

使用Matchers断言

使用assertWithMatcher:GREYMatcher执行断言。 所选元素将通过matcher判断其状态。例如,以下代码判断accessibility ID为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:创建断言。Matchers是轻量级的,并且适用于简单的断言。 然而,对于需要执行复杂逻辑的断言(如操作UI)再继续断言,自定义GREYAssertion可能是更好的选择。

自定义断言

你可以使用GREYAssertion协议或GREYAssertionBlock来创建自定义断言。以下代码使用GREYAssertionBlock编写断言来检查视图的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元素以确保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;
  }];
}

或者,你可以创建一个和GREYAssertion类相符的类。

错误处理器

默认情况下,在发生异常时,EarlGrey会使用错误处理器。 默认处理器会记录异常,截取屏幕截图,然后打印其路径以及其他有用的信息。你可以选择自定义错误处理器并替换掉全局默认的错误处理器,并使用EarlGrey setFailureHandler:安装在API上。 要创建自定义错误处理器,请编写一个遵循GREYFailureHandler协议的类:

@interface@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提供了自带的宏,可以在测试用例中用于断言和验证。 这些宏与XCTest提供的宏相似,档断言失败时调用全局的错误处理器。

  • GREYAssert(expression, reason, ...) — 如果表达式输出结果为false,则失败
  • GREYAssertTrue(expression, reason, ...) — 如果表达式输出结果为false,则失败。用于BOOL表达式
  • GREYAssertFalse(expression, reason, ...) — 如果表达式输出结果为true,则失败。用于BOOL表达式
  • GREYAssertNotNil(expression, reason, ...) — 如果表达式输出结果为nil,则失败。
  • GREYAssertNil(expression, reason, ...) — 如果表达式输出结果非nil,则失败。
  • GREYAssertEqual(left, right, reason, ...) — 如果 left != right时失败
  • GREYAssertNotEqual(left, right, reason, ...) — 如果 left == right时失败
  • GREYAssertEqualObjects(left, right, reason, ...) — 如果 [left isEqual:right]返回false时失败
  • GREYAssertNotEqualObjects(left, right, reason, ...) — 如果 [left isEqual:right]返回true时失败
  • GREYFail(reason, ...) — 失败后提供失败的原因
  • GREYFailWithDetails(reason, details, ...) — 失败后提供失败的原因和细节
布局测试

EarlGrey提供API来验证UI元素的布局,例如验证元素X位于元素Y的左侧。布局断言是根据NSLayoutConstraint构建的。要验证布局,需要先创建一个约束来描述布局,选择一个约束它的元素,然后使用grey_layout(…)声明它与约束匹配。 请注意,grey_layout(…)接受的所有约束都必须满足。 这可以简单地描述复杂的布局断言。

例如,以下代码就是指定元素位于所有其他元素右边。

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:创建简单的定向约束。 以下代码效果等同于前面的示例:

GREYLayoutConstraint *rightConstraint =
    [GREYLayoutConstraint layoutConstraintForDirection:kGREYLayoutDirectionRight
                                  andMinimumSeparation:0.0];

同步API

这些API可让你控制EarlGrey与待测应用程序同步。

GREYCondition

如果你的测试用例需要特殊处理,你可以使用GREYCondition等待或同步某些条件。 GREYCondition采用返回BOOLblock来表示是否满足条件。 在继续测试剩余部分之前,框架会轮询该block直到满足条件。 以下代码说明如何创建和使用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通过跟踪主要调度队列,主操作队列,网络,UI绘制和其他几个信号自动等待应用程序闲置,并仅在应用程序处于空闲状态时才执行交互。 但是,尽管有时应用程序处于忙碌状态,但仍可能会出现有些交互必须执行的情况。 例如,在消息类的应用程序中,照片可能正在上传并且相应UI绘制正在运行,此时仍然需要测试输入并发送文字信息。在这种情况下,可以使用kGREYConfigKeySynchronizationEnabled禁用EarlGrey的同步,如以下代码所示:

[[GREYConfiguration sharedInstance] setValue:@(NO)
                                forConfigKey:kGREYConfigKeySynchronizationEnabled];

一旦禁用,所有的交互都将继续进行,无需等待应用程序闲置,直到重新启用同步。 请注意,为了最大限度地提高测试效率,必须尽快重新启用同步。 而不是完全禁用同步,你可以配置同步参数以适应你的应用程序的需求。 例如:

  • kGREYConfigKeyNSTimerMaxTrackableInterval 用来设定非重复NSTimers的最大间隔。
  • kGREYConfigKeyDispatchAfterMaxTrackableDelay 通过 dispatch_after调用设定下一步操作的最大延迟时间。
  • kGREYConfigKeyURLBlacklistRegex 用来设定不需要同步的URL黑名单的正则表达式

GREYConfiguration中有几种类似的配置。 详细请参阅下面的一些特定用例。

网络

默认情况下,EarlGrey会与所有网络请求同步,但你可以通过正则表达式来自定义无需同步的请求。如果要将一个URL列入黑名单中,请创建一个与其匹配的正则表达式,将其添加到NSArray并将其传递给GREYConfiguration。 对于多个网址,请通过为每个网址创建一个正则表达式来重复上述的过程。例如,要告诉框架不要同步www.google.com和www.youtube.com,请执行以下操作:

NSArray *blacklist = @[ @".*www\\.google\\.com", @".*www\\.youtube\\.com" ];
[[GREYConfiguration sharedInstance] setValue:blacklist
                                forConfigKey:kGREYConfigKeyURLBlacklistRegex];

交互超时

无限重复的页面绘制或长时间运行的动画会影响同步。 绘制时间超过测试时间最大值的动画会导致超时异常。 为了避免这种情况,EarlGrey将绘制持续时间限制为5秒,并禁用重复绘制(连续绘制只运行一次)。要操作EarlGrey允许绘制运行更长时间,请更改允许的最大绘制持续时间值。 确保这不会导致你的测试超时。 以下代码将最大绘制持续时间增加到30秒。

[[GREYConfiguration sharedInstance] setValue:@(30.0)
                                forConfigKey:kGREYConfigKeyCALayerMaxAnimationDuration];

非主线程调用

由于分发队列的限制和EarlGrey与它们同步的方式,从dispatch_queue调用EarlGrey语句会导致活锁。 为了减少这种情况,我们引入了新的基于block的API来封装EarlGrey语句,并且可以从非主线程安全地调用它们:

  • grey_execute_sync(void (^block)()) — 同步。 阻塞直到执行完成。
  • grey_execute_async(void (^block)()) — 异步。

其他API

UI交互之外,你可以操作EarlGrey以多种方式控制设备和系统。

全局配置

GREYConfiguration类允许你配置框架。 它提供了配置同步,交互超时,操作约束checks,日志记录等方法。只要配置发生更改,它就会全局应用。 例如,以下代码将启用详细日志:

[[GREYConfiguration sharedInstance] setValue:@(YES)
                                forConfigKey:kGREYConfigKeyVerboseLogging];

控制设备的朝向

要旋转设备,请使用[EarlGrey rotateDeviceToOrientation:errorOrNil:]以指定设备朝向。例如,以下代码会操作你的系统(和你的应用)从竖屏变为横屏并且Home键位于右侧:

[EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft errorOrNil:nil];

你可以从以下模式中进行选择(有关更多信息,请参阅UIDeviceOrientation):

  • UIDeviceOrientationUnknown
  • UIDeviceOrientationPortrait
  • UIDeviceOrientationPortraitUpsideDown
  • UIDeviceOrientationLandscapeLeft
  • UIDeviceOrientationLandscapeRight
  • UIDeviceOrientationFaceUp
  • UIDeviceOrientationFaceDown

摇一摇操作

你可以使用[EarlGrey shakeDeviceWithError:]模拟模拟器中的摇一摇操作。