From 8ddf72c5eff7f14105065c2559676b82b7fd5d7b Mon Sep 17 00:00:00 2001 From: Leo Natan Date: Tue, 21 Aug 2018 15:43:31 +0300 Subject: [PATCH 1/4] Fix multiple elements matched when dealing with scrollviews. --- detox/ios/Detox/GREYMatchers+Detox.m | 95 ++++++++++++++++++++-------- detox/test/package.json | 6 +- 2 files changed, 72 insertions(+), 29 deletions(-) diff --git a/detox/ios/Detox/GREYMatchers+Detox.m b/detox/ios/Detox/GREYMatchers+Detox.m index f052d682a1..2e1a7de4d3 100644 --- a/detox/ios/Detox/GREYMatchers+Detox.m +++ b/detox/ios/Detox/GREYMatchers+Detox.m @@ -48,41 +48,84 @@ @implementation GREYMatchers (Detox) } -+ (id)detoxMatcherForScrollChildOfMatcher:(id)matcher +id detox_grey_kindOfClass(Class cls) { - // find scroll views in a more robust way, either the original matcher already points to a UIScrollView - // and if it isn't look for a child under it that is a UIScrollView - return grey_anyOf(grey_allOf(grey_anyOf(grey_kindOfClass([UIScrollView class]), - grey_kindOfClass([UIWebView class]), - nil), - matcher, - nil), - grey_allOf(grey_kindOfClass([UIScrollView class]), - grey_ancestor(matcher), + MatchesBlock matches = ^BOOL(id element) { + if(cls == UIScrollView.class && [element isKindOfClass:UIView.class] && [((UIView*)element).superview isKindOfClass:NSClassFromString(@"RCTScrollView")]) + { + return YES; + } + + return [element isKindOfClass:cls]; + }; + DescribeToBlock describe = ^void(id description) { + [description appendText:[NSString stringWithFormat:@"detox_kindOfClass('%@')", + NSStringFromClass(cls)]]; + }; + return [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matches descriptionBlock:describe]; +} + +id detox_grey_parent(id ancestorMatcher) +{ + MatchesBlock matches = ^BOOL(id element) { + id parent = nil; + + if ([element isKindOfClass:[UIView class]]) { + parent = [element superview]; + } else { + parent = [parent accessibilityContainer]; + } + + if (parent && [ancestorMatcher matches:parent]) { + return YES; + } + + return NO; + }; + DescribeToBlock describe = ^void(id description) { + [description appendText:[NSString stringWithFormat:@"parentThatMatches(%@)", + ancestorMatcher]]; + }; + return grey_allOf(grey_anyOf(grey_kindOfClass([UIView class]), + grey_respondsToSelector(@selector(accessibilityContainer)), nil), + [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matches + descriptionBlock:describe], nil); } ++ (id)detoxMatcherForScrollChildOfMatcher:(id)matcher +{ + return grey_anyOf(grey_allOf( + grey_anyOf(grey_kindOfClass([UIScrollView class]), + grey_kindOfClass([UIWebView class]), + grey_kindOfClass([UITextView class]), + nil), + matcher, + nil), + grey_allOf( + grey_kindOfClass([UIScrollView class]), + detox_grey_parent(matcher), + nil), + nil); +} + + (id)detoxMatcherAvoidingProblematicReactNativeElements:(id)matcher { - Class RN_RCTScrollView = NSClassFromString(@"RCTScrollView"); - if (!RN_RCTScrollView) - { - return matcher; - } - - // RCTScrollView is problematic because EarlGrey's visibility matcher adds a subview and this causes a RN assertion - // solution: if we match RCTScrollView, switch over to matching its contained UIScrollView - - return grey_anyOf(grey_allOf(matcher, - grey_not(grey_kindOfClass(RN_RCTScrollView)), + Class RN_RCTScrollView = NSClassFromString(@"RCTScrollView"); + if (!RN_RCTScrollView) + { + return matcher; + } + + return grey_anyOf( + grey_allOf(grey_not(grey_kindOfClass(RN_RCTScrollView)), + matcher, nil), - grey_allOf(grey_kindOfClass([UIScrollView class]), - grey_ancestor(grey_allOf(matcher, - grey_kindOfClass(RN_RCTScrollView), - nil)), + grey_allOf(detox_grey_parent(grey_kindOfClass(RN_RCTScrollView)), + detox_grey_parent(matcher), nil), - nil); + nil); } + (id)detoxMatcherForBoth:(id)firstMatcher and:(id)secondMatcher diff --git a/detox/test/package.json b/detox/test/package.json index 7ff566d304..3d361c7ad5 100644 --- a/detox/test/package.json +++ b/detox/test/package.json @@ -37,18 +37,18 @@ "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/example.app", "build": "set -o pipefail && xcodebuild -project ios/example.xcodeproj -scheme example_ci -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build | xcpretty", "type": "ios.simulator", - "name": "iPhone 8 Plus" + "name": "iPhone X" }, "ios.sim.release": { "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/example.app", "build": "set -o pipefail && export CODE_SIGNING_REQUIRED=NO && export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -project ios/example.xcodeproj -scheme example_ci -configuration Release -sdk iphonesimulator -derivedDataPath ios/build | xcpretty", "type": "ios.simulator", - "name": "iPhone 8 Plus" + "name": "iPhone X" }, "ios.none": { "binaryPath": "ios", "type": "ios.none", - "name": "iPhone 8 Plus", + "name": "iPhone X", "session": { "server": "ws://localhost:8099", "sessionId": "test" From c26c6b674cf89ab3c143341475a34e675827bcdb Mon Sep 17 00:00:00 2001 From: Leo Natan Date: Tue, 21 Aug 2018 15:45:17 +0300 Subject: [PATCH 2/4] Remove unneeded matcher creator --- detox/ios/Detox/GREYMatchers+Detox.m | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/detox/ios/Detox/GREYMatchers+Detox.m b/detox/ios/Detox/GREYMatchers+Detox.m index 2e1a7de4d3..e82ee9c152 100644 --- a/detox/ios/Detox/GREYMatchers+Detox.m +++ b/detox/ios/Detox/GREYMatchers+Detox.m @@ -48,23 +48,6 @@ @implementation GREYMatchers (Detox) } -id detox_grey_kindOfClass(Class cls) -{ - MatchesBlock matches = ^BOOL(id element) { - if(cls == UIScrollView.class && [element isKindOfClass:UIView.class] && [((UIView*)element).superview isKindOfClass:NSClassFromString(@"RCTScrollView")]) - { - return YES; - } - - return [element isKindOfClass:cls]; - }; - DescribeToBlock describe = ^void(id description) { - [description appendText:[NSString stringWithFormat:@"detox_kindOfClass('%@')", - NSStringFromClass(cls)]]; - }; - return [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matches descriptionBlock:describe]; -} - id detox_grey_parent(id ancestorMatcher) { MatchesBlock matches = ^BOOL(id element) { From 8f177e8c1405022c880be97ac6cd08851ddb7eba Mon Sep 17 00:00:00 2001 From: Leo Natan Date: Tue, 21 Aug 2018 15:46:32 +0300 Subject: [PATCH 3/4] Fix oops + style --- detox/ios/Detox/GREYMatchers+Detox.m | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/detox/ios/Detox/GREYMatchers+Detox.m b/detox/ios/Detox/GREYMatchers+Detox.m index e82ee9c152..9d3c642190 100644 --- a/detox/ios/Detox/GREYMatchers+Detox.m +++ b/detox/ios/Detox/GREYMatchers+Detox.m @@ -50,30 +50,33 @@ @implementation GREYMatchers (Detox) id detox_grey_parent(id ancestorMatcher) { - MatchesBlock matches = ^BOOL(id element) { + MatchesBlock matches = ^BOOL(id element) + { id parent = nil; - if ([element isKindOfClass:[UIView class]]) { + if ([element isKindOfClass:[UIView class]]) + { parent = [element superview]; } else { - parent = [parent accessibilityContainer]; + parent = [element accessibilityContainer]; } - if (parent && [ancestorMatcher matches:parent]) { + if (parent && [ancestorMatcher matches:parent]) + { return YES; } return NO; }; - DescribeToBlock describe = ^void(id description) { + DescribeToBlock describe = ^void(id description) + { [description appendText:[NSString stringWithFormat:@"parentThatMatches(%@)", ancestorMatcher]]; }; return grey_allOf(grey_anyOf(grey_kindOfClass([UIView class]), grey_respondsToSelector(@selector(accessibilityContainer)), nil), - [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matches - descriptionBlock:describe], + [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matches descriptionBlock:describe], nil); } From 0500930608a46fee93bf9beb5df6996f7cab9b21 Mon Sep 17 00:00:00 2001 From: Leo Natan Date: Tue, 21 Aug 2018 21:32:34 +0300 Subject: [PATCH 4/4] Further refine matchers to prevent false positives in the native world. --- detox/ios/Detox/GREYMatchers+Detox.m | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/detox/ios/Detox/GREYMatchers+Detox.m b/detox/ios/Detox/GREYMatchers+Detox.m index 9d3c642190..796f326063 100644 --- a/detox/ios/Detox/GREYMatchers+Detox.m +++ b/detox/ios/Detox/GREYMatchers+Detox.m @@ -82,14 +82,21 @@ @implementation GREYMatchers (Detox) + (id)detoxMatcherForScrollChildOfMatcher:(id)matcher { + //No RN—Life is always good. + Class RN_RCTScrollView = NSClassFromString(@"RCTScrollView"); + if (!RN_RCTScrollView) + { + return matcher; + } + + //Either take scroll views or web views that match the provided matcher, or take views whose parent is a RCTScrollView subclass and the parent matches the provided matcher. return grey_anyOf(grey_allOf( grey_anyOf(grey_kindOfClass([UIScrollView class]), grey_kindOfClass([UIWebView class]), - grey_kindOfClass([UITextView class]), nil), matcher, nil), - grey_allOf( + grey_allOf(detox_grey_parent(grey_kindOfClass(RN_RCTScrollView)), grey_kindOfClass([UIScrollView class]), detox_grey_parent(matcher), nil), @@ -98,12 +105,14 @@ @implementation GREYMatchers (Detox) + (id)detoxMatcherAvoidingProblematicReactNativeElements:(id)matcher { + //No RN—Life is always good. Class RN_RCTScrollView = NSClassFromString(@"RCTScrollView"); if (!RN_RCTScrollView) { return matcher; } + //Either take items which are not RCTScrollView subclasses and match the provided matcher, or take items whose parent is RCTScrollView and the parent matches the provided matcher. return grey_anyOf( grey_allOf(grey_not(grey_kindOfClass(RN_RCTScrollView)), matcher,