Files
custom_wda/WebDriverAgentLib/Commands/FBFindElementCommands.m

187 lines
8.4 KiB
Mathematica
Raw Permalink Normal View History

2026-02-03 16:52:44 +08:00
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "FBFindElementCommands.h"
#import "FBAlert.h"
#import "FBConfiguration.h"
#import "FBElementCache.h"
#import "FBExceptions.h"
#import "FBMacros.h"
#import "FBRouteRequest.h"
#import "FBSession.h"
#import "XCTestPrivateSymbols.h"
#import "XCUIApplication+FBHelpers.h"
#import "XCUIElement+FBClassChain.h"
#import "XCUIElement+FBFind.h"
#import "XCUIElement+FBIsVisible.h"
#import "XCUIElement+FBUID.h"
#import "XCUIElement+FBUtilities.h"
#import "XCUIElement+FBWebDriverAttributes.h"
static id<FBResponsePayload> FBNoSuchElementErrorResponseForRequest(FBRouteRequest *request)
{
return FBResponseWithStatus([FBCommandStatus noSuchElementErrorWithMessage:[NSString stringWithFormat:@"unable to find an element using '%@', value '%@'", request.arguments[@"using"], request.arguments[@"value"]]
traceback:[NSString stringWithFormat:@"%@", NSThread.callStackSymbols]]);
}
@implementation FBFindElementCommands
#pragma mark - <FBCommandHandler>
+ (NSArray *)routes
{
return
@[
[[FBRoute POST:@"/element"] respondWithTarget:self action:@selector(handleFindElement:)],
[[FBRoute POST:@"/elements"] respondWithTarget:self action:@selector(handleFindElements:)],
[[FBRoute POST:@"/element/:uuid/element"] respondWithTarget:self action:@selector(handleFindSubElement:)],
[[FBRoute POST:@"/element/:uuid/elements"] respondWithTarget:self action:@selector(handleFindSubElements:)],
[[FBRoute GET:@"/wda/element/:uuid/getVisibleCells"] respondWithTarget:self action:@selector(handleFindVisibleCells:)],
#if TARGET_OS_TV
[[FBRoute GET:@"/element/active"] respondWithTarget:self action:@selector(handleGetFocusedElement:)],
#else
[[FBRoute GET:@"/element/active"] respondWithTarget:self action:@selector(handleGetActiveElement:)],
#endif
];
}
#pragma mark - Commands
+ (id<FBResponsePayload>)handleFindElement:(FBRouteRequest *)request
{
FBSession *session = request.session;
XCUIElement *element = [self.class elementUsing:request.arguments[@"using"]
withValue:request.arguments[@"value"]
under:session.activeApplication];
if (!element) {
return FBNoSuchElementErrorResponseForRequest(request);
}
return FBResponseWithCachedElement(element, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
}
+ (id<FBResponsePayload>)handleFindElements:(FBRouteRequest *)request
{
FBSession *session = request.session;
NSArray *elements = [self.class elementsUsing:request.arguments[@"using"]
withValue:request.arguments[@"value"]
under:session.activeApplication
shouldReturnAfterFirstMatch:NO];
return FBResponseWithCachedElements(elements, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
}
+ (id<FBResponsePayload>)handleFindVisibleCells:(FBRouteRequest *)request
{
FBElementCache *elementCache = request.session.elementCache;
XCUIElement *element = [elementCache elementForUUID:(NSString *)request.parameters[@"uuid"]];
id<FBXCElementSnapshot> snapshot = [element fb_customSnapshot];
NSArray<id<FBXCElementSnapshot>> *visibleCellSnapshots = [snapshot descendantsByFilteringWithBlock:^BOOL(id<FBXCElementSnapshot> shot) {
return shot.elementType == XCUIElementTypeCell
&& [FBXCElementSnapshotWrapper ensureWrapped:shot].wdVisible;
}];
NSArray *cells = [element fb_filterDescendantsWithSnapshots:visibleCellSnapshots
onlyChildren:NO];
return FBResponseWithCachedElements(cells, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
}
+ (id<FBResponsePayload>)handleFindSubElement:(FBRouteRequest *)request
{
FBElementCache *elementCache = request.session.elementCache;
XCUIElement *element = [elementCache elementForUUID:(NSString *)request.parameters[@"uuid"]
checkStaleness:NO];
XCUIElement *foundElement = [self.class elementUsing:request.arguments[@"using"]
withValue:request.arguments[@"value"]
under:element];
if (!foundElement) {
return FBNoSuchElementErrorResponseForRequest(request);
}
return FBResponseWithCachedElement(foundElement, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
}
+ (id<FBResponsePayload>)handleFindSubElements:(FBRouteRequest *)request
{
FBElementCache *elementCache = request.session.elementCache;
XCUIElement *element = [elementCache elementForUUID:(NSString *)request.parameters[@"uuid"]
checkStaleness:NO];
NSArray *foundElements = [self.class elementsUsing:request.arguments[@"using"]
withValue:request.arguments[@"value"]
under:element
shouldReturnAfterFirstMatch:NO];
return FBResponseWithCachedElements(foundElements, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
}
+ (id<FBResponsePayload>)handleGetActiveElement:(FBRouteRequest *)request
{
XCUIElement *element = request.session.activeApplication.fb_activeElement;
if (nil == element) {
return FBNoSuchElementErrorResponseForRequest(request);
}
return FBResponseWithCachedElement(element, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
}
#if TARGET_OS_TV
+ (id<FBResponsePayload>)handleGetFocusedElement:(FBRouteRequest *)request
{
XCUIElement *element = request.session.activeApplication.fb_focusedElement;
return element == nil
? FBNoSuchElementErrorResponseForRequest(request)
: FBResponseWithCachedElement(element, request.session.elementCache, FBConfiguration.shouldUseCompactResponses);
}
#endif
#pragma mark - Helpers
+ (XCUIElement *)elementUsing:(NSString *)usingText withValue:(NSString *)value under:(XCUIElement *)element
{
return [[self elementsUsing:usingText
withValue:value
under:element
shouldReturnAfterFirstMatch:YES] firstObject];
}
+ (NSArray *)elementsUsing:(NSString *)usingText
withValue:(NSString *)value
under:(XCUIElement *)element
shouldReturnAfterFirstMatch:(BOOL)shouldReturnAfterFirstMatch
{
if ([usingText isEqualToString:@"partial link text"]
|| [usingText isEqualToString:@"link text"]) {
NSArray *components = [value componentsSeparatedByString:@"="];
NSString *propertyValue = components.lastObject;
NSString *propertyName = (components.count < 2 ? @"name" : components.firstObject);
return [element fb_descendantsMatchingProperty:propertyName
value:propertyValue
partialSearch:[usingText containsString:@"partial"]];
} else if ([usingText isEqualToString:@"class name"]) {
return [element fb_descendantsMatchingClassName:value
shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch];
} else if ([usingText isEqualToString:@"class chain"]) {
return [element fb_descendantsMatchingClassChain:value
shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch];
} else if ([usingText isEqualToString:@"xpath"]) {
return [element fb_descendantsMatchingXPathQuery:value
shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch];
} else if ([usingText isEqualToString:@"predicate string"]) {
return [element fb_descendantsMatchingPredicate:[NSPredicate predicateWithFormat:value]
shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch];
} else if ([usingText isEqualToString:@"name"]
|| [usingText isEqualToString:@"id"]
|| [usingText isEqualToString:@"accessibility id"]) {
return [element fb_descendantsMatchingIdentifier:value
shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch];
} else {
@throw [NSException exceptionWithName:FBElementAttributeUnknownException
reason:[NSString stringWithFormat:@"Invalid locator requested: %@", usingText]
userInfo:nil];
}
}
@end