Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,13 @@ jobs:
runtime-version: ${{ env.IOS_RUNTIME_VERSION }}
preferred-device-name: iPhone 17 Pro

- name: Run iOS simulator smoke replay
- name: Prepare iOS runner
run: |
pnpm clean:daemon
node --experimental-strip-types src/bin.ts prepare ios-runner --platform ios --timeout 240000

- name: Run iOS simulator smoke replay
run: |
node --experimental-strip-types src/bin.ts test test/integration/replays/ios/simulator/01-settings.ad --retries 2 --report-junit test/artifacts/replays-ios-simulator-smoke.junit.xml

- name: Run iOS physical device smoke replay
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/perf-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,13 @@ jobs:
runtime-version: ${{ env.IOS_RUNTIME_VERSION }}
preferred-device-name: iPhone 17 Pro

- name: Run iOS command perf benchmark
- name: Prepare iOS runner
run: |
pnpm clean:daemon
node --experimental-strip-types src/bin.ts prepare ios-runner --platform ios --timeout 240000

- name: Run iOS command perf benchmark
run: |
node --experimental-strip-types scripts/perf/run.ts \
--platform ios \
--device "iPhone 17 Pro" \
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/replays-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ jobs:
runtime-version: ${{ env.IOS_RUNTIME_VERSION }}
preferred-device-name: iPhone 17 Pro

- name: Prepare iOS runner
run: |
pnpm clean:daemon
node --experimental-strip-types src/bin.ts prepare ios-runner --platform ios --timeout 240000

- name: Run iOS simulator replay suite
run: node --experimental-strip-types src/bin.ts test test/integration/replays/ios/simulator --retries 2 --report-junit test/artifacts/replays-ios-simulator.junit.xml

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ NS_ASSUME_NONNULL_BEGIN
radius:(double)radius
durationMs:(double)durationMs;

+ (NSString * _Nullable)synthesizeSwipeWithApplication:(id)application
x:(double)x
y:(double)y
x2:(double)x2
y2:(double)y2
durationMs:(double)durationMs;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ static id RunnerPointerPath(
double durationMs,
double side
);
static id RunnerSwipePointerPath(
const RunnerXCTestEventBridge *bridge,
CGPoint start,
CGPoint end,
double durationMs
);
static CGPoint RunnerPointerPointAt(
double x,
double y,
Expand All @@ -58,6 +64,8 @@ static CGPoint RunnerPointerPointAt(
double t,
double side
);
static CGPoint RunnerInterpolatedPoint(CGPoint start, CGPoint end, double t);
static double RunnerSmoothStep(double t);

@implementation RunnerSynthesizedGesture

Expand Down Expand Up @@ -87,6 +95,26 @@ + (NSString * _Nullable)synthesizeTransformWithApplication:(id)application
}
}

+ (NSString * _Nullable)synthesizeSwipeWithApplication:(id)application
x:(double)x
y:(double)y
x2:(double)x2
y2:(double)y2
durationMs:(double)durationMs {
@try {
return [self trySynthesizeSwipeWithApplication:application
x:x
y:y
x2:x2
y2:y2
durationMs:durationMs];
} @catch (NSException *exception) {
NSString *name = exception.name ?: @"NSException";
NSString *reason = exception.reason ?: @"private XCTest event synthesis failed";
return [NSString stringWithFormat:@"%@: %@", name, reason];
}
}

+ (NSString * _Nullable)trySynthesizeTransformWithApplication:(id)application
x:(double)x
y:(double)y
Expand Down Expand Up @@ -151,6 +179,51 @@ + (NSString * _Nullable)trySynthesizeTransformWithApplication:(id)application
return nil;
}

+ (NSString * _Nullable)trySynthesizeSwipeWithApplication:(id)application
x:(double)x
y:(double)y
x2:(double)x2
y2:(double)y2
durationMs:(double)durationMs {
RunnerXCTestEventBridge bridge;
NSString *missing = RunnerResolveXCTestEventBridge(application, &bridge);
if (missing != nil) {
return missing;
}

NSInteger interfaceOrientation =
((RunnerMsgSendInteger)objc_msgSend)(application, bridge.interfaceOrientationSelector);
NSInteger targetProcessID = ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.processIDSelector);
if (targetProcessID <= 0) {
return @"private XCTest event synthesis unavailable: could not resolve target process ID";
}

id record = ((RunnerMsgSendInitRecord)objc_msgSend)(
[bridge.recordClass alloc],
bridge.initRecordSelector,
@"agent-device-swipe",
interfaceOrientation
);
if (record == nil) {
return @"private XCTest event synthesis failed: could not create event record";
}
((RunnerMsgSendSetInteger)objc_msgSend)(record, bridge.setTargetProcessIDSelector, targetProcessID);

id path = RunnerSwipePointerPath(&bridge, CGPointMake(x, y), CGPointMake(x2, y2), durationMs);
if (path == nil) {
return @"private XCTest event synthesis failed: could not create pointer path";
}
((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.addPathSelector, path);

NSError *error = nil;
BOOL ok = ((RunnerMsgSendSynthesize)objc_msgSend)(record, bridge.synthesizeSelector, &error);
if (!ok) {
NSString *detail = error.localizedDescription ?: @"synthesizeWithError returned false";
return [NSString stringWithFormat:@"private XCTest event synthesis failed: %@", detail];
}
return nil;
}

static NSString * _Nullable RunnerResolveXCTestEventBridge(
id application,
RunnerXCTestEventBridge *bridge
Expand Down Expand Up @@ -270,6 +343,31 @@ static id RunnerPointerPath(
return path;
}

static id RunnerSwipePointerPath(
const RunnerXCTestEventBridge *bridge,
CGPoint start,
CGPoint end,
double durationMs
) {
id path =
((RunnerMsgSendInitPath)objc_msgSend)([bridge->pathClass alloc], bridge->initPathSelector, start, 0.0);
if (path == nil) {
return nil;
}

int frameCount = MAX(3, (int)(durationMs / 16.0));
NSTimeInterval durationSeconds = durationMs / 1000.0;
for (int index = 1; index <= frameCount; index += 1) {
double t = (double)index / (double)frameCount;
CGPoint point = RunnerInterpolatedPoint(start, end, RunnerSmoothStep(t));
NSTimeInterval offset = durationSeconds * t;
((RunnerMsgSendPathMove)objc_msgSend)(path, bridge->moveSelector, point, offset);
}

((RunnerMsgSendPathOffset)objc_msgSend)(path, bridge->liftSelector, durationSeconds);
return path;
}

static CGPoint RunnerPointerPointAt(
double x,
double y,
Expand All @@ -294,4 +392,15 @@ static CGPoint RunnerPointerPointAt(
return CGPointMake(centerX + cos(angle) * radius * side, centerY + sin(angle) * radius * side);
}

static CGPoint RunnerInterpolatedPoint(CGPoint start, CGPoint end, double t) {
return CGPointMake(
start.x + (end.x - start.x) * t,
start.y + (end.y - start.y) * t
);
}

static double RunnerSmoothStep(double t) {
return t * t * (3.0 - 2.0 * t);
}

@end
Loading
Loading