Skip to content

Commit a975b89

Browse files
feat: Add XPath extensions (#1144)
1 parent 1807fa8 commit a975b89

9 files changed

Lines changed: 744 additions & 10 deletions

File tree

WebDriverAgent.xcodeproj/project.pbxproj

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
641EE5D92240C5CA00173FCB /* XCUIElement+FBPickerWheel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7136A4781E8918E60024FC3D /* XCUIElement+FBPickerWheel.m */; };
5353
641EE5DA2240C5CA00173FCB /* XCUIApplicationProcessDelay.m in Sources */ = {isa = PBXBuildFile; fileRef = 6385F4A5220A40760095BBDB /* XCUIApplicationProcessDelay.m */; };
5454
641EE5DB2240C5CA00173FCB /* FBXPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 711084431DA3AA7500F913D6 /* FBXPath.m */; };
55+
71B2E0042733FB970074B004 /* FBXPathExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 71B2E0022733FB970074B002 /* FBXPathExtensions.m */; };
5556
641EE5DC2240C5CA00173FCB /* XCUIApplication+FBAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = 719CD8FB2126C88B00C7D0C2 /* XCUIApplication+FBAlert.m */; };
5657
641EE5DE2240C5CA00173FCB /* XCUIApplication+FBTouchAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BD20721F86116100B36EC2 /* XCUIApplication+FBTouchAction.m */; };
5758
641EE5DF2240C5CA00173FCB /* FBWebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB78D1CAEDF0C008C271F /* FBWebServer.m */; };
@@ -316,6 +317,7 @@
316317
64E3502F2AC0B6FE005F3ACB /* NSDictionary+FBUtf8SafeDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 716F0D9F2A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.h */; };
317318
711084441DA3AA7500F913D6 /* FBXPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 711084421DA3AA7500F913D6 /* FBXPath.h */; settings = {ATTRIBUTES = (Public, ); }; };
318319
711084451DA3AA7500F913D6 /* FBXPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 711084431DA3AA7500F913D6 /* FBXPath.m */; };
320+
71B2E0062733FB970074B006 /* FBXPathExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 71B2E0022733FB970074B002 /* FBXPathExtensions.m */; };
319321
7119097C2152580600BA3C7E /* XCUIScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = 7119097B2152580600BA3C7E /* XCUIScreen.h */; settings = {ATTRIBUTES = (Public, ); }; };
320322
7119E1EC1E891F8600D0B125 /* FBPickerWheelSelectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7119E1EB1E891F8600D0B125 /* FBPickerWheelSelectTests.m */; };
321323
711CD03425ED1106001C01D2 /* XCUIScreenDataSource-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 711CD03325ED1106001C01D2 /* XCUIScreenDataSource-Protocol.h */; };
@@ -976,6 +978,8 @@
976978
64B26509228CE4FF002A5025 /* FBTVNavigationTracker-Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FBTVNavigationTracker-Private.h"; sourceTree = "<group>"; };
977979
711084421DA3AA7500F913D6 /* FBXPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBXPath.h; sourceTree = "<group>"; };
978980
711084431DA3AA7500F913D6 /* FBXPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBXPath.m; sourceTree = "<group>"; };
981+
71B2E0012733FB970074B001 /* FBXPathExtensions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBXPathExtensions.h; sourceTree = "<group>"; };
982+
71B2E0022733FB970074B002 /* FBXPathExtensions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBXPathExtensions.m; sourceTree = "<group>"; };
979983
7119097B2152580600BA3C7E /* XCUIScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCUIScreen.h; sourceTree = "<group>"; };
980984
7119E1EB1E891F8600D0B125 /* FBPickerWheelSelectTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBPickerWheelSelectTests.m; sourceTree = "<group>"; };
981985
711CD03325ED1106001C01D2 /* XCUIScreenDataSource-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIScreenDataSource-Protocol.h"; sourceTree = "<group>"; };
@@ -2000,9 +2004,11 @@
20002004
714D88CA2733FB970074A925 /* FBXMLGenerationOptions.h */,
20012005
714D88CB2733FB970074A925 /* FBXMLGenerationOptions.m */,
20022006
712A0C861DA3E55D007D02E5 /* FBXPath-Private.h */,
2003-
711084421DA3AA7500F913D6 /* FBXPath.h */,
2004-
711084431DA3AA7500F913D6 /* FBXPath.m */,
2005-
EE6B64FB1D0F86EF00E85F5D /* XCTestPrivateSymbols.h */,
2007+
711084421DA3AA7500F913D6 /* FBXPath.h */,
2008+
711084431DA3AA7500F913D6 /* FBXPath.m */,
2009+
71B2E0012733FB970074B001 /* FBXPathExtensions.h */,
2010+
71B2E0022733FB970074B002 /* FBXPathExtensions.m */,
2011+
EE6B64FB1D0F86EF00E85F5D /* XCTestPrivateSymbols.h */,
20062012
EE6B64FC1D0F86EF00E85F5D /* XCTestPrivateSymbols.m */,
20072013
633E904A220DEE7F007CADF9 /* XCUIApplicationProcessDelay.h */,
20082014
6385F4A5220A40760095BBDB /* XCUIApplicationProcessDelay.m */,
@@ -3156,6 +3162,7 @@
31563162
641EE5D92240C5CA00173FCB /* XCUIElement+FBPickerWheel.m in Sources */,
31573163
641EE5DA2240C5CA00173FCB /* XCUIApplicationProcessDelay.m in Sources */,
31583164
641EE5DB2240C5CA00173FCB /* FBXPath.m in Sources */,
3165+
71B2E0042733FB970074B004 /* FBXPathExtensions.m in Sources */,
31593166
71C8E55425399A6B008572C1 /* XCUIApplication+FBQuiescence.m in Sources */,
31603167
641EE5DC2240C5CA00173FCB /* XCUIApplication+FBAlert.m in Sources */,
31613168
641EE70F2240CE4800173FCB /* FBTVNavigationTracker.m in Sources */,
@@ -3277,6 +3284,7 @@
32773284
6385F4A7220A40760095BBDB /* XCUIApplicationProcessDelay.m in Sources */,
32783285
71A5C67529A4F39600421C37 /* XCTIssue+FBPatcher.m in Sources */,
32793286
711084451DA3AA7500F913D6 /* FBXPath.m in Sources */,
3287+
71B2E0062733FB970074B006 /* FBXPathExtensions.m in Sources */,
32803288
719CD8FD2126C88B00C7D0C2 /* XCUIApplication+FBAlert.m in Sources */,
32813289
13DE7A45287C2A8D003243C6 /* FBXCAccessibilityElement.m in Sources */,
32823290
641EE70E2240CE4800173FCB /* FBTVNavigationTracker.m in Sources */,

WebDriverAgentLib/Utilities/FBXPath-Private.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ NS_ASSUME_NONNULL_BEGIN
5151
document:(xmlDocPtr)doc
5252
contextNode:(nullable xmlNodePtr)contextNode;
5353

54+
+ (xmlXPathObjectPtr)evaluate:(NSString *)xpathQuery
55+
document:(xmlDocPtr)doc
56+
contextNode:(nullable xmlNodePtr)contextNode
57+
errorMessage:(NSString * _Nullable * _Nullable)errorMessage;
58+
5459
@end
5560

5661
NS_ASSUME_NONNULL_END

WebDriverAgentLib/Utilities/FBXPath.m

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#import "FBLogger.h"
1515
#import "FBMacros.h"
1616
#import "FBXMLGenerationOptions.h"
17+
#import "FBXPathExtensions.h"
1718
#import "FBXCElementSnapshotWrapper+Helpers.h"
1819
#import "NSString+FBXMLSafeString.h"
1920
#import "XCUIApplication.h"
@@ -152,7 +153,17 @@ @implementation FBXPath
152153

153154
+ (id)throwException:(NSString *)name forQuery:(NSString *)xpathQuery
154155
{
155-
NSString *reason = [NSString stringWithFormat:@"Cannot evaluate results for XPath expression \"%@\"", xpathQuery];
156+
return [self throwException:name forQuery:xpathQuery detail:nil];
157+
}
158+
159+
+ (id)throwException:(NSString *)name forQuery:(NSString *)xpathQuery detail:(nullable NSString *)detail
160+
{
161+
NSString *reason;
162+
if (nil != detail) {
163+
reason = [NSString stringWithFormat:@"Cannot evaluate results for XPath expression \"%@\": %@", xpathQuery, detail];
164+
} else {
165+
reason = [NSString stringWithFormat:@"Cannot evaluate results for XPath expression \"%@\"", xpathQuery];
166+
}
156167
@throw [NSException exceptionWithName:name reason:reason userInfo:@{}];
157168
return nil;
158169
}
@@ -284,16 +295,18 @@ + (nullable NSString *)xmlStringWithRootElement:(id<FBElement>)root
284295
contextNode = nodeSet->nodeTab[0];
285296
}
286297
}
298+
NSString *evaluationError = nil;
287299
xmlXPathObjectPtr queryResult = [self evaluate:xpathQuery
288300
document:doc
289-
contextNode:contextNode];
301+
contextNode:contextNode
302+
errorMessage:&evaluationError];
290303
if (NULL != contextNodeQueryResult) {
291304
xmlXPathFreeObject(contextNodeQueryResult);
292305
}
293306
if (NULL == queryResult) {
294307
xmlFreeTextWriter(writer);
295308
xmlFreeDoc(doc);
296-
return [self throwException:FBInvalidXPathException forQuery:xpathQuery];
309+
return [self throwException:FBInvalidXPathException forQuery:xpathQuery detail:evaluationError];
297310
}
298311

299312
NSArray *matchingSnapshots = [self collectMatchingSnapshots:queryResult->nodesetval
@@ -435,6 +448,14 @@ + (int)xmlRepresentationWithRootElement:(id<FBXCElementSnapshot>)root
435448
+ (xmlXPathObjectPtr)evaluate:(NSString *)xpathQuery
436449
document:(xmlDocPtr)doc
437450
contextNode:(nullable xmlNodePtr)contextNode
451+
{
452+
return [self evaluate:xpathQuery document:doc contextNode:contextNode errorMessage:nil];
453+
}
454+
455+
+ (xmlXPathObjectPtr)evaluate:(NSString *)xpathQuery
456+
document:(xmlDocPtr)doc
457+
contextNode:(nullable xmlNodePtr)contextNode
458+
errorMessage:(NSString * _Nullable * _Nullable)errorMessage
438459
{
439460
xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
440461
if (NULL == xpathCtx) {
@@ -443,10 +464,21 @@ + (xmlXPathObjectPtr)evaluate:(NSString *)xpathQuery
443464
}
444465
xpathCtx->node = NULL == contextNode ? doc->children : contextNode;
445466

467+
FBXPathExtensions *extensions = [FBXPathExtensions new];
468+
[extensions registerFunctionsWithContext:xpathCtx];
469+
446470
xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression((const xmlChar *)[xpathQuery UTF8String], xpathCtx);
447471
if (NULL == xpathObj) {
472+
NSString *detail = extensions.lastEvaluationError;
473+
if (NULL != errorMessage) {
474+
*errorMessage = detail;
475+
}
476+
if (nil != detail) {
477+
[FBLogger logFmt:@"Failed to evaluate XPath query \"%@\": %@", xpathQuery, detail];
478+
} else {
479+
[FBLogger logFmt:@"Failed to invoke libxml2>xmlXPathEvalExpression for XPath query \"%@\"", xpathQuery];
480+
}
448481
xmlXPathFreeContext(xpathCtx);
449-
[FBLogger logFmt:@"Failed to invoke libxml2>xmlXPathEvalExpression for XPath query \"%@\"", xpathQuery];
450482
return NULL;
451483
}
452484
xmlXPathFreeContext(xpathCtx);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#import <Foundation/Foundation.h>
10+
11+
#ifdef __clang__
12+
#pragma clang diagnostic push
13+
#pragma clang diagnostic ignored "-Wpadded"
14+
#endif
15+
16+
#import <libxml/xpath.h>
17+
18+
#ifdef __clang__
19+
#pragma clang diagnostic pop
20+
#endif
21+
22+
NS_ASSUME_NONNULL_BEGIN
23+
24+
@interface FBXPathExtensions : NSObject
25+
26+
/**
27+
Registers XPath 2-compatible extension functions on the given libxml2 context.
28+
*/
29+
- (void)registerFunctionsWithContext:(xmlXPathContextPtr)xpathCtx;
30+
31+
/**
32+
Human-readable message for the most recent XPath extension evaluation failure on this instance,
33+
for example an invalid regular expression pattern or flags. Nil when no extension error has occurred.
34+
Scoped to the libxml2 context this instance is registered with; each evaluation should use its own instance.
35+
*/
36+
@property (nonatomic, nullable, readonly, copy) NSString *lastEvaluationError;
37+
38+
@end
39+
40+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)