Appium WebDriverAgent 源码菜鸟解析篇

codeskyblue · 2021年04月25日 · 最后由 codeskyblue 回复于 2021年04月27日 · 4700 次阅读

前言

为什么说是菜鸟解析篇呢,菜鸟说的是我本人,因为我 OC 实在不咋地,仅仅是能看懂。请各位高手能够稍微看几眼,好好教育我一下。

项目地址 https://github.com/appium/WebDriverAgent

代码结构

|-- Configurations
|-- PrivateHeaders
|   |-- AccessibilityUtilities
|   |-- MobileCoreServices
|   |-- TextInput
|   |-- UIKitCore
|   `-- XCTest
|-- WebDriverAgent.xcodeproj (子目录省略)
|-- WebDriverAgentLib
|   |-- Categories
|   |-- Commands
|   |-- Routing
|   |-- Utilities
|   `-- Vendor
|       |-- CocoaAsyncSocket
|       |-- CocoaHTTPServer
|       |-- RoutingHTTPServer
|       `-- YYCache
|-- WebDriverAgentRunner
|        `-- UITestingUITests.m
`-- WebDriverAgentTests (子目录省略)

入口代码在 WebDriverAgentRunner/UITestingUITests.m

这里 setUp 做了一些基础的设置(日志、键盘、是否开启自动截图(默认关闭))
testRunner 函数,初始了 FBWebServer 这个类。然后启动了 HTTP 服务。
最后那个函数 webServerDidRequestShutdown 实现了@protocol FBWebServerDelegate

FBWebServer

这个类的实现定义在 WebDriverAgentLib/Routing/FBWebServer.m

startServing 函数里面,先开启 http 服务,然后在开启截图广播(也就是 Mjpeg 服务)
最后调用 mainRunLoop 一直运行到 keepAlive 为 NO 或直到世界末日。

接下来看看 startHTTPServer 的实现

这个函数,继承了 RoutingHTTPServer,这个服务又继承了 HTTPServer(这个就是 CocoaHTTPServer)感觉应该是个开源的实现,先不看了。
其中我用红框框住了一两个函数,其中一个函数

[self registerRouteHandlers:[self.class collectCommandHandlerClasses]]; 这个函数的实现非常的优美。

+ (NSArray<Class<FBCommandHandler>> *)collectCommandHandlerClasses
{
  NSArray *handlersClasses = FBClassesThatConformsToProtocol(@protocol(FBCommandHandler));
  NSMutableArray *handlers = [NSMutableArray array];
  for (Class aClass in handlersClasses) {
    if ([aClass respondsToSelector:@selector(shouldRegisterAutomatically)]) {
      if (![aClass shouldRegisterAutomatically]) {
        continue;
      }
    }
    [handlers addObject:aClass];
  }
  return handlers.copy;
}

对于不怎么懂 OC 的我看的实在有点费劲。不过最终在强大的谷歌帮助下还是理解了,转化成 python 代码是这样的

handlersClasses: tuple = FBClassesThatConformsToProtocol(FBCommandHandler)
handlers: list = []
for aClass in handlersClasses:
  if hasattr(aClass, "shouldRegisterAutomatically"):
    if not aClass.shouldRegisterAutomatically():
      continue
    handlers.append(aClass)
  return handlers[:]

FBClassesThatConformsToProtocol(@protocol(FBCommandHandler)); 这个函数是通过objc_getClassListclass_conformsToProtocol来查找所有实现了 FBCommandHandler 的 protocol(Java 中叫接口) 的类。而所有实现了这个接口的类,目前都放在了目录 WebDriverAgentLib/Commands目录下。
所有的路由定义在每个 FBXXCommands.m 中的 routes 函数下面。

添加/openatx/tap 函数

理解了逻辑之后,我们尝试自己实现了一个 /openatx/tap 的路由。

创建一个文件 WebDriverAgentLib/Commands/FBOpenatxCommands.h,内容为

#import <Foundation/Foundation.h>

#import <WebDriverAgentLib/FBCommandHandler.h>

NS_ASSUME_NONNULL_BEGIN

@interface FBOpenatxCommands : NSObject <FBCommandHandler>

@end

NS_ASSUME_NONNULL_END

创建文件 WebDriverAgentLib/Commands/FBOpenatxCommands.m,内容为

#import "FBOpenatxCommands.h"
#import "XCUIElement+FBTap.h"

@interface FBOpenatxCommands ()
@end

@implementation FBOpenatxCommands

#pragma mark - <FBCommandHandler>

+ (NSArray *)routes
{
  return
  @[
    [[FBRoute POST:@"/openatx/tap"].withoutSession respondWithTarget:self action:@selector(handleTap:)]
  ];
}

+ (id<FBResponsePayload>)handleTap:(FBRouteRequest *)request
{
  CGPoint tapPoint = CGPointMake((CGFloat)[request.arguments[@"x"] doubleValue], (CGFloat)[request.arguments[@"y"] doubleValue]);
  point = FBInvertPointForApplication(tapPoint, request.session.activeApplication.frame.size, application.interfaceOrientation);

  XCUICoordinate* tapElement = [[application coordinateWithNormalizedOffset:CGVectorMake(0, 0)] coordinateWithOffset:CGVectorMake(point.x, point.y)];
  [tapElement tap];

  return FBResponseWithOK();
}

然后编译在手机上运行就可以实现,/openatx/tap这个接口了。

使用方法

POST $DEVICE_URL/openatx/tap
Payload为 `{"x": 10, "y": 10}` 

调试

计算请求时间

先从简单的改起来。 FBWebServer.m 这个文件有个成员函数 registerServerKeyRouteHandlers

其中的/health 请求的逻辑改为

[self.server get:@"/health" withBlock:^(RouteRequest *request, RouteResponse *response) {
    NSDate *requestStart = [NSDate date]; // 增加
    [response respondWithString:@"I-AM-ALIVE"];
    NSTimeInterval execTime = [[NSDate date] timeIntervalSinceDate:requestStart]; // 增加
    NSLog(@"executionTime = %.2f seconds", execTime); // 增加
  }];

为了方便使用,继续封装一下

void measureBlockTime(NSString*title, void (^block)(void)) {
  NSDate *requestStart = [NSDate date]; // 增加
  block();
  NSTimeInterval execTime = [[NSDate date] timeIntervalSinceDate:requestStart]; // 增加
  NSLog(@"%@ executionTime = %.2f seconds", title, execTime); // 增加
}

[self.server get:@"/health" withBlock:^(RouteRequest *request, RouteResponse *response) {
  measureBlockTime(@"/health", ^{
    [response respondWithString:@"I-AM-ALIVE"];
  });
}];

后文

写不动了,哎,就这样吧。

参考资料

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 6 条回复 时间 点赞

前排 给大佬点赞😃

搜集实现 protocal 的类,真的不错。
不过万万没想到,python 也可以类似实现吗?

问一下,不依赖服务端,能不能直接用 wad 独立执行测试?

都不依赖服务端了,为什么不直接用 XCTest 的函数去跑测试

期待下次来个 XCTest 的教程👍

少女 回复

我这个 OC 技术看看代码还行,让我有写,真的有点为难我了

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册