Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Support] add predicate_string #3

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions DeviceAgent.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
/* 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 */; };
3BE0245123F2E66D0052DD40 /* QuerySpecifierByPredicate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BE0245023F2E66D0052DD40 /* QuerySpecifierByPredicate.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 */; };
Expand Down Expand Up @@ -750,6 +751,8 @@

/* Begin PBXFileReference section */
0AA3924D23279A68000E799B /* SpringBoardAlertsCurrentLanguageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = SpringBoardAlertsCurrentLanguageTests.m; sourceTree = "<group>"; };
3BE0244F23F2E5810052DD40 /* QuerySpecifierByPredicate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QuerySpecifierByPredicate.h; sourceTree = "<group>"; };
3BE0245023F2E66D0052DD40 /* QuerySpecifierByPredicate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QuerySpecifierByPredicate.m; sourceTree = "<group>"; };
4107F8FC231D7262003961AF /* Resources.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Resources.xcassets; sourceTree = "<group>"; };
4166C9AE22E7009800C8BEBF /* XCWebViews.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCWebViews.m; sourceTree = "<group>"; };
634244EC948D56732C2565E5 /* SpringBoardAlerts.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = SpringBoardAlerts.m; sourceTree = "<group>"; tabWidth = 4; usesTabs = 0; };
Expand Down Expand Up @@ -2144,6 +2147,8 @@
899696FE1CB5C93400BB42E2 /* QuerySpecifierByCoordinate.m */,
89B9519F1CF5B297007FD0AB /* QuerySpecifierByType.h */,
89B951A01CF5B297007FD0AB /* QuerySpecifierByType.m */,
3BE0244F23F2E5810052DD40 /* QuerySpecifierByPredicate.h */,
3BE0245023F2E66D0052DD40 /* QuerySpecifierByPredicate.m */,
F536799F1D7C324E009956D0 /* QuerySpecifierByMark.h */,
F53679A01D7C324E009956D0 /* QuerySpecifierByMark.m */,
);
Expand Down Expand Up @@ -3309,6 +3314,7 @@
899696D41CB44D9B00BB42E2 /* GestureConfiguration.m in Sources */,
899696EB1CB5857C00BB42E2 /* JSONKeyValidator.m in Sources */,
F55F81981C6DD07500A945C8 /* MultipartMessageHeader.m in Sources */,
3BE0245123F2E66D0052DD40 /* QuerySpecifierByPredicate.m in Sources */,
F5538B8B1E28BEB9003EC5F3 /* CBXLogging.m in Sources */,
899697061CB5D5CF00BB42E2 /* Gesture+Options.m in Sources */,
89C5F35F1C9C51680093A018 /* Query.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

#import <Foundation/Foundation.h>
#import "QuerySpecifier.h"

/**
Specify elements by matchingPredicate (see XCUIElementQuery.h for all predicate methods).

The predicate will be evaluated against objects of type id<XCUIElementAttributes>.

This specifier matches any elements belonging to the provided predicate.

## Usage:

{ "predicate_string" : "label MATCHES '(Safari|News)' AND elementType == 'StaticText'" }
*/
@interface QuerySpecifierByPredicate : QuerySpecifier<QuerySpecifier>
@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@

#import "QuerySpecifierByPredicate.h"
#import "JSONUtils.h"
#import "CBXException.h"
#import "CBX-XCTest-Umbrella.h"

@implementation QuerySpecifierByPredicate

+ (NSString *)name { return @"predicate_string"; }

/* The method uses matchingPredicate to find UI elements by any type of predicate. Please
* The general form looks like:
* 1. Using XCUIElementAttributes: identifier, title, label, value, elementType, enabled, selected, hasFocus, placeholderValue
* Please, see XCUIElementAttributes protocol to check all the properties.
* 2. Using AND/OR logic to construct compound predicates directly: "label MATCHES '(Safari|News)' AND elementType == 'StaticText'".
* 3. Using any regular expressions: MATCHES, CONTAINS, ... .
*
* The full information about predicates and how to construct it can be found in official Apple source as:
* 1. 'Predicate Programming Guide': https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html#//apple_ref/doc/uid/TP40001789
* 2. NSRegularExpression documentation: https://developer.apple.com/documentation/foundation/nsregularexpression?language=objc
*
* In this method, we replace only incoming element type on XCUIElementType int value.
* All the rest things in predicate should be constructed as normal predicate regarding guidelines from the document above.
*/
- (XCUIElementQuery *)applyInternal:(XCUIElementQuery *)query {
NSString *actualPredicateString = self.value;
NSString *resultPredicateString = nil;

NSString *pattern = @"(.*)elementType(.*)";
NSRange range = [actualPredicateString rangeOfString:pattern options:NSRegularExpressionSearch];

if (range.location != NSNotFound) {
NSLog(@"Predicate '%@' contains elementType. Proceeed with replacement of XCUIElementType String value on int value.", actualPredicateString);

NSString *matchPattern = @"elementType == '(\\w+)'";
NSRange matchRange = [actualPredicateString rangeOfString:matchPattern options:NSRegularExpressionSearch];

if (matchRange.location != NSNotFound) {
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:matchPattern options:0 error:nil];
NSString *actualMatch = [actualPredicateString substringWithRange:matchRange];
NSString *replacementString = [self replacementStringForElementType:actualMatch];

resultPredicateString = [regex stringByReplacingMatchesInString:actualPredicateString options:0 range:NSMakeRange(0, [actualPredicateString length]) withTemplate:replacementString];
NSLog(@"Replace original predicate '%@' with the replacement: %@", actualPredicateString, resultPredicateString);
} else {
@throw [CBXException withFormat:@"Can not parse element type. Actual string: '%@'. Expected type predicate form as: elementType == 'Button'", self.value];
}
} else {
resultPredicateString = actualPredicateString;
NSLog(@"Predicate does not contain elementType attribute. Proceeed with the original value: %@", resultPredicateString);
}

NSPredicate *predicate = [NSPredicate predicateWithFormat:resultPredicateString];

return [query matchingPredicate:predicate];
}

/*
* Replace any occurences of XCUIElementType from string value with single quotes into int value.
* The method return the original string with this replacement
*
* For example:
* - incoming method argument as simple predicate of String type: original = "elementType == 'StaticText'"
* - returns replacement string as predicate as well: replacementString = "elementType == 48"
*/
- (NSString *)replacementStringForElementType:(NSString *)original {
// Find any text within single quotes
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"'(.*)'" options:0 error:NULL];
NSTextCheckingResult *match = [regex firstMatchInString:original options:0 range:NSMakeRange(0, [original length])];
NSString *shortTypeName = [original substringWithRange:[match rangeAtIndex:1]];
NSLog(@"Found element type '%@' in predicate string: %@", shortTypeName, original);

// Replace String type occurence on int
NSUInteger type = [JSONUtils elementTypeForString:shortTypeName];
NSLog(@"Replace element type '%@' into int: %i", shortTypeName, (unsigned int)type);

// Provide new simple predicate with int element type
NSString *replacementString = [NSString stringWithFormat:@"elementType == %i", (unsigned int)type];

return replacementString;
}

@end