初始化提交
This commit is contained in:
38
WebDriverAgentLib/Routing/FBCommandHandler.h
Normal file
38
WebDriverAgentLib/Routing/FBCommandHandler.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#import <WebDriverAgentLib/FBCommandStatus.h>
|
||||
#import <WebDriverAgentLib/FBResponsePayload.h>
|
||||
#import <WebDriverAgentLib/FBRoute.h>
|
||||
#import <WebDriverAgentLib/FBResponsePayload.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Protocol for Classes to declare intent to implement responses to commands
|
||||
*/
|
||||
@protocol FBCommandHandler <NSObject>
|
||||
|
||||
/**
|
||||
* Should return map of FBRouteCommandHandler block with keys as supported routes
|
||||
*
|
||||
* @return map an NSArray<FBRoute *> of routes.
|
||||
*/
|
||||
+ (NSArray *)routes;
|
||||
|
||||
@optional
|
||||
/**
|
||||
* @return BOOL deciding if class should be added to route handlers automatically, default (if not implemented) is YES
|
||||
*/
|
||||
+ (BOOL)shouldRegisterAutomatically;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
80
WebDriverAgentLib/Routing/FBCommandStatus.h
Normal file
80
WebDriverAgentLib/Routing/FBCommandStatus.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
#import <WebDriverAgentLib/FBHTTPStatusCodes.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FBCommandStatus : NSObject
|
||||
|
||||
@property (nonatomic, nullable, readonly) id value;
|
||||
@property (nonatomic, nullable, readonly) NSString* error;
|
||||
@property (nonatomic, nullable, readonly) NSString* message;
|
||||
@property (nonatomic, nullable, readonly) NSString* traceback;
|
||||
@property (nonatomic, readonly) HTTPStatusCode statusCode;
|
||||
@property (nonatomic, readonly) BOOL hasError;
|
||||
|
||||
+ (instancetype)ok;
|
||||
|
||||
+ (instancetype)okWithValue:(nullable id)value;
|
||||
|
||||
+ (instancetype)unknownErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)unsupportedOperationErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)unableToCaptureScreenErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)noSuchElementErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)invalidElementStateErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)invalidArgumentErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)staleElementReferenceErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)invalidSelectorErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)noAlertOpenErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)unexpectedAlertOpenErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)notImplementedErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)sessionNotCreatedError:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)invalidCoordinatesErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)unknownCommandErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)timeoutErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)elementNotVisibleErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
+ (instancetype)noSuchDriverErrorWithMessage:(nullable NSString *)message
|
||||
traceback:(nullable NSString *)traceback;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
279
WebDriverAgentLib/Routing/FBCommandStatus.m
Normal file
279
WebDriverAgentLib/Routing/FBCommandStatus.m
Normal file
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* 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 "FBCommandStatus.h"
|
||||
|
||||
static NSString *const FB_UNKNOWN_ERROR = @"unknown error";
|
||||
static const HTTPStatusCode FB_UNKNOWN_ERROR_CODE = kHTTPStatusCodeInternalServerError;
|
||||
static NSString *const FB_UNKNOWN_ERROR_MSG = @"An unknown server-side error occurred while processing the command";
|
||||
|
||||
static NSString *const FB_UNABLE_TO_CAPTURE_ERROR = @"unable to capture screen";
|
||||
static const HTTPStatusCode FB_UNABLE_TO_CAPTURE_ERROR_CODE = kHTTPStatusCodeInternalServerError;
|
||||
static NSString *const FB_UNABLE_TO_CAPTURE_MSG = @"A screen capture was made impossible";
|
||||
|
||||
static NSString *const FB_NO_SUCH_ELEMENT_ERROR = @"no such element";
|
||||
static const HTTPStatusCode FB_NO_SUCH_ELEMENT_ERROR_CODE = kHTTPStatusCodeNotFound;
|
||||
static NSString *const FB_NO_SUCH_ELEMENT_MSG = @"An element could not be located on the page using the given search parameters";
|
||||
|
||||
static NSString *const FB_INVALID_ELEMENT_STATE_ERROR = @"invalid element state";
|
||||
static const HTTPStatusCode FB_INVALID_ELEMENT_STATE_ERROR_CODE = kHTTPStatusCodeBadRequest;
|
||||
static NSString *const FB_INVALID_ELEMENT_STATE_MSG = @"An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element)";
|
||||
|
||||
static NSString *const FB_INVALID_ARGUMENT_ERROR = @"invalid argument";
|
||||
static const HTTPStatusCode FB_INVALID_ARGUMENT_ERROR_CODE = kHTTPStatusCodeBadRequest;
|
||||
static NSString *const FB_INVALID_ARGUMENT_MSG = @"The arguments passed to the command are either invalid or malformed";
|
||||
|
||||
static NSString *const FB_STALE_ELEMENT_REF_ERROR = @"stale element reference";
|
||||
static const HTTPStatusCode FB_STALE_ELEMENT_REF_ERROR_CODE = kHTTPStatusCodeNotFound;
|
||||
static NSString *const FB_STALE_ELEMENT_REF_MSG = @"An element command failed because the referenced element is no longer attached to the DOM";
|
||||
|
||||
static NSString *const FB_INVALID_SELECTOR_ERROR = @"invalid selector";
|
||||
static const HTTPStatusCode FB_INVALID_SELECTOR_ERROR_CODE = kHTTPStatusCodeBadRequest;
|
||||
static NSString *const FB_INVALID_SELECTOR_MSG = @"Argument was an invalid selector (e.g. XPath/Class Chain)";
|
||||
|
||||
static NSString *const FB_NO_ALERT_OPEN_ERROR = @"no such alert";
|
||||
static const HTTPStatusCode FB_NO_ALERT_OPEN_ERROR_CODE = kHTTPStatusCodeNotFound;
|
||||
static NSString *const FB_NO_ALERT_OPEN_MSG = @"An attempt was made to operate on a modal dialog when one was not open";
|
||||
|
||||
static NSString *const FB_UNEXPECTED_ALERT_OPEN_ERROR = @"unexpected alert open";
|
||||
static const HTTPStatusCode FB_UNEXPECTED_ALERT_OPEN_ERROR_CODE = kHTTPStatusCodeInternalServerError;
|
||||
static NSString *const FB_UNEXPECTED_ALERT_OPEN_MSG = @"A modal dialog was open, blocking this operation";
|
||||
|
||||
static NSString *const FB_NOT_IMPLEMENTED_ERROR = @"unknown method";
|
||||
static const HTTPStatusCode FB_NOT_IMPLEMENTED_ERROR_CODE = kHTTPStatusCodeMethodNotAllowed;
|
||||
static NSString *const FB_NOT_IMPLEMENTED_MSG = @"Method is not implemented";
|
||||
|
||||
static NSString *const FB_SESSION_NOT_CREATED_ERROR = @"session not created";
|
||||
static const HTTPStatusCode FB_SESSION_NOT_CREATED_ERROR_CODE = kHTTPStatusCodeInternalServerError;
|
||||
static NSString *const FB_SESSION_NOT_CREATED_MSG = @"A new session could not be created";
|
||||
|
||||
static NSString *const FB_INVALID_COORDINATES_ERROR = @"invalid coordinates";
|
||||
static const HTTPStatusCode FB_INVALID_COORDINATES_ERROR_CODE = kHTTPStatusCodeBadRequest;
|
||||
static NSString *const FB_INVALID_COORDINATES_MSG = @"The coordinates provided to an interactions operation are invalid";
|
||||
|
||||
static NSString *const FB_UNSUPPORTED_OPERATION_ERROR = @"unsupported operation";
|
||||
static const HTTPStatusCode FB_UNSUPPORTED_OPERATION_ERROR_CODE = kHTTPStatusCodeInternalServerError;
|
||||
static NSString *const FB_UNSUPPORTED_OPERATION_ERROR_MSG = @"The requested operation is not supported";
|
||||
|
||||
static NSString *const FB_UNKNOWN_COMMAND_ERROR = @"unknown command";
|
||||
static const HTTPStatusCode FB_UNKNOWN_COMMAND_ERROR_CODE = kHTTPStatusCodeNotFound;
|
||||
static NSString *const FB_UNKNOWN_COMMAND_MSG = @"The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource";
|
||||
|
||||
static NSString *const FB_TIMEOUT_ERROR = @"timeout";
|
||||
static const HTTPStatusCode FB_TIMEOUT_ERROR_CODE = kHTTPStatusCodeRequestTimeout;
|
||||
static NSString *const FB_TIMEOUT_MSG = @"An operation did not complete before its timeout expired";
|
||||
|
||||
static NSString *const FB_ELEMENT_NOT_VISIBLE_ERROR = @"element not visible";
|
||||
static const HTTPStatusCode FB_ELEMENT_NOT_VISIBLE_ERROR_CODE = kHTTPStatusCodeBadRequest;
|
||||
static NSString *const FB_ELEMENT_NOT_VISIBLE_MSG = @"An element command could not be completed because the element is not visible on the page";
|
||||
|
||||
static NSString *const FB_NO_SUCH_DRIVER_ERROR = @"invalid session id";
|
||||
static const HTTPStatusCode FB_NO_SUCH_DRIVER_ERROR_CODE = kHTTPStatusCodeNotFound;
|
||||
static NSString *const FB_NO_SUCH_DRIVER_MSG = @"A session is either terminated or not started";
|
||||
|
||||
|
||||
@implementation FBCommandStatus
|
||||
|
||||
- (instancetype)initWithValue:(nullable id)value
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_value = value;
|
||||
_message = nil;
|
||||
_error = nil;
|
||||
_traceback = nil;
|
||||
_statusCode = kHTTPStatusCodeOK;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithError:(NSString *)error
|
||||
statusCode:(HTTPStatusCode)statusCode
|
||||
message:(NSString *)message
|
||||
traceback:(nullable NSString *)traceback
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_error = error;
|
||||
_statusCode = statusCode;
|
||||
_message = message;
|
||||
_traceback = traceback;
|
||||
_value = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)hasError
|
||||
{
|
||||
return self.statusCode != kHTTPStatusCodeOK;
|
||||
}
|
||||
|
||||
+ (instancetype)ok
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithValue:nil];
|
||||
}
|
||||
|
||||
+ (instancetype)okWithValue:(id)value
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithValue:value];
|
||||
}
|
||||
|
||||
+ (instancetype)unknownErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_UNKNOWN_ERROR
|
||||
statusCode:FB_UNKNOWN_ERROR_CODE
|
||||
message:message ?: FB_UNKNOWN_ERROR_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)unsupportedOperationErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_UNSUPPORTED_OPERATION_ERROR
|
||||
statusCode:FB_UNSUPPORTED_OPERATION_ERROR_CODE
|
||||
message:message ?: FB_UNSUPPORTED_OPERATION_ERROR_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)unableToCaptureScreenErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_UNABLE_TO_CAPTURE_ERROR
|
||||
statusCode:FB_UNABLE_TO_CAPTURE_ERROR_CODE
|
||||
message:message ?: FB_UNABLE_TO_CAPTURE_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)noSuchElementErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_NO_SUCH_ELEMENT_ERROR
|
||||
statusCode:FB_NO_SUCH_ELEMENT_ERROR_CODE
|
||||
message:message ?: FB_NO_SUCH_ELEMENT_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)invalidElementStateErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_INVALID_ELEMENT_STATE_ERROR
|
||||
statusCode:FB_INVALID_ELEMENT_STATE_ERROR_CODE
|
||||
message:message ?: FB_INVALID_ELEMENT_STATE_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)invalidArgumentErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_INVALID_ARGUMENT_ERROR
|
||||
statusCode:FB_INVALID_ARGUMENT_ERROR_CODE
|
||||
message:message ?: FB_INVALID_ARGUMENT_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)staleElementReferenceErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_STALE_ELEMENT_REF_ERROR
|
||||
statusCode:FB_STALE_ELEMENT_REF_ERROR_CODE
|
||||
message:message ?: FB_STALE_ELEMENT_REF_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)invalidSelectorErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_INVALID_SELECTOR_ERROR
|
||||
statusCode:FB_INVALID_SELECTOR_ERROR_CODE
|
||||
message:message ?: FB_INVALID_SELECTOR_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)noAlertOpenErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_NO_ALERT_OPEN_ERROR
|
||||
statusCode:FB_NO_ALERT_OPEN_ERROR_CODE
|
||||
message:message ?: FB_NO_ALERT_OPEN_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)unexpectedAlertOpenErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_UNEXPECTED_ALERT_OPEN_ERROR
|
||||
statusCode:FB_UNEXPECTED_ALERT_OPEN_ERROR_CODE
|
||||
message:message ?: FB_UNEXPECTED_ALERT_OPEN_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)notImplementedErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_NOT_IMPLEMENTED_ERROR
|
||||
statusCode:FB_NOT_IMPLEMENTED_ERROR_CODE
|
||||
message:message ?: FB_NOT_IMPLEMENTED_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)sessionNotCreatedError:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_SESSION_NOT_CREATED_ERROR
|
||||
statusCode:FB_SESSION_NOT_CREATED_ERROR_CODE
|
||||
message:message ?: FB_SESSION_NOT_CREATED_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)invalidCoordinatesErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_INVALID_COORDINATES_ERROR
|
||||
statusCode:FB_INVALID_COORDINATES_ERROR_CODE
|
||||
message:message ?: FB_INVALID_COORDINATES_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)unknownCommandErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_UNKNOWN_COMMAND_ERROR
|
||||
statusCode:FB_UNKNOWN_COMMAND_ERROR_CODE
|
||||
message:message ?: FB_UNKNOWN_COMMAND_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)timeoutErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_TIMEOUT_ERROR
|
||||
statusCode:FB_TIMEOUT_ERROR_CODE
|
||||
message:message ?: FB_TIMEOUT_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)elementNotVisibleErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_ELEMENT_NOT_VISIBLE_ERROR
|
||||
statusCode:FB_ELEMENT_NOT_VISIBLE_ERROR_CODE
|
||||
message:message ?: FB_ELEMENT_NOT_VISIBLE_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
+ (instancetype)noSuchDriverErrorWithMessage:(NSString *)message
|
||||
traceback:(NSString *)traceback
|
||||
{
|
||||
return [[FBCommandStatus alloc] initWithError:FB_NO_SUCH_DRIVER_ERROR
|
||||
statusCode:FB_NO_SUCH_DRIVER_ERROR_CODE
|
||||
message:message ?: FB_NO_SUCH_DRIVER_MSG
|
||||
traceback:traceback];
|
||||
}
|
||||
|
||||
@end
|
||||
92
WebDriverAgentLib/Routing/FBElement.h
Normal file
92
WebDriverAgentLib/Routing/FBElement.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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 <CoreGraphics/CoreGraphics.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Protocol that should be implemented by class that can return element properties defined in WebDriver Spec
|
||||
*/
|
||||
@protocol FBElement <NSObject>
|
||||
|
||||
/*! Element's frame in normalized (rounded dimensions without Infinity values) CGRect format */
|
||||
@property (nonatomic, readonly, assign) CGRect wdFrame;
|
||||
|
||||
/*! Represents the element's frame as a CGRect, preserving the actual values. */
|
||||
@property (nonatomic, readonly, assign) CGRect wdNativeFrame;
|
||||
|
||||
/*! Element's wsFrame in NSDictionary format */
|
||||
@property (nonatomic, readonly, copy) NSDictionary *wdRect;
|
||||
|
||||
/*! Element's name */
|
||||
@property (nonatomic, readonly, copy, nullable) NSString *wdName;
|
||||
|
||||
/*! Element's label */
|
||||
@property (nonatomic, readonly, copy, nullable) NSString *wdLabel;
|
||||
|
||||
/*! Element's selected state */
|
||||
@property (nonatomic, readonly, getter = isWDSelected) BOOL wdSelected;
|
||||
|
||||
/*! Element's type */
|
||||
@property (nonatomic, readonly, copy) NSString *wdType;
|
||||
|
||||
/*! Element's accessibility traits as a comma-separated string */
|
||||
@property (nonatomic, readonly, copy) NSString *wdTraits;
|
||||
|
||||
/*! Element's value */
|
||||
@property (nonatomic, readonly, strong, nullable) NSString *wdValue;
|
||||
|
||||
/*! Element's unique identifier */
|
||||
@property (nonatomic, readonly, copy, nullable) NSString *wdUID;
|
||||
|
||||
/*! Whether element is enabled */
|
||||
@property (nonatomic, readonly, getter = isWDEnabled) BOOL wdEnabled;
|
||||
|
||||
/*! Whether element is visible */
|
||||
@property (nonatomic, readonly, getter = isWDVisible) BOOL wdVisible;
|
||||
|
||||
/*! Whether element is accessible */
|
||||
@property (nonatomic, readonly, getter = isWDAccessible) BOOL wdAccessible;
|
||||
|
||||
/*! Whether element is an accessibility container (contains children of any depth that are accessible) */
|
||||
@property (nonatomic, readonly, getter = isWDAccessibilityContainer) BOOL wdAccessibilityContainer;
|
||||
|
||||
/*! Whether element is focused */
|
||||
@property (nonatomic, readonly, getter = isWDFocused) BOOL wdFocused;
|
||||
|
||||
/*! Whether element is hittable */
|
||||
@property (nonatomic, readonly, getter = isWDHittable) BOOL wdHittable;
|
||||
|
||||
/*! Element's index relatively to its parent. Starts from zero */
|
||||
@property (nonatomic, readonly) NSUInteger wdIndex;
|
||||
|
||||
/*! Element's placeholder value */
|
||||
@property (nonatomic, readonly, copy, nullable) NSString *wdPlaceholderValue;
|
||||
|
||||
/*! Element's minimum value */
|
||||
@property (nonatomic, readonly, strong, nullable) NSNumber *wdMinValue;
|
||||
|
||||
/*! Element's maximum value */
|
||||
@property (nonatomic, readonly, strong, nullable) NSNumber *wdMaxValue;
|
||||
|
||||
/**
|
||||
Returns value of given property specified in WebDriver Spec
|
||||
Check the FBElement protocol to get list of supported attributes.
|
||||
This method also supports shortcuts, like wdName == name, wdValue == value.
|
||||
|
||||
@param name WebDriver Spec property name
|
||||
@return the corresponding property value
|
||||
@throws FBUnknownAttributeException if there is no matching attribute defined in FBElement protocol
|
||||
*/
|
||||
- (nullable id)fb_valueForWDAttributeName:(NSString *__nullable)name;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
61
WebDriverAgentLib/Routing/FBElementCache.h
Normal file
61
WebDriverAgentLib/Routing/FBElementCache.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@class XCUIElement;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// This constant defines the size of the element cache, which puts an upper limit
|
||||
// on the amount of elements which can be stored in the cache.
|
||||
// Based on the data in https://github.com/facebook/WebDriverAgent/pull/896, each
|
||||
// element consumes about 100KB of memory; so 1024 elements would consume 100 MB of
|
||||
// memory.
|
||||
extern const int ELEMENT_CACHE_SIZE;
|
||||
|
||||
@interface FBElementCache : NSObject
|
||||
|
||||
/**
|
||||
Stores element in cache
|
||||
|
||||
@param element element to store
|
||||
@return element's uuid or nil in case the element uid cannnot be extracted
|
||||
*/
|
||||
- (nullable NSString *)storeElement:(XCUIElement *)element;
|
||||
|
||||
/**
|
||||
Returns cached element resolved with default snapshot attributes
|
||||
|
||||
@param uuid uuid of element to fetch
|
||||
@return element
|
||||
@throws FBInvalidArgumentException if uuid is nil
|
||||
*/
|
||||
- (XCUIElement *)elementForUUID:(NSString *)uuid;
|
||||
|
||||
/**
|
||||
Returns cached element resolved with default snapshot attributes
|
||||
|
||||
@param uuid uuid of element to fetch
|
||||
@param checkStaleness Whether to throw FBStaleElementException if the found element is not present in DOM anymore
|
||||
@return element
|
||||
@throws FBStaleElementException if `checkStaleness` is enabled
|
||||
@throws FBInvalidArgumentException if uuid is nil
|
||||
*/
|
||||
- (XCUIElement *)elementForUUID:(NSString *)uuid checkStaleness:(BOOL)checkStaleness;
|
||||
|
||||
/**
|
||||
Checks element existence in the cache
|
||||
|
||||
@returns YES if the element with the given UUID is present in cache
|
||||
*/
|
||||
- (BOOL)hasElementWithUUID:(nullable NSString *)uuid;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
100
WebDriverAgentLib/Routing/FBElementCache.m
Normal file
100
WebDriverAgentLib/Routing/FBElementCache.m
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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 "FBElementCache.h"
|
||||
|
||||
#import "LRUCache.h"
|
||||
#import "FBAlert.h"
|
||||
#import "FBExceptions.h"
|
||||
#import "FBXCodeCompatibility.h"
|
||||
#import "XCTestPrivateSymbols.h"
|
||||
#import "XCUIElement.h"
|
||||
#import "XCUIElement+FBCaching.h"
|
||||
#import "XCUIElement+FBUtilities.h"
|
||||
#import "XCUIElement+FBWebDriverAttributes.h"
|
||||
#import "XCUIElement+FBUID.h"
|
||||
#import "XCUIElement+FBResolve.h"
|
||||
#import "XCUIElementQuery.h"
|
||||
|
||||
const int ELEMENT_CACHE_SIZE = 1024;
|
||||
|
||||
@interface FBElementCache ()
|
||||
@property (nonatomic, strong) LRUCache *elementCache;
|
||||
@end
|
||||
|
||||
@implementation FBElementCache
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
_elementCache = [[LRUCache alloc] initWithCapacity:ELEMENT_CACHE_SIZE];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)storeElement:(XCUIElement *)element
|
||||
{
|
||||
NSString *uuid = element.fb_cacheId;
|
||||
if (nil == uuid) {
|
||||
return nil;
|
||||
}
|
||||
@synchronized (self.elementCache) {
|
||||
[self.elementCache setObject:element forKey:uuid];
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
- (XCUIElement *)elementForUUID:(NSString *)uuid
|
||||
{
|
||||
return [self elementForUUID:uuid checkStaleness:NO];
|
||||
}
|
||||
|
||||
- (XCUIElement *)elementForUUID:(NSString *)uuid checkStaleness:(BOOL)checkStaleness
|
||||
{
|
||||
if (!uuid) {
|
||||
NSString *reason = [NSString stringWithFormat:@"Cannot extract cached element for UUID: %@", uuid];
|
||||
@throw [NSException exceptionWithName:FBInvalidArgumentException reason:reason userInfo:@{}];
|
||||
}
|
||||
|
||||
XCUIElement *element;
|
||||
@synchronized (self.elementCache) {
|
||||
element = [self.elementCache objectForKey:uuid];
|
||||
}
|
||||
if (nil == element) {
|
||||
NSString *reason = [NSString stringWithFormat:@"The element identified by \"%@\" is either not present or it has expired from the internal cache. Try to find it again", uuid];
|
||||
@throw [NSException exceptionWithName:FBStaleElementException reason:reason userInfo:@{}];
|
||||
}
|
||||
if (checkStaleness) {
|
||||
@try {
|
||||
[element fb_standardSnapshot];
|
||||
} @catch (NSException *exception) {
|
||||
// if the snapshot method threw FBStaleElementException (implying the element is stale) we need to explicitly remove it from the cache, PR: https://github.com/appium/WebDriverAgent/pull/985
|
||||
if ([exception.name isEqualToString:FBStaleElementException]) {
|
||||
@synchronized (self.elementCache) {
|
||||
[self.elementCache removeObjectForKey:uuid];
|
||||
}
|
||||
}
|
||||
@throw exception;
|
||||
}
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
- (BOOL)hasElementWithUUID:(NSString *)uuid
|
||||
{
|
||||
if (nil == uuid) {
|
||||
return NO;
|
||||
}
|
||||
@synchronized (self.elementCache) {
|
||||
return nil != [self.elementCache objectForKey:(NSString *)uuid];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
76
WebDriverAgentLib/Routing/FBElementUtils.h
Normal file
76
WebDriverAgentLib/Routing/FBElementUtils.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
#import <WebDriverAgentLib/FBElement.h>
|
||||
|
||||
@protocol FBXCAccessibilityElement;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*! Notification used to notify about unknown attribute name */
|
||||
extern NSString *const FBUnknownAttributeException;
|
||||
|
||||
@interface FBElementUtils : NSObject
|
||||
|
||||
/**
|
||||
Returns property name defined by FBElement protocol for the given WebDriver Spec property name.
|
||||
Check the FBElement protocol to get list of supported attributes.
|
||||
This method also supports shortcuts, like wdName == name, wdValue == value.
|
||||
In case the corresponding attribute has a getter defined then the name of the getter witll be returned instead,
|
||||
which makes this method compatible with KVO lookup
|
||||
|
||||
@param name WebDriver Spec property name
|
||||
@return the corresponding property name
|
||||
@throws FBUnknownAttributeException if there is no matching attribute defined in FBElement protocol
|
||||
*/
|
||||
+ (NSString *)wdAttributeNameForAttributeName:(NSString *)name;
|
||||
|
||||
/**
|
||||
Collects all the unique element types from an array of elements.
|
||||
|
||||
@param elements array of elements
|
||||
@return set of unique element types (XCUIElementType items) or an empty set in case the input is empty
|
||||
*/
|
||||
+ (NSSet<NSNumber *> *)uniqueElementTypesWithElements:(NSArray<id<FBElement>> *)elements;
|
||||
|
||||
/**
|
||||
Returns mapping of all possible FBElement protocol properties aliases
|
||||
|
||||
@return dictionary of matching property aliases with their real names as values or getter method names if exist
|
||||
for KVO lookup
|
||||
*/
|
||||
+ (NSDictionary<NSString *, NSString *> *)wdAttributeNamesMapping;
|
||||
|
||||
/**
|
||||
Gets the unique identifier of the particular XCAccessibilityElement instance in form of UUIDv4.
|
||||
|
||||
@param element accessiblity element instance
|
||||
@return the unique element identifier or nil if it cannot be retrieved
|
||||
*/
|
||||
+ (nullable NSString *)uidWithAccessibilityElement:(id<FBXCAccessibilityElement>)element;
|
||||
|
||||
/**
|
||||
Gets the unique identifier of the particular XCAccessibilityElement instance.
|
||||
|
||||
@param element accessiblity element instance
|
||||
@return the unique element identifier or nil if it cannot be retrieved
|
||||
*/
|
||||
+ (unsigned long long)idWithAccessibilityElement:(id<FBXCAccessibilityElement>)element;
|
||||
|
||||
/**
|
||||
Retrieves the list of required instance methods of the given protocol
|
||||
|
||||
@param protocol target protocol reference
|
||||
@return set of selector names
|
||||
*/
|
||||
+ (NSSet<NSString *> *)selectorNamesWithProtocol:(Protocol *)protocol;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
153
WebDriverAgentLib/Routing/FBElementUtils.m
Normal file
153
WebDriverAgentLib/Routing/FBElementUtils.m
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 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 <objc/runtime.h>
|
||||
|
||||
#import "FBXCAccessibilityElement.h"
|
||||
#import "FBElementUtils.h"
|
||||
#import "FBElementTypeTransformer.h"
|
||||
|
||||
NSString *const FBUnknownAttributeException = @"FBUnknownAttributeException";
|
||||
static NSString *const WD_PREFIX = @"wd";
|
||||
static NSString *const OBJC_PROP_GETTER_PREFIX = @"G";
|
||||
static NSString *const OBJC_PROP_ATTRIBS_SEPARATOR = @",";
|
||||
|
||||
@implementation FBElementUtils
|
||||
|
||||
+ (NSSet<NSString *> *)selectorNamesWithProtocol:(Protocol *)protocol
|
||||
{
|
||||
unsigned int count;
|
||||
struct objc_method_description *methods = protocol_copyMethodDescriptionList(protocol, YES, YES, &count);
|
||||
NSMutableSet<NSString *> *result = [NSMutableSet set];
|
||||
for (unsigned int i = 0; i < count; i++) {
|
||||
SEL sel = methods[i].name;
|
||||
if (nil != sel) {
|
||||
[result addObject:NSStringFromSelector(sel)];
|
||||
}
|
||||
}
|
||||
free(methods);
|
||||
return result.copy;
|
||||
}
|
||||
|
||||
+ (NSString *)wdAttributeNameForAttributeName:(NSString *)name
|
||||
{
|
||||
NSAssert(name.length > 0, @"Attribute name cannot be empty", nil);
|
||||
NSDictionary *attributeNamesMapping = [self.class wdAttributeNamesMapping];
|
||||
NSString *result = attributeNamesMapping[name];
|
||||
if (nil == result) {
|
||||
NSString *description = [NSString stringWithFormat:@"The attribute '%@' is unknown. Valid attribute names are: %@", name, [attributeNamesMapping.allKeys sortedArrayUsingSelector:@selector(compare:)]];
|
||||
@throw [NSException exceptionWithName:FBUnknownAttributeException reason:description userInfo:@{}];
|
||||
return nil;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (NSSet<NSNumber *> *)uniqueElementTypesWithElements:(NSArray<id<FBElement>> *)elements
|
||||
{
|
||||
NSMutableSet *matchingTypes = [NSMutableSet set];
|
||||
[elements enumerateObjectsUsingBlock:^(id<FBElement> element, NSUInteger elementIdx, BOOL *stopElementsEnum) {
|
||||
[matchingTypes addObject: @([FBElementTypeTransformer elementTypeWithTypeName:element.wdType])];
|
||||
}];
|
||||
return matchingTypes.copy;
|
||||
}
|
||||
|
||||
+ (NSDictionary<NSString *, NSString *> *)wdAttributeNamesMapping
|
||||
{
|
||||
static NSDictionary *attributeNamesMapping;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSMutableDictionary *wdPropertyGettersMapping = [NSMutableDictionary new];
|
||||
unsigned int propsCount = 0;
|
||||
Protocol * aProtocol = objc_getProtocol(protocol_getName(@protocol(FBElement)));
|
||||
objc_property_t *properties = protocol_copyPropertyList(aProtocol, &propsCount);
|
||||
for (unsigned int i = 0; i < propsCount; ++i) {
|
||||
objc_property_t property = properties[i];
|
||||
const char *name = property_getName(property);
|
||||
NSString *nsName = [NSString stringWithUTF8String:name];
|
||||
if (nil == nsName || ![nsName hasPrefix:WD_PREFIX]) {
|
||||
continue;
|
||||
}
|
||||
[wdPropertyGettersMapping setObject:[NSNull null] forKey:nsName];
|
||||
const char *c_attributes = property_getAttributes(property);
|
||||
NSString *attributes = [NSString stringWithUTF8String:c_attributes];
|
||||
if (nil == attributes) {
|
||||
continue;
|
||||
}
|
||||
// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
|
||||
NSArray *splitAttrs = [attributes componentsSeparatedByString:OBJC_PROP_ATTRIBS_SEPARATOR];
|
||||
for (NSString *part in splitAttrs) {
|
||||
if ([part hasPrefix:OBJC_PROP_GETTER_PREFIX]) {
|
||||
[wdPropertyGettersMapping setObject:[part substringFromIndex:1] forKey:nsName];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(properties);
|
||||
|
||||
NSMutableDictionary *resultCache = [NSMutableDictionary new];
|
||||
for (NSString *propName in wdPropertyGettersMapping) {
|
||||
if ([[wdPropertyGettersMapping valueForKey:propName] isKindOfClass:NSNull.class]) {
|
||||
// no getter
|
||||
[resultCache setValue:propName forKey:propName];
|
||||
} else {
|
||||
// has getter method
|
||||
[resultCache setValue:[wdPropertyGettersMapping objectForKey:propName] forKey:propName];
|
||||
}
|
||||
NSString *aliasName;
|
||||
if (propName.length <= WD_PREFIX.length + 1) {
|
||||
aliasName = [NSString stringWithFormat:@"%@",
|
||||
[propName substringWithRange:NSMakeRange(WD_PREFIX.length, 1)].lowercaseString];
|
||||
} else {
|
||||
NSString *propNameWithoutPrefix = [propName substringFromIndex:WD_PREFIX.length];
|
||||
NSString *firstPropNameCharacter = [propNameWithoutPrefix substringWithRange:NSMakeRange(0, 1)];
|
||||
if (![propNameWithoutPrefix isEqualToString:[propNameWithoutPrefix uppercaseString]]) {
|
||||
// Lowercase the first character for the alias if the property name is not an uppercase abbreviation
|
||||
firstPropNameCharacter = firstPropNameCharacter.lowercaseString;
|
||||
}
|
||||
aliasName = [NSString stringWithFormat:@"%@%@", firstPropNameCharacter, [propNameWithoutPrefix substringFromIndex:1]];
|
||||
}
|
||||
if ([[wdPropertyGettersMapping valueForKey:propName] isKindOfClass:NSNull.class]) {
|
||||
// no getter
|
||||
[resultCache setValue:propName forKey:aliasName];
|
||||
} else {
|
||||
// has getter method
|
||||
[resultCache setValue:[wdPropertyGettersMapping objectForKey:propName] forKey:aliasName];
|
||||
}
|
||||
}
|
||||
attributeNamesMapping = resultCache.copy;
|
||||
});
|
||||
return attributeNamesMapping;
|
||||
}
|
||||
|
||||
+ (NSString *)uidWithAccessibilityElement:(id<FBXCAccessibilityElement>)element
|
||||
{
|
||||
unsigned long long elementId = [self.class idWithAccessibilityElement:element];
|
||||
int processId = element.processIdentifier;
|
||||
if (elementId < 1 || processId < 1) {
|
||||
return nil;
|
||||
}
|
||||
uint8_t b[16] = {0};
|
||||
memcpy(b, &elementId, sizeof(long long));
|
||||
memcpy(b + sizeof(long long), &processId, sizeof(int));
|
||||
NSUUID *uuidValue = [[NSUUID alloc] initWithUUIDBytes:b];
|
||||
return uuidValue.UUIDString;
|
||||
}
|
||||
|
||||
static BOOL FBShouldUsePayloadForUIDExtraction = YES;
|
||||
static dispatch_once_t oncePayloadToken;
|
||||
+ (unsigned long long)idWithAccessibilityElement:(id<FBXCAccessibilityElement>)element
|
||||
{
|
||||
dispatch_once(&oncePayloadToken, ^{
|
||||
FBShouldUsePayloadForUIDExtraction = [(NSObject *)element respondsToSelector:@selector(payload)];
|
||||
});
|
||||
return FBShouldUsePayloadForUIDExtraction
|
||||
? [[element.payload objectForKey:@"uid.elementID"] longLongValue]
|
||||
: [[(NSObject *)element valueForKey:@"_elementID"] longLongValue];
|
||||
}
|
||||
|
||||
@end
|
||||
29
WebDriverAgentLib/Routing/FBExceptionHandler.h
Normal file
29
WebDriverAgentLib/Routing/FBExceptionHandler.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
#import <WebDriverAgentLib/FBWebServer.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Class used to handle exceptions raised by command handlers
|
||||
*/
|
||||
@interface FBExceptionHandler : NSObject
|
||||
|
||||
/**
|
||||
Handles 'exception' for 'webServer' raised while handling 'response'
|
||||
|
||||
@param exception exception that needs handling
|
||||
@param response response related to that exception
|
||||
*/
|
||||
- (void)handleException:(NSException *)exception forResponse:(RouteResponse *)response;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
58
WebDriverAgentLib/Routing/FBExceptionHandler.m
Normal file
58
WebDriverAgentLib/Routing/FBExceptionHandler.m
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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 "FBExceptionHandler.h"
|
||||
|
||||
#import "RouteResponse.h"
|
||||
|
||||
#import "FBResponsePayload.h"
|
||||
#import "FBExceptions.h"
|
||||
|
||||
@implementation FBExceptionHandler
|
||||
|
||||
- (void)handleException:(NSException *)exception forResponse:(RouteResponse *)response
|
||||
{
|
||||
FBCommandStatus *commandStatus;
|
||||
NSString *traceback = [NSString stringWithFormat:@"%@", exception.callStackSymbols];
|
||||
if ([exception.name isEqualToString:FBSessionDoesNotExistException]) {
|
||||
commandStatus = [FBCommandStatus noSuchDriverErrorWithMessage:exception.reason
|
||||
traceback:traceback];
|
||||
} else if ([exception.name isEqualToString:FBInvalidArgumentException]
|
||||
|| [exception.name isEqualToString:FBElementAttributeUnknownException]
|
||||
|| [exception.name isEqualToString:FBApplicationMissingException]) {
|
||||
commandStatus = [FBCommandStatus invalidArgumentErrorWithMessage:exception.reason
|
||||
traceback:traceback];
|
||||
} else if ([exception.name isEqualToString:FBApplicationCrashedException]
|
||||
|| [exception.name isEqualToString:FBApplicationDeadlockDetectedException]) {
|
||||
commandStatus = [FBCommandStatus invalidElementStateErrorWithMessage:exception.reason
|
||||
traceback:traceback];
|
||||
} else if ([exception.name isEqualToString:FBInvalidXPathException]
|
||||
|| [exception.name isEqualToString:FBClassChainQueryParseException]) {
|
||||
commandStatus = [FBCommandStatus invalidSelectorErrorWithMessage:exception.reason
|
||||
traceback:traceback];
|
||||
} else if ([exception.name isEqualToString:FBElementNotVisibleException]) {
|
||||
commandStatus = [FBCommandStatus elementNotVisibleErrorWithMessage:exception.reason
|
||||
traceback:traceback];
|
||||
} else if ([exception.name isEqualToString:FBStaleElementException]) {
|
||||
commandStatus = [FBCommandStatus staleElementReferenceErrorWithMessage:exception.reason
|
||||
traceback:traceback];
|
||||
} else if ([exception.name isEqualToString:FBTimeoutException]) {
|
||||
commandStatus = [FBCommandStatus timeoutErrorWithMessage:exception.reason
|
||||
traceback:traceback];
|
||||
} else if ([exception.name isEqualToString:FBSessionCreationException]) {
|
||||
commandStatus = [FBCommandStatus sessionNotCreatedError:exception.reason
|
||||
traceback:traceback];
|
||||
} else {
|
||||
commandStatus = [FBCommandStatus unknownErrorWithMessage:exception.reason
|
||||
traceback:traceback];
|
||||
}
|
||||
id<FBResponsePayload> payload = FBResponseWithStatus(commandStatus);
|
||||
[payload dispatchWithResponse:response];
|
||||
}
|
||||
|
||||
@end
|
||||
60
WebDriverAgentLib/Routing/FBExceptions.h
Normal file
60
WebDriverAgentLib/Routing/FBExceptions.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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 <XCTest/XCTest.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*! Exception used to notify about missing session */
|
||||
extern NSString *const FBSessionDoesNotExistException;
|
||||
|
||||
/*! Exception used to notify about session creation issues */
|
||||
extern NSString *const FBSessionCreationException;
|
||||
|
||||
/*! Exception used to notify about application deadlock */
|
||||
extern NSString *const FBApplicationDeadlockDetectedException;
|
||||
|
||||
/*! Exception used to notify about unknown attribute */
|
||||
extern NSString *const FBElementAttributeUnknownException;
|
||||
|
||||
/*! Exception used to notify about invalid argument */
|
||||
extern NSString *const FBInvalidArgumentException;
|
||||
|
||||
/*! Exception used to notify about invisibility of an element while trying to interact with it */
|
||||
extern NSString *const FBElementNotVisibleException;
|
||||
|
||||
/*! Exception used to notify about a timeout */
|
||||
extern NSString *const FBTimeoutException;
|
||||
|
||||
/**
|
||||
The exception happends if the cached element does not exist in DOM anymore
|
||||
*/
|
||||
extern NSString *const FBStaleElementException;
|
||||
|
||||
/**
|
||||
The exception happends if the provided XPath expession cannot be compiled because of a syntax error
|
||||
*/
|
||||
extern NSString *const FBInvalidXPathException;
|
||||
/**
|
||||
The exception happends if any internal error is triggered during XPath matching procedure
|
||||
*/
|
||||
extern NSString *const FBXPathQueryEvaluationException;
|
||||
|
||||
/*! Exception used to notify about invalid class chain query */
|
||||
extern NSString *const FBClassChainQueryParseException;
|
||||
|
||||
/*! Exception used to notify about application crash */
|
||||
extern NSString *const FBApplicationCrashedException;
|
||||
|
||||
/*! Exception used to notify about the application is not installed */
|
||||
extern NSString *const FBApplicationMissingException;
|
||||
|
||||
/*! Exception used to notify about WDA incompatibility with the current platform version */
|
||||
extern NSString *const FBIncompatibleWdaException;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
24
WebDriverAgentLib/Routing/FBExceptions.m
Normal file
24
WebDriverAgentLib/Routing/FBExceptions.m
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 "FBExceptions.h"
|
||||
|
||||
NSString *const FBInvalidArgumentException = @"FBInvalidArgumentException";
|
||||
NSString *const FBSessionCreationException = @"FBSessionCreationException";
|
||||
NSString *const FBSessionDoesNotExistException = @"FBSessionDoesNotExistException";
|
||||
NSString *const FBApplicationDeadlockDetectedException = @"FBApplicationDeadlockDetectedException";
|
||||
NSString *const FBElementAttributeUnknownException = @"FBElementAttributeUnknownException";
|
||||
NSString *const FBElementNotVisibleException = @"FBElementNotVisibleException";
|
||||
NSString *const FBTimeoutException = @"FBTimeoutException";
|
||||
NSString *const FBStaleElementException = @"FBStaleElementException";
|
||||
NSString *const FBInvalidXPathException = @"FBInvalidXPathException";
|
||||
NSString *const FBXPathQueryEvaluationException = @"FBXPathQueryEvaluationException";
|
||||
NSString *const FBClassChainQueryParseException = @"FBClassChainQueryParseException";
|
||||
NSString *const FBApplicationCrashedException = @"FBApplicationCrashedException";
|
||||
NSString *const FBApplicationMissingException = @"FBApplicationMissingException";
|
||||
NSString *const FBIncompatibleWdaException = @"FBIncompatibleWdaException";
|
||||
581
WebDriverAgentLib/Routing/FBHTTPStatusCodes.h
Normal file
581
WebDriverAgentLib/Routing/FBHTTPStatusCodes.h
Normal file
@@ -0,0 +1,581 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Neo Visionaries Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FBHTTPStatusCodes_h
|
||||
#define FBHTTPStatusCodes_h
|
||||
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Typedef
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* HTTP status codes.
|
||||
*
|
||||
* The list here is based on the description at Wikipedia.
|
||||
* The initial version of this list was written on April 20, 2013.
|
||||
*
|
||||
* @see <a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes">List of HTTP status codes</a>
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
/*--------------------------------------------------
|
||||
* 1xx Informational
|
||||
*------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* 100 Continue.
|
||||
*/
|
||||
kHTTPStatusCodeContinue = 100,
|
||||
|
||||
/**
|
||||
* 101 Switching Protocols.
|
||||
*/
|
||||
kHTTPStatusCodeSwitchingProtocols = 101,
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_WEBDAV) && !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_2518)
|
||||
/**
|
||||
* 103 Processing (WebDAV; RFC 2518).
|
||||
*/
|
||||
kHTTPStatusCodeProcessing = 102,
|
||||
#endif
|
||||
|
||||
/*--------------------------------------------------
|
||||
* 2xx Success
|
||||
*------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* 200 OK.
|
||||
*/
|
||||
kHTTPStatusCodeOK = 200,
|
||||
|
||||
/**
|
||||
* 201 Created.
|
||||
*/
|
||||
kHTTPStatusCodeCreated = 201,
|
||||
|
||||
/**
|
||||
* 202 Accepted.
|
||||
*/
|
||||
kHTTPStatusCodeAccepted = 202,
|
||||
|
||||
/**
|
||||
* 203 Non-Authoritative Information (since HTTP/1.1).
|
||||
*/
|
||||
kHTTPStatusCodeNonAuthoritativeInformation = 203,
|
||||
|
||||
/**
|
||||
* 204 No Content.
|
||||
*/
|
||||
kHTTPStatusCodeNoContent = 204,
|
||||
|
||||
/**
|
||||
* 205 Reset Content.
|
||||
*/
|
||||
kHTTPStatusCodeResetContent = 205,
|
||||
|
||||
/**
|
||||
* 206 Partial Content.
|
||||
*/
|
||||
kHTTPStatusCodePartialContent = 206,
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_WEBDAV) && !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_4918)
|
||||
/**
|
||||
* 207 Multi-Status (WebDAV; RFC 4918).
|
||||
*/
|
||||
kHTTPStatusCodeMultiStatus = 207,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_WEBDAV) && !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_5842)
|
||||
/**
|
||||
* 208 Already Reported (WebDAV; RFC 5842).
|
||||
*/
|
||||
kHTTPStatusCodeAlreadyReported = 208,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_3229)
|
||||
/**
|
||||
* 226 IM Used (RFC 3229)
|
||||
*/
|
||||
kHTTPStatusCodeIMUsed = 226,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP) && !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_2326)
|
||||
/**
|
||||
* 250 Low on Storage Space (RTSP; RFC 2326).
|
||||
*/
|
||||
kHTTPStatusCodeLowOnStorageSpace = 250,
|
||||
#endif
|
||||
|
||||
/*--------------------------------------------------
|
||||
* 3xx Redirection
|
||||
*------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* 300 Multiple Choices.
|
||||
*/
|
||||
kHTTPStatusCodeMultipleChoices = 300,
|
||||
|
||||
/**
|
||||
* 301 Moved Permanently.
|
||||
*/
|
||||
kHTTPStatusCodeMovedPermanently = 301,
|
||||
|
||||
/**
|
||||
* 302 Found.
|
||||
*/
|
||||
kHTTPStatusCodeFound = 302,
|
||||
|
||||
/**
|
||||
* 303 See Other (since HTTP/1.1).
|
||||
*/
|
||||
kHTTPStatusCodeSeeOther = 303,
|
||||
|
||||
/**
|
||||
* 304 Not Modified.
|
||||
*/
|
||||
kHTTPStatusCodeNotModified = 304,
|
||||
|
||||
/**
|
||||
* 305 Use Proxy (since HTTP/1.1).
|
||||
*/
|
||||
kHTTPStatusCodeUseProxy = 305,
|
||||
|
||||
/**
|
||||
* 306 Switch Proxy.
|
||||
*/
|
||||
kHTTPStatusCodeSwitchProxy = 306,
|
||||
|
||||
/**
|
||||
* 307 Temporary Redirect (since HTTP/1.1).
|
||||
*/
|
||||
kHTTPStatusCodeTemporaryRedirect = 307,
|
||||
|
||||
/**
|
||||
* 308 Permanent Redirect (approved as experimental RFC).
|
||||
*/
|
||||
kHTTPStatusCodePermanentRedirect = 308,
|
||||
|
||||
/*--------------------------------------------------
|
||||
* 4xx Client Error
|
||||
*------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* 400 Bad Request.
|
||||
*/
|
||||
kHTTPStatusCodeBadRequest = 400,
|
||||
|
||||
/**
|
||||
* 401 Unauthorized.
|
||||
*/
|
||||
kHTTPStatusCodeUnauthorized = 401,
|
||||
|
||||
/**
|
||||
* 402 Payment Required.
|
||||
*/
|
||||
kHTTPStatusCodePaymentRequired = 402,
|
||||
|
||||
/**
|
||||
* 403 Forbidden.
|
||||
*/
|
||||
kHTTPStatusCodeForbidden = 403,
|
||||
|
||||
/**
|
||||
* 404 Not Found.
|
||||
*/
|
||||
kHTTPStatusCodeNotFound = 404,
|
||||
|
||||
/**
|
||||
* 405 Method Not Allowed.
|
||||
*/
|
||||
kHTTPStatusCodeMethodNotAllowed = 405,
|
||||
|
||||
/**
|
||||
* 406 Not Acceptable.
|
||||
*/
|
||||
kHTTPStatusCodeNotAcceptable = 406,
|
||||
|
||||
/**
|
||||
* 407 Proxy Authentication Required.
|
||||
*/
|
||||
kHTTPStatusCodeProxyAuthenticationRequired = 407,
|
||||
|
||||
/**
|
||||
* 408 Request Timeout.
|
||||
*/
|
||||
kHTTPStatusCodeRequestTimeout = 408,
|
||||
|
||||
/**
|
||||
* 409 Conflict.
|
||||
*/
|
||||
kHTTPStatusCodeConflict = 409,
|
||||
|
||||
/**
|
||||
* 410 Gone.
|
||||
*/
|
||||
kHTTPStatusCodeGone = 410,
|
||||
|
||||
/**
|
||||
* 411 Length Required.
|
||||
*/
|
||||
kHTTPStatusCodeLengthRequired = 411,
|
||||
|
||||
/**
|
||||
* 412 Precondition Failed.
|
||||
*/
|
||||
kHTTPStatusCodePreconditionFailed = 412,
|
||||
|
||||
/**
|
||||
* 413 Request Entity Too Large.
|
||||
*/
|
||||
kHTTPStatusCodeRequestEntityTooLarge = 413,
|
||||
|
||||
/**
|
||||
* 414 Request-URI Too Long.
|
||||
*/
|
||||
kHTTPStatusCodeRequestURITooLong = 414,
|
||||
|
||||
/**
|
||||
* 415 Unsupported Media Type.
|
||||
*/
|
||||
kHTTPStatusCodeUnsupportedMediaType = 415,
|
||||
|
||||
/**
|
||||
* 416 Requested Range Not Satisfiable.
|
||||
*/
|
||||
kHTTPStatusCodeRequestedRangeNotSatisfiable = 416,
|
||||
|
||||
/**
|
||||
* 417 Expectation Failed.
|
||||
*/
|
||||
kHTTPStatusCodeExpectationFailed = 417,
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_2324)
|
||||
/**
|
||||
* 418 I'm a teapot (RFC 2324).
|
||||
*/
|
||||
kHTTPStatusCodeImATeapot = 418,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_TWITTER)
|
||||
/**
|
||||
* 420 Enhance Your Calm (Twitter).
|
||||
*/
|
||||
kHTTPStatusCodeEnhanceYourCalm = 420,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_WEBDAV) && !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_4918)
|
||||
/**
|
||||
* 422 Unprocessable Entity (WebDAV; RFC 4918).
|
||||
*/
|
||||
kHTTPStatusCodeUnprocessableEntity = 422,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_WEBDAV) && !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_4918)
|
||||
/**
|
||||
* 423 Locked (WebDAV; RFC 4918).
|
||||
*/
|
||||
kHTTPStatusCodeLocked = 423,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_WEBDAV) && !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_4918)
|
||||
/**
|
||||
* 424 Failed Dependency (WebDAV; RFC 4918).
|
||||
*/
|
||||
kHTTPStatusCodeFailedDependency = 424,
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 425 Unordered Collection (Internet draft).
|
||||
*/
|
||||
kHTTPStatusCodeUnorderedCollection = 425,
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_2817)
|
||||
/**
|
||||
* 426 Upgrade Required (RFC 2817).
|
||||
*/
|
||||
kHTTPStatusCodeUpgradeRequired = 426,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_6585)
|
||||
/**
|
||||
* 428 Precondition Required (RFC 6585).
|
||||
*/
|
||||
kHTTPStatusCodePreconditionRequired = 428,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_6585)
|
||||
/**
|
||||
* 429 Too Many Requests (RFC 6585).
|
||||
*/
|
||||
kHTTPStatusCodeTooManyRequests = 429,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_6585)
|
||||
/**
|
||||
* 431 Request Header Fields Too Large (RFC 6585).
|
||||
*/
|
||||
kHTTPStatusCodeRequestHeaderFieldsTooLarge = 431,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_NGINX)
|
||||
/**
|
||||
* 444 No Response (Nginx).
|
||||
*/
|
||||
kHTTPStatusCodeNoResponse = 444,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_MICROSOFT)
|
||||
/**
|
||||
* 449 Retry With (Microsoft).
|
||||
*/
|
||||
kHTTPStatusCodeRetryWith = 449,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_MICROSOFT)
|
||||
/**
|
||||
* 450 Blocked by Windows Parental Controls (Microsoft).
|
||||
*/
|
||||
kHTTPStatusCodeBlockedByWindowsParentalControls = 450,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 451 Parameter Not Understood (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeParameterNotUnderstood = 451,
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 451 Unavailable For Legal Reasons (Internet draft).
|
||||
*/
|
||||
kHTTPStatusCodeUnavailableForLegalReasons = 451,
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_MICROSOFT)
|
||||
/**
|
||||
* 451 Redirect (Microsoft).
|
||||
*/
|
||||
kHTTPStatusCodeRedirect = 451,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 452 Conference Not Found (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeConferenceNotFound = 452,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 453 Not Enough Bandwidth (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeNotEnoughBandwidth = 453,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 454 Session Not Found (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeSessionNotFound = 454,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 455 Method Not Valid in This State (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeMethodNotValidInThisState = 455,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 456 Header Field Not Valid for Resource (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeHeaderFieldNotValidForResource = 456,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 457 Invalid Range (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeInvalidRange = 457,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 458 Parameter Is Read-Only (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeParameterIsReadOnly = 458,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 459 Aggregate Operation Not Allowed (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeAggregateOperationNotAllowed = 459,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 460 Only Aggregate Operation Allowed (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeOnlyAggregateOperationAllowed = 460,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 461 Unsupported Transport (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeUnsupportedTransport = 461,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 462 Destination Unreachable (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeDestinationUnreachable = 462,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_NGINX)
|
||||
/**
|
||||
* 494 Request Header Too Large (Nginx).
|
||||
*/
|
||||
kHTTPStatusCodeRequestHeaderTooLarge = 494,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_NGINX)
|
||||
/**
|
||||
* 495 Cert Error (Nginx).
|
||||
*/
|
||||
kHTTPStatusCodeCertError = 495,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_NGINX)
|
||||
/**
|
||||
* 496 No Cert (Nginx).
|
||||
*/
|
||||
kHTTPStatusCodeNoCert = 496,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_NGINX)
|
||||
/**
|
||||
* 497 HTTP to HTTPS (Nginx).
|
||||
*/
|
||||
kHTTPStatusCodeHTTPToHTTPS = 497,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_NGINX)
|
||||
/**
|
||||
* 499 Client Closed Request (Nginx).
|
||||
*/
|
||||
kHTTPStatusCodeClientClosedRequest = 499,
|
||||
#endif
|
||||
|
||||
/*--------------------------------------------------
|
||||
* 5xx Server Error
|
||||
*------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* 500 Internal Server Error.
|
||||
*/
|
||||
kHTTPStatusCodeInternalServerError = 500,
|
||||
|
||||
/**
|
||||
* 501 Not Implemented
|
||||
*/
|
||||
kHTTPStatusCodeNotImplemented = 501,
|
||||
|
||||
/**
|
||||
* 502 Bad Gateway.
|
||||
*/
|
||||
kHTTPStatusCodeBadGateway = 502,
|
||||
|
||||
/**
|
||||
* 503 Service Unavailable.
|
||||
*/
|
||||
kHTTPStatusCodeServiceUnavailable = 503,
|
||||
|
||||
/**
|
||||
* 504 Gateway Timeout.
|
||||
*/
|
||||
kHTTPStatusCodeGatewayTimeout = 504,
|
||||
|
||||
/**
|
||||
* 505 HTTP Version Not Supported.
|
||||
*/
|
||||
kHTTPStatusCodeHTTPVersionNotSupported = 505,
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_2295)
|
||||
/**
|
||||
* 506 Variant Also Negotiates (RFC 2295).
|
||||
*/
|
||||
kHTTPStatusCodeVariantAlsoNegotiates = 506,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_WEBDAV) && !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_4918)
|
||||
/**
|
||||
* 507 Insufficient Storage (WebDAV; RFC 4918).
|
||||
*/
|
||||
kHTTPStatusCodeInsufficientStorage = 507,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_WEBDAV) && !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_5842)
|
||||
/**
|
||||
* 508 Loop Detected (WebDAV; RFC 5842).
|
||||
*/
|
||||
kHTTPStatusCodeLoopDetected = 508,
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 509 Bandwidth Limit Exceeded (Apache bw/limited extension).
|
||||
*/
|
||||
kHTTPStatusCodeBandwidthLimitExceeded = 509,
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_2774)
|
||||
/**
|
||||
* 510 Not Extended (RFC 2774).
|
||||
*/
|
||||
kHTTPStatusCodeNotExtended = 510,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RFC_6585)
|
||||
/**
|
||||
* 511 Network Authentication Required (RFC 6585).
|
||||
*/
|
||||
kHTTPStatusCodeNetworkAuthenticationRequired = 511,
|
||||
#endif
|
||||
|
||||
#if !defined(HTTP_STATUS_CODES_EXCLUDE_RTSP)
|
||||
/**
|
||||
* 551 Option not supported (RTSP).
|
||||
*/
|
||||
kHTTPStatusCodeOptionNotSupported = 551,
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 598 Network read timeout error (Unknown).
|
||||
*/
|
||||
kHTTPStatusCodeNetworkReadTimeoutError = 598,
|
||||
|
||||
/**
|
||||
* 599 Network connect timeout error (Unknown).
|
||||
*/
|
||||
kHTTPStatusCodeNetworkConnectTimeoutError = 599
|
||||
}
|
||||
HTTPStatusCode;
|
||||
|
||||
|
||||
#endif
|
||||
29
WebDriverAgentLib/Routing/FBResponseJSONPayload.h
Normal file
29
WebDriverAgentLib/Routing/FBResponseJSONPayload.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#import <WebDriverAgentLib/FBResponsePayload.h>
|
||||
#import <WebDriverAgentLib/FBHTTPStatusCodes.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Class that represents WebDriverAgent JSON repsonse
|
||||
*/
|
||||
@interface FBResponseJSONPayload : NSObject <FBResponsePayload>
|
||||
|
||||
/**
|
||||
Initializer for JSON respond that converts given 'dictionary' to JSON
|
||||
*/
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dictionary
|
||||
httpStatusCode:(HTTPStatusCode)httpStatusCode;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
59
WebDriverAgentLib/Routing/FBResponseJSONPayload.m
Normal file
59
WebDriverAgentLib/Routing/FBResponseJSONPayload.m
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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 "FBResponseJSONPayload.h"
|
||||
|
||||
#import "FBLogger.h"
|
||||
#import "NSDictionary+FBUtf8SafeDictionary.h"
|
||||
#import "RouteResponse.h"
|
||||
|
||||
@interface FBResponseJSONPayload ()
|
||||
|
||||
@property (nonatomic, copy, readonly) NSDictionary *dictionary;
|
||||
@property (nonatomic, readonly) HTTPStatusCode httpStatusCode;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FBResponseJSONPayload
|
||||
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dictionary
|
||||
httpStatusCode:(HTTPStatusCode)httpStatusCode
|
||||
{
|
||||
NSParameterAssert(dictionary);
|
||||
if (!dictionary) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_dictionary = dictionary;
|
||||
_httpStatusCode = httpStatusCode;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dispatchWithResponse:(RouteResponse *)response
|
||||
{
|
||||
NSError *error;
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self.dictionary
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:&error];
|
||||
NSCAssert(jsonData, @"Valid JSON must be responded, error of %@", error);
|
||||
if (nil == [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]) {
|
||||
[FBLogger log:@"The incoming data cannot be encoded to UTF-8 JSON. Applying lossy conversion as a workaround."];
|
||||
jsonData = [NSJSONSerialization dataWithJSONObject:[self.dictionary fb_utf8SafeDictionary]
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:&error];
|
||||
}
|
||||
NSCAssert(jsonData, @"Valid JSON must be responded, error of %@", error);
|
||||
[response setHeader:@"Content-Type" value:@"application/json;charset=UTF-8"];
|
||||
[response setStatusCode:self.httpStatusCode];
|
||||
[response respondWithData:jsonData];
|
||||
}
|
||||
|
||||
@end
|
||||
74
WebDriverAgentLib/Routing/FBResponsePayload.h
Normal file
74
WebDriverAgentLib/Routing/FBResponsePayload.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#import <WebDriverAgentLib/FBCommandStatus.h>
|
||||
|
||||
@class FBElementCache;
|
||||
@class RouteResponse;
|
||||
@class XCUIElement;
|
||||
@protocol FBResponsePayload;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Returns 'FBCommandStatusNoError' response payload
|
||||
*/
|
||||
id<FBResponsePayload> FBResponseWithOK(void);
|
||||
|
||||
/**
|
||||
Returns 'FBCommandStatusNoError' response payload with given 'object'
|
||||
*/
|
||||
id<FBResponsePayload> FBResponseWithObject(id _Nullable object);
|
||||
|
||||
/**
|
||||
Returns 'FBCommandStatusNoError' response payload with given 'element', which will be also cached in 'elementCache'
|
||||
*/
|
||||
id<FBResponsePayload> FBResponseWithCachedElement(XCUIElement *element, FBElementCache *elementCache, BOOL compact);
|
||||
|
||||
/**
|
||||
Returns 'FBCommandStatusNoError' response payload with given array of 'elements', which will be also cached in 'elementCache'
|
||||
*/
|
||||
id<FBResponsePayload> FBResponseWithCachedElements(NSArray<XCUIElement *> *elements, FBElementCache *elementCache, BOOL compact);
|
||||
|
||||
/**
|
||||
Returns 'FBCommandStatusUnhandled' response payload with given error's description
|
||||
*/
|
||||
id<FBResponsePayload> FBResponseWithUnknownError(NSError *error);
|
||||
|
||||
/**
|
||||
Returns 'FBCommandStatusUnhandled' response payload with given error message
|
||||
*/
|
||||
id<FBResponsePayload> FBResponseWithUnknownErrorFormat(NSString *errorFormat, ...) NS_FORMAT_FUNCTION(1,2);
|
||||
|
||||
/**
|
||||
Returns 'status' response payload with given object
|
||||
*/
|
||||
id<FBResponsePayload> FBResponseWithStatus(FBCommandStatus *status);
|
||||
|
||||
/**
|
||||
Returns a response payload as a NSDictionary for given element.
|
||||
Set compact=NO to include further attributes (defined by FBConfiguration.elementResponseAttributes)
|
||||
*/
|
||||
NSDictionary *FBDictionaryResponseWithElement(XCUIElement *element, BOOL compact);
|
||||
|
||||
|
||||
/**
|
||||
Protocol for objects that can dispatch some kind of a payload for given 'response'
|
||||
*/
|
||||
@protocol FBResponsePayload <NSObject>
|
||||
|
||||
/**
|
||||
Dispatch constructed payload into given response
|
||||
*/
|
||||
- (void)dispatchWithResponse:(RouteResponse *)response;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
148
WebDriverAgentLib/Routing/FBResponsePayload.m
Normal file
148
WebDriverAgentLib/Routing/FBResponsePayload.m
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 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 "FBResponsePayload.h"
|
||||
|
||||
#import "FBElementCache.h"
|
||||
#import "FBResponseJSONPayload.h"
|
||||
#import "FBSession.h"
|
||||
#import "FBMathUtils.h"
|
||||
#import "FBConfiguration.h"
|
||||
#import "FBMacros.h"
|
||||
#import "FBProtocolHelpers.h"
|
||||
#import "XCUIElementQuery.h"
|
||||
#import "XCUIElement+FBResolve.h"
|
||||
#import "XCUIElement+FBUID.h"
|
||||
#import "XCUIElement+FBUtilities.h"
|
||||
#import "XCUIElement+FBWebDriverAttributes.h"
|
||||
|
||||
NSString *arbitraryAttrPrefix = @"attribute/";
|
||||
|
||||
id<FBResponsePayload> FBResponseWithOK(void)
|
||||
{
|
||||
return FBResponseWithStatus(FBCommandStatus.ok);
|
||||
}
|
||||
|
||||
id<FBResponsePayload> FBResponseWithObject(id object)
|
||||
{
|
||||
return FBResponseWithStatus([FBCommandStatus okWithValue:object]);
|
||||
}
|
||||
|
||||
XCUIElement *maybeStable(XCUIElement *element)
|
||||
{
|
||||
BOOL useNativeCachingStrategy = nil == FBSession.activeSession
|
||||
? YES
|
||||
: FBSession.activeSession.useNativeCachingStrategy;
|
||||
if (useNativeCachingStrategy) {
|
||||
return element;
|
||||
}
|
||||
|
||||
XCUIElement *result = element;
|
||||
id<FBXCElementSnapshot> snapshot = element.lastSnapshot
|
||||
?: element.fb_cachedSnapshot
|
||||
?: [element fb_standardSnapshot];
|
||||
NSString *uid = [FBXCElementSnapshotWrapper wdUIDWithSnapshot:snapshot];
|
||||
if (nil != uid) {
|
||||
result = [element fb_stableInstanceWithUid:uid];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
id<FBResponsePayload> FBResponseWithCachedElement(XCUIElement *element, FBElementCache *elementCache, BOOL compact)
|
||||
{
|
||||
[elementCache storeElement:maybeStable(element)];
|
||||
NSDictionary *response = FBDictionaryResponseWithElement(element, compact);
|
||||
element.lastSnapshot = nil;
|
||||
return FBResponseWithStatus([FBCommandStatus okWithValue:response]);
|
||||
}
|
||||
|
||||
id<FBResponsePayload> FBResponseWithCachedElements(NSArray<XCUIElement *> *elements, FBElementCache *elementCache, BOOL compact)
|
||||
{
|
||||
NSMutableArray *elementsResponse = [NSMutableArray array];
|
||||
for (XCUIElement *element in elements) {
|
||||
[elementCache storeElement:maybeStable(element)];
|
||||
[elementsResponse addObject:FBDictionaryResponseWithElement(element, compact)];
|
||||
element.lastSnapshot = nil;
|
||||
}
|
||||
return FBResponseWithStatus([FBCommandStatus okWithValue:elementsResponse]);
|
||||
}
|
||||
|
||||
id<FBResponsePayload> FBResponseWithUnknownError(NSError *error)
|
||||
{
|
||||
return FBResponseWithStatus([FBCommandStatus unknownErrorWithMessage:error.description traceback:nil]);
|
||||
}
|
||||
|
||||
id<FBResponsePayload> FBResponseWithUnknownErrorFormat(NSString *format, ...)
|
||||
{
|
||||
va_list argList;
|
||||
va_start(argList, format);
|
||||
NSString *errorMessage = [[NSString alloc] initWithFormat:format arguments:argList];
|
||||
id<FBResponsePayload> payload = FBResponseWithStatus([FBCommandStatus unknownErrorWithMessage:errorMessage
|
||||
traceback:nil]);
|
||||
va_end(argList);
|
||||
return payload;
|
||||
}
|
||||
|
||||
id<FBResponsePayload> FBResponseWithStatus(FBCommandStatus *status)
|
||||
{
|
||||
NSMutableDictionary* response = [NSMutableDictionary dictionary];
|
||||
response[@"sessionId"] = [FBSession activeSession].identifier ?: NSNull.null;
|
||||
if (nil == status.error) {
|
||||
response[@"value"] = status.value ?: NSNull.null;
|
||||
} else {
|
||||
response[@"value"] = @{
|
||||
@"error": (id)status.error,
|
||||
@"message": status.message ?: @"",
|
||||
@"traceback": status.traceback ?: @""
|
||||
};
|
||||
}
|
||||
return [[FBResponseJSONPayload alloc] initWithDictionary:response.copy
|
||||
httpStatusCode:status.statusCode];
|
||||
}
|
||||
|
||||
inline NSDictionary *FBDictionaryResponseWithElement(XCUIElement *element, BOOL compact)
|
||||
{
|
||||
__block NSDictionary *elementResponse = nil;
|
||||
@autoreleasepool {
|
||||
id<FBXCElementSnapshot> snapshot = element.lastSnapshot
|
||||
?: element.fb_cachedSnapshot
|
||||
?: [element fb_customSnapshot];
|
||||
NSDictionary *compactResult = FBToElementDict((NSString *)[FBXCElementSnapshotWrapper wdUIDWithSnapshot:snapshot]);
|
||||
if (compact) {
|
||||
elementResponse = compactResult;
|
||||
return elementResponse;
|
||||
}
|
||||
|
||||
NSMutableDictionary *result = compactResult.mutableCopy;
|
||||
FBXCElementSnapshotWrapper *wrappedSnapshot = [FBXCElementSnapshotWrapper ensureWrapped:snapshot];
|
||||
NSArray *fields = [FBConfiguration.elementResponseAttributes componentsSeparatedByString:@","];
|
||||
for (NSString *field in fields) {
|
||||
// 'name' here is the w3c-approved identifier for what we mean by 'type'
|
||||
if ([field isEqualToString:@"name"] || [field isEqualToString:@"type"]) {
|
||||
result[field] = wrappedSnapshot.wdType;
|
||||
} else if ([field isEqualToString:@"text"]) {
|
||||
result[field] = FBFirstNonEmptyValue(wrappedSnapshot.wdValue, wrappedSnapshot.wdLabel) ?: [NSNull null];
|
||||
} else if ([field isEqualToString:@"rect"]) {
|
||||
result[field] = wrappedSnapshot.wdRect;
|
||||
} else if ([field isEqualToString:@"enabled"]) {
|
||||
result[field] = @(wrappedSnapshot.wdEnabled);
|
||||
} else if ([field isEqualToString:@"displayed"]) {
|
||||
result[field] = @(wrappedSnapshot.wdVisible);
|
||||
} else if ([field isEqualToString:@"selected"]) {
|
||||
result[field] = @(wrappedSnapshot.wdSelected);
|
||||
} else if ([field isEqualToString:@"label"]) {
|
||||
result[field] = wrappedSnapshot.wdLabel ?: [NSNull null];
|
||||
} else if ([field hasPrefix:arbitraryAttrPrefix]) {
|
||||
NSString *attributeName = [field substringFromIndex:[arbitraryAttrPrefix length]];
|
||||
result[field] = [wrappedSnapshot fb_valueForWDAttributeName:attributeName] ?: [NSNull null];
|
||||
}
|
||||
}
|
||||
elementResponse = result.copy;
|
||||
}
|
||||
return elementResponse;
|
||||
}
|
||||
77
WebDriverAgentLib/Routing/FBRoute.h
Normal file
77
WebDriverAgentLib/Routing/FBRoute.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@protocol FBResponsePayload;
|
||||
@class FBRouteRequest;
|
||||
@class RouteResponse;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef __nonnull id<FBResponsePayload> (^FBRouteSyncHandler)(FBRouteRequest *request);
|
||||
|
||||
/**
|
||||
Class that represents route
|
||||
*/
|
||||
@interface FBRoute : NSObject
|
||||
|
||||
/*! Route's verb (eg. POST, GET, DELETE) */
|
||||
@property (nonatomic, copy, readonly) NSString *verb;
|
||||
|
||||
/*! Route's path */
|
||||
@property (nonatomic, copy, readonly) NSString *path;
|
||||
|
||||
/**
|
||||
Convenience constructor for GET route with given pathPattern
|
||||
*/
|
||||
+ (instancetype)GET:(NSString *)pathPattern;
|
||||
|
||||
/**
|
||||
Convenience constructor for POST route with given pathPattern
|
||||
*/
|
||||
+ (instancetype)POST:(NSString *)pathPattern;
|
||||
|
||||
/**
|
||||
Convenience constructor for PUT route with given pathPattern
|
||||
*/
|
||||
+ (instancetype)PUT:(NSString *)pathPattern;
|
||||
|
||||
/**
|
||||
Convenience constructor for DELETE route with given pathPattern
|
||||
*/
|
||||
+ (instancetype)DELETE:(NSString *)pathPattern;
|
||||
|
||||
/**
|
||||
Convenience constructor for OPTIONS route with given pathPattern
|
||||
*/
|
||||
+ (instancetype)OPTIONS:(NSString *)pathPattern;
|
||||
|
||||
/**
|
||||
Chain-able constructor that handles response with given FBRouteSyncHandler block
|
||||
*/
|
||||
- (instancetype)respondWithBlock:(FBRouteSyncHandler)handler;
|
||||
|
||||
/**
|
||||
Chain-able constructor that handles response with given FBRouteSyncHandler block
|
||||
*/
|
||||
- (instancetype)respondWithTarget:(id)target action:(SEL)selector;
|
||||
|
||||
/**
|
||||
Chain-able constructor for route that does NOT require session
|
||||
*/
|
||||
- (instancetype)withoutSession;
|
||||
|
||||
/**
|
||||
Dispatches response for request
|
||||
*/
|
||||
- (void)mountRequest:(FBRouteRequest *)request intoResponse:(RouteResponse *)response;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
174
WebDriverAgentLib/Routing/FBRoute.m
Normal file
174
WebDriverAgentLib/Routing/FBRoute.m
Normal file
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* 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 "FBRoute.h"
|
||||
#import "FBRouteRequest-Private.h"
|
||||
|
||||
#import <objc/message.h>
|
||||
|
||||
#import "FBExceptionHandler.h"
|
||||
#import "FBExceptions.h"
|
||||
#import "FBResponsePayload.h"
|
||||
#import "FBSession.h"
|
||||
|
||||
@interface FBRoute ()
|
||||
@property (nonatomic, assign, readwrite) BOOL requiresSession;
|
||||
@property (nonatomic, copy, readwrite) NSString *verb;
|
||||
@property (nonatomic, copy, readwrite) NSString *path;
|
||||
|
||||
- (void)decorateRequest:(FBRouteRequest *)request;
|
||||
|
||||
@end
|
||||
|
||||
static NSString *const FBRouteSessionPrefix = @"/session/:sessionID";
|
||||
|
||||
@interface FBRoute_TargetAction : FBRoute
|
||||
@property (nonatomic, strong, readwrite) id target;
|
||||
@property (nonatomic, assign, readwrite) SEL action;
|
||||
@end
|
||||
|
||||
|
||||
@implementation FBRoute_TargetAction
|
||||
|
||||
- (void)mountRequest:(FBRouteRequest *)request intoResponse:(RouteResponse *)response
|
||||
{
|
||||
[self decorateRequest:request];
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wcast-function-type-strict"
|
||||
id<FBResponsePayload> (*requestMsgSend)(id, SEL, FBRouteRequest *) = ((id<FBResponsePayload>(*)(id, SEL, FBRouteRequest *))objc_msgSend);
|
||||
#pragma clang diagnostic pop
|
||||
id<FBResponsePayload> payload = requestMsgSend(self.target, self.action, request);
|
||||
[payload dispatchWithResponse:response];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface FBRoute_Sync : FBRoute
|
||||
@property (nonatomic, copy, readwrite) FBRouteSyncHandler handler;
|
||||
@end
|
||||
|
||||
|
||||
@implementation FBRoute_Sync
|
||||
|
||||
- (void)mountRequest:(FBRouteRequest *)request intoResponse:(RouteResponse *)response
|
||||
{
|
||||
[self decorateRequest:request];
|
||||
id<FBResponsePayload> payload = self.handler(request);
|
||||
[payload dispatchWithResponse:response];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation FBRoute
|
||||
|
||||
+ (instancetype)withVerb:(NSString *)verb path:(NSString *)pathPattern requiresSession:(BOOL)requiresSession
|
||||
{
|
||||
FBRoute *route = [self new];
|
||||
route.verb = verb;
|
||||
route.path = [FBRoute pathPatternWithSession:pathPattern requiresSession:requiresSession];
|
||||
route.requiresSession = requiresSession;
|
||||
return route;
|
||||
}
|
||||
|
||||
+ (instancetype)OPTIONS:(NSString *)pathPattern
|
||||
{
|
||||
return [self withVerb:@"OPTIONS" path:pathPattern requiresSession:NO];
|
||||
}
|
||||
|
||||
+ (instancetype)GET:(NSString *)pathPattern
|
||||
{
|
||||
return [self withVerb:@"GET" path:pathPattern requiresSession:YES];
|
||||
}
|
||||
|
||||
+ (instancetype)POST:(NSString *)pathPattern
|
||||
{
|
||||
return [self withVerb:@"POST" path:pathPattern requiresSession:YES];
|
||||
}
|
||||
|
||||
+ (instancetype)PUT:(NSString *)pathPattern
|
||||
{
|
||||
return [self withVerb:@"PUT" path:pathPattern requiresSession:YES];
|
||||
}
|
||||
|
||||
+ (instancetype)DELETE:(NSString *)pathPattern
|
||||
{
|
||||
return [self withVerb:@"DELETE" path:pathPattern requiresSession:YES];
|
||||
}
|
||||
|
||||
+ (NSString *)pathPatternWithSession:(NSString *)pathPattern requiresSession:(BOOL)requiresSession
|
||||
{
|
||||
NSRange range = [pathPattern rangeOfString:FBRouteSessionPrefix];
|
||||
if (requiresSession) {
|
||||
if (range.location != 0) {
|
||||
pathPattern = [FBRouteSessionPrefix stringByAppendingPathComponent:pathPattern];
|
||||
}
|
||||
} else {
|
||||
if (range.location == 0) {
|
||||
pathPattern = [pathPattern stringByReplacingCharactersInRange:range withString:@""];
|
||||
}
|
||||
}
|
||||
if (pathPattern.length == 0) {
|
||||
pathPattern = @"/";
|
||||
}
|
||||
return pathPattern;
|
||||
}
|
||||
|
||||
- (instancetype)withoutSession
|
||||
{
|
||||
self.requiresSession = NO;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)respondWithBlock:(FBRouteSyncHandler)handler
|
||||
{
|
||||
FBRoute_Sync *route = [FBRoute_Sync withVerb:self.verb path:self.path requiresSession:self.requiresSession];
|
||||
route.handler = handler;
|
||||
return route;
|
||||
}
|
||||
|
||||
- (instancetype)respondWithTarget:(id)target action:(SEL)action
|
||||
{
|
||||
FBRoute_TargetAction *route = [FBRoute_TargetAction withVerb:self.verb path:self.path requiresSession:self.requiresSession];
|
||||
route.target = target;
|
||||
route.action = action;
|
||||
return route;
|
||||
}
|
||||
|
||||
- (void)decorateRequest:(FBRouteRequest *)request
|
||||
{
|
||||
if (!self.requiresSession) {
|
||||
return;
|
||||
}
|
||||
NSString *sessionID = request.parameters[@"sessionID"];
|
||||
if (!sessionID) {
|
||||
[self raiseNoSessionException];
|
||||
return;
|
||||
}
|
||||
FBSession *session = [FBSession sessionWithIdentifier:sessionID];
|
||||
if (!session) {
|
||||
[self raiseNoSessionException];
|
||||
return;
|
||||
}
|
||||
request.session = session;
|
||||
}
|
||||
|
||||
- (void)raiseNoSessionException
|
||||
{
|
||||
[[NSException exceptionWithName:FBSessionDoesNotExistException reason:@"Session does not exist" userInfo:nil] raise];
|
||||
}
|
||||
|
||||
- (void)mountRequest:(FBRouteRequest *)request intoResponse:(RouteResponse *)response
|
||||
{
|
||||
id<FBResponsePayload> payload = FBResponseWithStatus([FBCommandStatus unknownCommandErrorWithMessage:@"Unhandled route"
|
||||
traceback:[NSString stringWithFormat:@"%@", NSThread.callStackSymbols]]);
|
||||
[payload dispatchWithResponse:response];
|
||||
}
|
||||
|
||||
@end
|
||||
20
WebDriverAgentLib/Routing/FBRouteRequest-Private.h
Normal file
20
WebDriverAgentLib/Routing/FBRouteRequest-Private.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 <WebDriverAgentLib/FBRouteRequest.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FBRouteRequest ()
|
||||
@property (nonatomic, strong, readwrite) NSURL *URL;
|
||||
@property (nonatomic, copy, readwrite) NSDictionary *parameters;
|
||||
@property (nonatomic, copy, readwrite) NSDictionary *arguments;
|
||||
@property (nonatomic, strong, readwrite) FBSession *session;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
39
WebDriverAgentLib/Routing/FBRouteRequest.h
Normal file
39
WebDriverAgentLib/Routing/FBRouteRequest.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@class FBSession;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Class that represents WebDriverAgent command request
|
||||
*/
|
||||
@interface FBRouteRequest : NSObject
|
||||
|
||||
/*! Request's URL */
|
||||
@property (nonatomic, strong, readonly) NSURL *URL;
|
||||
|
||||
/*! Parameters sent with that request */
|
||||
@property (nonatomic, copy, readonly) NSDictionary *parameters;
|
||||
|
||||
/*! Arguments sent with that request */
|
||||
@property (nonatomic, copy, readonly) NSDictionary *arguments;
|
||||
|
||||
/*! Session associated with that request */
|
||||
@property (nonatomic, strong, readonly) FBSession *session;
|
||||
|
||||
/**
|
||||
Convenience constructor for request
|
||||
*/
|
||||
+ (instancetype)routeRequestWithURL:(NSURL *)URL parameters:(NSDictionary *)parameters arguments:(NSDictionary *)arguments;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
32
WebDriverAgentLib/Routing/FBRouteRequest.m
Normal file
32
WebDriverAgentLib/Routing/FBRouteRequest.m
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 "FBRouteRequest-Private.h"
|
||||
|
||||
@implementation FBRouteRequest
|
||||
|
||||
+ (instancetype)routeRequestWithURL:(NSURL *)URL parameters:(NSDictionary *)parameters arguments:(NSDictionary *)arguments
|
||||
{
|
||||
FBRouteRequest *request = [self.class new];
|
||||
request.URL = URL;
|
||||
request.parameters = parameters;
|
||||
request.arguments = arguments;
|
||||
return request;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:
|
||||
@"Request URL %@ | Params %@ | Arguments %@",
|
||||
self.URL,
|
||||
self.parameters,
|
||||
self.arguments
|
||||
];
|
||||
}
|
||||
|
||||
@end
|
||||
56
WebDriverAgentLib/Routing/FBScreenRecordingContainer.h
Normal file
56
WebDriverAgentLib/Routing/FBScreenRecordingContainer.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class FBScreenRecordingPromise;
|
||||
|
||||
@interface FBScreenRecordingContainer : NSObject
|
||||
|
||||
/** The amount of video FPS */
|
||||
@property (readonly, nonatomic) NSUInteger fps;
|
||||
/** Codec to use, where 0 is h264, 1 - HEVC */
|
||||
@property (readonly, nonatomic) long long codec;
|
||||
/** Keep the currently active screen resording promise. Equals to nil if no active screen recordings are running */
|
||||
@property (readonly, nonatomic, nullable) FBScreenRecordingPromise* screenRecordingPromise;
|
||||
/** The timestamp of the video startup as Unix float seconds */
|
||||
@property (readonly, nonatomic, nullable) NSNumber *startedAt;
|
||||
|
||||
/**
|
||||
@return singleton instance
|
||||
*/
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/**
|
||||
Keeps current screen recording promise
|
||||
|
||||
@param screenRecordingPromise a promise to set
|
||||
@param fps FPS value
|
||||
@param codec Codec value
|
||||
*/
|
||||
- (void)storeScreenRecordingPromise:(FBScreenRecordingPromise *)screenRecordingPromise
|
||||
fps:(NSUInteger)fps
|
||||
codec:(long long)codec;
|
||||
/**
|
||||
Resets the current screen recording promise
|
||||
*/
|
||||
- (void)reset;
|
||||
|
||||
/**
|
||||
Transforms the container content to a dictionary.
|
||||
|
||||
@return May return nil if no screen recording is currently running
|
||||
*/
|
||||
- (nullable NSDictionary *)toDictionary;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
72
WebDriverAgentLib/Routing/FBScreenRecordingContainer.m
Normal file
72
WebDriverAgentLib/Routing/FBScreenRecordingContainer.m
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
*
|
||||
* 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 "FBScreenRecordingContainer.h"
|
||||
|
||||
#import "FBScreenRecordingPromise.h"
|
||||
|
||||
@interface FBScreenRecordingContainer ()
|
||||
|
||||
@property (readwrite) NSUInteger fps;
|
||||
@property (readwrite) long long codec;
|
||||
@property (readwrite) FBScreenRecordingPromise* screenRecordingPromise;
|
||||
@property (readwrite) NSNumber *startedAt;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FBScreenRecordingContainer
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static FBScreenRecordingContainer *instance;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[self alloc] init];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (void)storeScreenRecordingPromise:(FBScreenRecordingPromise *)screenRecordingPromise
|
||||
fps:(NSUInteger)fps
|
||||
codec:(long long)codec;
|
||||
{
|
||||
self.fps = fps;
|
||||
self.codec = codec;
|
||||
self.screenRecordingPromise = screenRecordingPromise;
|
||||
self.startedAt = @([NSDate.date timeIntervalSince1970]);
|
||||
}
|
||||
|
||||
- (void)reset;
|
||||
{
|
||||
self.fps = 0;
|
||||
self.codec = 0;
|
||||
if (nil != self.screenRecordingPromise) {
|
||||
[XCTContext runActivityNamed:@"Video Cleanup" block:^(id<XCTActivity> activity){
|
||||
[activity addAttachment:(XCTAttachment *)self.screenRecordingPromise.nativePromise];
|
||||
}];
|
||||
self.screenRecordingPromise = nil;
|
||||
}
|
||||
self.startedAt = nil;
|
||||
}
|
||||
|
||||
- (nullable NSDictionary *)toDictionary
|
||||
{
|
||||
if (nil == self.screenRecordingPromise) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return @{
|
||||
@"fps": @(self.fps),
|
||||
@"codec": @(self.codec),
|
||||
@"uuid": [self.screenRecordingPromise identifier].UUIDString ?: [NSNull null],
|
||||
@"startedAt": self.startedAt ?: [NSNull null],
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
29
WebDriverAgentLib/Routing/FBScreenRecordingPromise.h
Normal file
29
WebDriverAgentLib/Routing/FBScreenRecordingPromise.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 <XCTest/XCTest.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FBScreenRecordingPromise : NSObject
|
||||
|
||||
/** Unique identiifier of the video recording, also used as the default file name */
|
||||
@property (nonatomic, readonly) NSUUID *identifier;
|
||||
/** Native screen recording promise */
|
||||
@property (nonatomic, readonly) id nativePromise;
|
||||
|
||||
/**
|
||||
Creates a wrapper object for a native screen recording promise
|
||||
|
||||
@param promise Native promise object to be wrapped
|
||||
*/
|
||||
- (instancetype)initWithNativePromise:(id)promise;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
31
WebDriverAgentLib/Routing/FBScreenRecordingPromise.m
Normal file
31
WebDriverAgentLib/Routing/FBScreenRecordingPromise.m
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
*
|
||||
* 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 "FBScreenRecordingPromise.h"
|
||||
|
||||
@interface FBScreenRecordingPromise ()
|
||||
@property (readwrite) id nativePromise;
|
||||
@end
|
||||
|
||||
@implementation FBScreenRecordingPromise
|
||||
|
||||
- (instancetype)initWithNativePromise:(id)promise
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
self.nativePromise = promise;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUUID *)identifier
|
||||
{
|
||||
return (NSUUID *)[self.nativePromise valueForKey:@"_UUID"];
|
||||
}
|
||||
|
||||
@end
|
||||
39
WebDriverAgentLib/Routing/FBScreenRecordingRequest.h
Normal file
39
WebDriverAgentLib/Routing/FBScreenRecordingRequest.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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 <XCTest/XCTest.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FBScreenRecordingRequest : NSObject
|
||||
|
||||
/** The amount of video FPS */
|
||||
@property (readonly, nonatomic) NSUInteger fps;
|
||||
/** Codec to use, where 0 is h264, 1 - HEVC */
|
||||
@property (readonly, nonatomic) long long codec;
|
||||
|
||||
/**
|
||||
Creates a custom wrapper for a screen recording reqeust
|
||||
|
||||
@param fps FPS value, see baove
|
||||
@param codec Codex value, see above
|
||||
*/
|
||||
- (instancetype)initWithFps:(NSUInteger)fps codec:(long long)codec;
|
||||
|
||||
/**
|
||||
Transforms the current wrapper instance to a native object,
|
||||
which is ready to be passed to XCTest APIs
|
||||
|
||||
@param error If there was a failure converting the instance to a native object
|
||||
@returns Native object instance
|
||||
*/
|
||||
- (nullable id)toNativeRequestWithError:(NSError **)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
94
WebDriverAgentLib/Routing/FBScreenRecordingRequest.m
Normal file
94
WebDriverAgentLib/Routing/FBScreenRecordingRequest.m
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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 "FBScreenRecordingRequest.h"
|
||||
|
||||
#import "FBErrorBuilder.h"
|
||||
#import "XCUIScreen.h"
|
||||
|
||||
@implementation FBScreenRecordingRequest
|
||||
|
||||
- (instancetype)initWithFps:(NSUInteger)fps codec:(long long)codec
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_fps = fps;
|
||||
_codec = codec;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable id)createVideoEncodingWithError:(NSError **)error
|
||||
{
|
||||
Class videoEncodingClass = NSClassFromString(@"XCTVideoEncoding");
|
||||
if (nil == videoEncodingClass) {
|
||||
[[[FBErrorBuilder builder]
|
||||
withDescription:@"Cannot find XCTVideoEncoding class"]
|
||||
buildError:error];
|
||||
return nil;
|
||||
}
|
||||
|
||||
id videoEncodingAllocated = [videoEncodingClass alloc];
|
||||
SEL videoEncodingConstructorSelector = NSSelectorFromString(@"initWithCodec:frameRate:");
|
||||
if (![videoEncodingAllocated respondsToSelector:videoEncodingConstructorSelector]) {
|
||||
[[[FBErrorBuilder builder]
|
||||
withDescription:@"'initWithCodec:frameRate:' contructor is not found on XCTVideoEncoding class"]
|
||||
buildError:error];
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMethodSignature *videoEncodingContructorSignature = [videoEncodingAllocated methodSignatureForSelector:videoEncodingConstructorSelector];
|
||||
NSInvocation *videoEncodingInitInvocation = [NSInvocation invocationWithMethodSignature:videoEncodingContructorSignature];
|
||||
[videoEncodingInitInvocation setSelector:videoEncodingConstructorSelector];
|
||||
long long codec = self.codec;
|
||||
[videoEncodingInitInvocation setArgument:&codec atIndex:2];
|
||||
double frameRate = self.fps;
|
||||
[videoEncodingInitInvocation setArgument:&frameRate atIndex:3];
|
||||
[videoEncodingInitInvocation invokeWithTarget:videoEncodingAllocated];
|
||||
id __unsafe_unretained result;
|
||||
[videoEncodingInitInvocation getReturnValue:&result];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (id)toNativeRequestWithError:(NSError **)error
|
||||
{
|
||||
Class screenRecordingRequestClass = NSClassFromString(@"XCTScreenRecordingRequest");
|
||||
if (nil == screenRecordingRequestClass) {
|
||||
[[[FBErrorBuilder builder]
|
||||
withDescription:@"Cannot find XCTScreenRecordingRequest class"]
|
||||
buildError:error];
|
||||
return nil;
|
||||
}
|
||||
|
||||
id screenRecordingRequestAllocated = [screenRecordingRequestClass alloc];
|
||||
SEL screenRecordingRequestConstructorSelector = NSSelectorFromString(@"initWithScreenID:rect:preferredEncoding:");
|
||||
if (![screenRecordingRequestAllocated respondsToSelector:screenRecordingRequestConstructorSelector]) {
|
||||
[[[FBErrorBuilder builder]
|
||||
withDescription:@"'initWithScreenID:rect:preferredEncoding:' contructor is not found on XCTScreenRecordingRequest class"]
|
||||
buildError:error];
|
||||
return nil;
|
||||
}
|
||||
id videoEncoding = [self createVideoEncodingWithError:error];
|
||||
if (nil == videoEncoding) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMethodSignature *screenRecordingRequestContructorSignature = [screenRecordingRequestAllocated methodSignatureForSelector:screenRecordingRequestConstructorSelector];
|
||||
NSInvocation *screenRecordingRequestInitInvocation = [NSInvocation invocationWithMethodSignature:screenRecordingRequestContructorSignature];
|
||||
[screenRecordingRequestInitInvocation setSelector:screenRecordingRequestConstructorSelector];
|
||||
long long mainScreenId = XCUIScreen.mainScreen.displayID;
|
||||
[screenRecordingRequestInitInvocation setArgument:&mainScreenId atIndex:2];
|
||||
CGRect fullScreenRect = CGRectNull;
|
||||
[screenRecordingRequestInitInvocation setArgument:&fullScreenRect atIndex:3];
|
||||
[screenRecordingRequestInitInvocation setArgument:&videoEncoding atIndex:4];
|
||||
[screenRecordingRequestInitInvocation invokeWithTarget:screenRecordingRequestAllocated];
|
||||
id __unsafe_unretained result;
|
||||
[screenRecordingRequestInitInvocation getReturnValue:&result];
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
26
WebDriverAgentLib/Routing/FBSession-Private.h
Normal file
26
WebDriverAgentLib/Routing/FBSession-Private.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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 <WebDriverAgentLib/FBSession.h>
|
||||
|
||||
@class FBElementCache;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FBSession ()
|
||||
@property (nonatomic, copy, readwrite) NSString *identifier;
|
||||
@property (nonatomic, strong, readwrite) FBElementCache *elementCache;
|
||||
|
||||
/**
|
||||
Sets session as current session
|
||||
*/
|
||||
+ (void)markSessionActive:(FBSession *)session;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
141
WebDriverAgentLib/Routing/FBSession.h
Normal file
141
WebDriverAgentLib/Routing/FBSession.h
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@class FBElementCache;
|
||||
@class XCUIApplication;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** Bundle identifier of Mobile Safari browser */
|
||||
extern NSString* const FB_SAFARI_BUNDLE_ID;
|
||||
|
||||
/**
|
||||
Class that represents testing session
|
||||
*/
|
||||
@interface FBSession : NSObject
|
||||
|
||||
/*! Application tested during that session */
|
||||
@property (nonatomic, readonly) XCUIApplication *activeApplication;
|
||||
|
||||
/*! Session's identifier */
|
||||
@property (nonatomic, readonly) NSString *identifier;
|
||||
|
||||
/*! Element cache related to that session */
|
||||
@property (nonatomic, readonly) FBElementCache *elementCache;
|
||||
|
||||
/*! The identifier of the active application */
|
||||
@property (nonatomic) NSString *defaultActiveApplication;
|
||||
|
||||
/*! The action to apply to unexpected alerts. Either "accept"/"dismiss" or nil/empty string (by default) to do nothing */
|
||||
@property (nonatomic, nullable) NSString *defaultAlertAction;
|
||||
|
||||
/*! Whether to use the native caching strategy for elements or the custom one: https://discuss.appium.io/t/elements-state-coming-from-xpath-vs-ios-predicate-string/34016 */
|
||||
@property (nonatomic) BOOL useNativeCachingStrategy;
|
||||
|
||||
/*! Keeps cached visibility values for the current snapshots tree */
|
||||
@property (nonatomic, readonly) NSMutableDictionary<NSNumber *, NSMutableDictionary<NSString *, NSNumber *> *> *elementsVisibilityCache;
|
||||
|
||||
+ (nullable instancetype)activeSession;
|
||||
|
||||
/**
|
||||
Fetches session for given identifier.
|
||||
If identifier doesn't match activeSession identifier, will return nil.
|
||||
|
||||
@param identifier Identifier for searched session
|
||||
@return session. Can return nil if session does not exists
|
||||
*/
|
||||
+ (nullable instancetype)sessionWithIdentifier:(NSString *)identifier;
|
||||
|
||||
/**
|
||||
Creates and saves new session for application
|
||||
|
||||
@param application The application that we want to create session for
|
||||
@return new session
|
||||
*/
|
||||
+ (instancetype)initWithApplication:(nullable XCUIApplication *)application;
|
||||
|
||||
/**
|
||||
Creates and saves new session for application with default alert handling behaviour
|
||||
|
||||
@param application The application that we want to create session for
|
||||
@param defaultAlertAction The default reaction to on-screen alert. Either 'accept' or 'dismiss'
|
||||
@return new session
|
||||
*/
|
||||
+ (instancetype)initWithApplication:(nullable XCUIApplication *)application
|
||||
defaultAlertAction:(NSString *)defaultAlertAction;
|
||||
|
||||
/**
|
||||
Kills application associated with that session and removes session
|
||||
*/
|
||||
- (void)kill;
|
||||
|
||||
/**
|
||||
Launch an application with given bundle identifier in scope of current session.
|
||||
!This method is only available since Xcode9 SDK
|
||||
|
||||
@param bundleIdentifier Valid bundle identifier of the application to be launched
|
||||
@param shouldWaitForQuiescence whether to wait for quiescence on application startup
|
||||
@param arguments The optional array of application command line arguments. The arguments are going to be applied if the application was not running before.
|
||||
@param environment The optional dictionary of environment variables for the application, which is going to be executed. The environment variables are going to be applied if the application was not running before.
|
||||
@return The application instance
|
||||
*/
|
||||
- (XCUIApplication *)launchApplicationWithBundleId:(NSString *)bundleIdentifier
|
||||
shouldWaitForQuiescence:(nullable NSNumber *)shouldWaitForQuiescence
|
||||
arguments:(nullable NSArray<NSString *> *)arguments
|
||||
environment:(nullable NSDictionary <NSString *, NSString *> *)environment;
|
||||
|
||||
/**
|
||||
Activate an application with given bundle identifier in scope of current session.
|
||||
!This method is only available since Xcode9 SDK
|
||||
|
||||
@param bundleIdentifier Valid bundle identifier of the application to be activated
|
||||
@return The application instance
|
||||
*/
|
||||
- (XCUIApplication *)activateApplicationWithBundleId:(NSString *)bundleIdentifier;
|
||||
|
||||
/**
|
||||
Terminate an application with the given bundle id. The application should be previously
|
||||
executed by launchApplicationWithBundleId method or passed to the init method.
|
||||
|
||||
@param bundleIdentifier Valid bundle identifier of the application to be terminated
|
||||
@return Either YES if the app has been successfully terminated or NO if it was not running
|
||||
*/
|
||||
- (BOOL)terminateApplicationWithBundleId:(NSString *)bundleIdentifier;
|
||||
|
||||
/**
|
||||
Get the state of the particular application in scope of the current session.
|
||||
!This method is only returning reliable results since Xcode9 SDK
|
||||
|
||||
@param bundleIdentifier Valid bundle identifier of the application to get the state from
|
||||
@return Application state as integer number. See
|
||||
https://developer.apple.com/documentation/xctest/xcuiapplicationstate?language=objc
|
||||
for more details on possible enum values
|
||||
*/
|
||||
- (NSUInteger)applicationStateWithBundleId:(NSString *)bundleIdentifier;
|
||||
|
||||
/**
|
||||
Allows to enable automated session alerts monitoring.
|
||||
Repeated calls are ignored if alerts monitoring has been already enabled.
|
||||
|
||||
@returns YES if the actual alerts monitoring state has been changed
|
||||
*/
|
||||
- (BOOL)enableAlertsMonitor;
|
||||
|
||||
/**
|
||||
Allows to disable automated alerts monitoring
|
||||
Repeated calls are ignored if alerts monitoring has been already disabled.
|
||||
|
||||
@returns YES if the actual alerts monitoring state has been changed
|
||||
*/
|
||||
- (BOOL)disableAlertsMonitor;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
295
WebDriverAgentLib/Routing/FBSession.m
Normal file
295
WebDriverAgentLib/Routing/FBSession.m
Normal file
@@ -0,0 +1,295 @@
|
||||
/**
|
||||
* 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 "FBSession.h"
|
||||
#import "FBSession-Private.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import "FBXCAccessibilityElement.h"
|
||||
#import "FBAlertsMonitor.h"
|
||||
#import "FBConfiguration.h"
|
||||
#import "FBElementCache.h"
|
||||
#import "FBExceptions.h"
|
||||
#import "FBMacros.h"
|
||||
#import "FBScreenRecordingContainer.h"
|
||||
#import "FBScreenRecordingPromise.h"
|
||||
#import "FBScreenRecordingRequest.h"
|
||||
#import "FBXCodeCompatibility.h"
|
||||
#import "FBXCTestDaemonsProxy.h"
|
||||
#import "XCUIApplication+FBQuiescence.h"
|
||||
#import "XCUIElement.h"
|
||||
#import "XCUIElement+FBClassChain.h"
|
||||
|
||||
/*!
|
||||
The intial value for the default application property.
|
||||
Setting this value to `defaultActiveApplication` property forces WDA to use the internal
|
||||
automated algorithm to determine the active on-screen application
|
||||
*/
|
||||
NSString *const FBDefaultApplicationAuto = @"auto";
|
||||
|
||||
NSString *const FB_SAFARI_BUNDLE_ID = @"com.apple.mobilesafari";
|
||||
|
||||
@interface FBSession ()
|
||||
@property (nullable, nonatomic) XCUIApplication *testedApplication;
|
||||
@property (nonatomic) BOOL isTestedApplicationExpectedToRun;
|
||||
@property (nonatomic) BOOL shouldAppsWaitForQuiescence;
|
||||
@property (nonatomic, nullable) FBAlertsMonitor *alertsMonitor;
|
||||
@property (nonatomic, readwrite) NSMutableDictionary<NSNumber *, NSMutableDictionary<NSString *, NSNumber *> *> *elementsVisibilityCache;
|
||||
@end
|
||||
|
||||
@interface FBSession (FBAlertsMonitorDelegate)
|
||||
|
||||
- (void)didDetectAlert:(FBAlert *)alert;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FBSession (FBAlertsMonitorDelegate)
|
||||
|
||||
- (void)didDetectAlert:(FBAlert *)alert
|
||||
{
|
||||
NSString *autoClickAlertSelector = FBConfiguration.autoClickAlertSelector;
|
||||
if ([autoClickAlertSelector length] > 0) {
|
||||
@try {
|
||||
NSArray<XCUIElement*> *matches = [alert.alertElement fb_descendantsMatchingClassChain:autoClickAlertSelector
|
||||
shouldReturnAfterFirstMatch:YES];
|
||||
if (matches.count > 0) {
|
||||
[[matches objectAtIndex:0] tap];
|
||||
}
|
||||
} @catch (NSException *e) {
|
||||
[FBLogger logFmt:@"Could not click at the alert element '%@'. Original error: %@",
|
||||
autoClickAlertSelector, e.description];
|
||||
}
|
||||
// This setting has priority over other settings if enabled
|
||||
return;
|
||||
}
|
||||
|
||||
if (nil == self.defaultAlertAction || 0 == self.defaultAlertAction.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
if ([self.defaultAlertAction isEqualToString:@"accept"]) {
|
||||
if (![alert acceptWithError:&error]) {
|
||||
[FBLogger logFmt:@"Cannot accept the alert. Original error: %@", error.description];
|
||||
}
|
||||
} else if ([self.defaultAlertAction isEqualToString:@"dismiss"]) {
|
||||
if (![alert dismissWithError:&error]) {
|
||||
[FBLogger logFmt:@"Cannot dismiss the alert. Original error: %@", error.description];
|
||||
}
|
||||
} else {
|
||||
[FBLogger logFmt:@"'%@' default alert action is unsupported", self.defaultAlertAction];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation FBSession
|
||||
|
||||
static FBSession *_activeSession = nil;
|
||||
|
||||
+ (instancetype)activeSession
|
||||
{
|
||||
return _activeSession;
|
||||
}
|
||||
|
||||
+ (void)markSessionActive:(FBSession *)session
|
||||
{
|
||||
if (_activeSession) {
|
||||
[_activeSession kill];
|
||||
}
|
||||
_activeSession = session;
|
||||
}
|
||||
|
||||
+ (instancetype)sessionWithIdentifier:(NSString *)identifier
|
||||
{
|
||||
if (!identifier) {
|
||||
return nil;
|
||||
}
|
||||
if (![identifier isEqualToString:_activeSession.identifier]) {
|
||||
return nil;
|
||||
}
|
||||
return _activeSession;
|
||||
}
|
||||
|
||||
+ (instancetype)initWithApplication:(XCUIApplication *)application
|
||||
{
|
||||
FBSession *session = [FBSession new];
|
||||
session.useNativeCachingStrategy = YES;
|
||||
session.alertsMonitor = nil;
|
||||
session.defaultAlertAction = nil;
|
||||
session.elementsVisibilityCache = [NSMutableDictionary dictionary];
|
||||
session.identifier = [[NSUUID UUID] UUIDString];
|
||||
session.defaultActiveApplication = FBDefaultApplicationAuto;
|
||||
session.testedApplication = nil;
|
||||
session.isTestedApplicationExpectedToRun = nil != application && application.running;
|
||||
if (application) {
|
||||
session.testedApplication = application;
|
||||
session.shouldAppsWaitForQuiescence = application.fb_shouldWaitForQuiescence;
|
||||
}
|
||||
session.elementCache = [FBElementCache new];
|
||||
[FBSession markSessionActive:session];
|
||||
return session;
|
||||
}
|
||||
|
||||
+ (instancetype)initWithApplication:(nullable XCUIApplication *)application
|
||||
defaultAlertAction:(NSString *)defaultAlertAction
|
||||
{
|
||||
FBSession *session = [self.class initWithApplication:application];
|
||||
session.defaultAlertAction = [defaultAlertAction lowercaseString];
|
||||
[session enableAlertsMonitor];
|
||||
return session;
|
||||
}
|
||||
|
||||
- (BOOL)enableAlertsMonitor
|
||||
{
|
||||
if (nil != self.alertsMonitor) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
self.alertsMonitor = [[FBAlertsMonitor alloc] init];
|
||||
self.alertsMonitor.delegate = (id<FBAlertsMonitorDelegate>)self;
|
||||
[self.alertsMonitor enable];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)disableAlertsMonitor
|
||||
{
|
||||
if (nil == self.alertsMonitor) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self.alertsMonitor disable];
|
||||
self.alertsMonitor = nil;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)kill
|
||||
{
|
||||
if (nil == _activeSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self disableAlertsMonitor];
|
||||
|
||||
FBScreenRecordingPromise *activeScreenRecording = FBScreenRecordingContainer.sharedInstance.screenRecordingPromise;
|
||||
if (nil != activeScreenRecording) {
|
||||
NSError *error;
|
||||
if (![FBXCTestDaemonsProxy stopScreenRecordingWithUUID:activeScreenRecording.identifier error:&error]) {
|
||||
[FBLogger logFmt:@"%@", error];
|
||||
}
|
||||
[FBScreenRecordingContainer.sharedInstance reset];
|
||||
}
|
||||
|
||||
if (nil != self.testedApplication
|
||||
&& FBConfiguration.shouldTerminateApp
|
||||
&& self.testedApplication.running
|
||||
&& ![self.testedApplication fb_isSameAppAs:XCUIApplication.fb_systemApplication]) {
|
||||
@try {
|
||||
[self.testedApplication terminate];
|
||||
} @catch (NSException *e) {
|
||||
[FBLogger logFmt:@"%@", e.description];
|
||||
}
|
||||
}
|
||||
|
||||
_activeSession = nil;
|
||||
}
|
||||
|
||||
- (XCUIApplication *)activeApplication
|
||||
{
|
||||
BOOL isAuto = [self.defaultActiveApplication isEqualToString:FBDefaultApplicationAuto];
|
||||
NSString *defaultBundleId = isAuto ? nil : self.defaultActiveApplication;
|
||||
|
||||
if (nil != defaultBundleId && [self applicationStateWithBundleId:defaultBundleId] >= XCUIApplicationStateRunningForeground) {
|
||||
return [self makeApplicationWithBundleId:defaultBundleId];
|
||||
}
|
||||
|
||||
if (nil != self.testedApplication) {
|
||||
XCUIApplicationState testedAppState = self.testedApplication.state;
|
||||
if (testedAppState >= XCUIApplicationStateRunningForeground) {
|
||||
NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"%K == %@ OR %K IN {%@, %@}",
|
||||
@"elementType", @(XCUIElementTypeAlert),
|
||||
// To look for `SBTransientOverlayWindow` elements. See https://github.com/appium/WebDriverAgent/pull/946
|
||||
@"identifier", @"SBTransientOverlayWindow",
|
||||
// To look for 'criticalAlertSetting' elements https://developer.apple.com/documentation/usernotifications/unnotificationsettings/criticalalertsetting
|
||||
// See https://github.com/appium/appium/issues/20835
|
||||
@"NotificationShortLookView"];
|
||||
if ([FBConfiguration shouldRespectSystemAlerts]
|
||||
&& [[XCUIApplication.fb_systemApplication descendantsMatchingType:XCUIElementTypeAny]
|
||||
matchingPredicate:searchPredicate].count > 0) {
|
||||
return XCUIApplication.fb_systemApplication;
|
||||
}
|
||||
return (XCUIApplication *)self.testedApplication;
|
||||
}
|
||||
if (self.isTestedApplicationExpectedToRun && testedAppState <= XCUIApplicationStateNotRunning) {
|
||||
NSString *description = [NSString stringWithFormat:@"The application under test with bundle id '%@' is not running, possibly crashed", self.testedApplication.bundleID];
|
||||
@throw [NSException exceptionWithName:FBApplicationCrashedException reason:description userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
return [XCUIApplication fb_activeApplicationWithDefaultBundleId:defaultBundleId];
|
||||
}
|
||||
|
||||
- (XCUIApplication *)launchApplicationWithBundleId:(NSString *)bundleIdentifier
|
||||
shouldWaitForQuiescence:(nullable NSNumber *)shouldWaitForQuiescence
|
||||
arguments:(nullable NSArray<NSString *> *)arguments
|
||||
environment:(nullable NSDictionary <NSString *, NSString *> *)environment
|
||||
{
|
||||
XCUIApplication *app = [self makeApplicationWithBundleId:bundleIdentifier];
|
||||
if (nil == shouldWaitForQuiescence) {
|
||||
// Iherit the quiescence check setting from the main app under test by default
|
||||
app.fb_shouldWaitForQuiescence = nil != self.testedApplication && self.shouldAppsWaitForQuiescence;
|
||||
} else {
|
||||
app.fb_shouldWaitForQuiescence = [shouldWaitForQuiescence boolValue];
|
||||
}
|
||||
if (!app.running) {
|
||||
app.launchArguments = arguments ?: @[];
|
||||
app.launchEnvironment = environment ?: @{};
|
||||
[app launch];
|
||||
} else {
|
||||
[app activate];
|
||||
}
|
||||
if ([app fb_isSameAppAs:self.testedApplication]) {
|
||||
self.isTestedApplicationExpectedToRun = YES;
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
- (XCUIApplication *)activateApplicationWithBundleId:(NSString *)bundleIdentifier
|
||||
{
|
||||
XCUIApplication *app = [self makeApplicationWithBundleId:bundleIdentifier];
|
||||
[app activate];
|
||||
return app;
|
||||
}
|
||||
|
||||
- (BOOL)terminateApplicationWithBundleId:(NSString *)bundleIdentifier
|
||||
{
|
||||
XCUIApplication *app = [self makeApplicationWithBundleId:bundleIdentifier];
|
||||
if ([app fb_isSameAppAs:self.testedApplication]) {
|
||||
self.isTestedApplicationExpectedToRun = NO;
|
||||
}
|
||||
if (app.running) {
|
||||
[app terminate];
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSUInteger)applicationStateWithBundleId:(NSString *)bundleIdentifier
|
||||
{
|
||||
return [self makeApplicationWithBundleId:bundleIdentifier].state;
|
||||
}
|
||||
|
||||
- (XCUIApplication *)makeApplicationWithBundleId:(NSString *)bundleIdentifier
|
||||
{
|
||||
return nil != self.testedApplication && [bundleIdentifier isEqualToString:(NSString *)self.testedApplication.bundleID]
|
||||
? self.testedApplication
|
||||
: [[XCUIApplication alloc] initWithBundleIdentifier:bundleIdentifier];
|
||||
}
|
||||
|
||||
@end
|
||||
65
WebDriverAgentLib/Routing/FBTCPSocket.h
Normal file
65
WebDriverAgentLib/Routing/FBTCPSocket.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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 "GCDAsyncSocket.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol FBTCPSocketDelegate
|
||||
|
||||
/**
|
||||
The callback which is fired on new TCP client connection
|
||||
|
||||
@param newClient The newly connected socket
|
||||
*/
|
||||
- (void)didClientConnect:(GCDAsyncSocket *)newClient;
|
||||
|
||||
/**
|
||||
The callback which is fired when the TCP server receives a data from a connected client
|
||||
|
||||
@param client The client, which sent the data
|
||||
*/
|
||||
- (void)didClientSendData:(GCDAsyncSocket *)client;
|
||||
|
||||
/**
|
||||
The callback which is fired when TCP client disconnects
|
||||
|
||||
@param client The actual diconnected client
|
||||
*/
|
||||
- (void)didClientDisconnect:(GCDAsyncSocket *)client;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface FBTCPSocket : NSObject
|
||||
|
||||
@property (nullable, nonatomic) id<FBTCPSocketDelegate> delegate;
|
||||
|
||||
/**
|
||||
Creates TCP socket isntance which is going to be started on the specified port
|
||||
|
||||
@param port The actual port number
|
||||
@return self instance
|
||||
*/
|
||||
- (instancetype)initWithPort:(uint16_t)port;
|
||||
|
||||
/**
|
||||
Starts TCP socket listener on the specified port
|
||||
|
||||
@param error The alias to the actual startup error descirption or nil if the socket has started and is listening
|
||||
@return NO If there was an error
|
||||
*/
|
||||
- (BOOL)startWithError:(NSError **)error;
|
||||
|
||||
/**
|
||||
Stops the socket if it is running
|
||||
*/
|
||||
- (void)stop;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
85
WebDriverAgentLib/Routing/FBTCPSocket.m
Normal file
85
WebDriverAgentLib/Routing/FBTCPSocket.m
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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 "FBTCPSocket.h"
|
||||
|
||||
|
||||
@interface FBTCPSocket()
|
||||
@property (readonly, nonatomic) dispatch_queue_t socketQueue;
|
||||
@property (readonly, nonatomic) GCDAsyncSocket *listeningSocket;
|
||||
@property (readonly, nonatomic) NSMutableArray *connectedClients;
|
||||
@property (readonly, nonatomic) uint16_t port;
|
||||
@end
|
||||
|
||||
|
||||
@interface FBTCPSocket(AsyncSocket) <GCDAsyncSocketDelegate>
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation FBTCPSocket
|
||||
|
||||
- (instancetype)initWithPort:(uint16_t)port
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_socketQueue = dispatch_queue_create("socketQueue", NULL);
|
||||
_listeningSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_socketQueue];
|
||||
_connectedClients = [[NSMutableArray alloc] initWithCapacity:1];
|
||||
_port = port;
|
||||
_delegate = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)startWithError:(NSError **)error
|
||||
{
|
||||
if (![self.listeningSocket acceptOnPort:self.port error:error]) {
|
||||
return NO;;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
@synchronized(self.connectedClients) {
|
||||
for (NSUInteger i = 0; i < [self.connectedClients count]; i++) {
|
||||
[[self.connectedClients objectAtIndex:i] disconnect];
|
||||
}
|
||||
}
|
||||
|
||||
[self.listeningSocket disconnect];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation FBTCPSocket(AsyncSocket)
|
||||
|
||||
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
|
||||
{
|
||||
@synchronized(self.connectedClients) {
|
||||
[self.connectedClients addObject:newSocket];
|
||||
}
|
||||
[self.delegate didClientConnect:newSocket];
|
||||
}
|
||||
|
||||
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
|
||||
{
|
||||
[self.delegate didClientSendData:sock];
|
||||
}
|
||||
|
||||
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
|
||||
{
|
||||
@synchronized(self.connectedClients) {
|
||||
[self.connectedClients removeObject:sock];
|
||||
}
|
||||
[self.delegate didClientDisconnect:sock];
|
||||
}
|
||||
|
||||
@end
|
||||
52
WebDriverAgentLib/Routing/FBWebServer.h
Normal file
52
WebDriverAgentLib/Routing/FBWebServer.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@class RouteResponse, RoutingHTTPServer, FBExceptionHandler;
|
||||
@protocol FBWebServerDelegate;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
HTTP and USB service wrapper, handling requests and responses
|
||||
*/
|
||||
@interface FBWebServer : NSObject
|
||||
|
||||
/**
|
||||
Server delegate.
|
||||
*/
|
||||
@property (weak, nonatomic) id<FBWebServerDelegate> delegate;
|
||||
|
||||
/**
|
||||
Starts WebDriverAgent service by booting HTTP and USB server
|
||||
*/
|
||||
- (void)startServing;
|
||||
|
||||
/**
|
||||
Stops WebDriverAgent service, shutting down HTTP and USB servers.
|
||||
*/
|
||||
- (void)stopServing;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
The protocol allowing the server delegate to handle messages from the server.
|
||||
*/
|
||||
@protocol FBWebServerDelegate <NSObject>
|
||||
|
||||
/**
|
||||
The server requested WebDriverAgent service shutdown.
|
||||
|
||||
@param webServer Server instance.
|
||||
*/
|
||||
- (void)webServerDidRequestShutdown:(FBWebServer *)webServer;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
240
WebDriverAgentLib/Routing/FBWebServer.m
Normal file
240
WebDriverAgentLib/Routing/FBWebServer.m
Normal file
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* 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 "FBWebServer.h"
|
||||
|
||||
#import "RoutingConnection.h"
|
||||
#import "RoutingHTTPServer.h"
|
||||
|
||||
#import "FBCommandHandler.h"
|
||||
#import "FBErrorBuilder.h"
|
||||
#import "FBExceptionHandler.h"
|
||||
#import "FBMjpegServer.h"
|
||||
#import "FBRouteRequest.h"
|
||||
#import "FBRuntimeUtils.h"
|
||||
#import "FBSession.h"
|
||||
#import "FBTCPSocket.h"
|
||||
#import "FBUnknownCommands.h"
|
||||
#import "FBConfiguration.h"
|
||||
#import "FBLogger.h"
|
||||
|
||||
#import "XCUIDevice+FBHelpers.h"
|
||||
|
||||
static NSString *const FBServerURLBeginMarker = @"ServerURLHere->";
|
||||
static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
|
||||
|
||||
@interface FBHTTPConnection : RoutingConnection
|
||||
@end
|
||||
|
||||
@implementation FBHTTPConnection
|
||||
|
||||
- (void)handleResourceNotFound
|
||||
{
|
||||
[FBLogger logFmt:@"Received request for %@ which we do not handle", self.requestURI];
|
||||
[super handleResourceNotFound];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface FBWebServer ()
|
||||
@property (nonatomic, strong) FBExceptionHandler *exceptionHandler;
|
||||
@property (nonatomic, strong) RoutingHTTPServer *server;
|
||||
@property (atomic, assign) BOOL keepAlive;
|
||||
@property (nonatomic, nullable) FBTCPSocket *screenshotsBroadcaster;
|
||||
@end
|
||||
|
||||
@implementation FBWebServer
|
||||
|
||||
+ (NSArray<Class<FBCommandHandler>> *)collectCommandHandlerClasses
|
||||
{
|
||||
NSArray *handlersClasses = FBClassesThatConformsToProtocol(@protocol(FBCommandHandler));
|
||||
NSMutableArray *handlers = [NSMutableArray array];
|
||||
for (Class aClass in handlersClasses) {
|
||||
if ([aClass respondsToSelector:@selector(shouldRegisterAutomatically)]) {
|
||||
if (![aClass shouldRegisterAutomatically]) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
[handlers addObject:aClass];
|
||||
}
|
||||
return handlers.copy;
|
||||
}
|
||||
|
||||
- (void)startServing
|
||||
{
|
||||
[FBLogger logFmt:@"Built at %s %s", __DATE__, __TIME__];
|
||||
self.exceptionHandler = [FBExceptionHandler new];
|
||||
[self startHTTPServer];
|
||||
[self initScreenshotsBroadcaster];
|
||||
|
||||
self.keepAlive = YES;
|
||||
NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
|
||||
while (self.keepAlive &&
|
||||
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
|
||||
}
|
||||
|
||||
- (void)startHTTPServer
|
||||
{
|
||||
self.server = [[RoutingHTTPServer alloc] init];
|
||||
[self.server setRouteQueue:dispatch_get_main_queue()];
|
||||
[self.server setDefaultHeader:@"Server" value:@"WebDriverAgent/1.0"];
|
||||
[self.server setDefaultHeader:@"Access-Control-Allow-Origin" value:@"*"];
|
||||
[self.server setDefaultHeader:@"Access-Control-Allow-Headers" value:@"Content-Type, X-Requested-With"];
|
||||
[self.server setConnectionClass:[FBHTTPConnection self]];
|
||||
|
||||
[self registerRouteHandlers:[self.class collectCommandHandlerClasses]];
|
||||
[self registerServerKeyRouteHandlers];
|
||||
|
||||
NSRange serverPortRange = FBConfiguration.bindingPortRange;
|
||||
NSError *error;
|
||||
BOOL serverStarted = NO;
|
||||
|
||||
for (NSUInteger index = 0; index < serverPortRange.length; index++) {
|
||||
NSInteger port = serverPortRange.location + index;
|
||||
[self.server setPort:(UInt16)port];
|
||||
|
||||
serverStarted = [self attemptToStartServer:self.server onPort:port withError:&error];
|
||||
if (serverStarted) {
|
||||
break;
|
||||
}
|
||||
|
||||
[FBLogger logFmt:@"Failed to start web server on port %ld with error %@", (long)port, [error description]];
|
||||
}
|
||||
|
||||
if (!serverStarted) {
|
||||
[FBLogger logFmt:@"Last attempt to start web server failed with error %@", [error description]];
|
||||
abort();
|
||||
}
|
||||
[FBLogger logFmt:@"%@http://%@:%d%@", FBServerURLBeginMarker, [XCUIDevice sharedDevice].fb_wifiIPAddress ?: @"localhost", [self.server port], FBServerURLEndMarker];
|
||||
}
|
||||
|
||||
- (void)initScreenshotsBroadcaster
|
||||
{
|
||||
[self readMjpegSettingsFromEnv];
|
||||
self.screenshotsBroadcaster = [[FBTCPSocket alloc]
|
||||
initWithPort:(uint16_t)FBConfiguration.mjpegServerPort];
|
||||
self.screenshotsBroadcaster.delegate = [[FBMjpegServer alloc] init];
|
||||
NSError *error;
|
||||
if (![self.screenshotsBroadcaster startWithError:&error]) {
|
||||
[FBLogger logFmt:@"Cannot init screenshots broadcaster service on port %@. Original error: %@", @(FBConfiguration.mjpegServerPort), error.description];
|
||||
self.screenshotsBroadcaster = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopScreenshotsBroadcaster
|
||||
{
|
||||
if (nil == self.screenshotsBroadcaster) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self.screenshotsBroadcaster stop];
|
||||
}
|
||||
|
||||
- (void)readMjpegSettingsFromEnv
|
||||
{
|
||||
NSDictionary *env = NSProcessInfo.processInfo.environment;
|
||||
NSString *scalingFactor = [env objectForKey:@"MJPEG_SCALING_FACTOR"];
|
||||
if (scalingFactor != nil && [scalingFactor length] > 0) {
|
||||
[FBConfiguration setMjpegScalingFactor:[scalingFactor floatValue]];
|
||||
}
|
||||
NSString *screenshotQuality = [env objectForKey:@"MJPEG_SERVER_SCREENSHOT_QUALITY"];
|
||||
if (screenshotQuality != nil && [screenshotQuality length] > 0) {
|
||||
[FBConfiguration setMjpegServerScreenshotQuality:[screenshotQuality integerValue]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopServing
|
||||
{
|
||||
[FBSession.activeSession kill];
|
||||
[self stopScreenshotsBroadcaster];
|
||||
if (self.server.isRunning) {
|
||||
[self.server stop:NO];
|
||||
}
|
||||
self.keepAlive = NO;
|
||||
}
|
||||
|
||||
- (BOOL)attemptToStartServer:(RoutingHTTPServer *)server onPort:(NSInteger)port withError:(NSError **)error
|
||||
{
|
||||
server.port = (UInt16)port;
|
||||
NSError *innerError = nil;
|
||||
BOOL started = [server start:&innerError];
|
||||
if (!started) {
|
||||
if (!error) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *description = @"Unknown Error when Starting server";
|
||||
if ([innerError.domain isEqualToString:NSPOSIXErrorDomain] && innerError.code == EADDRINUSE) {
|
||||
description = [NSString stringWithFormat:@"Unable to start web server on port %ld", (long)port];
|
||||
}
|
||||
return
|
||||
[[[[FBErrorBuilder builder]
|
||||
withDescription:description]
|
||||
withInnerError:innerError]
|
||||
buildError:error];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)registerRouteHandlers:(NSArray *)commandHandlerClasses
|
||||
{
|
||||
for (Class<FBCommandHandler> commandHandler in commandHandlerClasses) {
|
||||
NSArray *routes = [commandHandler routes];
|
||||
for (FBRoute *route in routes) {
|
||||
[self.server handleMethod:route.verb withPath:route.path block:^(RouteRequest *request, RouteResponse *response) {
|
||||
NSDictionary *arguments = [NSJSONSerialization JSONObjectWithData:request.body options:NSJSONReadingMutableContainers error:NULL];
|
||||
FBRouteRequest *routeParams = [FBRouteRequest
|
||||
routeRequestWithURL:request.url
|
||||
parameters:request.params
|
||||
arguments:arguments ?: @{}
|
||||
];
|
||||
|
||||
[FBLogger verboseLog:routeParams.description];
|
||||
|
||||
@try {
|
||||
[route mountRequest:routeParams intoResponse:response];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[self handleException:exception forResponse:response];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleException:(NSException *)exception forResponse:(RouteResponse *)response
|
||||
{
|
||||
[self.exceptionHandler handleException:exception forResponse:response];
|
||||
}
|
||||
|
||||
- (void)registerServerKeyRouteHandlers
|
||||
{
|
||||
[self.server get:@"/health" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
||||
[response respondWithString:@"<!DOCTYPE html><html><title>Health Check</title><body><p>I-AM-ALIVE</p></body></html>"];
|
||||
}];
|
||||
|
||||
NSString *calibrationPage = @"<html>"
|
||||
"<title>{\"x\":null,\"y\":null}</title>"
|
||||
"<header>"
|
||||
"<script>document.addEventListener(\"click\",function(e){document.title=JSON.stringify({x:e.clientX,y:e.clientY})})</script>"
|
||||
"</header>"
|
||||
"</html>";
|
||||
[self.server get:@"/calibrate" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
||||
[response respondWithString:calibrationPage];
|
||||
}];
|
||||
|
||||
[self.server get:@"/wda/shutdown" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
||||
[response respondWithString:@"Shutting down"];
|
||||
[self.delegate webServerDidRequestShutdown:self];
|
||||
}];
|
||||
|
||||
[self registerRouteHandlers:@[FBUnknownCommands.class]];
|
||||
}
|
||||
|
||||
@end
|
||||
34
WebDriverAgentLib/Routing/FBXCAccessibilityElement.h
Normal file
34
WebDriverAgentLib/Routing/FBXCAccessibilityElement.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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 <XCTest/XCTest.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol FBXCAccessibilityElement <NSObject>
|
||||
|
||||
@property(readonly) id payload; // @synthesize payload=_payload;
|
||||
@property(readonly) int processIdentifier; // @synthesize processIdentifier=_processIdentifier;
|
||||
@property(readonly) const struct __AXUIElement *AXUIElement; // @synthesize AXUIElement=_axElement;
|
||||
@property(readonly, getter=isNative) BOOL native;
|
||||
|
||||
+ (id)elementWithAXUIElement:(struct __AXUIElement *)arg1;
|
||||
+ (id)elementWithProcessIdentifier:(int)arg1;
|
||||
+ (id)deviceElement;
|
||||
+ (id)mockElementWithProcessIdentifier:(int)arg1 payload:(id)arg2;
|
||||
+ (id)mockElementWithProcessIdentifier:(int)arg1;
|
||||
|
||||
- (id)initWithMockProcessIdentifier:(int)arg1 payload:(id)arg2;
|
||||
- (id)initWithAXUIElement:(struct __AXUIElement *)arg1;
|
||||
- (id)init;
|
||||
|
||||
@end
|
||||
|
||||
BOOL FBIsAXElementEqualToOther(id<FBXCAccessibilityElement> first, id<FBXCAccessibilityElement> second);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
17
WebDriverAgentLib/Routing/FBXCAccessibilityElement.m
Normal file
17
WebDriverAgentLib/Routing/FBXCAccessibilityElement.m
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 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 "FBXCAccessibilityElement.h"
|
||||
|
||||
#import "FBElementUtils.h"
|
||||
|
||||
BOOL FBIsAXElementEqualToOther(id<FBXCAccessibilityElement> first, id<FBXCAccessibilityElement> second)
|
||||
{
|
||||
return nil != second && [[FBElementUtils uidWithAccessibilityElement:first]
|
||||
isEqualToString:([FBElementUtils uidWithAccessibilityElement:second] ?: @"")];
|
||||
}
|
||||
34
WebDriverAgentLib/Routing/FBXCDeviceEvent.h
Normal file
34
WebDriverAgentLib/Routing/FBXCDeviceEvent.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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 <XCTest/XCTest.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol FBXCDeviceEvent
|
||||
|
||||
@property unsigned long long type; // @synthesize type=_type;
|
||||
@property double rotation; // @synthesize rotation=_rotation;
|
||||
@property double duration; // @synthesize duration=_duration;
|
||||
@property unsigned int usage; // @synthesize usage=_usage;
|
||||
@property unsigned int eventPage; // @synthesize eventPage=_eventPage;
|
||||
@property(readonly) BOOL isButtonHoldEvent;
|
||||
|
||||
+ (id)deviceEventForDigitalCrownRotation:(double)arg1 velocity:(double)arg2;
|
||||
+ (id)deviceEventWithPage:(unsigned int)arg1 usage:(unsigned int)arg2 duration:(double)arg3;
|
||||
|
||||
- (void)dispatch;
|
||||
|
||||
@end
|
||||
|
||||
_Nullable id<FBXCDeviceEvent> FBCreateXCDeviceEvent(unsigned int page,
|
||||
unsigned int usage,
|
||||
double duration,
|
||||
NSError **error);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
42
WebDriverAgentLib/Routing/FBXCDeviceEvent.m
Normal file
42
WebDriverAgentLib/Routing/FBXCDeviceEvent.m
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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 "FBXCDeviceEvent.h"
|
||||
|
||||
#import "FBErrorBuilder.h"
|
||||
|
||||
id<FBXCDeviceEvent> FBCreateXCDeviceEvent(unsigned int page,
|
||||
unsigned int usage,
|
||||
double duration,
|
||||
NSError **error)
|
||||
{
|
||||
Class xcDeviceEventClass = NSClassFromString(@"XCDeviceEvent");
|
||||
if (nil == xcDeviceEventClass) {
|
||||
[[[FBErrorBuilder builder]
|
||||
withDescription:@"Cannot find XCDeviceEvent class"]
|
||||
buildError:error];
|
||||
return nil;
|
||||
}
|
||||
SEL deviceEventFactorySelector = NSSelectorFromString(@"deviceEventWithPage:usage:duration:");
|
||||
if (![xcDeviceEventClass respondsToSelector:deviceEventFactorySelector]) {
|
||||
[[[FBErrorBuilder builder]
|
||||
withDescription:@"'deviceEventWithPage:usage:duration:' factory method is not found on XCDeviceEvent class"]
|
||||
buildError:error];
|
||||
return nil;
|
||||
}
|
||||
NSMethodSignature *deviceEventFactorySignature = [xcDeviceEventClass methodSignatureForSelector:deviceEventFactorySelector];
|
||||
NSInvocation *deviceEventFactoryInvocation = [NSInvocation invocationWithMethodSignature:deviceEventFactorySignature];
|
||||
[deviceEventFactoryInvocation setSelector:deviceEventFactorySelector];
|
||||
[deviceEventFactoryInvocation setArgument:&page atIndex:2];
|
||||
[deviceEventFactoryInvocation setArgument:&usage atIndex:3];
|
||||
[deviceEventFactoryInvocation setArgument:&duration atIndex:4];
|
||||
[deviceEventFactoryInvocation invokeWithTarget:xcDeviceEventClass];
|
||||
id<FBXCDeviceEvent> __unsafe_unretained instance;
|
||||
[deviceEventFactoryInvocation getReturnValue:&instance];
|
||||
return instance;
|
||||
}
|
||||
82
WebDriverAgentLib/Routing/FBXCElementSnapshot.h
Normal file
82
WebDriverAgentLib/Routing/FBXCElementSnapshot.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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 <XCTest/XCTest.h>
|
||||
#import <WebDriverAgentLib/CDStructures.h>
|
||||
|
||||
@protocol FBXCAccessibilityElement;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol FBXCElementSnapshot <NSObject, XCUIElementAttributes>
|
||||
|
||||
@property BOOL hasFocus; // @synthesize hasFocus=_hasFocus;
|
||||
@property BOOL hasKeyboardFocus; // @synthesize hasKeyboardFocus=_hasKeyboardFocus;
|
||||
@property(copy) NSDictionary *additionalAttributes; // @synthesize additionalAttributes=_additionalAttributes;
|
||||
@property(copy) NSArray *userTestingAttributes; // @synthesize userTestingAttributes=_userTestingAttributes;
|
||||
@property unsigned long long traits; // @synthesize traits=_traits;
|
||||
@property BOOL isMainWindow; // @synthesize isMainWindow=_isMainWindow;
|
||||
@property(copy) NSArray *children; // @synthesize children=_children;
|
||||
@property id<FBXCElementSnapshot> parent; // @synthesize parent=_parent;
|
||||
@property(retain) id<FBXCAccessibilityElement> parentAccessibilityElement; // @synthesize parentAccessibilityElement=_parentAccessibilityElement;
|
||||
@property(retain) id<FBXCAccessibilityElement> accessibilityElement; // @synthesize accessibilityElement=_accessibilityElement;
|
||||
@property(readonly) NSArray *suggestedHitpoints;
|
||||
@property(readonly) struct CGRect visibleFrame;
|
||||
@property(readonly) id<FBXCElementSnapshot> scrollView;
|
||||
@property(readonly, copy) NSString *truncatedValueString;
|
||||
@property(readonly) long long depth;
|
||||
@property(readonly, copy) id<FBXCElementSnapshot> pathFromRoot;
|
||||
@property(readonly) BOOL isTopLevelTouchBarElement;
|
||||
@property(readonly) BOOL isTouchBarElement;
|
||||
@property(readonly, copy) NSString *sparseTreeDescription;
|
||||
@property(readonly, copy) NSString *compactDescription;
|
||||
@property(readonly, copy) NSString *pathDescription;
|
||||
@property(readonly) NSString *recursiveDescriptionIncludingAccessibilityElement;
|
||||
@property(readonly) NSString *recursiveDescription;
|
||||
@property(readonly, copy) NSArray *identifiers;
|
||||
@property(nonatomic) unsigned long long generation; // @synthesize generation=_generation;
|
||||
/*! DO NOT USE DIRECTLY! */
|
||||
@property(nonatomic) XCUIApplication *application; // @synthesize application=_application;
|
||||
/*! DO NOT USE DIRECTLY! */
|
||||
@property(readonly) struct CGPoint hitPointForScrolling;
|
||||
/*! DO NOT USE DIRECTLY! Please use fb_hitPoint instead */
|
||||
@property(readonly) struct CGPoint hitPoint;
|
||||
|
||||
- (id)_uniquelyIdentifyingObjectiveCCode;
|
||||
- (id)_uniquelyIdentifyingSwiftCode;
|
||||
- (BOOL)_isAncestorOfElement:(id)arg1;
|
||||
- (BOOL)_isDescendantOfElement:(id)arg1;
|
||||
- (BOOL)_frameFuzzyMatchesElement:(id)arg1;
|
||||
- (BOOL)_fuzzyMatchesElement:(id)arg1;
|
||||
- (BOOL)_matchesElement:(id)arg1;
|
||||
- (BOOL)matchesTreeWithRoot:(id)arg1;
|
||||
- (void)mergeTreeWithSnapshot:(id)arg1;
|
||||
- (id)_childMatchingElement:(id)arg1;
|
||||
- (NSArray<id<FBXCElementSnapshot>> *)_allDescendants;
|
||||
- (BOOL)hasDescendantMatchingFilter:(CDUnknownBlockType)arg1;
|
||||
- (NSArray<id<FBXCElementSnapshot>> *)descendantsByFilteringWithBlock:(BOOL(^)(id<FBXCElementSnapshot> snapshot))block;
|
||||
- (id)elementSnapshotMatchingAccessibilityElement:(id)arg1;
|
||||
- (void)enumerateDescendantsUsingBlock:(void(^)(id<FBXCElementSnapshot> snapshot))block;
|
||||
- (id)recursiveDescriptionWithIndent:(id)arg1 includeAccessibilityElement:(BOOL)arg2;
|
||||
- (id)init;
|
||||
- (struct CGPoint)hostingAndOrientationTransformedPoint:(struct CGPoint)arg1;
|
||||
- (struct CGPoint)_transformPoint:(struct CGPoint)arg1 windowContextID:(id)arg2 windowDisplayID:(id)arg3;
|
||||
- (id)hitTest:(struct CGPoint)arg1;
|
||||
|
||||
// Available since Xcode 10
|
||||
- (id)hitPoint:(NSError **)error;
|
||||
|
||||
// Since Xcode 10.2
|
||||
+ (id)axAttributesForElementSnapshotKeyPaths:(id)arg1 isMacOS:(_Bool)arg2;
|
||||
// Since Xcode 10.0
|
||||
+ (NSArray<NSString *> *)sanitizedElementSnapshotHierarchyAttributesForAttributes:(nullable NSArray<NSString *> *)arg1
|
||||
isMacOS:(_Bool)arg2;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
9
WebDriverAgentLib/Routing/FBXCElementSnapshot.m
Normal file
9
WebDriverAgentLib/Routing/FBXCElementSnapshot.m
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 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 "FBXCElementSnapshot.h"
|
||||
29
WebDriverAgentLib/Routing/FBXCElementSnapshotWrapper.h
Normal file
29
WebDriverAgentLib/Routing/FBXCElementSnapshotWrapper.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 "FBXCElementSnapshot.h"
|
||||
#import "FBElement.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FBXCElementSnapshotWrapper : NSObject<FBXCElementSnapshot>
|
||||
|
||||
/*!Wrapped snapshot instance */
|
||||
@property (nonatomic, readonly) id<FBXCElementSnapshot> snapshot;
|
||||
|
||||
/**
|
||||
Wraps the given snapshot.. If the given snapshot is already wrapped then the result remains unchanged.
|
||||
|
||||
@param snapshot snapshot instance to wrap
|
||||
@returns wrapper instance
|
||||
*/
|
||||
+ (nullable instancetype)ensureWrapped:(nullable id<FBXCElementSnapshot>)snapshot;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
114
WebDriverAgentLib/Routing/FBXCElementSnapshotWrapper.m
Normal file
114
WebDriverAgentLib/Routing/FBXCElementSnapshotWrapper.m
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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 "FBXCElementSnapshotWrapper.h"
|
||||
|
||||
#import "FBElementUtils.h"
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-protocol-property-synthesis"
|
||||
#pragma clang diagnostic ignored "-Wprotocol"
|
||||
|
||||
@implementation FBXCElementSnapshotWrapper
|
||||
|
||||
- (instancetype)initWithSnapshot:(id<FBXCElementSnapshot>)snapshot;
|
||||
{
|
||||
self->_snapshot = snapshot;
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)ensureWrapped:(id<FBXCElementSnapshot>)snapshot
|
||||
{
|
||||
if (nil == snapshot) {
|
||||
return nil;
|
||||
}
|
||||
return [(NSObject *)snapshot isKindOfClass:self.class]
|
||||
? (FBXCElementSnapshotWrapper *)snapshot
|
||||
: [[FBXCElementSnapshotWrapper alloc] initWithSnapshot:snapshot];
|
||||
}
|
||||
|
||||
// Attributes are queried most often,
|
||||
// so we prefer them to have direct accessors defined here
|
||||
// rather than to use message forwarding via forwardingTargetForSelector,
|
||||
// which is slow
|
||||
|
||||
- (NSString *)identifier
|
||||
{
|
||||
return self.snapshot.identifier;
|
||||
}
|
||||
|
||||
- (CGRect)frame
|
||||
{
|
||||
return self.snapshot.frame;
|
||||
}
|
||||
|
||||
- (id)value
|
||||
{
|
||||
return self.snapshot.value;
|
||||
}
|
||||
|
||||
- (NSString *)title
|
||||
{
|
||||
return self.snapshot.title;
|
||||
}
|
||||
|
||||
- (NSString *)label
|
||||
{
|
||||
return self.snapshot.label;
|
||||
}
|
||||
|
||||
- (XCUIElementType)elementType
|
||||
{
|
||||
return self.snapshot.elementType;
|
||||
}
|
||||
|
||||
- (BOOL)isEnabled
|
||||
{
|
||||
return self.snapshot.enabled;
|
||||
}
|
||||
|
||||
- (XCUIUserInterfaceSizeClass)horizontalSizeClass
|
||||
{
|
||||
return self.snapshot.horizontalSizeClass;
|
||||
}
|
||||
|
||||
- (XCUIUserInterfaceSizeClass)verticalSizeClass
|
||||
{
|
||||
return self.snapshot.verticalSizeClass;
|
||||
}
|
||||
|
||||
- (NSString *)placeholderValue
|
||||
{
|
||||
return self.snapshot.placeholderValue;
|
||||
}
|
||||
|
||||
- (BOOL)isSelected
|
||||
{
|
||||
return self.snapshot.selected;
|
||||
}
|
||||
|
||||
#if !TARGET_OS_OSX
|
||||
- (BOOL)hasFocus
|
||||
{
|
||||
return self.snapshot.hasFocus;
|
||||
}
|
||||
#endif
|
||||
|
||||
- (id)forwardingTargetForSelector:(SEL)aSelector
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
static NSSet<NSString *> *names;
|
||||
dispatch_once(&onceToken, ^{
|
||||
names = [FBElementUtils selectorNamesWithProtocol:@protocol(FBXCElementSnapshot)];
|
||||
});
|
||||
return [names containsObject:NSStringFromSelector(aSelector)] ? self.snapshot : nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
Reference in New Issue
Block a user