Part2
实现了屏幕的展示,下一步就是控制了。
目前都在用UITest的方式控制设备,比如facebook的WebdriverAgent,现在已经不维护了,转做idb去了。原版有点问题,我们用appium的版本。
github链接 WebDriverAgent
WebdriverAgent通过XCTest等库的私有方法,做一些骚操作。
鼠标事件
鼠标有三种事件:按下、松开、移动。
打开FBElementCommands.m,看到有tap
、doubletap
、touchAndHold
等事件。 tap对应鼠标按下和松开。只有松开后才能算完整的tap事件,看来是没法实时点击了。
为了对应鼠标的事件,找到XCPointerEvent这个类,简单实现个点击事件
appium的wda版本中已经实现了通用方法,这里尝试实操下
在routes方法中注册自定义的接口路径和方法
[[FBRoute POST:@"/wda/fasttap"] respondWithTarget:self action:@selector(handleFastTapActions:)],
通过XCPointerEventPath等私有api,执行鼠标事件
+ (id<FBResponsePayload>)handleFastTap:(FBRouteRequest *)request
{
CGPoint tapPoint = CGPointMake((CGFloat)[request.arguments[@"x"] doubleValue], (CGFloat)[request.arguments[@"y"] doubleValue]);
XCSynthesizedEventRecord *event = [[XCSynthesizedEventRecord alloc] initWithName:@"tap" interfaceOrientation:1];
XCPointerEventPath *path = [[XCPointerEventPath alloc] initForTouchAtPoint:tapPoint offset:0];
[path liftUpAtOffset:0];
[event addPointerEventPath:path];
[[XCUIDevice.sharedDevice eventSynthesizer] synthesizeEvent:event completion:(id)^(BOOL result, NSError *invokeError) {
}];
return FBResponseWithOK();
}
对于复杂的拖拽,一样可以用上面的办法
首先注册下方法
[[FBRoute POST:@"/wda/fasttaps"] respondWithTarget:self action:@selector(handleFastTapActions:)],
web端将一次完整的鼠标按下-》拖动xN -》松开事件作为请求参数请求fasttaps接口,接口将每种鼠标事件转为对应的PointerEvent,最后一起执行。
+ (id<FBResponsePayload>)handleFastTapActions:(FBRouteRequest *)request
{
NSArray *taps = request.arguments[@"taps"];
if(taps.count < 2){
return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:@"At lease two tap event is needed" traceback:nil]);
}
NSDictionary *firstTap = taps[0];
CGPoint firstPos = CGPointMake([firstTap[@"x"] floatValue], [firstTap[@"y"] floatValue]);
XCPointerEventPath *path = [[XCPointerEventPath alloc] initForTouchAtPoint:firstPos offset:0];
for(NSUInteger i=1;i<taps.count;++i){
NSDictionary *tapInfo = taps[i];
CGPoint pos = CGPointMake([tapInfo[@"x"] floatValue], [tapInfo[@"y"] floatValue]);
double time = [tapInfo[@"time"] doubleValue];
int type = [tapInfo[@"type"] intValue];
//0 down
//1 up
//2 move
switch (type) {
case 0:
return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:@"Only one touch down is allowed!" traceback:nil]);
case 1:
[path liftUpAtOffset:time];
break;
case 2:
[path moveToPoint:pos atOffset:time];
break;
default:
break;
}
}
XCSynthesizedEventRecord *event = [[XCSynthesizedEventRecord alloc] initWithName:@"tap" interfaceOrientation:1];
[event addPointerEventPath:path];
[[XCUIDevice.sharedDevice eventSynthesizer] synthesizeEvent:event completion:(id)^(BOOL result, NSError *invokeError) {
}];
return FBResponseWithOK();
}
这种方案仍然是要等鼠标抬起才会触发一系列操作,一样存在延迟问题。目前通过其他私有api还没找到解决方案。
键盘事件
键盘事件有每个key的keydown和keyup,事件中还包含辅助按键的按下情况,wda原版直接通过文本输入的方式,达到这个效果。这样很多键都会无法实现,比如组合键cmd+r等,只能支持文本输入。
继续看XCPointerEvent类
+ (id)keyboardEventForKeyCode:(unsigned long long)arg1 keyPhase:(unsigned long long)arg2 modifierFlags:(unsigned long long)arg3 offset:(double)arg4;
- arg1 keycode
- keyPhase 0或1,按下还是松开
- modifierFlags 辅助键
- offset 时间偏移量
看起来有希望组合个 XCPointerEvent事件,添加到EventPath中。
但是失败了。
暂时先只支持文本吧
+(id<FBResponsePayload>)handleInputKey:(FBRouteRequest *)request
{
NSString *key = request.arguments[@"key"];
//一些特殊键的处理
NSDictionary *map = @{
@"Backspace":XCUIKeyboardKeyDelete,
@"Tab":XCUIKeyboardKeyTab,
@"Enter":XCUIKeyboardKeyReturn,
@"Escape":XCUIKeyboardKeyEscape
};
if(key.length > 1){
NSString *mapKey = map[key];
if(mapKey)key = mapKey;
else return FBResponseWithOK();//暂无法处理,忽略
}
XCPointerEventPath *path = [[XCPointerEventPath alloc] initForTextInput];
[path typeText:key atOffset:0 typingSpeed:60];
XCSynthesizedEventRecord *event = [[XCSynthesizedEventRecord alloc] initWithName:@"Single-Finger Touch Action" interfaceOrientation:1];
[event addPointerEventPath:path];
[[XCUIDevice.sharedDevice eventSynthesizer] synthesizeEvent:event completion:(id)^(BOOL result, NSError *invokeError) {
}];
return FBResponseWithOK();
}
注:appium的wda中都实现了相应方法,代码健壮。这里简单实操下。
命令行方式调用
用命令行启动
xcodebuild -destination "..." -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner test
映射wda端口到本机
iproxy localport wdaport
多设备
连接多设备的情况,需要给每个wda一个单独的端口,并且指定测试的目标设备
env USE_PORT=端口号 xcodebuild -destination "id=设备id" -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner test
端口无法连接的情况,需要用iproxy映射手机上的wda端口到mac端口,同时指定设备号以支持多设备连接的情况
iproxy localport deviceport deviceid
其他功能
安装app、剪贴板、相册等功能。
略
集成
最后将视频工具、iproxy、wda集成在一个程序中控制,监听端口连接情况,自动运行、停止、重启系列工具。
实现,没啥好说的,略。