@swmansion/argent 0.31.1-next.9 → 0.31.2-next.0

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.
Files changed (31) hide show
  1. package/bin/argent-android-devtools-0.3.0.apk +0 -0
  2. package/bin/darwin/ax-service +0 -0
  3. package/bin/darwin/tvos-ax-service +0 -0
  4. package/bin/darwin/tvos-hid-daemon +0 -0
  5. package/bin/tcp/ax-service +0 -0
  6. package/dist/cli-cmds.mjs +10 -5
  7. package/dist/installer.mjs +525 -312
  8. package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Commands.swift +2 -0
  9. package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Gestures.swift +96 -0
  10. package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerUITests-Bridging-Header.h +1 -0
  11. package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentTouchSynthesizer.h +40 -0
  12. package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentTouchSynthesizer.m +149 -0
  13. package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/RunnerProtocol.swift +22 -5
  14. package/dist/mcp-server.mjs +7 -1
  15. package/dist/tool-args.generated.d.ts +2 -2
  16. package/dist/tool-server.cjs +1531 -992
  17. package/dylibs/libArgentInjectionBootstrap.dylib +0 -0
  18. package/dylibs/libKeyboardPatch.dylib +0 -0
  19. package/dylibs/libNativeDevtoolsIos.dylib +0 -0
  20. package/dylibs/tcp/libArgentInjectionBootstrap.dylib +0 -0
  21. package/dylibs/tcp/libKeyboardPatch.dylib +0 -0
  22. package/dylibs/tcp/libNativeDevtoolsIos.dylib +0 -0
  23. package/dylibs/tvos/libArgentInjectionBootstrap.dylib +0 -0
  24. package/dylibs/tvos/libKeyboardPatch.dylib +0 -0
  25. package/dylibs/tvos/libNativeDevtoolsIos.dylib +0 -0
  26. package/package.json +1 -1
  27. package/rules/argent.md +1 -1
  28. package/skills/argent-create-e2e/SKILL.md +1 -1
  29. package/skills/argent-ios-device-interact/SKILL.md +3 -3
  30. package/skills/argent-qa-e2e/SKILL.md +1 -1
  31. package/skills/argent-screen-recording/SKILL.md +5 -4
@@ -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:
@@ -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 {
@@ -1 +1,2 @@
1
1
  #import "ArgentExceptionGuard.h"
2
+ #import "ArgentTouchSynthesizer.h"
@@ -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, .type, .keyboardReturn, .keyboardDelete,
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, .type, .keyboardReturn,
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.
@@ -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",
@@ -28888,6 +28891,9 @@ function toMcpToolList(tools) {
28888
28891
  return tools.filter((t) => !t.hideFromMcp).map(toMcpTool);
28889
28892
  }
28890
28893
 
28894
+ // ../argent-mcp/src/instructions.ts
28895
+ var SERVER_INSTRUCTIONS = "Argent \u2014 iOS Simulator, Android Emulator, and Chromium app control for interacting, testing, profiling and debugging mobile and Chromium applications. Interaction tools return the screen after the action: a screenshot plus the accessibility element tree with normalized tap frames. Take coordinates from that tree; call describe (or debugger-component-tree) only when no fresh tree is available \u2014 never guess coordinates from pixels. Do not stop simulator servers when a task finishes: the next task reuses them. Call stop-all-simulator-servers when the user asks to clean up or ends the session, or when a skill tells you to recycle a device's services, always with devices: [...] naming the devices this session used. Never stop a Metro the user started unless they ask. One tool-server is shared by every agent using this argent install, so an unscoped call tears down their devices too \u2014 reserve it for a deliberate machine-wide cleanup. Full guidance is in the argent rule loaded from .claude/rules/argent.md.";
28896
+
28891
28897
  // ../argent-mcp/src/installed-version.ts
28892
28898
  import * as fs11 from "node:fs";
28893
28899
  import * as path16 from "node:path";
@@ -29148,7 +29154,7 @@ ${desc}` }];
29148
29154
  { name: "argent", version: getInstalledVersion() },
29149
29155
  {
29150
29156
  capabilities: { tools: {} },
29151
- instructions: "Argent \u2014 iOS Simulator, Android Emulator, and Chromium app control for interacting, testing, profiling and debugging mobile and Chromium applications. Interaction tools return the screen after the action: a screenshot plus the accessibility element tree with normalized tap frames. Take coordinates from that tree; call describe (or debugger-component-tree) only when no fresh tree is available \u2014 never guess coordinates from pixels. Do not stop simulator servers when a task finishes: the next task reuses them. Call stop-all-simulator-servers when the user asks to clean up or ends the session, or when a skill tells you to recycle a device's services, always with devices: [...] naming the devices this session used. Never stop a Metro the user started unless they ask. One tool-server is shared by every agent using this argent install, so an unscoped call tears down their devices too \u2014 reserve it for a deliberate machine-wide cleanup. Full guidance is in the argent rule loaded from .claude/rules/argent.md."
29157
+ instructions: SERVER_INSTRUCTIONS
29152
29158
  }
29153
29159
  );
29154
29160
  server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -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": {