Skip to content

Commit 7400701

Browse files
authored
perf: speed up iOS swipes and harden runner cache (#676)
* perf: speed up ios swipes and harden runner cache * fix: harden maestro replay smoke tests
1 parent 6946c2f commit 7400701

37 files changed

Lines changed: 2025 additions & 324 deletions

.github/workflows/ios.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,13 @@ jobs:
5454
runtime-version: ${{ env.IOS_RUNTIME_VERSION }}
5555
preferred-device-name: iPhone 17 Pro
5656

57-
- name: Run iOS simulator smoke replay
57+
- name: Prepare iOS runner
5858
run: |
5959
pnpm clean:daemon
60+
node --experimental-strip-types src/bin.ts prepare ios-runner --platform ios --timeout 240000
61+
62+
- name: Run iOS simulator smoke replay
63+
run: |
6064
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
6165
6266
- name: Run iOS physical device smoke replay

.github/workflows/perf-nightly.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,13 @@ jobs:
6060
runtime-version: ${{ env.IOS_RUNTIME_VERSION }}
6161
preferred-device-name: iPhone 17 Pro
6262

63-
- name: Run iOS command perf benchmark
63+
- name: Prepare iOS runner
6464
run: |
6565
pnpm clean:daemon
66+
node --experimental-strip-types src/bin.ts prepare ios-runner --platform ios --timeout 240000
67+
68+
- name: Run iOS command perf benchmark
69+
run: |
6670
node --experimental-strip-types scripts/perf/run.ts \
6771
--platform ios \
6872
--device "iPhone 17 Pro" \

.github/workflows/replays-nightly.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ jobs:
7777
runtime-version: ${{ env.IOS_RUNTIME_VERSION }}
7878
preferred-device-name: iPhone 17 Pro
7979

80+
- name: Prepare iOS runner
81+
run: |
82+
pnpm clean:daemon
83+
node --experimental-strip-types src/bin.ts prepare ios-runner --platform ios --timeout 240000
84+
8085
- name: Run iOS simulator replay suite
8186
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
8287

ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ NS_ASSUME_NONNULL_BEGIN
1414
radius:(double)radius
1515
durationMs:(double)durationMs;
1616

17+
+ (NSString * _Nullable)synthesizeSwipeWithApplication:(id)application
18+
x:(double)x
19+
y:(double)y
20+
x2:(double)x2
21+
y2:(double)y2
22+
durationMs:(double)durationMs;
23+
1724
@end
1825

1926
NS_ASSUME_NONNULL_END

ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ static id RunnerPointerPath(
4747
double durationMs,
4848
double side
4949
);
50+
static id RunnerSwipePointerPath(
51+
const RunnerXCTestEventBridge *bridge,
52+
CGPoint start,
53+
CGPoint end,
54+
double durationMs
55+
);
5056
static CGPoint RunnerPointerPointAt(
5157
double x,
5258
double y,
@@ -58,6 +64,8 @@ static CGPoint RunnerPointerPointAt(
5864
double t,
5965
double side
6066
);
67+
static CGPoint RunnerInterpolatedPoint(CGPoint start, CGPoint end, double t);
68+
static double RunnerSmoothStep(double t);
6169

6270
@implementation RunnerSynthesizedGesture
6371

@@ -87,6 +95,26 @@ + (NSString * _Nullable)synthesizeTransformWithApplication:(id)application
8795
}
8896
}
8997

98+
+ (NSString * _Nullable)synthesizeSwipeWithApplication:(id)application
99+
x:(double)x
100+
y:(double)y
101+
x2:(double)x2
102+
y2:(double)y2
103+
durationMs:(double)durationMs {
104+
@try {
105+
return [self trySynthesizeSwipeWithApplication:application
106+
x:x
107+
y:y
108+
x2:x2
109+
y2:y2
110+
durationMs:durationMs];
111+
} @catch (NSException *exception) {
112+
NSString *name = exception.name ?: @"NSException";
113+
NSString *reason = exception.reason ?: @"private XCTest event synthesis failed";
114+
return [NSString stringWithFormat:@"%@: %@", name, reason];
115+
}
116+
}
117+
90118
+ (NSString * _Nullable)trySynthesizeTransformWithApplication:(id)application
91119
x:(double)x
92120
y:(double)y
@@ -151,6 +179,51 @@ + (NSString * _Nullable)trySynthesizeTransformWithApplication:(id)application
151179
return nil;
152180
}
153181

182+
+ (NSString * _Nullable)trySynthesizeSwipeWithApplication:(id)application
183+
x:(double)x
184+
y:(double)y
185+
x2:(double)x2
186+
y2:(double)y2
187+
durationMs:(double)durationMs {
188+
RunnerXCTestEventBridge bridge;
189+
NSString *missing = RunnerResolveXCTestEventBridge(application, &bridge);
190+
if (missing != nil) {
191+
return missing;
192+
}
193+
194+
NSInteger interfaceOrientation =
195+
((RunnerMsgSendInteger)objc_msgSend)(application, bridge.interfaceOrientationSelector);
196+
NSInteger targetProcessID = ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.processIDSelector);
197+
if (targetProcessID <= 0) {
198+
return @"private XCTest event synthesis unavailable: could not resolve target process ID";
199+
}
200+
201+
id record = ((RunnerMsgSendInitRecord)objc_msgSend)(
202+
[bridge.recordClass alloc],
203+
bridge.initRecordSelector,
204+
@"agent-device-swipe",
205+
interfaceOrientation
206+
);
207+
if (record == nil) {
208+
return @"private XCTest event synthesis failed: could not create event record";
209+
}
210+
((RunnerMsgSendSetInteger)objc_msgSend)(record, bridge.setTargetProcessIDSelector, targetProcessID);
211+
212+
id path = RunnerSwipePointerPath(&bridge, CGPointMake(x, y), CGPointMake(x2, y2), durationMs);
213+
if (path == nil) {
214+
return @"private XCTest event synthesis failed: could not create pointer path";
215+
}
216+
((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.addPathSelector, path);
217+
218+
NSError *error = nil;
219+
BOOL ok = ((RunnerMsgSendSynthesize)objc_msgSend)(record, bridge.synthesizeSelector, &error);
220+
if (!ok) {
221+
NSString *detail = error.localizedDescription ?: @"synthesizeWithError returned false";
222+
return [NSString stringWithFormat:@"private XCTest event synthesis failed: %@", detail];
223+
}
224+
return nil;
225+
}
226+
154227
static NSString * _Nullable RunnerResolveXCTestEventBridge(
155228
id application,
156229
RunnerXCTestEventBridge *bridge
@@ -270,6 +343,31 @@ static id RunnerPointerPath(
270343
return path;
271344
}
272345

346+
static id RunnerSwipePointerPath(
347+
const RunnerXCTestEventBridge *bridge,
348+
CGPoint start,
349+
CGPoint end,
350+
double durationMs
351+
) {
352+
id path =
353+
((RunnerMsgSendInitPath)objc_msgSend)([bridge->pathClass alloc], bridge->initPathSelector, start, 0.0);
354+
if (path == nil) {
355+
return nil;
356+
}
357+
358+
int frameCount = MAX(3, (int)(durationMs / 16.0));
359+
NSTimeInterval durationSeconds = durationMs / 1000.0;
360+
for (int index = 1; index <= frameCount; index += 1) {
361+
double t = (double)index / (double)frameCount;
362+
CGPoint point = RunnerInterpolatedPoint(start, end, RunnerSmoothStep(t));
363+
NSTimeInterval offset = durationSeconds * t;
364+
((RunnerMsgSendPathMove)objc_msgSend)(path, bridge->moveSelector, point, offset);
365+
}
366+
367+
((RunnerMsgSendPathOffset)objc_msgSend)(path, bridge->liftSelector, durationSeconds);
368+
return path;
369+
}
370+
273371
static CGPoint RunnerPointerPointAt(
274372
double x,
275373
double y,
@@ -294,4 +392,15 @@ static CGPoint RunnerPointerPointAt(
294392
return CGPointMake(centerX + cos(angle) * radius * side, centerY + sin(angle) * radius * side);
295393
}
296394

395+
static CGPoint RunnerInterpolatedPoint(CGPoint start, CGPoint end, double t) {
396+
return CGPointMake(
397+
start.x + (end.x - start.x) * t,
398+
start.y + (end.y - start.y) * t
399+
);
400+
}
401+
402+
static double RunnerSmoothStep(double t) {
403+
return t * t * (3.0 - 2.0 * t);
404+
}
405+
297406
@end

0 commit comments

Comments
 (0)