@swmansion/argent 0.31.2-next.3 → 0.31.2-next.5
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 +1 -1
- package/dist/installer.mjs +1 -1
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentExceptionGuard.h +3 -0
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentExceptionGuard.m +11 -3
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Gestures.swift +27 -19
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Quiescence.swift +95 -0
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+TextEntry.swift +2 -2
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/RunnerProtocol.swift +5 -5
- package/dist/tool-args.generated.d.ts +1 -1
- package/dist/tool-server.cjs +127 -23
- package/package.json +1 -1
package/dist/cli-cmds.mjs
CHANGED
|
@@ -35797,7 +35797,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
35797
35797
|
var SESSION_ID2 = randomUUID5();
|
|
35798
35798
|
function readCliVersion() {
|
|
35799
35799
|
if (true) {
|
|
35800
|
-
return "0.31.2-next.
|
|
35800
|
+
return "0.31.2-next.5";
|
|
35801
35801
|
}
|
|
35802
35802
|
return "0.0.0";
|
|
35803
35803
|
}
|
package/dist/installer.mjs
CHANGED
|
@@ -16922,7 +16922,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
16922
16922
|
var SESSION_ID = randomUUID4();
|
|
16923
16923
|
function readCliVersion() {
|
|
16924
16924
|
if (true) {
|
|
16925
|
-
return "0.31.2-next.
|
|
16925
|
+
return "0.31.2-next.5";
|
|
16926
16926
|
}
|
|
16927
16927
|
return "0.0.0";
|
|
16928
16928
|
}
|
|
@@ -11,6 +11,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
11
11
|
/// Returns nil when the block completed, otherwise "Name: reason".
|
|
12
12
|
+ (NSString *_Nullable)runCatching:(NS_NOESCAPE dispatch_block_t)block;
|
|
13
13
|
|
|
14
|
+
/// Returns nil when the block completed, otherwise the exception it raised.
|
|
15
|
+
+ (NSException *_Nullable)exceptionFrom:(NS_NOESCAPE dispatch_block_t)block;
|
|
16
|
+
|
|
14
17
|
@end
|
|
15
18
|
|
|
16
19
|
NS_ASSUME_NONNULL_END
|
|
@@ -3,13 +3,21 @@
|
|
|
3
3
|
@implementation ArgentExceptionGuard
|
|
4
4
|
|
|
5
5
|
+ (NSString *_Nullable)runCatching:(NS_NOESCAPE dispatch_block_t)block {
|
|
6
|
+
NSException *exception = [self exceptionFrom:block];
|
|
7
|
+
if (exception == nil) {
|
|
8
|
+
return nil;
|
|
9
|
+
}
|
|
10
|
+
NSString *name = exception.name ?: @"NSException";
|
|
11
|
+
NSString *reason = exception.reason ?: @"unhandled Objective-C exception";
|
|
12
|
+
return [NSString stringWithFormat:@"%@: %@", name, reason];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
+ (NSException *_Nullable)exceptionFrom:(NS_NOESCAPE dispatch_block_t)block {
|
|
6
16
|
@try {
|
|
7
17
|
block();
|
|
8
18
|
return nil;
|
|
9
19
|
} @catch (NSException *exception) {
|
|
10
|
-
|
|
11
|
-
NSString *reason = exception.reason ?: @"unhandled Objective-C exception";
|
|
12
|
-
return [NSString stringWithFormat:@"%@: %@", name, reason];
|
|
20
|
+
return exception;
|
|
13
21
|
}
|
|
14
22
|
}
|
|
15
23
|
|
package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Gestures.swift
CHANGED
|
@@ -21,10 +21,10 @@ extension ArgentRunnerSession {
|
|
|
21
21
|
|
|
22
22
|
/// Taps at the wire coordinates: one tap, the native double-tap when
|
|
23
23
|
/// `numberOfTaps` is 2, or a loop of single taps above that. XCUICoordinate
|
|
24
|
-
/// has no N-tap API, and the loop is not one gesture
|
|
25
|
-
///
|
|
26
|
-
///
|
|
27
|
-
///
|
|
24
|
+
/// has no N-tap API, and the loop is not one gesture: each tap is its own
|
|
25
|
+
/// synthesized event and XPC round trip, about 300 ms apart on a
|
|
26
|
+
/// simulator, which a double-tap recognizer can still pair. That is
|
|
27
|
+
/// documented, not refused.
|
|
28
28
|
func performTap(_ request: CommandRequest, on app: XCUIApplication)
|
|
29
29
|
-> Envelope
|
|
30
30
|
{
|
|
@@ -39,15 +39,17 @@ extension ArgentRunnerSession {
|
|
|
39
39
|
|
|
40
40
|
let coordinate = point(app, x, y, relativeTo: app.frame.origin)
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
case 2:
|
|
46
|
-
coordinate.doubleTap()
|
|
47
|
-
default:
|
|
48
|
-
// Separate taps, not one gesture (see the doc comment above).
|
|
49
|
-
for _ in 0..<taps {
|
|
42
|
+
skippingQuiescence(of: app) {
|
|
43
|
+
switch taps {
|
|
44
|
+
case 1:
|
|
50
45
|
coordinate.tap()
|
|
46
|
+
case 2:
|
|
47
|
+
coordinate.doubleTap()
|
|
48
|
+
default:
|
|
49
|
+
// Separate taps, not one gesture (see the doc comment above).
|
|
50
|
+
for _ in 0..<taps {
|
|
51
|
+
coordinate.tap()
|
|
52
|
+
}
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -63,7 +65,11 @@ extension ArgentRunnerSession {
|
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
let seconds = max(0.05, (request.durationMs ?? 800) / 1000)
|
|
66
|
-
point(app, x, y, relativeTo: app.frame.origin)
|
|
68
|
+
let coordinate = point(app, x, y, relativeTo: app.frame.origin)
|
|
69
|
+
|
|
70
|
+
skippingQuiescence(of: app) {
|
|
71
|
+
coordinate.press(forDuration: seconds)
|
|
72
|
+
}
|
|
67
73
|
|
|
68
74
|
return .success(MessagePayload(message: "long-pressed"))
|
|
69
75
|
}
|
|
@@ -112,12 +118,14 @@ extension ArgentRunnerSession {
|
|
|
112
118
|
velocity = .default
|
|
113
119
|
}
|
|
114
120
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
skippingQuiescence(of: app) {
|
|
122
|
+
start.press(
|
|
123
|
+
forDuration: startHold,
|
|
124
|
+
thenDragTo: end,
|
|
125
|
+
withVelocity: velocity,
|
|
126
|
+
thenHoldForDuration: endHold
|
|
127
|
+
)
|
|
128
|
+
}
|
|
121
129
|
|
|
122
130
|
return .success(MessagePayload(message: "dragged"))
|
|
123
131
|
}
|
package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Quiescence.swift
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
|
|
3
|
+
/// XCTest waits up to 60s before and after every synthesized event for the
|
|
4
|
+
/// app to report idle. A screen that never does, such as one holding a paused
|
|
5
|
+
/// UIViewPropertyAnimator (expo-blur), stalls each gesture for two minutes,
|
|
6
|
+
/// past its watchdog budget. The runner skips those waits and runs XCTest's
|
|
7
|
+
/// idle check itself under a short cap. Every API here is XCTest SPI, probed
|
|
8
|
+
/// before use: when any is missing, the event runs with XCTest's own waits.
|
|
9
|
+
extension ArgentRunnerSession {
|
|
10
|
+
/// The longest a gesture waits for the app to go idle before its event.
|
|
11
|
+
/// Covers a navigation transition, so a gesture sent right after the one
|
|
12
|
+
/// that started it lands on the new screen instead of mid-transition.
|
|
13
|
+
static let preEventSettleTimeout: TimeInterval = 2
|
|
14
|
+
|
|
15
|
+
private static let performWithInteractionOptions = NSSelectorFromString(
|
|
16
|
+
"_performWithInteractionOptions:block:"
|
|
17
|
+
)
|
|
18
|
+
/// Bit 1 skips the pre-event wait, bit 2 the post-event one.
|
|
19
|
+
private static let skipQuiescenceOptions: UInt32 = 0b11
|
|
20
|
+
|
|
21
|
+
private static let applicationImpl = NSSelectorFromString("applicationImpl")
|
|
22
|
+
private static let currentProcess = NSSelectorFromString("currentProcess")
|
|
23
|
+
private static let initiateQuiescenceChecks = NSSelectorFromString(
|
|
24
|
+
"_initiateQuiescenceChecksIncludingAnimationsIdle:"
|
|
25
|
+
)
|
|
26
|
+
private static let makeQuiescenceExpectation = NSSelectorFromString(
|
|
27
|
+
"_makeQuiescenceExpectation"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
/// Waits at most `preEventSettleTimeout` for `app` to go idle, then runs
|
|
31
|
+
/// `body`, which synthesizes events into it, without XCTest's waits.
|
|
32
|
+
func skippingQuiescence(of app: XCUIApplication, _ body: () -> Void) {
|
|
33
|
+
let selector = Self.performWithInteractionOptions
|
|
34
|
+
|
|
35
|
+
guard app.responds(to: selector),
|
|
36
|
+
awaitQuiescence(of: app, timeout: Self.preEventSettleTimeout)
|
|
37
|
+
else {
|
|
38
|
+
body()
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
typealias Perform = @convention(c) (
|
|
43
|
+
NSObject, Selector, UInt32, @convention(block) () -> Void
|
|
44
|
+
) -> Void
|
|
45
|
+
let perform = unsafeBitCast(app.method(for: selector), to: Perform.self)
|
|
46
|
+
|
|
47
|
+
// XCTest restores the prior options only when the block returns, and
|
|
48
|
+
// every XCUIApplication for this bundle id shares them, so an
|
|
49
|
+
// exception must not unwind through it.
|
|
50
|
+
var exception: NSException?
|
|
51
|
+
withoutActuallyEscaping(body) { body in
|
|
52
|
+
perform(app, selector, Self.skipQuiescenceOptions) {
|
|
53
|
+
exception = ArgentExceptionGuard.exception(from: body)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exception?.raise()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// XCTest's own idle check (event loop and animations), under `timeout`.
|
|
60
|
+
/// False when any SPI it needs is missing.
|
|
61
|
+
private func awaitQuiescence(of app: XCUIApplication, timeout: TimeInterval)
|
|
62
|
+
-> Bool
|
|
63
|
+
{
|
|
64
|
+
guard
|
|
65
|
+
let impl = Self.object(app, Self.applicationImpl),
|
|
66
|
+
let process = Self.object(impl, Self.currentProcess),
|
|
67
|
+
process.responds(to: Self.initiateQuiescenceChecks)
|
|
68
|
+
else {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
typealias Initiate = @convention(c) (NSObject, Selector, Bool) -> Void
|
|
73
|
+
let initiate = unsafeBitCast(
|
|
74
|
+
process.method(for: Self.initiateQuiescenceChecks),
|
|
75
|
+
to: Initiate.self
|
|
76
|
+
)
|
|
77
|
+
initiate(process, Self.initiateQuiescenceChecks, true)
|
|
78
|
+
|
|
79
|
+
guard
|
|
80
|
+
let expectation = Self.object(process, Self.makeQuiescenceExpectation)
|
|
81
|
+
as? XCTestExpectation
|
|
82
|
+
else {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_ = XCTWaiter.wait(for: [expectation], timeout: timeout)
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// The object a zero-argument getter returns, or nil when `target` lacks it.
|
|
91
|
+
private static func object(_ target: NSObject, _ getter: Selector) -> NSObject? {
|
|
92
|
+
guard target.responds(to: getter) else { return nil }
|
|
93
|
+
return target.perform(getter)?.takeUnretainedValue() as? NSObject
|
|
94
|
+
}
|
|
95
|
+
}
|
package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+TextEntry.swift
CHANGED
|
@@ -77,7 +77,7 @@ extension ArgentRunnerSession {
|
|
|
77
77
|
app.keyboards.keys.matching(submitKey).firstMatch,
|
|
78
78
|
] where candidate.exists && candidate.isHittable {
|
|
79
79
|
let label = candidate.label
|
|
80
|
-
candidate.tap()
|
|
80
|
+
skippingQuiescence(of: app) { candidate.tap() }
|
|
81
81
|
|
|
82
82
|
return .success(
|
|
83
83
|
MessagePayload(message: "pressed keyboard \(label)")
|
|
@@ -131,7 +131,7 @@ extension ArgentRunnerSession {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
let exceptionDescription = ArgentExceptionGuard.runCatching {
|
|
134
|
-
app.typeText(text)
|
|
134
|
+
self.skippingQuiescence(of: app) { app.typeText(text) }
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
if let exceptionDescription {
|
|
@@ -70,10 +70,10 @@ extension CommandKind {
|
|
|
70
70
|
switch self {
|
|
71
71
|
// XCTest types long strings in real time, so typing needs extra room.
|
|
72
72
|
case .type: return 55
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
73
|
+
// tap, longPress and drag outlast XCTest's ~60s pre-event idle wait,
|
|
74
|
+
// which runs where the runner cannot skip it
|
|
75
|
+
// (ArgentRunnerSession+Quiescence.swift). touchPaths never waits for
|
|
76
|
+
// idle but shares the gesture budget.
|
|
77
77
|
case .tap, .longPress, .drag, .touchPaths: return 75
|
|
78
78
|
default: return 30
|
|
79
79
|
}
|
|
@@ -117,7 +117,7 @@ struct CommandRequest: Codable {
|
|
|
117
117
|
let y: Double?
|
|
118
118
|
/// `tap`: number of taps. Defaults to 1; 2 is the native double-tap.
|
|
119
119
|
/// Higher counts loop single taps on the device: there is no native N-tap
|
|
120
|
-
/// API, so they
|
|
120
|
+
/// API, so they are not one multi-tap gesture.
|
|
121
121
|
let numberOfTaps: Int?
|
|
122
122
|
/// `drag`: absolute start and end points.
|
|
123
123
|
let fromX: Double?
|
|
@@ -408,7 +408,7 @@ export interface ArgentToolArgs {
|
|
|
408
408
|
x: number;
|
|
409
409
|
/** Normalized vertical position 0.0–1.0 (top=0, bottom=1), not pixels */
|
|
410
410
|
y: number;
|
|
411
|
-
/** Number of taps/clicks dispatched as ONE multi-tap gesture (2 = double-tap / double-click). The taps land inside the OS double-tap window; on Chromium each click carries an escalating CDP clickCount so dblclick actually fires; on physical iOS 2 is the native double-tap and higher counts
|
|
411
|
+
/** Number of taps/clicks dispatched as ONE multi-tap gesture (2 = double-tap / double-click). The taps land inside the OS double-tap window; on Chromium each click carries an escalating CDP clickCount so dblclick actually fires; on physical iOS 2 is the native double-tap and higher counts loop single taps, which a double-tap recognizer may pair. On HarmonyOS only 2 is one gesture; 3 or more are separate injections a device round trip apart, which the OS reads as single taps. Default 1. */
|
|
412
412
|
clickCount?: number;
|
|
413
413
|
};
|
|
414
414
|
keyboard: {
|
package/dist/tool-server.cjs
CHANGED
|
@@ -101080,7 +101080,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
101080
101080
|
var SESSION_ID = (0, import_node_crypto3.randomUUID)();
|
|
101081
101081
|
function readCliVersion() {
|
|
101082
101082
|
if (true) {
|
|
101083
|
-
return "0.31.2-next.
|
|
101083
|
+
return "0.31.2-next.5";
|
|
101084
101084
|
}
|
|
101085
101085
|
return "0.0.0";
|
|
101086
101086
|
}
|
|
@@ -142136,7 +142136,7 @@ var zodSchema17 = external_exports.object({
|
|
|
142136
142136
|
x: external_exports.number().min(0).max(1).describe("Normalized horizontal position 0.0\u20131.0 (left=0, right=1), not pixels"),
|
|
142137
142137
|
y: external_exports.number().min(0).max(1).describe("Normalized vertical position 0.0\u20131.0 (top=0, bottom=1), not pixels"),
|
|
142138
142138
|
clickCount: external_exports.number().int().min(1).max(10).optional().describe(
|
|
142139
|
-
"Number of taps/clicks dispatched as ONE multi-tap gesture (2 = double-tap / double-click). The taps land inside the OS double-tap window; on Chromium each click carries an escalating CDP clickCount so dblclick actually fires; on physical iOS 2 is the native double-tap and higher counts
|
|
142139
|
+
"Number of taps/clicks dispatched as ONE multi-tap gesture (2 = double-tap / double-click). The taps land inside the OS double-tap window; on Chromium each click carries an escalating CDP clickCount so dblclick actually fires; on physical iOS 2 is the native double-tap and higher counts loop single taps, which a double-tap recognizer may pair. <flag:harmonyos>On HarmonyOS only 2 is one gesture; 3 or more are separate injections a device round trip apart, which the OS reads as single taps. </flag:harmonyos>Default 1."
|
|
142140
142140
|
)
|
|
142141
142141
|
});
|
|
142142
142142
|
function tapDescription(params, tense) {
|
|
@@ -142209,6 +142209,9 @@ Returns { tapped: true, timestampMs }. On physical iOS, reactivated: true = app
|
|
|
142209
142209
|
On a physical iPhone use \`describe\`; \`native-describe-screen\` is simulator-only.
|
|
142210
142210
|
Before tapping, determine the correct coordinates by using discovery tools. Pick by platform: iOS / Android use \`describe\`, \`native-describe-screen\`, or \`debugger-component-tree\`; Chromium uses \`describe\` (the DOM walker)<flag:harmonyos> and HarmonyOS uses \`describe\` (the \`uitest\` layout dump)</flag:harmonyos>, since the native and RN-specific discovery tools don't apply<flag:harmonyos> to either</flag:harmonyos>. More information in \`argent-device-interact\` skill`,
|
|
142211
142211
|
alwaysLoad: true,
|
|
142212
|
+
// A physical iPhone's first call waits out the runner build, past the MCP fetch timeout,
|
|
142213
|
+
// whose abort replays the call: the gesture would land once per replay.
|
|
142214
|
+
longRunning: true,
|
|
142212
142215
|
searchHint: "tap press button element device simulator emulator chromium touch down up click",
|
|
142213
142216
|
zodSchema: zodSchema17,
|
|
142214
142217
|
capability: capability9,
|
|
@@ -142336,6 +142339,9 @@ Physical iOS: an edge gesture (back-swipe) needs fromX 0 exactly; durationMs set
|
|
|
142336
142339
|
HarmonyOS: durationMs sets drag speed, clamped to uitest's 200-40000 px/s, so a short slow swipe ends early and a long fast one runs late.</flag:harmonyos>
|
|
142337
142340
|
Pass momentum:false for a momentum-free swipe that lands where the finger lifts (little to no fling at the 300 default), when you need a deterministic scroll distance; it needs durationMs >= 150 and is rejected below that, a shorter ease-out leaving the OS too little wall clock to read the deceleration as a stop. At 150 it lands short of the lift point instead, and 2 of 47 runs still flung backwards. A plain swipe takes any duration up to 10000ms and is delivered as close to the speed it was authored as a 16ms frame allows: below ~32ms the whole travel lands in one or two frames, which the OS flings as hard as it flings anything. Returns { swiped: true, timestampMs }. On physical iOS, reactivated: true = app was re-fronted; re-describe. Fails if the simulator-server / emulator backend<flag:harmonyos>, or \`hdc\` on HarmonyOS,</flag:harmonyos> is not reachable for the given device.`,
|
|
142338
142341
|
alwaysLoad: true,
|
|
142342
|
+
// A physical iPhone's first call waits out the runner build, past the MCP fetch timeout,
|
|
142343
|
+
// whose abort replays the call: the gesture would land once per replay.
|
|
142344
|
+
longRunning: true,
|
|
142339
142345
|
searchHint: "swipe scroll drag pan gesture device simulator emulator touch move",
|
|
142340
142346
|
zodSchema: zodSchema18,
|
|
142341
142347
|
capability: capability10,
|
|
@@ -142893,6 +142899,9 @@ Example pinch-to-zoom (with interpolate:10 for smoothness):
|
|
|
142893
142899
|
interpolate: 10`,
|
|
142894
142900
|
zodSchema: zodSchema21,
|
|
142895
142901
|
capability: capability13,
|
|
142902
|
+
// A physical iPhone's first call waits out the runner build, past the MCP fetch timeout,
|
|
142903
|
+
// whose abort replays the call: the gesture would land once per replay.
|
|
142904
|
+
longRunning: true,
|
|
142896
142905
|
// Declare the runner only for a train execute can play. A rejected train must not pay a runner cold start.
|
|
142897
142906
|
services: (params) => {
|
|
142898
142907
|
const device = resolveDevice(params.udid);
|
|
@@ -142980,6 +142989,9 @@ Auto-generates interpolated frames at ~60fps<flag:harmonyos> on iOS and Android;
|
|
|
142980
142989
|
Use when you need to zoom in or out on a map, image, or zoomable view. Physical iPhones play both fingers through the runner as one event stream. Returns { pinched: true, timestampMs }. On physical iOS, reactivated: true = app was re-fronted; re-describe. Fails if the simulator-server / emulator backend<flag:harmonyos>, or \`hdc\` on HarmonyOS,</flag:harmonyos> is not reachable for the given device, or on a physical iPhone when the app is not drawn in portrait.`,
|
|
142981
142990
|
zodSchema: zodSchema22,
|
|
142982
142991
|
capability: capability14,
|
|
142992
|
+
// A physical iPhone's first call waits out the runner build, past the MCP fetch timeout,
|
|
142993
|
+
// whose abort replays the call: the gesture would land once per replay.
|
|
142994
|
+
longRunning: true,
|
|
142983
142995
|
services: (params) => {
|
|
142984
142996
|
const device = resolveDevice(params.udid);
|
|
142985
142997
|
if (device.platform === "harmony") return {};
|
|
@@ -143115,6 +143127,9 @@ Use when you need to rotate a map, image picker, or any rotateable UI element. R
|
|
|
143115
143127
|
Size the orbit with radius, or with radiusX and radiusY together (the pair overrides radius); one half of the pair alone, or none of the three, is rejected.`,
|
|
143116
143128
|
zodSchema: zodSchema23,
|
|
143117
143129
|
capability: capability15,
|
|
143130
|
+
// A physical iPhone's first call waits out the runner build, past the MCP fetch timeout,
|
|
143131
|
+
// whose abort replays the call: the gesture would land once per replay.
|
|
143132
|
+
longRunning: true,
|
|
143118
143133
|
services: (params) => {
|
|
143119
143134
|
const device = resolveDevice(params.udid);
|
|
143120
143135
|
return isIosPhysicalDevice(device) ? { iosDeviceRunner: iosDeviceRunnerRef(device) } : { simulatorServer: simulatorServerRef(device) };
|
|
@@ -143238,6 +143253,9 @@ Returns { pressed: buttonName }.
|
|
|
143238
143253
|
Fails if the device backend is not reachable: the simulator-server for iOS, <flag:harmonyos>\`hdc\` for HarmonyOS, </flag:harmonyos>or \`adb\` for Android (Android presses are injected with \`adb shell input keyevent\`).`,
|
|
143239
143254
|
zodSchema: zodSchema24,
|
|
143240
143255
|
capability: capability16,
|
|
143256
|
+
// A physical iPhone's first call waits out the runner build, past the MCP fetch timeout,
|
|
143257
|
+
// whose abort replays the call: the press would land once per replay.
|
|
143258
|
+
longRunning: true,
|
|
143241
143259
|
// Declare only the service the resolved path actually consumes. The Android
|
|
143242
143260
|
// path uses `adb`, so a sim-server here would spawn a service the tool never
|
|
143243
143261
|
// uses (up to a 30s ready-wait) and could throw ServiceInitializationError
|
|
@@ -158850,8 +158868,8 @@ var import_util14 = require("util");
|
|
|
158850
158868
|
var DEFAULT_EXEC_TIMEOUT_MS = 6e4;
|
|
158851
158869
|
var DEFAULT_EXEC_MAX_BUFFER = 256 * 1024 * 1024;
|
|
158852
158870
|
async function execFileAsyncWithTimeout(file2, args, options = {}) {
|
|
158853
|
-
const
|
|
158854
|
-
const { stdout, stderr } = await
|
|
158871
|
+
const execFileAsync36 = (0, import_util14.promisify)(import_child_process2.execFile);
|
|
158872
|
+
const { stdout, stderr } = await execFileAsync36(file2, args, {
|
|
158855
158873
|
encoding: "utf-8",
|
|
158856
158874
|
...options,
|
|
158857
158875
|
// Applied after ...options so a caller cannot weaken the guards; maxBuffer
|
|
@@ -179008,18 +179026,68 @@ function probeArgentToolServer(host, port, timeoutMs = 500) {
|
|
|
179008
179026
|
|
|
179009
179027
|
// ../tool-server/src/utils/simulator-watcher.ts
|
|
179010
179028
|
var fs69 = __toESM(require("node:fs"));
|
|
179011
|
-
var import_node_child_process45 = require("node:child_process");
|
|
179012
|
-
var import_node_util33 = require("node:util");
|
|
179013
179029
|
init_external_devices();
|
|
179014
|
-
|
|
179030
|
+
|
|
179031
|
+
// ../tool-server/src/utils/simctl-exec.ts
|
|
179032
|
+
var import_node_child_process45 = require("node:child_process");
|
|
179033
|
+
var SimctlTimeoutError = class extends Error {
|
|
179034
|
+
constructor(args, timeoutMs) {
|
|
179035
|
+
super(`xcrun ${args.join(" ")} did not answer within ${timeoutMs} ms`);
|
|
179036
|
+
this.name = "SimctlTimeoutError";
|
|
179037
|
+
}
|
|
179038
|
+
};
|
|
179039
|
+
function execSimctl(args, options = {}) {
|
|
179040
|
+
const timeoutMs = options.timeoutMs ?? SIMCTL_SPAWN_TIMEOUT_MS;
|
|
179041
|
+
return new Promise((resolve16, reject) => {
|
|
179042
|
+
if (options.signal?.aborted) {
|
|
179043
|
+
reject(new Error(`xcrun ${args.join(" ")} aborted`));
|
|
179044
|
+
return;
|
|
179045
|
+
}
|
|
179046
|
+
const child = (0, import_node_child_process45.spawn)("xcrun", args, { detached: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
179047
|
+
let stdout = "";
|
|
179048
|
+
let stderr = "";
|
|
179049
|
+
let settled = false;
|
|
179050
|
+
child.stdout.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
|
|
179051
|
+
child.stderr.setEncoding("utf8").on("data", (chunk) => stderr += chunk);
|
|
179052
|
+
const settle3 = (err) => {
|
|
179053
|
+
if (settled) return;
|
|
179054
|
+
settled = true;
|
|
179055
|
+
clearTimeout(timer);
|
|
179056
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
179057
|
+
if (err) reject(err);
|
|
179058
|
+
else resolve16(stdout);
|
|
179059
|
+
};
|
|
179060
|
+
const killGroup2 = () => {
|
|
179061
|
+
if (child.pid) signalGroup(child.pid, "SIGKILL");
|
|
179062
|
+
};
|
|
179063
|
+
const onAbort = () => {
|
|
179064
|
+
killGroup2();
|
|
179065
|
+
settle3(new Error(`xcrun ${args.join(" ")} aborted`));
|
|
179066
|
+
};
|
|
179067
|
+
const timer = setTimeout(() => {
|
|
179068
|
+
killGroup2();
|
|
179069
|
+
settle3(new SimctlTimeoutError(args, timeoutMs));
|
|
179070
|
+
}, timeoutMs);
|
|
179071
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
179072
|
+
child.on("error", (err) => settle3(err));
|
|
179073
|
+
child.on("close", (code, sig) => {
|
|
179074
|
+
if (code === 0) return settle3(null);
|
|
179075
|
+
settle3(
|
|
179076
|
+
new Error(
|
|
179077
|
+
`xcrun ${args.join(" ")} exited with ${sig ?? `code ${code}`}` + (stderr.trim() ? `: ${stderr.trim()}` : "")
|
|
179078
|
+
)
|
|
179079
|
+
);
|
|
179080
|
+
});
|
|
179081
|
+
});
|
|
179082
|
+
}
|
|
179083
|
+
|
|
179084
|
+
// ../tool-server/src/utils/simulator-watcher.ts
|
|
179015
179085
|
var POLL_INTERVAL_MS2 = 1e4;
|
|
179016
|
-
|
|
179017
|
-
|
|
179018
|
-
|
|
179019
|
-
|
|
179020
|
-
|
|
179021
|
-
"--json"
|
|
179022
|
-
]);
|
|
179086
|
+
var MAX_BACKOFF_TICKS = 29;
|
|
179087
|
+
async function getBootedUdidsInSet(deviceSet, signal) {
|
|
179088
|
+
const stdout = await execSimctl([...simctlPrefix(deviceSet), "list", "devices", "--json"], {
|
|
179089
|
+
signal
|
|
179090
|
+
});
|
|
179023
179091
|
const data = JSON.parse(stdout);
|
|
179024
179092
|
const udids = /* @__PURE__ */ new Set();
|
|
179025
179093
|
for (const devices of Object.values(data.devices)) {
|
|
@@ -179032,13 +179100,17 @@ async function getBootedUdidsInSet(deviceSet) {
|
|
|
179032
179100
|
}
|
|
179033
179101
|
return udids;
|
|
179034
179102
|
}
|
|
179035
|
-
async function getBootedUdids() {
|
|
179103
|
+
async function getBootedUdids(signal) {
|
|
179036
179104
|
const sets = [
|
|
179037
179105
|
null,
|
|
179038
179106
|
...configuredAdditionalDeviceSets().filter((p) => fs69.existsSync(p))
|
|
179039
179107
|
];
|
|
179040
|
-
const perSet = await Promise.
|
|
179041
|
-
|
|
179108
|
+
const perSet = await Promise.allSettled(sets.map((set2) => getBootedUdidsInSet(set2, signal)));
|
|
179109
|
+
const failures = perSet.flatMap((r) => r.status === "rejected" ? [r.reason] : []);
|
|
179110
|
+
if (failures.length) {
|
|
179111
|
+
throw failures.find((err) => err instanceof SimctlTimeoutError) ?? failures[0];
|
|
179112
|
+
}
|
|
179113
|
+
return new Set(perSet.flatMap((r) => r.status === "fulfilled" ? [...r.value] : []));
|
|
179042
179114
|
}
|
|
179043
179115
|
async function initUdid(registry2, udid, trackedServices) {
|
|
179044
179116
|
const ndRef = nativeDevtoolsRef({ id: udid, platform: "ios", kind: "simulator" });
|
|
@@ -179051,6 +179123,9 @@ async function initUdid(registry2, udid, trackedServices) {
|
|
|
179051
179123
|
function startSimulatorWatcher(registry2) {
|
|
179052
179124
|
const trackedServices = /* @__PURE__ */ new Map();
|
|
179053
179125
|
const reportedSkips = /* @__PURE__ */ new Set();
|
|
179126
|
+
const stopController = new AbortController();
|
|
179127
|
+
let consecutiveTimeouts = 0;
|
|
179128
|
+
let ticksToSkip = 0;
|
|
179054
179129
|
function withoutForeignSimulators(booted) {
|
|
179055
179130
|
const ours = /* @__PURE__ */ new Set();
|
|
179056
179131
|
const claimed = /* @__PURE__ */ new Set();
|
|
@@ -179099,10 +179174,23 @@ function startSimulatorWatcher(registry2) {
|
|
|
179099
179174
|
let booted;
|
|
179100
179175
|
let claimed;
|
|
179101
179176
|
try {
|
|
179102
|
-
({ ours: booted, claimed } = withoutForeignSimulators(
|
|
179103
|
-
|
|
179177
|
+
({ ours: booted, claimed } = withoutForeignSimulators(
|
|
179178
|
+
await getBootedUdids(stopController.signal)
|
|
179179
|
+
));
|
|
179180
|
+
} catch (err) {
|
|
179181
|
+
if (err instanceof SimctlTimeoutError) {
|
|
179182
|
+
consecutiveTimeouts += 1;
|
|
179183
|
+
ticksToSkip = Math.min(2 ** consecutiveTimeouts - 1, MAX_BACKOFF_TICKS);
|
|
179184
|
+
if (consecutiveTimeouts === 1) {
|
|
179185
|
+
process.stderr.write(
|
|
179186
|
+
`[simulator-watcher] ${err.message}; retrying with backoff until it does. If Xcode was just updated, open Xcode once or run \`sudo xcodebuild -runFirstLaunch\`.
|
|
179187
|
+
`
|
|
179188
|
+
);
|
|
179189
|
+
}
|
|
179190
|
+
}
|
|
179104
179191
|
return;
|
|
179105
179192
|
}
|
|
179193
|
+
consecutiveTimeouts = 0;
|
|
179106
179194
|
await handBackClaimed(claimed);
|
|
179107
179195
|
const newUdids = [...booted].filter((udid) => !trackedServices.has(udid));
|
|
179108
179196
|
const pendingAttempts = newUdids.map(
|
|
@@ -179127,9 +179215,24 @@ function startSimulatorWatcher(registry2) {
|
|
|
179127
179215
|
}
|
|
179128
179216
|
}
|
|
179129
179217
|
const ready2 = poll(true);
|
|
179130
|
-
|
|
179131
|
-
})
|
|
179132
|
-
|
|
179218
|
+
let inFlight2 = ready2.catch(() => {
|
|
179219
|
+
}).finally(() => inFlight2 = null);
|
|
179220
|
+
const interval2 = setInterval(() => {
|
|
179221
|
+
if (inFlight2) return;
|
|
179222
|
+
if (ticksToSkip > 0) {
|
|
179223
|
+
ticksToSkip -= 1;
|
|
179224
|
+
return;
|
|
179225
|
+
}
|
|
179226
|
+
inFlight2 = poll(false).catch(() => {
|
|
179227
|
+
}).finally(() => inFlight2 = null);
|
|
179228
|
+
}, POLL_INTERVAL_MS2);
|
|
179229
|
+
return {
|
|
179230
|
+
stop: () => {
|
|
179231
|
+
clearInterval(interval2);
|
|
179232
|
+
stopController.abort();
|
|
179233
|
+
},
|
|
179234
|
+
ready: ready2
|
|
179235
|
+
};
|
|
179133
179236
|
}
|
|
179134
179237
|
|
|
179135
179238
|
// ../tool-server/src/utils/preview-window.ts
|
|
@@ -179452,6 +179555,7 @@ function start() {
|
|
|
179452
179555
|
if (exitCode > finalExitCode) finalExitCode = exitCode;
|
|
179453
179556
|
if (shuttingDown) return;
|
|
179454
179557
|
shuttingDown = true;
|
|
179558
|
+
stopWatcher();
|
|
179455
179559
|
eventLog?.info({
|
|
179456
179560
|
type: "tool_server.stopping",
|
|
179457
179561
|
msg: "Tool server is stopping.",
|
|
@@ -179466,7 +179570,6 @@ function start() {
|
|
|
179466
179570
|
}
|
|
179467
179571
|
previewWindow.dispose();
|
|
179468
179572
|
updateChecker.dispose();
|
|
179469
|
-
stopWatcher();
|
|
179470
179573
|
httpHandle.dispose();
|
|
179471
179574
|
try {
|
|
179472
179575
|
await registry2.dispose();
|
|
@@ -179529,6 +179632,7 @@ function start() {
|
|
|
179529
179632
|
}
|
|
179530
179633
|
});
|
|
179531
179634
|
Promise.all([watcherReady, identityWarm]).then(() => {
|
|
179635
|
+
if (shuttingDown) return;
|
|
179532
179636
|
server = httpHandle.app.listen(PORT, HOST, () => {
|
|
179533
179637
|
listening = true;
|
|
179534
179638
|
const addr = server.address();
|
package/package.json
CHANGED