agent-device 0.15.2 → 0.16.1
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/README.md +51 -155
- package/android-multitouch-helper/README.md +41 -0
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.16.1.apk +0 -0
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.16.1.apk.sha256 +1 -0
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.16.1.manifest.json +10 -0
- package/android-snapshot-helper/README.md +2 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.16.1.apk +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.16.1.apk.sha256 +1 -0
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.15.2.manifest.json → agent-device-android-snapshot-helper-0.16.1.manifest.json} +6 -6
- package/dist/src/1231.js +1 -1
- package/dist/src/1769.js +7 -7
- package/dist/src/2099.js +1 -0
- package/dist/src/221.js +4 -4
- package/dist/src/3622.js +3 -0
- package/dist/src/7519.js +1 -0
- package/dist/src/7556.js +1 -1
- package/dist/src/89.js +1 -0
- package/dist/src/940.js +1 -1
- package/dist/src/9542.js +2 -2
- package/dist/src/9639.js +2 -2
- package/dist/src/989.js +1 -1
- package/dist/src/android-adb.d.ts +26 -0
- package/dist/src/android-adb.js +1 -1
- package/dist/src/android-snapshot-helper.d.ts +30 -0
- package/dist/src/batch.d.ts +9 -9
- package/dist/src/cli.js +495 -80
- package/dist/src/index.d.ts +47 -5
- package/dist/src/internal/daemon.js +69 -44
- package/dist/src/server.js +2 -2
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +1 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +19 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +297 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +144 -5
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +328 -23
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +3 -1
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +8 -0
- package/package.json +9 -3
- package/server.json +2 -2
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.15.2.apk +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.15.2.apk.sha256 +0 -1
- package/dist/src/1393.js +0 -1
- package/dist/src/2151.js +0 -438
- package/dist/src/3572.js +0 -1
- package/dist/src/7599.js +0 -3
- package/dist/src/9671.js +0 -1
package/dist/src/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`)}export{
|
|
1
|
+
import{createAgentDeviceClient as r}from"./9542.js";import{runCommand as e,isCommandName as t,listMcpToolDefinitions as n}from"./89.js";import{readVersion as i}from"./2099.js";let s=function(n={createClient:r,runCommand:e}){return{execute:async(r,e)=>{var i;if(!t(r))throw Error(`Unknown command tool: ${r}`);let s=n.createClient(function(r){if(!r||"object"!=typeof r||Array.isArray(r))return{};let e=r.stateDir;if(void 0===e)return{};if("string"!=typeof e||0===e.length)throw Error("Expected stateDir to be a non-empty string.");return{stateDir:e}}(e)),o=await n.runCommand(s,r,function(r){if(!r||"object"!=typeof r||Array.isArray(r))return r;let{stateDir:e,...t}=r;return t}(e));return{isError:!1,structuredContent:o,content:[{type:"text",text:"string"==typeof(i=o)?i:JSON.stringify(i,null,2)}]}}}}();async function o(r){if("2.0"!==r.jsonrpc||"string"!=typeof r.method)return u(r.id??null,-32600,"Invalid JSON-RPC request.");if(void 0===r.id)return null;try{var e,t;return e=r.id,t=await a(r.method,r.params),{jsonrpc:"2.0",id:e,result:t}}catch(e){if(e instanceof f)return u(r.id,-32601,e.message);return u(r.id,-32602,e instanceof Error?e.message:String(e))}}async function a(r,e){switch(r){case"initialize":return{protocolVersion:"2025-11-25",capabilities:{tools:{}},serverInfo:{name:"agent-device",version:i()}};case"ping":return{};case"tools/list":return{tools:n().map(r=>{var e;return{name:r.name,description:r.description,inputSchema:{...e=r.inputSchema,properties:{...e.properties,stateDir:{type:"string",description:"Agent-device state directory."}}}}})};case"tools/call":return await c(e);default:throw new f(`Unsupported MCP method: ${r}`)}}async function c(r){let e=function(r){if(!r||"object"!=typeof r||Array.isArray(r))throw Error("Expected object parameters.");return r}(r),t=function(r,e){let t=r[e];if("string"!=typeof t||0===t.length)throw Error(`Expected ${e} to be a non-empty string.`);return t}(e,"name");try{return await s.execute(t,e.arguments)}catch(r){return function(r,e=!1){return{isError:e,content:[{type:"text",text:r}]}}(r instanceof Error?r.message:String(r),!0)}}function u(r,e,t){return{jsonrpc:"2.0",id:r,error:{code:e,message:t}}}class f extends Error{}async function l(){let r=function(r={}){let e=r.handlePayload??p,t=r.write??m,n=Promise.resolve();return{push:r=>{var i;let s=Array.isArray(i=r)?1===i.length?i[0]?.id??null:null:i.id??null;n=n.then(async()=>{let n=await e(r);n&&t(n)}).catch(r=>{t({jsonrpc:"2.0",id:s,error:{code:-32603,message:r instanceof Error?r.message:String(r)}})})},idle:async()=>{await n}}}(),e=new y(e=>{r.push(e)});process.stdin.setEncoding("utf8"),process.stdin.on("data",r=>{try{e.push(r)}catch(r){m({jsonrpc:"2.0",id:null,error:{code:-32700,message:r instanceof Error?r.message:String(r)}})}}),await new Promise(r=>{process.stdin.on("end",r),process.stdin.on("close",r),process.stdin.resume()}),await r.idle()}function p(r){return Array.isArray(r)?d(r):o(r)}async function d(r){let e=[];for(let n of r){var t;e.push(...(t=await o(n))?[t]:[])}return e.length>0?e:null}class y{buffer="";sink;constructor(r){this.sink=r}push(r){for(this.buffer+=r;;){let r=this.tryReadLineMessage();if(void 0!==r){this.emit(r);continue}break}}tryReadLineMessage(){let r=this.buffer.indexOf("\n");if(-1===r)return;let e=this.buffer.slice(0,r).trim();return this.buffer=this.buffer.slice(r+1),e.length>0?e:void 0}emit(r){let e=JSON.parse(r);Array.isArray(e),this.sink(e)}}function m(r){process.stdout.write(`${JSON.stringify(r)}
|
|
2
|
+
`)}export{l as runAgentDeviceMcpServer};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
|
|
3
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
4
|
+
|
|
5
|
+
@interface RunnerSynthesizedGesture : NSObject
|
|
6
|
+
|
|
7
|
+
+ (NSString * _Nullable)synthesizeTransformWithApplication:(id)application
|
|
8
|
+
x:(double)x
|
|
9
|
+
y:(double)y
|
|
10
|
+
dx:(double)dx
|
|
11
|
+
dy:(double)dy
|
|
12
|
+
scale:(double)scale
|
|
13
|
+
degrees:(double)degrees
|
|
14
|
+
radius:(double)radius
|
|
15
|
+
durationMs:(double)durationMs;
|
|
16
|
+
|
|
17
|
+
@end
|
|
18
|
+
|
|
19
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#import "RunnerSynthesizedGesture.h"
|
|
2
|
+
|
|
3
|
+
#import <CoreGraphics/CoreGraphics.h>
|
|
4
|
+
#import <math.h>
|
|
5
|
+
#import <objc/message.h>
|
|
6
|
+
|
|
7
|
+
typedef NSInteger (*RunnerMsgSendInteger)(id, SEL);
|
|
8
|
+
typedef id (*RunnerMsgSendInitRecord)(id, SEL, NSString *, NSInteger);
|
|
9
|
+
typedef id (*RunnerMsgSendInitPath)(id, SEL, CGPoint, NSTimeInterval);
|
|
10
|
+
typedef void (*RunnerMsgSendPathMove)(id, SEL, CGPoint, NSTimeInterval);
|
|
11
|
+
typedef void (*RunnerMsgSendPathOffset)(id, SEL, NSTimeInterval);
|
|
12
|
+
typedef void (*RunnerMsgSendAddPath)(id, SEL, id);
|
|
13
|
+
typedef void (*RunnerMsgSendSetInteger)(id, SEL, NSInteger);
|
|
14
|
+
typedef BOOL (*RunnerMsgSendSynthesize)(id, SEL, NSError **);
|
|
15
|
+
|
|
16
|
+
typedef struct {
|
|
17
|
+
Class recordClass;
|
|
18
|
+
Class pathClass;
|
|
19
|
+
SEL initRecordSelector;
|
|
20
|
+
SEL addPathSelector;
|
|
21
|
+
SEL setTargetProcessIDSelector;
|
|
22
|
+
SEL synthesizeSelector;
|
|
23
|
+
SEL interfaceOrientationSelector;
|
|
24
|
+
SEL processIDSelector;
|
|
25
|
+
SEL initPathSelector;
|
|
26
|
+
SEL moveSelector;
|
|
27
|
+
SEL liftSelector;
|
|
28
|
+
} RunnerXCTestEventBridge;
|
|
29
|
+
|
|
30
|
+
static NSString * _Nullable RunnerResolveXCTestEventBridge(
|
|
31
|
+
id application,
|
|
32
|
+
RunnerXCTestEventBridge *bridge
|
|
33
|
+
);
|
|
34
|
+
static NSString * _Nullable RunnerRequireClass(Class cls, NSString *className);
|
|
35
|
+
static NSString * _Nullable RunnerRequireSelector(Class cls, SEL selector, NSString *selectorName);
|
|
36
|
+
static NSString * _Nullable RunnerRequireApplicationSelector(id application, SEL selector, NSString *selectorName);
|
|
37
|
+
static id RunnerPointerPath(
|
|
38
|
+
const RunnerXCTestEventBridge *bridge,
|
|
39
|
+
CGPoint start,
|
|
40
|
+
double x,
|
|
41
|
+
double y,
|
|
42
|
+
double dx,
|
|
43
|
+
double dy,
|
|
44
|
+
double scale,
|
|
45
|
+
double degrees,
|
|
46
|
+
double radius,
|
|
47
|
+
double durationMs,
|
|
48
|
+
double side
|
|
49
|
+
);
|
|
50
|
+
static CGPoint RunnerPointerPointAt(
|
|
51
|
+
double x,
|
|
52
|
+
double y,
|
|
53
|
+
double dx,
|
|
54
|
+
double dy,
|
|
55
|
+
double scale,
|
|
56
|
+
double degrees,
|
|
57
|
+
double baseRadius,
|
|
58
|
+
double t,
|
|
59
|
+
double side
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
@implementation RunnerSynthesizedGesture
|
|
63
|
+
|
|
64
|
+
+ (NSString * _Nullable)synthesizeTransformWithApplication:(id)application
|
|
65
|
+
x:(double)x
|
|
66
|
+
y:(double)y
|
|
67
|
+
dx:(double)dx
|
|
68
|
+
dy:(double)dy
|
|
69
|
+
scale:(double)scale
|
|
70
|
+
degrees:(double)degrees
|
|
71
|
+
radius:(double)radius
|
|
72
|
+
durationMs:(double)durationMs {
|
|
73
|
+
@try {
|
|
74
|
+
return [self trySynthesizeTransformWithApplication:application
|
|
75
|
+
x:x
|
|
76
|
+
y:y
|
|
77
|
+
dx:dx
|
|
78
|
+
dy:dy
|
|
79
|
+
scale:scale
|
|
80
|
+
degrees:degrees
|
|
81
|
+
radius:radius
|
|
82
|
+
durationMs:durationMs];
|
|
83
|
+
} @catch (NSException *exception) {
|
|
84
|
+
NSString *name = exception.name ?: @"NSException";
|
|
85
|
+
NSString *reason = exception.reason ?: @"private XCTest event synthesis failed";
|
|
86
|
+
return [NSString stringWithFormat:@"%@: %@", name, reason];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
+ (NSString * _Nullable)trySynthesizeTransformWithApplication:(id)application
|
|
91
|
+
x:(double)x
|
|
92
|
+
y:(double)y
|
|
93
|
+
dx:(double)dx
|
|
94
|
+
dy:(double)dy
|
|
95
|
+
scale:(double)scale
|
|
96
|
+
degrees:(double)degrees
|
|
97
|
+
radius:(double)radius
|
|
98
|
+
durationMs:(double)durationMs {
|
|
99
|
+
RunnerXCTestEventBridge bridge;
|
|
100
|
+
NSString *missing = RunnerResolveXCTestEventBridge(application, &bridge);
|
|
101
|
+
if (missing != nil) {
|
|
102
|
+
return missing;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
NSInteger interfaceOrientation =
|
|
106
|
+
((RunnerMsgSendInteger)objc_msgSend)(application, bridge.interfaceOrientationSelector);
|
|
107
|
+
NSInteger targetProcessID = ((RunnerMsgSendInteger)objc_msgSend)(application, bridge.processIDSelector);
|
|
108
|
+
if (targetProcessID <= 0) {
|
|
109
|
+
return @"private XCTest event synthesis unavailable: could not resolve target process ID";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
id record = ((RunnerMsgSendInitRecord)objc_msgSend)(
|
|
113
|
+
[bridge.recordClass alloc],
|
|
114
|
+
bridge.initRecordSelector,
|
|
115
|
+
@"agent-device-transform",
|
|
116
|
+
interfaceOrientation
|
|
117
|
+
);
|
|
118
|
+
if (record == nil) {
|
|
119
|
+
return @"private XCTest event synthesis failed: could not create event record";
|
|
120
|
+
}
|
|
121
|
+
((RunnerMsgSendSetInteger)objc_msgSend)(record, bridge.setTargetProcessIDSelector, targetProcessID);
|
|
122
|
+
|
|
123
|
+
double sides[] = {1.0, -1.0};
|
|
124
|
+
for (int index = 0; index < 2; index += 1) {
|
|
125
|
+
double side = sides[index];
|
|
126
|
+
id path = RunnerPointerPath(
|
|
127
|
+
&bridge,
|
|
128
|
+
RunnerPointerPointAt(x, y, dx, dy, scale, degrees, radius, 0.0, side),
|
|
129
|
+
x,
|
|
130
|
+
y,
|
|
131
|
+
dx,
|
|
132
|
+
dy,
|
|
133
|
+
scale,
|
|
134
|
+
degrees,
|
|
135
|
+
radius,
|
|
136
|
+
durationMs,
|
|
137
|
+
side
|
|
138
|
+
);
|
|
139
|
+
if (path == nil) {
|
|
140
|
+
return @"private XCTest event synthesis failed: could not create pointer path";
|
|
141
|
+
}
|
|
142
|
+
((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.addPathSelector, path);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
NSError *error = nil;
|
|
146
|
+
BOOL ok = ((RunnerMsgSendSynthesize)objc_msgSend)(record, bridge.synthesizeSelector, &error);
|
|
147
|
+
if (!ok) {
|
|
148
|
+
NSString *detail = error.localizedDescription ?: @"synthesizeWithError returned false";
|
|
149
|
+
return [NSString stringWithFormat:@"private XCTest event synthesis failed: %@", detail];
|
|
150
|
+
}
|
|
151
|
+
return nil;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static NSString * _Nullable RunnerResolveXCTestEventBridge(
|
|
155
|
+
id application,
|
|
156
|
+
RunnerXCTestEventBridge *bridge
|
|
157
|
+
) {
|
|
158
|
+
Class recordClass = NSClassFromString(@"XCSynthesizedEventRecord");
|
|
159
|
+
Class pathClass = NSClassFromString(@"XCPointerEventPath");
|
|
160
|
+
SEL initRecordSelector = NSSelectorFromString(@"initWithName:interfaceOrientation:");
|
|
161
|
+
SEL addPathSelector = NSSelectorFromString(@"addPointerEventPath:");
|
|
162
|
+
SEL setTargetProcessIDSelector = NSSelectorFromString(@"setTargetProcessID:");
|
|
163
|
+
SEL synthesizeSelector = NSSelectorFromString(@"synthesizeWithError:");
|
|
164
|
+
SEL interfaceOrientationSelector = NSSelectorFromString(@"interfaceOrientation");
|
|
165
|
+
SEL processIDSelector = NSSelectorFromString(@"processID");
|
|
166
|
+
SEL initPathSelector = NSSelectorFromString(@"initForTouchAtPoint:offset:");
|
|
167
|
+
SEL moveSelector = NSSelectorFromString(@"moveToPoint:atOffset:");
|
|
168
|
+
SEL liftSelector = NSSelectorFromString(@"liftUpAtOffset:");
|
|
169
|
+
|
|
170
|
+
NSString *missing = RunnerRequireClass(recordClass, @"XCSynthesizedEventRecord");
|
|
171
|
+
if (missing != nil) return missing;
|
|
172
|
+
missing = RunnerRequireClass(pathClass, @"XCPointerEventPath");
|
|
173
|
+
if (missing != nil) return missing;
|
|
174
|
+
missing = RunnerRequireSelector(recordClass, initRecordSelector, @"initWithName:interfaceOrientation:");
|
|
175
|
+
if (missing != nil) return missing;
|
|
176
|
+
missing = RunnerRequireSelector(recordClass, addPathSelector, @"addPointerEventPath:");
|
|
177
|
+
if (missing != nil) return missing;
|
|
178
|
+
missing = RunnerRequireSelector(recordClass, setTargetProcessIDSelector, @"setTargetProcessID:");
|
|
179
|
+
if (missing != nil) return missing;
|
|
180
|
+
missing = RunnerRequireSelector(recordClass, synthesizeSelector, @"synthesizeWithError:");
|
|
181
|
+
if (missing != nil) return missing;
|
|
182
|
+
missing = RunnerRequireSelector(pathClass, initPathSelector, @"initForTouchAtPoint:offset:");
|
|
183
|
+
if (missing != nil) return missing;
|
|
184
|
+
missing = RunnerRequireSelector(pathClass, moveSelector, @"moveToPoint:atOffset:");
|
|
185
|
+
if (missing != nil) return missing;
|
|
186
|
+
missing = RunnerRequireSelector(pathClass, liftSelector, @"liftUpAtOffset:");
|
|
187
|
+
if (missing != nil) return missing;
|
|
188
|
+
missing = RunnerRequireApplicationSelector(application, interfaceOrientationSelector, @"interfaceOrientation");
|
|
189
|
+
if (missing != nil) return missing;
|
|
190
|
+
missing = RunnerRequireApplicationSelector(application, processIDSelector, @"processID");
|
|
191
|
+
if (missing != nil) return missing;
|
|
192
|
+
|
|
193
|
+
*bridge = (RunnerXCTestEventBridge){
|
|
194
|
+
.recordClass = recordClass,
|
|
195
|
+
.pathClass = pathClass,
|
|
196
|
+
.initRecordSelector = initRecordSelector,
|
|
197
|
+
.addPathSelector = addPathSelector,
|
|
198
|
+
.setTargetProcessIDSelector = setTargetProcessIDSelector,
|
|
199
|
+
.synthesizeSelector = synthesizeSelector,
|
|
200
|
+
.interfaceOrientationSelector = interfaceOrientationSelector,
|
|
201
|
+
.processIDSelector = processIDSelector,
|
|
202
|
+
.initPathSelector = initPathSelector,
|
|
203
|
+
.moveSelector = moveSelector,
|
|
204
|
+
.liftSelector = liftSelector,
|
|
205
|
+
};
|
|
206
|
+
return nil;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
static NSString * _Nullable RunnerRequireClass(Class cls, NSString *className) {
|
|
210
|
+
if (cls == Nil) {
|
|
211
|
+
return [NSString stringWithFormat:@"private XCTest event synthesis unavailable: missing %@", className];
|
|
212
|
+
}
|
|
213
|
+
return nil;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
static NSString * _Nullable RunnerRequireSelector(Class cls, SEL selector, NSString *selectorName) {
|
|
217
|
+
if (![cls instancesRespondToSelector:selector]) {
|
|
218
|
+
return [NSString stringWithFormat:
|
|
219
|
+
@"private XCTest event synthesis unavailable: %@ missing %@",
|
|
220
|
+
NSStringFromClass(cls),
|
|
221
|
+
selectorName
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
return nil;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
static NSString * _Nullable RunnerRequireApplicationSelector(
|
|
228
|
+
id application,
|
|
229
|
+
SEL selector,
|
|
230
|
+
NSString *selectorName
|
|
231
|
+
) {
|
|
232
|
+
if (![application respondsToSelector:selector]) {
|
|
233
|
+
return [NSString stringWithFormat:
|
|
234
|
+
@"private XCTest event synthesis unavailable: XCUIApplication missing %@",
|
|
235
|
+
selectorName
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
return nil;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
static id RunnerPointerPath(
|
|
242
|
+
const RunnerXCTestEventBridge *bridge,
|
|
243
|
+
CGPoint start,
|
|
244
|
+
double x,
|
|
245
|
+
double y,
|
|
246
|
+
double dx,
|
|
247
|
+
double dy,
|
|
248
|
+
double scale,
|
|
249
|
+
double degrees,
|
|
250
|
+
double radius,
|
|
251
|
+
double durationMs,
|
|
252
|
+
double side
|
|
253
|
+
) {
|
|
254
|
+
id path =
|
|
255
|
+
((RunnerMsgSendInitPath)objc_msgSend)([bridge->pathClass alloc], bridge->initPathSelector, start, 0.0);
|
|
256
|
+
if (path == nil) {
|
|
257
|
+
return nil;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
int frameCount = MAX(3, (int)(durationMs / 16.0));
|
|
261
|
+
NSTimeInterval durationSeconds = durationMs / 1000.0;
|
|
262
|
+
for (int index = 1; index <= frameCount; index += 1) {
|
|
263
|
+
double t = (double)index / (double)frameCount;
|
|
264
|
+
CGPoint point = RunnerPointerPointAt(x, y, dx, dy, scale, degrees, radius, t, side);
|
|
265
|
+
NSTimeInterval offset = durationSeconds * t;
|
|
266
|
+
((RunnerMsgSendPathMove)objc_msgSend)(path, bridge->moveSelector, point, offset);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
((RunnerMsgSendPathOffset)objc_msgSend)(path, bridge->liftSelector, durationSeconds);
|
|
270
|
+
return path;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
static CGPoint RunnerPointerPointAt(
|
|
274
|
+
double x,
|
|
275
|
+
double y,
|
|
276
|
+
double dx,
|
|
277
|
+
double dy,
|
|
278
|
+
double scale,
|
|
279
|
+
double degrees,
|
|
280
|
+
double baseRadius,
|
|
281
|
+
double t,
|
|
282
|
+
double side
|
|
283
|
+
) {
|
|
284
|
+
double centerX = x + dx * t;
|
|
285
|
+
double centerY = y + dy * t;
|
|
286
|
+
double startRadius = baseRadius / MAX(scale, 1.0);
|
|
287
|
+
double endRadius = baseRadius;
|
|
288
|
+
if (scale < 1.0) {
|
|
289
|
+
startRadius = baseRadius;
|
|
290
|
+
endRadius = baseRadius * scale;
|
|
291
|
+
}
|
|
292
|
+
double radius = startRadius + (endRadius - startRadius) * t;
|
|
293
|
+
double angle = (-M_PI_2) + (degrees * M_PI / 180.0) * t;
|
|
294
|
+
return CGPointMake(centerX + cos(angle) * radius * side, centerY + sin(angle) * radius * side);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@end
|
package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift
CHANGED
|
@@ -252,7 +252,12 @@ extension RunnerTests {
|
|
|
252
252
|
)
|
|
253
253
|
case .tap:
|
|
254
254
|
if let selectorKey = command.selectorKey, let selectorValue = command.selectorValue {
|
|
255
|
-
let match = findElement(
|
|
255
|
+
let match = findElement(
|
|
256
|
+
app: activeApp,
|
|
257
|
+
selectorKey: selectorKey,
|
|
258
|
+
selectorValue: selectorValue,
|
|
259
|
+
allowNonHittableFallback: command.allowNonHittableCoordinateFallback == true
|
|
260
|
+
)
|
|
256
261
|
if match.isAmbiguous {
|
|
257
262
|
return Response(ok: false, error: ErrorPayload(code: "AMBIGUOUS_MATCH", message: "selector matched multiple elements"))
|
|
258
263
|
}
|
|
@@ -264,16 +269,24 @@ extension RunnerTests {
|
|
|
264
269
|
var outcome = RunnerInteractionOutcome.performed
|
|
265
270
|
let timing = measureGesture {
|
|
266
271
|
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
267
|
-
|
|
272
|
+
if match.usedNonHittableFallback {
|
|
273
|
+
// Maestro compatibility: RN E2E backdoor controls can be 1x1 and
|
|
274
|
+
// reported non-hittable by XCTest, while Maestro still taps their
|
|
275
|
+
// resolved bounds. Keep this behind the explicit replay-only flag.
|
|
276
|
+
outcome = tapAt(app: activeApp, x: frame.midX, y: frame.midY)
|
|
277
|
+
} else {
|
|
278
|
+
outcome = activateElement(app: activeApp, element: element, action: "tap by selector")
|
|
279
|
+
}
|
|
268
280
|
}
|
|
269
281
|
}
|
|
270
282
|
if let response = unsupportedResponse(for: outcome) {
|
|
271
283
|
return response
|
|
272
284
|
}
|
|
285
|
+
waitForTextEntryReadinessAfterTap(app: activeApp, element: element)
|
|
273
286
|
return Response(
|
|
274
287
|
ok: true,
|
|
275
288
|
data: DataPayload(
|
|
276
|
-
message: "tapped",
|
|
289
|
+
message: match.usedNonHittableFallback ? "tapped via non-hittable coordinate fallback" : "tapped",
|
|
277
290
|
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
278
291
|
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
279
292
|
x: touchFrame?.x,
|
|
@@ -729,6 +742,25 @@ extension RunnerTests {
|
|
|
729
742
|
dismissed: result.dismissed
|
|
730
743
|
)
|
|
731
744
|
)
|
|
745
|
+
case .keyboardReturn:
|
|
746
|
+
let result = pressKeyboardReturn(app: activeApp)
|
|
747
|
+
if !result.pressed {
|
|
748
|
+
return Response(
|
|
749
|
+
ok: false,
|
|
750
|
+
error: ErrorPayload(
|
|
751
|
+
code: "UNSUPPORTED_OPERATION",
|
|
752
|
+
message: "Unable to press the iOS keyboard return key"
|
|
753
|
+
)
|
|
754
|
+
)
|
|
755
|
+
}
|
|
756
|
+
return Response(
|
|
757
|
+
ok: true,
|
|
758
|
+
data: DataPayload(
|
|
759
|
+
message: "keyboardReturn",
|
|
760
|
+
visible: result.visible,
|
|
761
|
+
wasVisible: result.wasVisible
|
|
762
|
+
)
|
|
763
|
+
)
|
|
732
764
|
case .alert:
|
|
733
765
|
let action = (command.action ?? "get").lowercased()
|
|
734
766
|
guard let alert = resolveAlert(app: activeApp) else {
|
|
@@ -754,6 +786,82 @@ extension RunnerTests {
|
|
|
754
786
|
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
755
787
|
)
|
|
756
788
|
)
|
|
789
|
+
case .rotateGesture:
|
|
790
|
+
guard let degrees = command.degrees, degrees.isFinite else {
|
|
791
|
+
return Response(ok: false, error: ErrorPayload(message: "rotateGesture requires degrees"))
|
|
792
|
+
}
|
|
793
|
+
let velocity = command.velocity ?? (degrees >= 0 ? 1.0 : -1.0)
|
|
794
|
+
guard velocity.isFinite && velocity != 0 else {
|
|
795
|
+
return Response(ok: false, error: ErrorPayload(message: "rotateGesture velocity must be non-zero"))
|
|
796
|
+
}
|
|
797
|
+
var outcome = RunnerInteractionOutcome.performed
|
|
798
|
+
let timing = measureGesture {
|
|
799
|
+
outcome = rotateGesture(
|
|
800
|
+
app: activeApp,
|
|
801
|
+
degrees: degrees,
|
|
802
|
+
x: command.x,
|
|
803
|
+
y: command.y,
|
|
804
|
+
velocity: velocity
|
|
805
|
+
)
|
|
806
|
+
}
|
|
807
|
+
if let response = unsupportedResponse(for: outcome) {
|
|
808
|
+
return response
|
|
809
|
+
}
|
|
810
|
+
return Response(
|
|
811
|
+
ok: true,
|
|
812
|
+
data: DataPayload(
|
|
813
|
+
message: "rotatedGesture",
|
|
814
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
815
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
816
|
+
)
|
|
817
|
+
)
|
|
818
|
+
case .transformGesture:
|
|
819
|
+
guard
|
|
820
|
+
let x = command.x,
|
|
821
|
+
let y = command.y,
|
|
822
|
+
let dx = command.dx,
|
|
823
|
+
let dy = command.dy,
|
|
824
|
+
x.isFinite,
|
|
825
|
+
y.isFinite,
|
|
826
|
+
dx.isFinite,
|
|
827
|
+
dy.isFinite
|
|
828
|
+
else {
|
|
829
|
+
return Response(ok: false, error: ErrorPayload(message: "transformGesture requires finite x y dx dy"))
|
|
830
|
+
}
|
|
831
|
+
guard let scale = command.scale, scale.isFinite, scale > 0 else {
|
|
832
|
+
return Response(ok: false, error: ErrorPayload(message: "transformGesture requires scale > 0"))
|
|
833
|
+
}
|
|
834
|
+
guard let degrees = command.degrees, degrees.isFinite else {
|
|
835
|
+
return Response(ok: false, error: ErrorPayload(message: "transformGesture requires finite degrees"))
|
|
836
|
+
}
|
|
837
|
+
let durationMs = command.durationMs ?? 300
|
|
838
|
+
guard durationMs.isFinite && durationMs >= 16 else {
|
|
839
|
+
return Response(ok: false, error: ErrorPayload(message: "transformGesture durationMs must be >= 16"))
|
|
840
|
+
}
|
|
841
|
+
var outcome = RunnerInteractionOutcome.performed
|
|
842
|
+
let timing = measureGesture {
|
|
843
|
+
outcome = transformGesture(
|
|
844
|
+
app: activeApp,
|
|
845
|
+
x: x,
|
|
846
|
+
y: y,
|
|
847
|
+
dx: dx,
|
|
848
|
+
dy: dy,
|
|
849
|
+
scale: scale,
|
|
850
|
+
degrees: degrees,
|
|
851
|
+
durationMs: durationMs
|
|
852
|
+
)
|
|
853
|
+
}
|
|
854
|
+
if let response = unsupportedResponse(for: outcome) {
|
|
855
|
+
return response
|
|
856
|
+
}
|
|
857
|
+
return Response(
|
|
858
|
+
ok: true,
|
|
859
|
+
data: DataPayload(
|
|
860
|
+
message: "transformedGesture",
|
|
861
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
862
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
863
|
+
)
|
|
864
|
+
)
|
|
757
865
|
}
|
|
758
866
|
}
|
|
759
867
|
|
|
@@ -763,7 +871,27 @@ extension RunnerTests {
|
|
|
763
871
|
}
|
|
764
872
|
let delaySeconds = Double(max(command.delayMs ?? 0, 0)) / 1000.0
|
|
765
873
|
let textEntryMode = resolveTextEntryMode(command)
|
|
766
|
-
let target
|
|
874
|
+
let target: TextEntryTarget
|
|
875
|
+
if let selectorKey = command.selectorKey, let selectorValue = command.selectorValue {
|
|
876
|
+
let match = findElement(
|
|
877
|
+
app: activeApp,
|
|
878
|
+
selectorKey: selectorKey,
|
|
879
|
+
selectorValue: selectorValue,
|
|
880
|
+
allowNonHittableFallback: command.allowNonHittableCoordinateFallback == true
|
|
881
|
+
)
|
|
882
|
+
if match.isAmbiguous {
|
|
883
|
+
return Response(ok: false, error: ErrorPayload(code: "AMBIGUOUS_MATCH", message: "selector matched multiple elements"))
|
|
884
|
+
}
|
|
885
|
+
guard let element = match.element else {
|
|
886
|
+
return Response(ok: false, error: ErrorPayload(code: "NO_MATCH", message: "selector did not match an element"))
|
|
887
|
+
}
|
|
888
|
+
guard isTextEntryElement(element) else {
|
|
889
|
+
return Response(ok: false, error: ErrorPayload(code: "INVALID_TARGET", message: "selector did not match a text input"))
|
|
890
|
+
}
|
|
891
|
+
target = focusTextInputForTextEntry(app: activeApp, element: element)
|
|
892
|
+
} else {
|
|
893
|
+
target = focusTextInputForTextEntry(app: activeApp, x: command.x, y: command.y)
|
|
894
|
+
}
|
|
767
895
|
if textEntryMode == .replacement {
|
|
768
896
|
guard target.element != nil else {
|
|
769
897
|
let message =
|
|
@@ -791,6 +919,17 @@ extension RunnerTests {
|
|
|
791
919
|
)
|
|
792
920
|
)
|
|
793
921
|
}
|
|
794
|
-
|
|
922
|
+
let point = target.refreshPoint
|
|
923
|
+
let frame = activeApp.frame
|
|
924
|
+
return Response(
|
|
925
|
+
ok: true,
|
|
926
|
+
data: DataPayload(
|
|
927
|
+
message: textResult.repaired ? "typed after repair" : "typed",
|
|
928
|
+
x: point.map { Double($0.x) },
|
|
929
|
+
y: point.map { Double($0.y) },
|
|
930
|
+
referenceWidth: frame.isEmpty ? nil : Double(frame.width),
|
|
931
|
+
referenceHeight: frame.isEmpty ? nil : Double(frame.height)
|
|
932
|
+
)
|
|
933
|
+
)
|
|
795
934
|
}
|
|
796
935
|
}
|