承接上一篇 自动化工具 Nico,从零开始干掉 Appium,移动端自动化测试框架实现(二),最后这篇将用较短的篇幅给大家介绍我是如何实现 iOS 和 Android 的 inspector(元素审查工具)的
为了更方便的显示 UI 界面,且更容易制作,我选择了使用 web 端来承载整个元素树展示。同时我选用 Flask 一次性梭哈前后端(因为本身就是个体量不大的 web page,如果要用 react 或者 vue 有点杀鸡用牛刀的感觉),不得不说 Flask 在编写一些小型的 web 应用的时候真的是个利器,非常推荐大家试用。
左上方是元素结构展示,左下方是具体悬停的元素的详情,右侧是当前移动端画面展示
首先在启动初始化 inspector 的时候,会先向移动端的 sever 发送请求获取图像和元素树结构
// 在python端构建url装饰器
@app.route('/refresh_image')
def refresh_image():
port = int(os.environ.get('RemoteServerPort'))
platform = os.environ.get('nico_ui_platform')
if platform == "android":
new_data = send_tcp_request(port, "get_png_pic:100")
else:
new_data = send_tcp_request(port, "get_jpg_pic:1.0")
base64_data = base64.b64encode(new_data)
return base64_data
@app.route('/refresh_ui_xml')
def refresh_ui_xml():
root = dump_ui_tree()
# 构建HTML列表
html_list = xml_to_html_list(root)
# 渲染模板并传递构建的HTML列表
return html_list
// 这里使用jquery实现请求发送。
function refreshData() {
bounds_list = []
// 发送GET请求到服务器,刷新图片
$.get('/refresh_image', function(data) {
var img = document.querySelector('img');
img.src = 'data:image/png;base64,' + data;
img.setAttribute("id","image_")
});
// 发送GET请求到服务器,刷新XML
$.get('/refresh_ui_xml', function(data) {
var xmlContainer = document.querySelector('.content-inner');
xmlContainer.innerHTML = data;
initImageControl()
addTextControlHoverListeners(); // 添加新的事件监听器
addImageListeners(); // 添加新的事件监听器
});
}
拿到整个元素树之后,我通过递归遍历所有节点并为其创建一个个 div 标签
# 递归函数,用于将XML元素及其属性转换为HTML列表项
def xml_to_html_list(element, depth=0):
# 开始列表项
random_number = random.randint(100000, 999999)
html = f'<div class="node" style="text-indent: -1em; padding-left: {depth + 1}em; word-wrap: break-word;"'
# 如果元素有属性,将它们添加到列表项中
if element.attrib:
html += " " + " ".join([f'{k}="{v}"' for k, v in element.attrib.items()])
html += f'''identifier_number = "nico_{random_number}"'''
if depth != 0:
content = element.tag
else:
html += f'''id = "Title" '''
html += f'''current_package_name = {os.environ.get("current_package_name")} '''
html += f'''nico_ui_platform = {os.environ.get('nico_ui_platform')}'''
content = f"{element.tag} for {os.environ.get('nico_ui_platform')}"
html += f'><strong style="font-size: 2em;">{content}</strong>'
# 如果元素有属性,将它们作为文本添加
if element.attrib:
html += " (Attributes: "
html += ", ".join([f'{k}="{v}"' for k, v in element.attrib.items()])
html += ")"
# 如果元素有文本内容,将其添加到列表项中
if element.text and element.text.strip():
html += f" - Text: {element.text.strip()}"
html += "</div>"
# 处理子元素
children = list(element)
if children:
for child in children:
html += xml_to_html_list(child, depth + 1)
return html
我们在生成的 html 界面可以看到,每一级元素节点对应一个 div 标签。
由于 html 和 xml 拥有几乎相同的特性,所以转化起来其实很简单。然后除了处理转换之外,我还为每个 div 事件都添加了监听事件,方便当我们鼠标悬停在某一个节点时。下方会展示该元素对应的详细属性值。
细心的同学可能会注意到,我在xml_to_html_list
,这个方法开头加入了使用了一个随机数 random_number = random.randint(100000, 999999)
,这个可大有用处。使用过其他 inspector 的同学应该知道,当我们鼠标悬停在具体的控件节点上时,右侧图像通常会有一个半透明的遮罩,告知你当前控件节点在实际设备上的具体位置。而当鼠标悬停在图像上的某一位置时,也会出现一个半透明的遮罩,告知你当前悬停处,所显示的控件,同时左侧的元素树也会相应跳转到对应位置。
或者这样
为了实现这个功能,我在创建 div 层级的时候,每生成一个控件 div 标签的同时就会在右侧的图像上方添加一个透明的 div 标签。添加监听事件,当我们鼠标悬停在对应位置的控件上时,会将这个透明的控件改为半透明的绿色,来展示我们当前悬停位置的控件是什么。而图像上的透明 div 标签的长宽坐标就来源于左侧控件 div 标签里的提供的属性。
当然这样一来还不够,为了能够实现控件文本节点和控件图像的一一对应关系,前面提到的随机数就发挥了作用。我用这个随机数为每个控件文本 div 标签添加了唯一 id 属性identifier_number
,
在生成对应透明控件 div 标签时也加上一样的 id 属性img-identifier_number
那么这样就能实现当我鼠标悬停在任意一边的时候,可以传递该 id,在另一侧中找到相同 id 的 div 标签,并高亮处理。
于是整个 inspector 工具就完成啦!目前这个工具还是只能够看,未来有空也许会把点击等操作的功能一并加入进去。
写了这么多,终于也是到了尾声。其实当初之所以构建这个框架,除了给自己公司的项目使用,更多的还是抱着一个学习的态度。我是一个使用东西喜欢刨根问底,探究原理的人。研究一个东西最好的办法就是拆开它然后自己重现它。
在编程领域不外如此,熟读别人的源码,读深了,读透了,读到自己也能够独立写一个了,那就算成了。那么成了之后,我也希望能够让更多的人看到,于是我选择了开源,并且选择用一篇文章的形式将我的设计思路和做这个框架的心路历程写出来分享给大家看。一方面对于我来说
是一种回顾和记录,一方面能够为更多对这方面感兴趣的同学不敢说指引方向,只能说避避坑少走一些弯路。
之前有看到论坛里的有同学说,现在写技术文章的人越来越少了。有人说大环境这样,人人自危还有谁有心情有时间抽出来分享技术。怎么说呢,这确实是一个无解的题。但我们能将做就是在尚能安身立命的时候,仍然抱着对技术的热情,做更多的事。这样保证自己在大环境回归的那一天,仍能寒芒不减当年。也希望未来能够更好吧~!