因为公司一些自动化测试场景需要(对官方 app 进行 UI 自动化、多机并行跑 monkey ),这几天在基于 appium python client 的基础上编写 WebDriverAgent 的 python client,对 WebDriverAgent 进行单独支持。目前已经完成得差不多了,过程中发现不少坑,在这里记录下,顺便吐下槽。
PS:这个 client 由于公司政策不能放出来,请见谅。
这部分最坑。本来实现的是 WebDriver Spec 已经定义好的功能,结果实现方式还不一样,导致需要自己覆盖原有的 client 方法。
注:打字太累,下面 WebDriverAgent 均简称 WDA
1、 获取页面元素树 (page_source)
原请求:GET /source
WDA 实现:POST /source
另外要注意的是,WDA 返回的元素树默认是 json 格式,client 在进行 json 转 dict 时会把元素数也转为 dict ,需要手动转化回 json string 。
2、 错误信息处理
原格式:{'status': 1, 'sessionId': xxx, 'message': 'error message'}
WDA 实现:{'status': 1, 'sessionId': xxx, 'value': 'error message'}
如果是找元素出错,还会变成 {'status': 1, 'sessionId': xxx, 'value': {'description': 'error message', 'using': 'class name', 'value': 'test'}}
要修改的话,直接在 error_handler 修改 Exception 中的处理就好。
3、获取 textfield 元素的 text 属性
当输入框没有内容时,返回的 value 是 null(在 python client 里面会自动转换为 None )。记住:不要通过空字符串来判断 textfield 是否为空哦。当然,你也可以 override text 方法,把 None 转换回空字符串。
1、 查找元素
使用 inspector 时,属性名显示为 Class 。使用 source 时,属性名变成了 type 。当然搜索时,用 find_element_by_class_name('testClass')
等价于 find_element_by_link_text('type=testClass')
。
还有另一坑,在 inspector 和 source 中显示的其实都是精简后的名称。例如 inspector 中显示 'Class: Button' ,但你用 'Button' 这个 class name 去找是死活找不到的,因为在查找元素的时候它的完整名称是 XCUIElementTypeButton
。也就是你必须用 XCUIElementTypeButton
这个名称作为 class name 才能找到这个元素。
用 xpath 的同学也要注意,json 中的元素属性值和 xpath 会有些出入。举例如下:
{
"name": "ComputeSumButton",
"isEnabled": "1",
"value": null,
"label": "Compute Sum",
"isVisible": "1",
"rawIdentifier": "ComputeSumButton",
"type": "Button",
"frame": "{{94, 122}, {113, 37}}",
"rect": {
"origin": {
"y": 122,
"x": 94
},
"size": {
"width": 113,
"height": 37
}
}
<XCUIElementTypeButton type="XCUIElementTypeButton" name="ComputeSumButton" label="Compute Sum" private_indexPath="top,0,0,2" />
其中 xml 文件需要自己手动在 FBFindElementCommands.m
的 + (NSArray<XCElementSnapshot *> *)descendantsOfElementSnapshot:(XCElementSnapshot *)elementSnapshot withXPathQuery:(NSString *)xpathQuery
自己打断点:
+ (NSArray<XCElementSnapshot *> *)descendantsOfElementSnapshot:(XCElementSnapshot *)elementSnapshot withXPathQuery:(NSString *)xpathQuery
{
NSMutableDictionary *elementStore = [NSMutableDictionary dictionary];
DDXMLElement *xmlElement = [self XMLElementFromElement:elementSnapshot indexPath:@"top" elementStore:elementStore];
NSError *error;
// Add break point here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
NSArray *xpathNodes = [xmlElement nodesForXPath:xpathQuery error:&error];
if (![xpathNodes count]) {
return nil;
}
NSMutableArray *matchingSnapshots = [NSMutableArray array];
for (DDXMLElement *childXMLElement in xpathNodes) {
XCElementSnapshot *element = [elementStore objectForKey:[[childXMLElement attributeForName:kXMLIndexPathKey] stringValue]];
if (element) {
[matchingSnapshots addObject:element];
}
}
return matchingSnapshots;
}
打完断点后在 lldb 的调试窗口里输入 po [xmlElement xmlString]
获取。xpath 的查找也是基于这个 xml 的。
2、查看元素属性值
留意官方的 integration test FBElementAttributeTests.m
,不是所有元素都有 name, label 这两个属性值。如果刚好没有需要的属性值,轻则返回 None,重则直接在 WebDriverAgent 端产生错误。
虽然官方给了一个 wiki,但里面给到的 api 并不全。不过好在 WebDriverAgent 的代码结构还是比较清晰的,所以也可以简单地通过源码获取 api 信息。
接口相关处理文件全部在 WebDriverAgentLib/Commands
中。
一般一开始就是此文件处理的所有 command 的 route 列表。例如 FBDebugCommands.m
中:
+ (NSArray *)routes
{
return
@[
[[FBRoute POST:@"/source"] respondWithTarget:self action:@selector(handleGetTreeCommand:)],
[[FBRoute POST:@"/source"].withoutSession respondWithTarget:self action:@selector(handleGetTreeCommand:)],
];
}
其中第二个请求带有 .withoutSession
,表明请求中可以不带有 sessionId 这个字段。这个 route 应该是给 inspector 用的。
通过查看 route 指定的 selector 可以找到对应 handler ,handler 中如果会获取请求中的参数,会以 request.arguments[@"argName"]
的形式获取,如 FBDebugCommands.m
中
+ (id<FBResponsePayload>)handleGetTreeCommand:(FBRouteRequest *)request
{
FBApplication *application = request.session.application ?: [FBApplication fb_activeApplication];
if (!application) {
return FBResponseWithErrorFormat(@"There is no active application");
}
const BOOL accessibleTreeType = [request.arguments[@"accessible"] boolValue];
return FBResponseWithStatus(FBCommandStatusNoError, @{ @"tree": (accessibleTreeType ? application.fb_accessibilityTree : application.fb_tree) });
}
可以看到它会处理请求中的 accessible
参数,并会先转化为 boolean 类型。因此请求中可以带有 accessible
参数,且它的值必须是 true
或者 false
。
WebDriverAgent 作为新的基于 XCUITest 的测试工具,有其独到之处:
而且稳定性也还可以接受(针对所有支持命令编写的 50 条测试用例基本可以稳定保持通过,只是偶尔响应速度比较慢)。
虽然目前还有前面提到的这些坑,但都不是什么大坑,只要少许修改就能填掉。相信经过逐步完善,会像现在 Android 的 UIAutomator 那样成为主要的 UI 自动化测试框架之一。
最后,如果有同学想练手同时熟悉 appium python client ,不妨自己动手做个 WebDriverAgent python client 出来哦。