@swmansion/argent 0.31.1-next.10 → 0.31.1-next.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-cmds.mjs +4 -1
- package/dist/installer.mjs +4 -1
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Commands.swift +2 -0
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Gestures.swift +96 -0
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerUITests-Bridging-Header.h +1 -0
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentTouchSynthesizer.h +40 -0
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentTouchSynthesizer.m +149 -0
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/RunnerProtocol.swift +22 -5
- package/dist/mcp-server.mjs +3 -0
- package/dist/tool-args.generated.d.ts +2 -2
- package/dist/tool-server.cjs +1483 -964
- package/package.json +1 -1
- package/rules/argent.md +1 -1
- package/skills/argent-create-e2e/SKILL.md +1 -1
- package/skills/argent-ios-device-interact/SKILL.md +3 -3
- package/skills/argent-qa-e2e/SKILL.md +1 -1
- package/skills/argent-screen-recording/SKILL.md +5 -4
package/dist/cli-cmds.mjs
CHANGED
|
@@ -10741,6 +10741,9 @@ var FAILURE_COMMANDS = [
|
|
|
10741
10741
|
"hdc",
|
|
10742
10742
|
"deveco_emulator",
|
|
10743
10743
|
"xcrun_simctl",
|
|
10744
|
+
// The `sim-remote` CLI, which stands in for simctl (and more) against a
|
|
10745
|
+
// remote simulator.
|
|
10746
|
+
"sim_remote",
|
|
10744
10747
|
"xctrace",
|
|
10745
10748
|
"native_devtools",
|
|
10746
10749
|
"android_devtools",
|
|
@@ -35737,7 +35740,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
35737
35740
|
var SESSION_ID2 = randomUUID5();
|
|
35738
35741
|
function readCliVersion() {
|
|
35739
35742
|
if (true) {
|
|
35740
|
-
return "0.31.1-next.
|
|
35743
|
+
return "0.31.1-next.12";
|
|
35741
35744
|
}
|
|
35742
35745
|
return "0.0.0";
|
|
35743
35746
|
}
|
package/dist/installer.mjs
CHANGED
|
@@ -16107,6 +16107,9 @@ var FAILURE_COMMANDS = [
|
|
|
16107
16107
|
"hdc",
|
|
16108
16108
|
"deveco_emulator",
|
|
16109
16109
|
"xcrun_simctl",
|
|
16110
|
+
// The `sim-remote` CLI, which stands in for simctl (and more) against a
|
|
16111
|
+
// remote simulator.
|
|
16112
|
+
"sim_remote",
|
|
16110
16113
|
"xctrace",
|
|
16111
16114
|
"native_devtools",
|
|
16112
16115
|
"android_devtools",
|
|
@@ -16912,7 +16915,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
16912
16915
|
var SESSION_ID = randomUUID4();
|
|
16913
16916
|
function readCliVersion() {
|
|
16914
16917
|
if (true) {
|
|
16915
|
-
return "0.31.1-next.
|
|
16918
|
+
return "0.31.1-next.12";
|
|
16916
16919
|
}
|
|
16917
16920
|
return "0.0.0";
|
|
16918
16921
|
}
|
package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Commands.swift
CHANGED
|
@@ -159,6 +159,8 @@ extension ArgentRunnerSession {
|
|
|
159
159
|
return performLongPress(request, on: app)
|
|
160
160
|
case .drag:
|
|
161
161
|
return performDrag(request, on: app)
|
|
162
|
+
case .touchPaths:
|
|
163
|
+
return performTouchPaths(request, on: app)
|
|
162
164
|
case .type:
|
|
163
165
|
return performType(request, on: app)
|
|
164
166
|
case .keyboardReturn:
|
package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Gestures.swift
CHANGED
|
@@ -122,6 +122,102 @@ extension ArgentRunnerSession {
|
|
|
122
122
|
return .success(MessagePayload(message: "dragged"))
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/// Most fingers one `touchPaths` command plays. A hand has five.
|
|
126
|
+
private static let maxTouchFingers = 5
|
|
127
|
+
/// Longest `touchPaths` gesture, comfortably inside the command budget.
|
|
128
|
+
private static let maxTouchPathsMs = 30_000.0
|
|
129
|
+
|
|
130
|
+
/// Plays one path per finger, all at once: pinches, rotations, and any
|
|
131
|
+
/// single path with waypoints. XCUICoordinate only draws one straight drag,
|
|
132
|
+
/// so this goes through XCTest's event synthesizer (ArgentTouchSynthesizer).
|
|
133
|
+
///
|
|
134
|
+
/// Wire points are absolute screen points, the same space `tap` and `drag`
|
|
135
|
+
/// take, and times are measured from the start of the gesture. A finger
|
|
136
|
+
/// whose first waypoint is later than 0 touches down late, which is how a
|
|
137
|
+
/// second finger joins a gesture already under way.
|
|
138
|
+
func performTouchPaths(_ request: CommandRequest, on app: XCUIApplication)
|
|
139
|
+
-> Envelope
|
|
140
|
+
{
|
|
141
|
+
guard let fingers = request.fingers,
|
|
142
|
+
(1...Self.maxTouchFingers).contains(fingers.count)
|
|
143
|
+
else {
|
|
144
|
+
return .failure(
|
|
145
|
+
.invalidRequest,
|
|
146
|
+
"touchPaths requires between 1 and \(Self.maxTouchFingers) fingers"
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (index, finger) in fingers.enumerated() {
|
|
151
|
+
guard !finger.points.isEmpty else {
|
|
152
|
+
return .failure(
|
|
153
|
+
.invalidRequest,
|
|
154
|
+
"touchPaths finger \(index) has no waypoints"
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
var previousMs = 0.0
|
|
159
|
+
|
|
160
|
+
for point in finger.points {
|
|
161
|
+
guard point.x.isFinite, point.y.isFinite, point.tMs.isFinite,
|
|
162
|
+
point.tMs >= previousMs, point.tMs <= Self.maxTouchPathsMs
|
|
163
|
+
else {
|
|
164
|
+
return .failure(
|
|
165
|
+
.invalidRequest,
|
|
166
|
+
"touchPaths finger \(index) needs finite waypoints whose times do not decrease and stay within \(Int(Self.maxTouchPathsMs)) ms"
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
previousMs = point.tMs
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if let reason = ArgentTouchSynthesizer.unavailabilityReason() {
|
|
175
|
+
return .failure(
|
|
176
|
+
.unsupportedOperation,
|
|
177
|
+
"multi-finger touch is unavailable here: \(reason)",
|
|
178
|
+
hint:
|
|
179
|
+
"Use tap, swipe or drag for a single finger, or drive the app's own zoom controls."
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// The orientation the app draws in, not the way the phone is held: a
|
|
184
|
+
// portrait-only app on a phone turned sideways is still portrait. Wire
|
|
185
|
+
// points are in the app's space, which is the panel's own only in
|
|
186
|
+
// portrait. Other orientations have not been checked on hardware, and
|
|
187
|
+
// a touch that lands somewhere else without an error is worse than a
|
|
188
|
+
// refusal.
|
|
189
|
+
switch ArgentTouchSynthesizer.interfaceOrientation(ofApplication: app) {
|
|
190
|
+
case .portrait:
|
|
191
|
+
break
|
|
192
|
+
case .unknown:
|
|
193
|
+
return .failure(
|
|
194
|
+
.unsupportedOperation,
|
|
195
|
+
"touchPaths could not read the app's interface orientation",
|
|
196
|
+
hint:
|
|
197
|
+
"Use tap, swipe or drag for a single finger, or drive the app's own zoom controls."
|
|
198
|
+
)
|
|
199
|
+
default:
|
|
200
|
+
return .failure(
|
|
201
|
+
.unsupportedOperation,
|
|
202
|
+
"touchPaths has only been verified with the app drawn in portrait",
|
|
203
|
+
hint: "Turn the phone upright so the app draws in portrait, then retry."
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let waypoints = fingers.map { finger in
|
|
208
|
+
finger.points.map { [$0.x, $0.y, $0.tMs / 1000] as [NSNumber] }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if let failure = ArgentTouchSynthesizer.synthesizeFingers(
|
|
212
|
+
waypoints,
|
|
213
|
+
interfaceOrientation: .portrait
|
|
214
|
+
) {
|
|
215
|
+
return .failure(.commandFailed, failure)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return .success(MessagePayload(message: "touched"))
|
|
219
|
+
}
|
|
220
|
+
|
|
125
221
|
/// Reports the interaction viewport, the rectangle that normalized 0-1
|
|
126
222
|
/// coordinates map into.
|
|
127
223
|
func appViewport(_ app: XCUIApplication) -> Envelope {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
|
|
3
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
4
|
+
|
|
5
|
+
/// Multi-finger touch synthesis through XCTest's own event classes.
|
|
6
|
+
///
|
|
7
|
+
/// The public XCUICoordinate API can drive one finger along one straight
|
|
8
|
+
/// press-then-drag. Pinches, rotations and any path with waypoints need several
|
|
9
|
+
/// fingers moving at once, which XCTest builds internally from
|
|
10
|
+
/// XCPointerEventPath and XCSynthesizedEventRecord. Those classes are not in
|
|
11
|
+
/// any public header, so they are looked up by name at runtime: nothing links
|
|
12
|
+
/// against a private symbol, and an Xcode that renames or drops one makes
|
|
13
|
+
/// `unavailabilityReason` answer instead of the runner failing to load.
|
|
14
|
+
@interface ArgentTouchSynthesizer : NSObject
|
|
15
|
+
|
|
16
|
+
/// nil when every class and selector this needs is present, otherwise a
|
|
17
|
+
/// sentence naming what is missing.
|
|
18
|
+
+ (NSString *_Nullable)unavailabilityReason;
|
|
19
|
+
|
|
20
|
+
/// The orientation the app's interface is drawn in, which can differ from the
|
|
21
|
+
/// way the phone is held (a portrait-only app on a phone turned sideways).
|
|
22
|
+
/// UIInterfaceOrientationUnknown when this XCTest cannot say.
|
|
23
|
+
+ (UIInterfaceOrientation)interfaceOrientationOfApplication:(id)application;
|
|
24
|
+
|
|
25
|
+
/// Plays every finger at once as one event record, and returns once the last
|
|
26
|
+
/// finger has lifted. Each finger is an array of `[x, y, seconds]` waypoints:
|
|
27
|
+
/// points in the panel's own portrait space, and the time since the gesture
|
|
28
|
+
/// started. The finger touches down at its first waypoint, moves through the
|
|
29
|
+
/// rest in order, and lifts a hair after its last. Waypoint times must not
|
|
30
|
+
/// decrease.
|
|
31
|
+
///
|
|
32
|
+
/// Returns nil on success, otherwise "Name: reason" for the failure or the
|
|
33
|
+
/// NSException XCTest threw (Swift cannot catch those).
|
|
34
|
+
+ (NSString *_Nullable)synthesizeFingers:
|
|
35
|
+
(NSArray<NSArray<NSArray<NSNumber *> *> *> *)fingers
|
|
36
|
+
interfaceOrientation:(UIInterfaceOrientation)orientation;
|
|
37
|
+
|
|
38
|
+
@end
|
|
39
|
+
|
|
40
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#import "ArgentTouchSynthesizer.h"
|
|
2
|
+
|
|
3
|
+
#import <XCTest/XCTest.h>
|
|
4
|
+
#import <objc/message.h>
|
|
5
|
+
|
|
6
|
+
/// The selectors used on XCTest's private classes, declared as protocols so the
|
|
7
|
+
/// compiler can check the calls without a class symbol to link against.
|
|
8
|
+
@protocol ArgentPointerEventPath <NSObject>
|
|
9
|
+
- (instancetype)initForTouchAtPoint:(CGPoint)point offset:(NSTimeInterval)offset;
|
|
10
|
+
- (void)moveToPoint:(CGPoint)point atOffset:(NSTimeInterval)offset;
|
|
11
|
+
- (void)liftUpAtOffset:(NSTimeInterval)offset;
|
|
12
|
+
@end
|
|
13
|
+
|
|
14
|
+
@protocol ArgentSynthesizedEventRecord <NSObject>
|
|
15
|
+
- (instancetype)initWithName:(NSString *)name
|
|
16
|
+
displayID:(NSUInteger)displayID
|
|
17
|
+
interfaceOrientation:(UIInterfaceOrientation)orientation;
|
|
18
|
+
- (instancetype)initWithName:(NSString *)name
|
|
19
|
+
interfaceOrientation:(UIInterfaceOrientation)orientation;
|
|
20
|
+
- (void)addPointerEventPath:(id<ArgentPointerEventPath>)path;
|
|
21
|
+
- (BOOL)synthesizeWithError:(NSError *_Nullable *_Nullable)error;
|
|
22
|
+
@end
|
|
23
|
+
|
|
24
|
+
/// How long after its last waypoint a finger lifts, so the move and the lift
|
|
25
|
+
/// are two distinct events in the stream.
|
|
26
|
+
static const NSTimeInterval kLiftDelay = 0.01;
|
|
27
|
+
|
|
28
|
+
@implementation ArgentTouchSynthesizer
|
|
29
|
+
|
|
30
|
+
+ (NSString *_Nullable)unavailabilityReason {
|
|
31
|
+
Class pathClass = NSClassFromString(@"XCPointerEventPath");
|
|
32
|
+
Class recordClass = NSClassFromString(@"XCSynthesizedEventRecord");
|
|
33
|
+
|
|
34
|
+
if (pathClass == Nil || recordClass == Nil) {
|
|
35
|
+
return @"this Xcode's XCTest has no XCPointerEventPath/XCSynthesizedEventRecord";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
SEL pathSelectors[] = {
|
|
39
|
+
@selector(initForTouchAtPoint:offset:),
|
|
40
|
+
@selector(moveToPoint:atOffset:),
|
|
41
|
+
@selector(liftUpAtOffset:),
|
|
42
|
+
};
|
|
43
|
+
for (size_t i = 0; i < sizeof(pathSelectors) / sizeof(SEL); i++) {
|
|
44
|
+
if (![pathClass instancesRespondToSelector:pathSelectors[i]]) {
|
|
45
|
+
return [NSString stringWithFormat:@"XCPointerEventPath no longer responds to %@",
|
|
46
|
+
NSStringFromSelector(pathSelectors[i])];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Either record initializer will do; synthesizeFingers picks the newer one.
|
|
51
|
+
if (![recordClass instancesRespondToSelector:@selector(initWithName:displayID:interfaceOrientation:)] &&
|
|
52
|
+
![recordClass instancesRespondToSelector:@selector(initWithName:interfaceOrientation:)]) {
|
|
53
|
+
return @"XCSynthesizedEventRecord has neither initWithName:displayID:interfaceOrientation: "
|
|
54
|
+
@"nor initWithName:interfaceOrientation:";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
SEL recordSelectors[] = {
|
|
58
|
+
@selector(addPointerEventPath:),
|
|
59
|
+
@selector(synthesizeWithError:),
|
|
60
|
+
};
|
|
61
|
+
for (size_t i = 0; i < sizeof(recordSelectors) / sizeof(SEL); i++) {
|
|
62
|
+
if (![recordClass instancesRespondToSelector:recordSelectors[i]]) {
|
|
63
|
+
return [NSString stringWithFormat:@"XCSynthesizedEventRecord no longer responds to %@",
|
|
64
|
+
NSStringFromSelector(recordSelectors[i])];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return nil;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
+ (UIInterfaceOrientation)interfaceOrientationOfApplication:(id)application {
|
|
72
|
+
SEL selector = NSSelectorFromString(@"interfaceOrientation");
|
|
73
|
+
if (![application respondsToSelector:selector]) {
|
|
74
|
+
return UIInterfaceOrientationUnknown;
|
|
75
|
+
}
|
|
76
|
+
return ((UIInterfaceOrientation(*)(id, SEL))objc_msgSend)(application, selector);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// The main screen's display id, which the display-aware record initializer
|
|
80
|
+
/// routes touches by. nil when XCUIScreen does not expose one.
|
|
81
|
+
+ (NSNumber *_Nullable)mainDisplayID {
|
|
82
|
+
XCUIScreen *screen = XCUIScreen.mainScreen;
|
|
83
|
+
if (![screen respondsToSelector:NSSelectorFromString(@"displayID")]) {
|
|
84
|
+
return nil;
|
|
85
|
+
}
|
|
86
|
+
id value = [screen valueForKey:@"displayID"];
|
|
87
|
+
return [value isKindOfClass:NSNumber.class] ? value : nil;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// A new event record. Prefers the display-aware initializer, which newer
|
|
91
|
+
/// XCTest uses for its own touches, and falls back to the older one when this
|
|
92
|
+
/// XCTest or the screen cannot supply a display id.
|
|
93
|
+
+ (id<ArgentSynthesizedEventRecord>)recordWithInterfaceOrientation:
|
|
94
|
+
(UIInterfaceOrientation)orientation {
|
|
95
|
+
Class recordClass = NSClassFromString(@"XCSynthesizedEventRecord");
|
|
96
|
+
NSString *name = @"argent-touch-paths";
|
|
97
|
+
NSNumber *displayID = [self mainDisplayID];
|
|
98
|
+
|
|
99
|
+
if (displayID != nil &&
|
|
100
|
+
[recordClass instancesRespondToSelector:@selector(initWithName:displayID:interfaceOrientation:)]) {
|
|
101
|
+
return [[recordClass alloc] initWithName:name
|
|
102
|
+
displayID:displayID.unsignedIntegerValue
|
|
103
|
+
interfaceOrientation:orientation];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return [[recordClass alloc] initWithName:name interfaceOrientation:orientation];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
+ (NSString *_Nullable)synthesizeFingers:
|
|
110
|
+
(NSArray<NSArray<NSArray<NSNumber *> *> *> *)fingers
|
|
111
|
+
interfaceOrientation:(UIInterfaceOrientation)orientation {
|
|
112
|
+
NSString *unavailable = [self unavailabilityReason];
|
|
113
|
+
if (unavailable != nil) {
|
|
114
|
+
return unavailable;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@try {
|
|
118
|
+
id<ArgentSynthesizedEventRecord> record = [self recordWithInterfaceOrientation:orientation];
|
|
119
|
+
|
|
120
|
+
for (NSArray<NSArray<NSNumber *> *> *waypoints in fingers) {
|
|
121
|
+
NSArray<NSNumber *> *first = waypoints.firstObject;
|
|
122
|
+
id<ArgentPointerEventPath> path = [[NSClassFromString(@"XCPointerEventPath") alloc]
|
|
123
|
+
initForTouchAtPoint:CGPointMake(first[0].doubleValue, first[1].doubleValue)
|
|
124
|
+
offset:first[2].doubleValue];
|
|
125
|
+
|
|
126
|
+
for (NSUInteger i = 1; i < waypoints.count; i++) {
|
|
127
|
+
NSArray<NSNumber *> *waypoint = waypoints[i];
|
|
128
|
+
[path moveToPoint:CGPointMake(waypoint[0].doubleValue, waypoint[1].doubleValue)
|
|
129
|
+
atOffset:waypoint[2].doubleValue];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
[path liftUpAtOffset:waypoints.lastObject[2].doubleValue + kLiftDelay];
|
|
133
|
+
[record addPointerEventPath:path];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
NSError *error = nil;
|
|
137
|
+
if (![record synthesizeWithError:&error]) {
|
|
138
|
+
return [NSString stringWithFormat:@"XCTest refused the touch events: %@",
|
|
139
|
+
error.localizedDescription ?: @"no error given"];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return nil;
|
|
143
|
+
} @catch (NSException *exception) {
|
|
144
|
+
return [NSString stringWithFormat:@"%@: %@", exception.name ?: @"NSException",
|
|
145
|
+
exception.reason ?: @"unhandled Objective-C exception"];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@end
|
|
@@ -13,6 +13,7 @@ enum CommandKind: String, Codable {
|
|
|
13
13
|
case tap
|
|
14
14
|
case longPress
|
|
15
15
|
case drag
|
|
16
|
+
case touchPaths
|
|
16
17
|
case type
|
|
17
18
|
case keyboardReturn
|
|
18
19
|
case keyboardDelete
|
|
@@ -30,8 +31,8 @@ extension CommandKind {
|
|
|
30
31
|
switch self {
|
|
31
32
|
case .status, .viewport, .snapshot, .screenshot:
|
|
32
33
|
return true
|
|
33
|
-
case .tap, .longPress, .drag, .
|
|
34
|
-
.button, .shutdown:
|
|
34
|
+
case .tap, .longPress, .drag, .touchPaths, .type, .keyboardReturn,
|
|
35
|
+
.keyboardDelete, .button, .shutdown:
|
|
35
36
|
return false
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -41,8 +42,8 @@ extension CommandKind {
|
|
|
41
42
|
/// runner's own host app.
|
|
42
43
|
var requiresAppBundleId: Bool {
|
|
43
44
|
switch self {
|
|
44
|
-
case .viewport, .tap, .longPress, .drag, .
|
|
45
|
-
.keyboardDelete, .snapshot:
|
|
45
|
+
case .viewport, .tap, .longPress, .drag, .touchPaths, .type,
|
|
46
|
+
.keyboardReturn, .keyboardDelete, .snapshot:
|
|
46
47
|
return true
|
|
47
48
|
case .status, .button, .screenshot, .shutdown:
|
|
48
49
|
return false
|
|
@@ -73,7 +74,7 @@ extension CommandKind {
|
|
|
73
74
|
// never reports quiescent stalls the wait until XCTest gives up and
|
|
74
75
|
// synthesizes the event anyway, and a shorter budget would abandon a
|
|
75
76
|
// command that still completes.
|
|
76
|
-
case .tap, .longPress, .drag: return 75
|
|
77
|
+
case .tap, .longPress, .drag, .touchPaths: return 75
|
|
77
78
|
default: return 30
|
|
78
79
|
}
|
|
79
80
|
}
|
|
@@ -90,6 +91,20 @@ enum DeviceButton: String, Codable {
|
|
|
90
91
|
case actionButton
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
/// One point on a finger's path: an absolute point in the app's coordinate
|
|
95
|
+
/// space, and the time since the gesture started.
|
|
96
|
+
struct TouchWaypoint: Codable {
|
|
97
|
+
let x: Double
|
|
98
|
+
let y: Double
|
|
99
|
+
let tMs: Double
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// One finger of a `touchPaths` command: it touches down at the first
|
|
103
|
+
/// waypoint, moves through the rest in order, and lifts after the last.
|
|
104
|
+
struct TouchFinger: Codable {
|
|
105
|
+
let points: [TouchWaypoint]
|
|
106
|
+
}
|
|
107
|
+
|
|
93
108
|
struct CommandRequest: Codable {
|
|
94
109
|
let command: CommandKind
|
|
95
110
|
let commandId: String?
|
|
@@ -119,6 +134,8 @@ struct CommandRequest: Codable {
|
|
|
119
134
|
/// velocity is near zero and the scroll view does not fling. Mirrors the
|
|
120
135
|
/// simulator's ease-out `settle` swipe.
|
|
121
136
|
let settle: Bool?
|
|
137
|
+
/// `touchPaths`: one path per finger, all played at once.
|
|
138
|
+
let fingers: [TouchFinger]?
|
|
122
139
|
/// `type`: the text delivered to the focused input.
|
|
123
140
|
let text: String?
|
|
124
141
|
/// `button`: which hardware button to press.
|
package/dist/mcp-server.mjs
CHANGED
|
@@ -26024,6 +26024,9 @@ var FAILURE_COMMANDS = [
|
|
|
26024
26024
|
"hdc",
|
|
26025
26025
|
"deveco_emulator",
|
|
26026
26026
|
"xcrun_simctl",
|
|
26027
|
+
// The `sim-remote` CLI, which stands in for simctl (and more) against a
|
|
26028
|
+
// remote simulator.
|
|
26029
|
+
"sim_remote",
|
|
26027
26030
|
"xctrace",
|
|
26028
26031
|
"native_devtools",
|
|
26029
26032
|
"android_devtools",
|
|
@@ -792,9 +792,9 @@ export interface ArgentToolArgs {
|
|
|
792
792
|
udid: string;
|
|
793
793
|
/** Auto-stop cap in seconds (default 180, max 600). Set it to slightly more than the interaction you plan to capture. */
|
|
794
794
|
timeLimitSeconds?: number;
|
|
795
|
-
/** Default true. Collapse stretches where the screen does not change: the first second of each still stretch is kept, then unchanged frames are dropped until something moves again, so a long recording with brief activity comes back short instead of full of dead air. The returned durationMs is the trimmed length; wallClockMs/trimmedMs report what was removed. Set false to keep a faithful real-time recording. */
|
|
795
|
+
/** Default true. Collapse stretches where the screen does not change: the first second of each still stretch is kept, then unchanged frames are dropped until something moves again, so a long recording with brief activity comes back short instead of full of dead air (on a remote simulator the still stretches are removed whole). The returned durationMs is the trimmed length; wallClockMs/trimmedMs report what was removed. Set false to keep a faithful real-time recording. */
|
|
796
796
|
trimStatic?: boolean;
|
|
797
|
-
/** Default true. Draw simulator-server's touch visualizer into the recording: a pulse marks each tap, a comet trail follows swipes and drags, and paired markers show two-finger pinch/rotate, so the video makes clear where every interaction landed. Set false to record the raw screen with no overlay. */
|
|
797
|
+
/** Default true. Draw simulator-server's touch visualizer into the recording: a pulse marks each tap, a comet trail follows swipes and drags, and paired markers show two-finger pinch/rotate, so the video makes clear where every interaction landed. On remote (`remote:`) simulators the overlay is drawn on the runner and is also visible in the live stream until the recording stops. Set false to record the raw screen with no overlay. */
|
|
798
798
|
showTouches?: boolean;
|
|
799
799
|
};
|
|
800
800
|
"screen-recording-stop": {
|