Skip to content

Commit 0391236

Browse files
Merge remote-tracking branch 'upstream/develop' into add_setting_server_port
2 parents 07c0142 + 0a8e030 commit 0391236

File tree

7 files changed

+254
-0
lines changed

7 files changed

+254
-0
lines changed

DeviceAgent.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@
4747
/* Begin PBXBuildFile section */
4848
0AA3924F23279A68000E799B /* SpringBoardAlertsCurrentLanguageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0AA3924D23279A68000E799B /* SpringBoardAlertsCurrentLanguageTests.m */; };
4949
1A020FC02338BEB600D79E57 /* XCTest+CBXAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = F547174D204939DA0024AA0B /* XCTest+CBXAdditions.h */; };
50+
3B5444E52498241500532AE0 /* QuerySpecifierByDescendantType.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B81F693240E937F00825603 /* QuerySpecifierByDescendantType.h */; };
51+
3B5444EA249838AC00532AE0 /* QuerySpecifierTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B5444E02498216000532AE0 /* QuerySpecifierTests.m */; };
52+
3B81F695240E952F00825603 /* QuerySpecifierByDescendantType.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B81F694240E952F00825603 /* QuerySpecifierByDescendantType.m */; };
53+
3B81F696240E952F00825603 /* QuerySpecifierByDescendantType.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B81F694240E952F00825603 /* QuerySpecifierByDescendantType.m */; };
54+
3BE3ED1E2498CF0700830B19 /* XCTest+CBXAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = F547174F20496A200024AA0B /* XCTest+CBXAdditions.m */; };
5055
4107F8FE231D7298003961AF /* Resources.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4107F8FC231D7262003961AF /* Resources.xcassets */; };
5156
41188FEA22E9958D0012886A /* XCWebViews.m in Sources */ = {isa = PBXBuildFile; fileRef = 4166C9AE22E7009800C8BEBF /* XCWebViews.m */; };
5257
419BE54B231E46D800DF0ABD /* Resources.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4107F8FC231D7262003961AF /* Resources.xcassets */; };
@@ -753,6 +758,9 @@
753758

754759
/* Begin PBXFileReference section */
755760
0AA3924D23279A68000E799B /* SpringBoardAlertsCurrentLanguageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = SpringBoardAlertsCurrentLanguageTests.m; sourceTree = "<group>"; };
761+
3B5444E02498216000532AE0 /* QuerySpecifierTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QuerySpecifierTests.m; sourceTree = "<group>"; };
762+
3B81F693240E937F00825603 /* QuerySpecifierByDescendantType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QuerySpecifierByDescendantType.h; sourceTree = "<group>"; };
763+
3B81F694240E952F00825603 /* QuerySpecifierByDescendantType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QuerySpecifierByDescendantType.m; sourceTree = "<group>"; };
756764
4107F8FC231D7262003961AF /* Resources.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Resources.xcassets; sourceTree = "<group>"; };
757765
4166C9AE22E7009800C8BEBF /* XCWebViews.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCWebViews.m; sourceTree = "<group>"; };
758766
634244EC948D56732C2565E5 /* SpringBoardAlerts.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = SpringBoardAlerts.m; sourceTree = "<group>"; tabWidth = 4; usesTabs = 0; };
@@ -2153,6 +2161,8 @@
21532161
89D5380F1CA34CBE00F62E09 /* QuerySpecifierByTextLike.m */,
21542162
899696FD1CB5C93400BB42E2 /* QuerySpecifierByCoordinate.h */,
21552163
899696FE1CB5C93400BB42E2 /* QuerySpecifierByCoordinate.m */,
2164+
3B81F693240E937F00825603 /* QuerySpecifierByDescendantType.h */,
2165+
3B81F694240E952F00825603 /* QuerySpecifierByDescendantType.m */,
21562166
89B9519F1CF5B297007FD0AB /* QuerySpecifierByType.h */,
21572167
89B951A01CF5B297007FD0AB /* QuerySpecifierByType.m */,
21582168
F536799F1D7C324E009956D0 /* QuerySpecifierByMark.h */,
@@ -2267,6 +2277,7 @@
22672277
F58D27E81D4F947F000FF6C0 /* Queries */ = {
22682278
isa = PBXGroup;
22692279
children = (
2280+
3B5444E02498216000532AE0 /* QuerySpecifierTests.m */,
22702281
F58D27EA1D4F947F000FF6C0 /* CoordinateQueryTests.m */,
22712282
F58D27EB1D4F947F000FF6C0 /* Factory */,
22722283
F58D27ED1D4F947F000FF6C0 /* QuerySelectors */,
@@ -2501,6 +2512,7 @@
25012512
isa = PBXHeadersBuildPhase;
25022513
buildActionMask = 2147483647;
25032514
files = (
2515+
3B5444E52498241500532AE0 /* QuerySpecifierByDescendantType.h in Headers */,
25042516
F54C02B02043F0EF00FD8DDE /* XCTElementFilteringTransformer.h in Headers */,
25052517
F54C027E2043F0EF00FD8DDE /* XCTKVOExpectation.h in Headers */,
25062518
F54C02502043F0EF00FD8DDE /* XCUIApplicationProcess.h in Headers */,
@@ -3356,6 +3368,7 @@
33563368
F55F81A41C6DD07500A945C8 /* HTTPFileResponse.m in Sources */,
33573369
896586831CEB9B9800E8329C /* QueryFactory.m in Sources */,
33583370
F55F81941C6DD07500A945C8 /* HTTPServer.m in Sources */,
3371+
3B81F695240E952F00825603 /* QuerySpecifierByDescendantType.m in Sources */,
33593372
89D538161CA351F400F62E09 /* QuerySpecifierByIndex.m in Sources */,
33603373
89B951A21CF5B297007FD0AB /* QuerySpecifierByType.m in Sources */,
33613374
F5870B401CF0BEFF00B3376C /* CBXTouchEvent.m in Sources */,
@@ -3434,6 +3447,7 @@
34343447
buildActionMask = 2147483647;
34353448
files = (
34363449
F5061A8A2153B19900B85792 /* QueryConfigurationFactory.m in Sources */,
3450+
3BE3ED1E2498CF0700830B19 /* XCTest+CBXAdditions.m in Sources */,
34373451
F5061A742153B19900B85792 /* Testmanagerd.m in Sources */,
34383452
F5061A512153B0FF00B85792 /* SpringBoardAlertsTest.m in Sources */,
34393453
F5061A9C2153B4EF00B85792 /* HTTPAuthenticationRequest.m in Sources */,
@@ -3461,6 +3475,7 @@
34613475
F5061AAC2153B4EF00B85792 /* RouteRequest.m in Sources */,
34623476
F5061A682153B15E00B85792 /* ElementNotFoundException.m in Sources */,
34633477
F5061A4C2153B0FF00B85792 /* CBXDecimalRounderTest.m in Sources */,
3478+
3B81F696240E952F00825603 /* QuerySpecifierByDescendantType.m in Sources */,
34643479
F5061A642153B15E00B85792 /* SpringBoard.m in Sources */,
34653480
F5061A4E2153B0FF00B85792 /* CBXDeviceTest.m in Sources */,
34663481
F5061A522153B0FF00B85792 /* CBXOrientationTest.m in Sources */,
@@ -3503,6 +3518,7 @@
35033518
F5061A812153B19900B85792 /* QuerySpecifierByPropertyLike.m in Sources */,
35043519
F5061A592153B14400B85792 /* ThreadUtils.m in Sources */,
35053520
F5061A422153B0FF00B85792 /* QueryConfigurationFactoryTests.m in Sources */,
3521+
3B5444EA249838AC00532AE0 /* QuerySpecifierTests.m in Sources */,
35063522
F5061A502153B0FF00B85792 /* SpringBoardAlertTest.m in Sources */,
35073523
F5061A582153B14400B85792 /* GeometryUtils.m in Sources */,
35083524
F5061A782153B19900B85792 /* (null) in Sources */,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT license.
4+
5+
#import <Foundation/Foundation.h>
6+
#import "QuerySpecifier.h"
7+
8+
/**
9+
This specifier finds all descendants elements matching the descendant_type within the element matching the parent_type.
10+
11+
## Usage:
12+
13+
{ "descendant_element" : { "parent_type": "String", "descendant_type": "String" } }
14+
*/
15+
@interface QuerySpecifierByDescendantType : QuerySpecifier<QuerySpecifier>
16+
@end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#import "QuerySpecifierByDescendantType.h"
2+
#import "JSONUtils.h"
3+
4+
@implementation QuerySpecifierByDescendantType
5+
+ (NSString *)name { return @"descendant_element"; }
6+
7+
- (XCUIElementQuery *)applyInternal:(XCUIElementQuery *)query {
8+
NSString *error_message = [NSString stringWithFormat:@"%@%@%@%@",
9+
@"Malformed descendant_element value. ",
10+
@"Expected dictionary like: ",
11+
@"{ 'descendant_type' = Button; 'parent_type' = Keyboard; }",
12+
[NSString stringWithFormat:@"but actual is '%@'", self.value]];
13+
NSAssert([self.value isKindOfClass:[NSDictionary class]], error_message);
14+
15+
NSDictionary *parameters = self.value;
16+
17+
NSAssert(parameters.count == 2,
18+
@"Dictionary should have only 2 keys '%@' and '%@', but actual is '%@'",
19+
CBX_PARENT_TYPE_KEY, CBX_DESCENDANT_TYPE_KEY, parameters);
20+
NSAssert(parameters[CBX_PARENT_TYPE_KEY] != nil,
21+
@"Value for key '%@' should not be nil. Actual dictionary: %@",
22+
CBX_PARENT_TYPE_KEY, parameters);
23+
NSAssert(parameters[CBX_DESCENDANT_TYPE_KEY] != nil,
24+
@"Value for key '%@' should not be nil. Actual dictionary: %@",
25+
CBX_DESCENDANT_TYPE_KEY, parameters);
26+
27+
XCUIElementType parentType = [JSONUtils elementTypeForString:parameters[CBX_PARENT_TYPE_KEY]];
28+
XCUIElementQuery *contextQuery = [query matchingType:parentType identifier:nil];
29+
30+
XCUIElementType descendantType = [JSONUtils elementTypeForString:parameters[CBX_DESCENDANT_TYPE_KEY]];
31+
XCUIElementQuery *resultQuery = [contextQuery descendantsMatchingType:descendantType];
32+
33+
return resultQuery;
34+
}
35+
@end

Server/CBXConstants.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ static NSString *const CBX_HAS_KEYBOARD_FOCUS_KEY = @"has_keyboard_focus";
6161
static NSString *const CBX_HITABLE_KEY = @"hitable";
6262
static NSString *const CBX_HIT_POINT_KEY = @"hit_point";
6363
static NSString *const CBX_INDEX_KEY = @"index";
64+
static NSString *const CBX_PARENT_TYPE_KEY = @"parent_type";
65+
static NSString *const CBX_DESCENDANT_TYPE_KEY = @"descendant_type";
6466
static NSString *const CBX_PROPERTY_KEY = @"property";
6567
static NSString *const CBX_PROPERTY_LIKE_KEY = @"property_like";
6668
static NSString *const CBX_TEST_ID = @"test_id";
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#import <XCTest/XCTest.h>
2+
#import "QueryConfiguration.h"
3+
#import "QueryFactory.h"
4+
#import "QuerySpecifier.h"
5+
#import "Application.h"
6+
#import "CBXConstants.h"
7+
#import "XCTest+CBXAdditions.h"
8+
#import "CBXServerUnitTestUmbrellaHeader.h"
9+
10+
@interface QuerySpecifierTests : XCTestCase
11+
@end
12+
13+
@implementation QuerySpecifierTests
14+
15+
- (void)setUp {
16+
[super setUp];
17+
}
18+
19+
- (void)tearDown {
20+
[super tearDown];
21+
}
22+
23+
- (void)testQuerySpecifierByDescendantTypeThrowsExceptionForInvalidMainKeyCase {
24+
id invalidJson = @{@"descendantElement": @{@"parent_type" : @"Keyboard",
25+
@"descendant_type" : @"Button"}};
26+
27+
expect(^{
28+
[QueryConfiguration withJSON:invalidJson validator:nil];
29+
}).to.raise(@"InvalidArgumentException");
30+
}
31+
32+
- (void)testQuerySpecifierByDescendantTypeThrowsExceptionForInvalidParentKeyCase {
33+
// [Set Up] Stage 0: prepare query with invalid config
34+
id invalidJson = @{@"descendant_element": @{@"parent_typeeeee" : @"Keyboard",
35+
@"descendant_type" : @"Button"}};
36+
QueryConfiguration *queryConfig = [QueryConfiguration withJSON:invalidJson validator:nil];
37+
38+
Query *query = [QueryFactory queryWithQueryConfiguration:queryConfig];
39+
40+
// [Preconditions] Stage 1: prepare mock objects for app and query
41+
id appMock = OCMClassMock([Application class]);
42+
id uiAppMock = OCMClassMock([XCUIApplication class]);
43+
44+
id queryMock = OCMClassMock([XCUIElementQuery class]);
45+
46+
// [Preconditions] Stage 1: mock methods called in [query execute]
47+
OCMStub([uiAppMock cbxQueryForDescendantsOfAnyType]).andReturn(queryMock);
48+
OCMStub([appMock currentApplication]).andReturn(uiAppMock);
49+
50+
// [Check] Stage 2: throw Exception in case parent type key is malformed
51+
expect(^{
52+
[query execute];
53+
}).to.raise(@"NSInternalInconsistencyException");
54+
}
55+
56+
- (void)testQuerySpecifierByDescendantTypeThrowsExceptionForInvalidDescendantKeyCase {
57+
// [Set Up] Stage 0: prepare query with invalid config
58+
id invalidJson = @{@"descendant_element": @{@"parent_type" : @"Keyboard",
59+
@"descendantType" : @"Button"}};
60+
QueryConfiguration *queryConfig = [QueryConfiguration withJSON:invalidJson validator:nil];
61+
62+
Query *query = [QueryFactory queryWithQueryConfiguration:queryConfig];
63+
64+
// [Preconditions] Stage 1: prepare mock objects for app and query
65+
id appMock = OCMClassMock([Application class]);
66+
id uiAppMock = OCMClassMock([XCUIApplication class]);
67+
68+
id queryMock = OCMClassMock([XCUIElementQuery class]);
69+
70+
// [Preconditions] Stage 1: mock methods called in [query execute]
71+
OCMStub([uiAppMock cbxQueryForDescendantsOfAnyType]).andReturn(queryMock);
72+
OCMStub([appMock currentApplication]).andReturn(uiAppMock);
73+
74+
// [Check] Stage 2: throw Exception in case descendant type key is malformed
75+
expect(^{
76+
[query execute];
77+
}).to.raise(@"NSInternalInconsistencyException");
78+
}
79+
80+
- (void)testQuerySpecifierByDescendantTypeThrowsExceptionForInvalidMissingKeyCase {
81+
// [Set Up] Stage 0: prepare query with invalid config
82+
id invalidJson = @{@"descendant_element": @{@"parent_type" : @"Keyboard" }};
83+
QueryConfiguration *queryConfig = [QueryConfiguration withJSON:invalidJson validator:nil];
84+
85+
Query *query = [QueryFactory queryWithQueryConfiguration:queryConfig];
86+
87+
// [Preconditions] Stage 1: prepare mock objects for app and query
88+
id appMock = OCMClassMock([Application class]);
89+
id uiAppMock = OCMClassMock([XCUIApplication class]);
90+
91+
id queryMock = OCMClassMock([XCUIElementQuery class]);
92+
93+
// [Preconditions] Stage 1: mock methods called in [query execute]
94+
OCMStub([uiAppMock cbxQueryForDescendantsOfAnyType]).andReturn(queryMock);
95+
OCMStub([appMock currentApplication]).andReturn(uiAppMock);
96+
97+
// [Check] Stage 2: throw Exception in case missed descendant_type key
98+
expect(^{
99+
[query execute];
100+
}).to.raise(@"NSInternalInconsistencyException");
101+
}
102+
103+
- (void)testQuerySpecifierByDescendantTypeThrowsExceptionForInvalidParentTypeValueCase {
104+
// [Set Up] Stage 0: prepare query with invalid config
105+
id invalidJson = @{@"descendant_element": @{@"parent_type" : @"UIKeyboard",
106+
@"descendant_type" : @"Button"}};
107+
QueryConfiguration *queryConfig = [QueryConfiguration withJSON:invalidJson validator:nil];
108+
109+
Query *query = [QueryFactory queryWithQueryConfiguration:queryConfig];
110+
111+
// [Preconditions] Stage 1: prepare mock objects for app and query
112+
id appMock = OCMClassMock([Application class]);
113+
id uiAppMock = OCMClassMock([XCUIApplication class]);
114+
115+
id queryMock = OCMClassMock([XCUIElementQuery class]);
116+
117+
// [Preconditions] Stage 1: mock methods called in [query execute]
118+
OCMStub([uiAppMock cbxQueryForDescendantsOfAnyType]).andReturn(queryMock);
119+
OCMStub([appMock currentApplication]).andReturn(uiAppMock);
120+
121+
// [Check] Stage 2: throw Exception in case UIElement type value is invalid
122+
expect(^{
123+
[query execute];
124+
}).to.raise(@"CBXException");
125+
}
126+
127+
- (void)testQuerySpecifierByDescendantTypeReturnsArrayOfElementsForValidCase {
128+
// [Set Up] Stage 0: prepare query with valid config
129+
id validJson = @{@"descendant_element": @{@"parent_type" : @"Keyboard",
130+
@"descendant_type" : @"Button"}};
131+
QueryConfiguration *queryConfig = [QueryConfiguration withJSON:validJson validator:nil];
132+
133+
Query *query = [QueryFactory queryWithQueryConfiguration:queryConfig];
134+
135+
// [Preconditions] Stage 1: prepare mock objects for app and query
136+
// (we want mock interactions with real app object and
137+
// check specific implementation of applyInternal for QuerySpecifierByDescendantType)
138+
id appMock = OCMClassMock([Application class]);
139+
id uiAppMock = OCMClassMock([XCUIApplication class]);
140+
id element = OCMClassMock([XCUIElement class]);
141+
id arrayOfElements = [NSArray arrayWithObject:element];
142+
143+
id queryMock = OCMClassMock([XCUIElementQuery class]);
144+
id contextQueryMock = OCMClassMock([XCUIElementQuery class]);
145+
id resultQueryMock = OCMClassMock([XCUIElementQuery class]);
146+
147+
// [Preconditions] Stage 1: mock methods called in [query execute]
148+
OCMStub([uiAppMock cbxQueryForDescendantsOfAnyType]).andReturn(queryMock);
149+
OCMStub([appMock currentApplication]).andReturn(uiAppMock);
150+
151+
// [Expectations] Stage 2: set up expectations for [specifier applyInternal:query]
152+
// where specifier is QuerySpecifierByDescendantType
153+
XCUIElementType keyboard = XCUIElementTypeKeyboard;
154+
XCUIElementType button = XCUIElementTypeButton;
155+
OCMExpect([queryMock matchingType:keyboard identifier:nil]).andReturn(contextQueryMock);
156+
OCMExpect([contextQueryMock descendantsMatchingType:button]).andReturn(resultQueryMock);
157+
// [Expectations] Stage 2: set up expectations for [query execute]
158+
OCMExpect([resultQueryMock allElementsBoundByIndex]).andReturn(arrayOfElements);
159+
160+
// [Test Step] Stage 3: perform test step
161+
[query execute];
162+
163+
// [Check] Stage 4: verify [specifier applyInternal:query]
164+
OCMVerifyAll(queryMock);
165+
OCMVerifyAll(contextQueryMock);
166+
// [Check] Stage 4: verify [query execute]
167+
OCMVerifyAll(resultQueryMock);
168+
}
169+
170+
@end

cucumber/features/query.feature

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,12 @@ Then I query the text field using "marked" with string "Schreib!" and see value
7878
Then I query the text field using "marked" with string "Hello!" and see value "Hello!"
7979
Then I query the text field using "text" with string "Schreib!" and see value "Hello!"
8080
Then I query the text field using "text" with string "Hello!" and see value "Hello!"
81+
82+
@query
83+
Scenario: Descendant query returns array of elements
84+
Given I am looking at the Text Input with placeholder
85+
And I touch the text field
86+
When the keyboard is visible
87+
Then I query the keyboard using "descendant_element" and see keyboard buttons
88+
| parent_type | descendant_type |
89+
| Keyboard | Button |

cucumber/features/steps/query.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,3 +316,9 @@ def newlines_in_queries_supported?
316316

317317
expect(actual["value"]).to be == value
318318
end
319+
320+
Then(/^I query the keyboard using "descendant_element" and see keyboard buttons$/) do |table|
321+
locator = {descendant_element: table.hashes.first}
322+
actual_keyboard_buttons = wait_for_view(locator)
323+
expect(actual_keyboard_buttons.count).to be > 1
324+
end

0 commit comments

Comments
 (0)