From 28c8b6a0a2c04f4b8dd08a699c840d8dda0bb3be Mon Sep 17 00:00:00 2001 From: Viktar Karanevich Date: Tue, 3 Mar 2020 18:29:20 +0000 Subject: [PATCH 1/3] [Support] add descendant matching query specifier --- DeviceAgent.xcodeproj/project.pbxproj | 8 ++++++ .../QuerySpecifierByDescendantType.h | 16 ++++++++++++ .../QuerySpecifierByDescendantType.m | 25 +++++++++++++++++++ Server/CBXConstants.h | 2 ++ 4 files changed, 51 insertions(+) create mode 100644 Server/AutomationActions/Query/Specifiers/QuerySpecifierByDescendantType.h create mode 100644 Server/AutomationActions/Query/Specifiers/QuerySpecifierByDescendantType.m diff --git a/DeviceAgent.xcodeproj/project.pbxproj b/DeviceAgent.xcodeproj/project.pbxproj index d1342a2e..2789fa7d 100644 --- a/DeviceAgent.xcodeproj/project.pbxproj +++ b/DeviceAgent.xcodeproj/project.pbxproj @@ -47,6 +47,8 @@ /* Begin PBXBuildFile section */ 0AA3924F23279A68000E799B /* SpringBoardAlertsCurrentLanguageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0AA3924D23279A68000E799B /* SpringBoardAlertsCurrentLanguageTests.m */; }; 1A020FC02338BEB600D79E57 /* XCTest+CBXAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = F547174D204939DA0024AA0B /* XCTest+CBXAdditions.h */; }; + 3B81F695240E952F00825603 /* QuerySpecifierByDescendantType.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B81F694240E952F00825603 /* QuerySpecifierByDescendantType.m */; }; + 3B81F696240E952F00825603 /* QuerySpecifierByDescendantType.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B81F694240E952F00825603 /* QuerySpecifierByDescendantType.m */; }; 4107F8FE231D7298003961AF /* Resources.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4107F8FC231D7262003961AF /* Resources.xcassets */; }; 41188FEA22E9958D0012886A /* XCWebViews.m in Sources */ = {isa = PBXBuildFile; fileRef = 4166C9AE22E7009800C8BEBF /* XCWebViews.m */; }; 419BE54B231E46D800DF0ABD /* Resources.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4107F8FC231D7262003961AF /* Resources.xcassets */; }; @@ -753,6 +755,8 @@ /* Begin PBXFileReference section */ 0AA3924D23279A68000E799B /* SpringBoardAlertsCurrentLanguageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = SpringBoardAlertsCurrentLanguageTests.m; sourceTree = ""; }; + 3B81F693240E937F00825603 /* QuerySpecifierByDescendantType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QuerySpecifierByDescendantType.h; sourceTree = ""; }; + 3B81F694240E952F00825603 /* QuerySpecifierByDescendantType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QuerySpecifierByDescendantType.m; sourceTree = ""; }; 4107F8FC231D7262003961AF /* Resources.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Resources.xcassets; sourceTree = ""; }; 4166C9AE22E7009800C8BEBF /* XCWebViews.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCWebViews.m; sourceTree = ""; }; 634244EC948D56732C2565E5 /* SpringBoardAlerts.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = SpringBoardAlerts.m; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; @@ -2153,6 +2157,8 @@ 89D5380F1CA34CBE00F62E09 /* QuerySpecifierByTextLike.m */, 899696FD1CB5C93400BB42E2 /* QuerySpecifierByCoordinate.h */, 899696FE1CB5C93400BB42E2 /* QuerySpecifierByCoordinate.m */, + 3B81F693240E937F00825603 /* QuerySpecifierByDescendantType.h */, + 3B81F694240E952F00825603 /* QuerySpecifierByDescendantType.m */, 89B9519F1CF5B297007FD0AB /* QuerySpecifierByType.h */, 89B951A01CF5B297007FD0AB /* QuerySpecifierByType.m */, F536799F1D7C324E009956D0 /* QuerySpecifierByMark.h */, @@ -3356,6 +3362,7 @@ F55F81A41C6DD07500A945C8 /* HTTPFileResponse.m in Sources */, 896586831CEB9B9800E8329C /* QueryFactory.m in Sources */, F55F81941C6DD07500A945C8 /* HTTPServer.m in Sources */, + 3B81F695240E952F00825603 /* QuerySpecifierByDescendantType.m in Sources */, 89D538161CA351F400F62E09 /* QuerySpecifierByIndex.m in Sources */, 89B951A21CF5B297007FD0AB /* QuerySpecifierByType.m in Sources */, F5870B401CF0BEFF00B3376C /* CBXTouchEvent.m in Sources */, @@ -3461,6 +3468,7 @@ F5061AAC2153B4EF00B85792 /* RouteRequest.m in Sources */, F5061A682153B15E00B85792 /* ElementNotFoundException.m in Sources */, F5061A4C2153B0FF00B85792 /* CBXDecimalRounderTest.m in Sources */, + 3B81F696240E952F00825603 /* QuerySpecifierByDescendantType.m in Sources */, F5061A642153B15E00B85792 /* SpringBoard.m in Sources */, F5061A4E2153B0FF00B85792 /* CBXDeviceTest.m in Sources */, F5061A522153B0FF00B85792 /* CBXOrientationTest.m in Sources */, diff --git a/Server/AutomationActions/Query/Specifiers/QuerySpecifierByDescendantType.h b/Server/AutomationActions/Query/Specifiers/QuerySpecifierByDescendantType.h new file mode 100644 index 00000000..d883f16b --- /dev/null +++ b/Server/AutomationActions/Query/Specifiers/QuerySpecifierByDescendantType.h @@ -0,0 +1,16 @@ + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +#import +#import "QuerySpecifier.h" + +/** + This specifier finds all descendants elements matching the descendant_type within the element matching the parent_type. + + ## Usage: + + { "descendant_element" : { "parent_type": "String", "descendant_type": "String" } } + */ +@interface QuerySpecifierByDescendantType : QuerySpecifier +@end diff --git a/Server/AutomationActions/Query/Specifiers/QuerySpecifierByDescendantType.m b/Server/AutomationActions/Query/Specifiers/QuerySpecifierByDescendantType.m new file mode 100644 index 00000000..da192059 --- /dev/null +++ b/Server/AutomationActions/Query/Specifiers/QuerySpecifierByDescendantType.m @@ -0,0 +1,25 @@ +#import "QuerySpecifierByDescendantType.h" +#import "JSONUtils.h" +#import "CBX-XCTest-Umbrella.h" +#import "InvalidArgumentException.h" + +@implementation QuerySpecifierByDescendantType ++ (NSString *)name { return @"descendant_element"; } + +- (XCUIElementQuery *)applyInternal:(XCUIElementQuery *)query { + NSAssert([self.value isKindOfClass:[NSDictionary class]], @"Malformed descendant_element value. Expected dictionary, for example: { 'descendant_type' = Button; 'parent_type' = Keyboard; }, but actual '%@'", self.value); + + NSDictionary *parameters = self.value; + + NSAssert(parameters.count == 2, @"Dictionary should have only 2 keys '%@' and '%@', but actual dictionary is '%@'", CBX_PARENT_TYPE_KEY, CBX_DESCENDANT_TYPE_KEY, parameters); + NSAssert(parameters[CBX_PARENT_TYPE_KEY] != nil, @"Value for key '%@' should not be nil. Actual dictionary: %@", CBX_PARENT_TYPE_KEY, parameters); + NSAssert(parameters[CBX_DESCENDANT_TYPE_KEY] != nil, @"Value for key '%@' should not be nil. Actual dictionary: %@", CBX_DESCENDANT_TYPE_KEY, parameters); + + XCUIElementType parentType = [JSONUtils elementTypeForString:parameters[CBX_PARENT_TYPE_KEY]]; + XCUIElementType descendantType = [JSONUtils elementTypeForString:parameters[CBX_DESCENDANT_TYPE_KEY]]; + + XCUIElementQuery *contextQuery = [query matchingType:parentType identifier:nil]; + + return [contextQuery descendantsMatchingType:descendantType];; +} +@end diff --git a/Server/CBXConstants.h b/Server/CBXConstants.h index 92865702..cdcc8d40 100644 --- a/Server/CBXConstants.h +++ b/Server/CBXConstants.h @@ -61,6 +61,8 @@ static NSString *const CBX_HAS_KEYBOARD_FOCUS_KEY = @"has_keyboard_focus"; static NSString *const CBX_HITABLE_KEY = @"hitable"; static NSString *const CBX_HIT_POINT_KEY = @"hit_point"; static NSString *const CBX_INDEX_KEY = @"index"; +static NSString *const CBX_PARENT_TYPE_KEY = @"parent_type"; +static NSString *const CBX_DESCENDANT_TYPE_KEY = @"descendant_type"; static NSString *const CBX_PROPERTY_KEY = @"property"; static NSString *const CBX_PROPERTY_LIKE_KEY = @"property_like"; static NSString *const CBX_TEST_ID = @"test_id"; From cddfea1fcef86bb20733a36691fb0b9716c9f43d Mon Sep 17 00:00:00 2001 From: Viktar Karanevich Date: Tue, 16 Jun 2020 15:18:07 +0100 Subject: [PATCH 2/3] [Support] add OCMock integration tests for descendant query specifier --- DeviceAgent.xcodeproj/project.pbxproj | 8 + .../QuerySpecifierByDescendantType.m | 30 ++-- .../Queries/QuerySpecifierTests.m | 170 ++++++++++++++++++ 3 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 TestApp/DeviceAgentUnitTests/AutomationActions/Queries/QuerySpecifierTests.m diff --git a/DeviceAgent.xcodeproj/project.pbxproj b/DeviceAgent.xcodeproj/project.pbxproj index 2789fa7d..b8dbdd5e 100644 --- a/DeviceAgent.xcodeproj/project.pbxproj +++ b/DeviceAgent.xcodeproj/project.pbxproj @@ -47,8 +47,11 @@ /* Begin PBXBuildFile section */ 0AA3924F23279A68000E799B /* SpringBoardAlertsCurrentLanguageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0AA3924D23279A68000E799B /* SpringBoardAlertsCurrentLanguageTests.m */; }; 1A020FC02338BEB600D79E57 /* XCTest+CBXAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = F547174D204939DA0024AA0B /* XCTest+CBXAdditions.h */; }; + 3B5444E52498241500532AE0 /* QuerySpecifierByDescendantType.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B81F693240E937F00825603 /* QuerySpecifierByDescendantType.h */; }; + 3B5444EA249838AC00532AE0 /* QuerySpecifierTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B5444E02498216000532AE0 /* QuerySpecifierTests.m */; }; 3B81F695240E952F00825603 /* QuerySpecifierByDescendantType.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B81F694240E952F00825603 /* QuerySpecifierByDescendantType.m */; }; 3B81F696240E952F00825603 /* QuerySpecifierByDescendantType.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B81F694240E952F00825603 /* QuerySpecifierByDescendantType.m */; }; + 3BE3ED1E2498CF0700830B19 /* XCTest+CBXAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = F547174F20496A200024AA0B /* XCTest+CBXAdditions.m */; }; 4107F8FE231D7298003961AF /* Resources.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4107F8FC231D7262003961AF /* Resources.xcassets */; }; 41188FEA22E9958D0012886A /* XCWebViews.m in Sources */ = {isa = PBXBuildFile; fileRef = 4166C9AE22E7009800C8BEBF /* XCWebViews.m */; }; 419BE54B231E46D800DF0ABD /* Resources.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4107F8FC231D7262003961AF /* Resources.xcassets */; }; @@ -755,6 +758,7 @@ /* Begin PBXFileReference section */ 0AA3924D23279A68000E799B /* SpringBoardAlertsCurrentLanguageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = SpringBoardAlertsCurrentLanguageTests.m; sourceTree = ""; }; + 3B5444E02498216000532AE0 /* QuerySpecifierTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QuerySpecifierTests.m; sourceTree = ""; }; 3B81F693240E937F00825603 /* QuerySpecifierByDescendantType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QuerySpecifierByDescendantType.h; sourceTree = ""; }; 3B81F694240E952F00825603 /* QuerySpecifierByDescendantType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QuerySpecifierByDescendantType.m; sourceTree = ""; }; 4107F8FC231D7262003961AF /* Resources.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Resources.xcassets; sourceTree = ""; }; @@ -2273,6 +2277,7 @@ F58D27E81D4F947F000FF6C0 /* Queries */ = { isa = PBXGroup; children = ( + 3B5444E02498216000532AE0 /* QuerySpecifierTests.m */, F58D27EA1D4F947F000FF6C0 /* CoordinateQueryTests.m */, F58D27EB1D4F947F000FF6C0 /* Factory */, F58D27ED1D4F947F000FF6C0 /* QuerySelectors */, @@ -2507,6 +2512,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 3B5444E52498241500532AE0 /* QuerySpecifierByDescendantType.h in Headers */, F54C02B02043F0EF00FD8DDE /* XCTElementFilteringTransformer.h in Headers */, F54C027E2043F0EF00FD8DDE /* XCTKVOExpectation.h in Headers */, F54C02502043F0EF00FD8DDE /* XCUIApplicationProcess.h in Headers */, @@ -3441,6 +3447,7 @@ buildActionMask = 2147483647; files = ( F5061A8A2153B19900B85792 /* QueryConfigurationFactory.m in Sources */, + 3BE3ED1E2498CF0700830B19 /* XCTest+CBXAdditions.m in Sources */, F5061A742153B19900B85792 /* Testmanagerd.m in Sources */, F5061A512153B0FF00B85792 /* SpringBoardAlertsTest.m in Sources */, F5061A9C2153B4EF00B85792 /* HTTPAuthenticationRequest.m in Sources */, @@ -3511,6 +3518,7 @@ F5061A812153B19900B85792 /* QuerySpecifierByPropertyLike.m in Sources */, F5061A592153B14400B85792 /* ThreadUtils.m in Sources */, F5061A422153B0FF00B85792 /* QueryConfigurationFactoryTests.m in Sources */, + 3B5444EA249838AC00532AE0 /* QuerySpecifierTests.m in Sources */, F5061A502153B0FF00B85792 /* SpringBoardAlertTest.m in Sources */, F5061A582153B14400B85792 /* GeometryUtils.m in Sources */, F5061A782153B19900B85792 /* (null) in Sources */, diff --git a/Server/AutomationActions/Query/Specifiers/QuerySpecifierByDescendantType.m b/Server/AutomationActions/Query/Specifiers/QuerySpecifierByDescendantType.m index da192059..b2d35664 100644 --- a/Server/AutomationActions/Query/Specifiers/QuerySpecifierByDescendantType.m +++ b/Server/AutomationActions/Query/Specifiers/QuerySpecifierByDescendantType.m @@ -1,25 +1,35 @@ #import "QuerySpecifierByDescendantType.h" #import "JSONUtils.h" -#import "CBX-XCTest-Umbrella.h" -#import "InvalidArgumentException.h" @implementation QuerySpecifierByDescendantType + (NSString *)name { return @"descendant_element"; } - (XCUIElementQuery *)applyInternal:(XCUIElementQuery *)query { - NSAssert([self.value isKindOfClass:[NSDictionary class]], @"Malformed descendant_element value. Expected dictionary, for example: { 'descendant_type' = Button; 'parent_type' = Keyboard; }, but actual '%@'", self.value); + NSString *error_message = [NSString stringWithFormat:@"%@%@%@%@", + @"Malformed descendant_element value. ", + @"Expected dictionary like: ", + @"{ 'descendant_type' = Button; 'parent_type' = Keyboard; }", + [NSString stringWithFormat:@"but actual is '%@'", self.value]]; + NSAssert([self.value isKindOfClass:[NSDictionary class]], error_message); NSDictionary *parameters = self.value; - - NSAssert(parameters.count == 2, @"Dictionary should have only 2 keys '%@' and '%@', but actual dictionary is '%@'", CBX_PARENT_TYPE_KEY, CBX_DESCENDANT_TYPE_KEY, parameters); - NSAssert(parameters[CBX_PARENT_TYPE_KEY] != nil, @"Value for key '%@' should not be nil. Actual dictionary: %@", CBX_PARENT_TYPE_KEY, parameters); - NSAssert(parameters[CBX_DESCENDANT_TYPE_KEY] != nil, @"Value for key '%@' should not be nil. Actual dictionary: %@", CBX_DESCENDANT_TYPE_KEY, parameters); - XCUIElementType parentType = [JSONUtils elementTypeForString:parameters[CBX_PARENT_TYPE_KEY]]; - XCUIElementType descendantType = [JSONUtils elementTypeForString:parameters[CBX_DESCENDANT_TYPE_KEY]]; + NSAssert(parameters.count == 2, + @"Dictionary should have only 2 keys '%@' and '%@', but actual is '%@'", + CBX_PARENT_TYPE_KEY, CBX_DESCENDANT_TYPE_KEY, parameters); + NSAssert(parameters[CBX_PARENT_TYPE_KEY] != nil, + @"Value for key '%@' should not be nil. Actual dictionary: %@", + CBX_PARENT_TYPE_KEY, parameters); + NSAssert(parameters[CBX_DESCENDANT_TYPE_KEY] != nil, + @"Value for key '%@' should not be nil. Actual dictionary: %@", + CBX_DESCENDANT_TYPE_KEY, parameters); + XCUIElementType parentType = [JSONUtils elementTypeForString:parameters[CBX_PARENT_TYPE_KEY]]; XCUIElementQuery *contextQuery = [query matchingType:parentType identifier:nil]; - return [contextQuery descendantsMatchingType:descendantType];; + XCUIElementType descendantType = [JSONUtils elementTypeForString:parameters[CBX_DESCENDANT_TYPE_KEY]]; + XCUIElementQuery *resultQuery = [contextQuery descendantsMatchingType:descendantType]; + + return resultQuery; } @end diff --git a/TestApp/DeviceAgentUnitTests/AutomationActions/Queries/QuerySpecifierTests.m b/TestApp/DeviceAgentUnitTests/AutomationActions/Queries/QuerySpecifierTests.m new file mode 100644 index 00000000..1378b813 --- /dev/null +++ b/TestApp/DeviceAgentUnitTests/AutomationActions/Queries/QuerySpecifierTests.m @@ -0,0 +1,170 @@ +#import +#import "QueryConfiguration.h" +#import "QueryFactory.h" +#import "QuerySpecifier.h" +#import "Application.h" +#import "CBXConstants.h" +#import "XCTest+CBXAdditions.h" +#import "CBXServerUnitTestUmbrellaHeader.h" + +@interface QuerySpecifierTests : XCTestCase +@end + +@implementation QuerySpecifierTests + +- (void)setUp { + [super setUp]; +} + +- (void)tearDown { + [super tearDown]; +} + +- (void)testQuerySpecifierByDescendantTypeThrowsExceptionForInvalidMainKeyCase { + id invalidJson = @{@"descendantElement": @{@"parent_type" : @"Keyboard", + @"descendant_type" : @"Button"}}; + + expect(^{ + [QueryConfiguration withJSON:invalidJson validator:nil]; + }).to.raise(@"InvalidArgumentException"); +} + +- (void)testQuerySpecifierByDescendantTypeThrowsExceptionForInvalidParentKeyCase { + // [Set Up] Stage 0: prepare query with invalid config + id invalidJson = @{@"descendant_element": @{@"parent_typeeeee" : @"Keyboard", + @"descendant_type" : @"Button"}}; + QueryConfiguration *queryConfig = [QueryConfiguration withJSON:invalidJson validator:nil]; + + Query *query = [QueryFactory queryWithQueryConfiguration:queryConfig]; + + // [Preconditions] Stage 1: prepare mock objects for app and query + id appMock = OCMClassMock([Application class]); + id uiAppMock = OCMClassMock([XCUIApplication class]); + + id queryMock = OCMClassMock([XCUIElementQuery class]); + + // [Preconditions] Stage 1: mock methods called in [query execute] + OCMStub([uiAppMock cbxQueryForDescendantsOfAnyType]).andReturn(queryMock); + OCMStub([appMock currentApplication]).andReturn(uiAppMock); + + // [Check] Stage 2: throw Exception in case parent type key is malformed + expect(^{ + [query execute]; + }).to.raise(@"NSInternalInconsistencyException"); +} + +- (void)testQuerySpecifierByDescendantTypeThrowsExceptionForInvalidDescendantKeyCase { + // [Set Up] Stage 0: prepare query with invalid config + id invalidJson = @{@"descendant_element": @{@"parent_type" : @"Keyboard", + @"descendantType" : @"Button"}}; + QueryConfiguration *queryConfig = [QueryConfiguration withJSON:invalidJson validator:nil]; + + Query *query = [QueryFactory queryWithQueryConfiguration:queryConfig]; + + // [Preconditions] Stage 1: prepare mock objects for app and query + id appMock = OCMClassMock([Application class]); + id uiAppMock = OCMClassMock([XCUIApplication class]); + + id queryMock = OCMClassMock([XCUIElementQuery class]); + + // [Preconditions] Stage 1: mock methods called in [query execute] + OCMStub([uiAppMock cbxQueryForDescendantsOfAnyType]).andReturn(queryMock); + OCMStub([appMock currentApplication]).andReturn(uiAppMock); + + // [Check] Stage 2: throw Exception in case descendant type key is malformed + expect(^{ + [query execute]; + }).to.raise(@"NSInternalInconsistencyException"); +} + +- (void)testQuerySpecifierByDescendantTypeThrowsExceptionForInvalidMissingKeyCase { + // [Set Up] Stage 0: prepare query with invalid config + id invalidJson = @{@"descendant_element": @{@"parent_type" : @"Keyboard" }}; + QueryConfiguration *queryConfig = [QueryConfiguration withJSON:invalidJson validator:nil]; + + Query *query = [QueryFactory queryWithQueryConfiguration:queryConfig]; + + // [Preconditions] Stage 1: prepare mock objects for app and query + id appMock = OCMClassMock([Application class]); + id uiAppMock = OCMClassMock([XCUIApplication class]); + + id queryMock = OCMClassMock([XCUIElementQuery class]); + + // [Preconditions] Stage 1: mock methods called in [query execute] + OCMStub([uiAppMock cbxQueryForDescendantsOfAnyType]).andReturn(queryMock); + OCMStub([appMock currentApplication]).andReturn(uiAppMock); + + // [Check] Stage 2: throw Exception in case missed descendant_type key + expect(^{ + [query execute]; + }).to.raise(@"NSInternalInconsistencyException"); +} + +- (void)testQuerySpecifierByDescendantTypeThrowsExceptionForInvalidParentTypeValueCase { + // [Set Up] Stage 0: prepare query with invalid config + id invalidJson = @{@"descendant_element": @{@"parent_type" : @"UIKeyboard", + @"descendant_type" : @"Button"}}; + QueryConfiguration *queryConfig = [QueryConfiguration withJSON:invalidJson validator:nil]; + + Query *query = [QueryFactory queryWithQueryConfiguration:queryConfig]; + + // [Preconditions] Stage 1: prepare mock objects for app and query + id appMock = OCMClassMock([Application class]); + id uiAppMock = OCMClassMock([XCUIApplication class]); + + id queryMock = OCMClassMock([XCUIElementQuery class]); + + // [Preconditions] Stage 1: mock methods called in [query execute] + OCMStub([uiAppMock cbxQueryForDescendantsOfAnyType]).andReturn(queryMock); + OCMStub([appMock currentApplication]).andReturn(uiAppMock); + + // [Check] Stage 2: throw Exception in case UIElement type value is invalid + expect(^{ + [query execute]; + }).to.raise(@"CBXException"); +} + +- (void)testQuerySpecifierByDescendantTypeReturnsArrayOfElementsForValidCase { + // [Set Up] Stage 0: prepare query with valid config + id validJson = @{@"descendant_element": @{@"parent_type" : @"Keyboard", + @"descendant_type" : @"Button"}}; + QueryConfiguration *queryConfig = [QueryConfiguration withJSON:validJson validator:nil]; + + Query *query = [QueryFactory queryWithQueryConfiguration:queryConfig]; + + // [Preconditions] Stage 1: prepare mock objects for app and query + // (we want mock interactions with real app object and + // check specific implementation of applyInternal for QuerySpecifierByDescendantType) + id appMock = OCMClassMock([Application class]); + id uiAppMock = OCMClassMock([XCUIApplication class]); + id element = OCMClassMock([XCUIElement class]); + id arrayOfElements = [NSArray arrayWithObject:element]; + + id queryMock = OCMClassMock([XCUIElementQuery class]); + id contextQueryMock = OCMClassMock([XCUIElementQuery class]); + id resultQueryMock = OCMClassMock([XCUIElementQuery class]); + + // [Preconditions] Stage 1: mock methods called in [query execute] + OCMStub([uiAppMock cbxQueryForDescendantsOfAnyType]).andReturn(queryMock); + OCMStub([appMock currentApplication]).andReturn(uiAppMock); + + // [Expectations] Stage 2: set up expectations for [specifier applyInternal:query] + // where specifier is QuerySpecifierByDescendantType + XCUIElementType keyboard = XCUIElementTypeKeyboard; + XCUIElementType button = XCUIElementTypeButton; + OCMExpect([queryMock matchingType:keyboard identifier:nil]).andReturn(contextQueryMock); + OCMExpect([contextQueryMock descendantsMatchingType:button]).andReturn(resultQueryMock); + // [Expectations] Stage 2: set up expectations for [query execute] + OCMExpect([resultQueryMock allElementsBoundByIndex]).andReturn(arrayOfElements); + + // [Test Step] Stage 3: perform test step + [query execute]; + + // [Check] Stage 4: verify [specifier applyInternal:query] + OCMVerifyAll(queryMock); + OCMVerifyAll(contextQueryMock); + // [Check] Stage 4: verify [query execute] + OCMVerifyAll(resultQueryMock); +} + +@end From 2f0c2c51884af28bcc73d7ea244f8a9d2d291562 Mon Sep 17 00:00:00 2001 From: Viktar Karanevich Date: Wed, 17 Jun 2020 13:11:53 +0100 Subject: [PATCH 3/3] [Support] add cucumber test for descendant_element query --- cucumber/features/query.feature | 9 +++++++++ cucumber/features/steps/query.rb | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/cucumber/features/query.feature b/cucumber/features/query.feature index de656b77..9fddf261 100644 --- a/cucumber/features/query.feature +++ b/cucumber/features/query.feature @@ -78,3 +78,12 @@ Then I query the text field using "marked" with string "Schreib!" and see value Then I query the text field using "marked" with string "Hello!" and see value "Hello!" Then I query the text field using "text" with string "Schreib!" and see value "Hello!" Then I query the text field using "text" with string "Hello!" and see value "Hello!" + +@query +Scenario: Descendant query returns array of elements +Given I am looking at the Text Input with placeholder +And I touch the text field +When the keyboard is visible +Then I query the keyboard using "descendant_element" and see keyboard buttons + | parent_type | descendant_type | + | Keyboard | Button | diff --git a/cucumber/features/steps/query.rb b/cucumber/features/steps/query.rb index e0af34a7..73d874d7 100644 --- a/cucumber/features/steps/query.rb +++ b/cucumber/features/steps/query.rb @@ -316,3 +316,9 @@ def newlines_in_queries_supported? expect(actual["value"]).to be == value end + +Then(/^I query the keyboard using "descendant_element" and see keyboard buttons$/) do |table| + locator = {descendant_element: table.hashes.first} + actual_keyboard_buttons = wait_for_view(locator) + expect(actual_keyboard_buttons.count).to be > 1 +end