Skip to content

Commit

Permalink
feat: Add wrappers for native XCTest video recorder (#858)
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach authored Mar 7, 2024
1 parent 89880f5 commit 9728548
Show file tree
Hide file tree
Showing 14 changed files with 663 additions and 16 deletions.
8 changes: 8 additions & 0 deletions PrivateHeaders/XCTest/XCTRunnerDaemonSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@
@property(readonly) _Bool supportsLocationSimulation;
#endif

// Since Xcode 15.0-beta1
- (void)stopScreenRecordingWithUUID:(NSUUID *)arg1
withReply:(void (^)(NSError *))arg2;
- (void)startScreenRecordingWithRequest:(id/* XCTScreenRecordingRequest */)arg1
withReply:(void (^)(id/* XCTAttachmentFutureMetadata */, NSError *))arg2;
- (_Bool)supportsScreenRecording;
- (_Bool)preferScreenshotsOverScreenRecordings;

// Since Xcode 10.2
- (void)launchApplicationWithPath:(NSString *)arg1
bundleID:(NSString *)arg2
Expand Down
60 changes: 60 additions & 0 deletions WebDriverAgent.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions WebDriverAgentLib/Commands/FBVideoCommands.h
Original file line number Diff line number Diff line change
@@ -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. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <Foundation/Foundation.h>

#import <WebDriverAgentLib/FBCommandHandler.h>

NS_ASSUME_NONNULL_BEGIN

@interface FBVideoCommands : NSObject <FBCommandHandler>

@end

NS_ASSUME_NONNULL_END
85 changes: 85 additions & 0 deletions WebDriverAgentLib/Commands/FBVideoCommands.m
Original file line number Diff line number Diff line change
@@ -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. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import "FBVideoCommands.h"

#import "FBRouteRequest.h"
#import "FBScreenRecordingContainer.h"
#import "FBScreenRecordingPromise.h"
#import "FBScreenRecordingRequest.h"
#import "FBSession.h"
#import "FBXCTestDaemonsProxy.h"

const NSUInteger DEFAULT_FPS = 24;
const NSUInteger DEFAULT_CODEC = 0;

@implementation FBVideoCommands

+ (NSArray *)routes
{
return
@[
[[FBRoute POST:@"/wda/video/start"] respondWithTarget:self action:@selector(handleStartVideoRecording:)],
[[FBRoute POST:@"/wda/video/stop"] respondWithTarget:self action:@selector(handleStopVideoRecording:)],
[[FBRoute GET:@"/wda/video"] respondWithTarget:self action:@selector(handleGetVideoRecording:)],

[[FBRoute POST:@"/wda/video/start"].withoutSession respondWithTarget:self action:@selector(handleStartVideoRecording:)],
[[FBRoute POST:@"/wda/video/stop"].withoutSession respondWithTarget:self action:@selector(handleStopVideoRecording:)],
[[FBRoute GET:@"/wda/video"].withoutSession respondWithTarget:self action:@selector(handleGetVideoRecording:)],
];
}

+ (id<FBResponsePayload>)handleStartVideoRecording:(FBRouteRequest *)request
{
FBScreenRecordingPromise *activeScreenRecording = FBScreenRecordingContainer.sharedInstance.screenRecordingPromise;
if (nil != activeScreenRecording) {
return FBResponseWithObject([FBScreenRecordingContainer.sharedInstance toDictionary] ?: [NSNull null]);
}

NSNumber *fps = (NSNumber *)request.arguments[@"fps"] ?: @(DEFAULT_FPS);
NSNumber *codec = (NSNumber *)request.arguments[@"codec"] ?: @(DEFAULT_CODEC);
FBScreenRecordingRequest *recordingRequest = [[FBScreenRecordingRequest alloc] initWithFps:fps.integerValue
codec:codec.longLongValue];
NSError *error;
FBScreenRecordingPromise* promise = [FBXCTestDaemonsProxy startScreenRecordingWithRequest:recordingRequest
error:&error];
if (nil == promise) {
[FBScreenRecordingContainer.sharedInstance reset];
return FBResponseWithUnknownError(error);
}
[FBScreenRecordingContainer.sharedInstance storeScreenRecordingPromise:promise
fps:fps.integerValue
codec:codec.longLongValue];
return FBResponseWithObject([FBScreenRecordingContainer.sharedInstance toDictionary]);
}

+ (id<FBResponsePayload>)handleStopVideoRecording:(FBRouteRequest *)request
{
FBScreenRecordingPromise *activeScreenRecording = FBScreenRecordingContainer.sharedInstance.screenRecordingPromise;
if (nil == activeScreenRecording) {
return FBResponseWithOK();
}

NSUUID *recordingId = activeScreenRecording.identifier;
NSDictionary *response = [FBScreenRecordingContainer.sharedInstance toDictionary];
NSError *error;
if (![FBXCTestDaemonsProxy stopScreenRecordingWithUUID:recordingId error:&error]) {
[FBScreenRecordingContainer.sharedInstance reset];
return FBResponseWithUnknownError(error);
}
[FBScreenRecordingContainer.sharedInstance reset];
return FBResponseWithObject(response);
}

+ (id<FBResponsePayload>)handleGetVideoRecording:(FBRouteRequest *)request
{
return FBResponseWithObject([FBScreenRecordingContainer.sharedInstance toDictionary] ?: [NSNull null]);
}

@end
57 changes: 57 additions & 0 deletions WebDriverAgentLib/Routing/FBScreenRecordingContainer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
*
* 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. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#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
73 changes: 73 additions & 0 deletions WebDriverAgentLib/Routing/FBScreenRecordingContainer.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
*
* 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. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#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
30 changes: 30 additions & 0 deletions WebDriverAgentLib/Routing/FBScreenRecordingPromise.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* 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. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#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
32 changes: 32 additions & 0 deletions WebDriverAgentLib/Routing/FBScreenRecordingPromise.m
Original file line number Diff line number Diff line change
@@ -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. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#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
40 changes: 40 additions & 0 deletions WebDriverAgentLib/Routing/FBScreenRecordingRequest.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* 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. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#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
Loading

0 comments on commit 9728548

Please sign in to comment.