2

I was writing pretty complicated UI tests using XCTest, and recently switched to EarlGrey, because it's so much faster and way more reliable - Tests aren't randomly failing on a build server, and the test suite could take up to a half hour to run!

One thing that I haven't been able to do in EarlGrey, that I could do in XCTest, was randomly select an element.

For example, on a calendar collectionView, I could query for all collectionViewCells with 'identifier' using an NSPredicate, and then randomly select a day using [XCUIElementQuery count] to get an index, and then tap.

For now, I'm going to hard code it, but I would love to randomize date selection so that I don't have to rewrite tests if we change app code.

Please let me know if I can elaborate, looking forward to solving this!

ArielSD
  • 829
  • 10
  • 27
  • Have you taken a look at the `atIndex` API? You should be able to do something like: [[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"Collectio‌​nViewCell")] atIndex:0] assert:foo()]; – gran_profaci Jan 26 '17 at 00:21
  • Unfortunately, EarlGrey doesn't provide you how many elements were matched, so you're going to have to use some other way to figure that out. – gran_profaci Jan 26 '17 at 00:22

1 Answers1

4

Step 1 write a matcher that can count elements matching a given matcher using GREYElementMatcherBlock:

- (NSUInteger)elementCountMatchingMatcher:(id<GREYMatcher>)matcher {
  __block NSUInteger count = 0;
  GREYElementMatcherBlock *countMatcher = [GREYElementMatcherBlock matcherWithMatchesBlock:^BOOL(id element) {
    if ([matcher matches:element]) {
      count += 1;
    }
    return NO; // return NO so EarlGrey continues to search.
  } descriptionBlock:^(id<GREYDescription> description) {
    // Pass
  }];
  NSError *unused;
  [[EarlGrey selectElementWithMatcher:countMatcher] assertWithMatcher:grey_notNil() error:&unused];
  return count;
}

Step 2 Use % to select a random index

NSUInteger randomIndex = arc4random() % count;

Step 3 Finally use atIndex: to select that random element and perform action/assertion on it.

// Count all UIView's
NSUInteger count = [self elementCountMatchingMatcher:grey_kindOfClass([UIView class])];
// Find a random index.
NSUInteger randIndex = arc4random() % count;
// Tap the random UIView
[[[EarlGrey selectElementWithMatcher:grey_kindOfClass([UIView class])]
    atIndex:randIndex]
    performAction:grey_tap()];
Gautam
  • 286
  • 1
  • 3
  • This is great, thanks so much. A few questions, just because I really want to understand this: 1. Why do we `return NO;` in the block? If we returned `YES`, that block would stop iterating over elements? 2. Do these lines below assert that every element that increased `count` exists? `NSError *unused; [[EarlGrey selectElementWithMatcher:countMatcher] assertWithMatcher:grey_notNil() error:&unused]; ` Again, this is great, thanks so much. – ArielSD Jan 26 '17 at 16:30
  • Also, this works brilliantly! Last question: How come it wouldn't run without creating `NSError *unused`, even though we never take an action on the error? – ArielSD Jan 26 '17 at 17:08
  • 1
    You are spot on with 1: `YES` would stop iteration. About 2: those lines are what actually trigger the search with `countMatcher` that we created above it and we pass an error object to prevent EarlGrey from raising exceptions even if the EarlGrey statement failed, it can happen if count is 0 (as we are only concerned with getting element count here). And your welcome! – Gautam Jan 27 '17 at 17:25