web端远程控制iOS真机 - Part2 控制流

Part2

实现了屏幕的展示,下一步就是控制了。

目前都在用UITest的方式控制设备,比如facebook的WebdriverAgent,现在已经不维护了,转做idb去了。原版有点问题,我们用appium的版本。
github链接 WebDriverAgent

WebdriverAgent通过XCTest等库的私有方法,做一些骚操作。

鼠标事件

鼠标有三种事件:按下、松开、移动。
打开FBElementCommands.m,看到有tapdoubletaptouchAndHold等事件。 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集成在一个程序中控制,监听端口连接情况,自动运行、停止、重启系列工具。

实现,没啥好说的,略。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容