自动化工具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.tagelse: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工具就完成啦!目前这个工具还是只能够看,未来有空也许会把点击等操作的功能一并加入进去。
结束语:
写了这么多,终于也是到了尾声。其实当初之所以构建这个框架,除了给自己公司的项目使用,更多的还是抱着一个学习的态度。我是一个使用东西喜欢刨根问底,探究原理的人。研究一个东西最好的办法就是拆开它然后自己重现它。
在编程领域不外如此,熟读别人的源码,读深了,读透了,读到自己也能够独立写一个了,那就算成了。那么成了之后,我也希望能够让更多的人看到,于是我选择了开源,并且选择用一篇文章的形式将我的设计思路和做这个框架的心路历程写出来分享给大家看。一方面对于我来说是一种回顾和记录,一方面能够为更多对这方面感兴趣的同学不敢说指引方向,只能说避避坑少走一些弯路。
之前有看到论坛里的有同学说,现在写技术文章的人越来越少了。有人说大环境这样,人人自危还有谁有心情有时间抽出来分享技术。怎么说呢,这确实是一个无解的题。但我们能将做就是在尚能安身立命的时候,仍然抱着对技术的热情,做更多的事。这样保证自己在大环境回归的那一天,仍能寒芒不减当年。也希望未来能够更好吧~!
最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:【文末自行领取】
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

相关文章:
自动化工具Nico,从零开始干掉Appium,移动端自动化测试框架实现
这篇将用较短的篇幅给大家介绍我是如何实现iOS和Android的inspector(元素审查工具)的。 实现原理 为了更方便的显示UI界面,且更容易制作,我选择了使用web端来承载整个元素树展示。同时我选用Flask一次性梭哈前后端(因…...
Fast CRC32
链接: Fast CRC32 Error Checking Real life data tends to get corrupted because machines (and humans) are never as reliable as we wish for. One efficient way is make sure your data wasnt unintendedly modifiied is to generate some kind of hash. T…...
生成一个带有二维数据和对应标签的螺旋形数据集(非线性可分数据集)的代码解析
def create_dataset():np.random.seed(1)m 400 # 数据量N int(m/2) # 每个标签的实例数D 2 # 数据维度X np.zeros((m,D)) # 数据矩阵Y np.zeros((m,1), dtypeuint8) # 标签维度a 4 for j in range(2):ix range(N*j,N*(j1))t np.linspace(j*3.12,(j1)*3.12,N) np.rando…...
PHP unset() 函数的作用
PHP 中的 unset() 函数用于销毁指定的变量。具体来说,它会解除变量名与其数据之间的关联,从而释放该变量所占用的内存。不过需要注意的是,unset() 并不是删除变量的内容,而是取消对变量名的引用。如果变量是数组中的某个元素或者对…...
长篇故事可视化方法Story-Adapter:能够生成更高质量、更具细腻交互的故事图像,确保每一帧都能准确地传达故事情节。
今天给大家介绍一个最新的长篇故事可视化方法Story-Adapter,它的工作原理可以想象成一个画家在创作一幅长画卷。首先,画家根据故事的文本提示画出初步的图像。这些图像就像是画卷的草图。接下来,画家会不断回顾这些草图,逐步添加细…...
C++基础面试题 | 什么是C++中的运算符重载?
文章目录 回答重点:示例: 运算符重载的基本规则和注意事项: 回答重点: C的运算符重载是指可以为自定义类型(如类或结构体)定义运算符的行为,使其像内置类型一样使用运算符。通过重载运算符&…...
深入 IDEA 字节码世界:如何轻松查看 .class 文件?
前言: 作为一名 Java 开发者,理解字节码对于优化程序性能、调试错误以及深入了解 JVM 运行机制非常重要。IntelliJ IDEA 作为最流行的开发工具之一,为开发者提供了查看 .class 文件字节码的功能。在本文中,我将带你一步步探索如何…...
NodeJS 利用代码生成工具编写GRPC
生成的 gRPC 代码优点 自动化和效率: 减少手动编码:生成代码自动处理了消息的序列化和反序列化、服务接口的定义等,减少了手动编码的工作量。一致性:生成的代码确保了客户端和服务器之间的一致性,避免了手动编码可能带来的错误。跨语言支持: 多语言兼容:gRPC 支持多种编…...
uni-app基础语法(一)
我们今天的学习目标 基础语法1. 创建新页面2.pages配置页面3.tabbar配置4.condition 启动模式配置 基础语法 1. 创建新页面 2.pages配置页面 属性类型默认值描述pathString配置页面路径styleObject配置页面窗口表现,配置项参考pageStyle 我们来通过style修改页面的…...
Linux:进程控制(三)——进程程序替换
目录 一、概念 二、使用 1.单进程程序替换 2.多进程程序替换 3.exec接口 4.execle 一、概念 背景 当前进程在运行的时候,所执行的代码来自于自己的源文件。使用fork创建子进程后,子进程执行的程序中代码内容和父进程是相同的,如果子进…...
LeetCode279:完全平方数
题目链接:279. 完全平方数 - 力扣(LeetCode) 代码如下 class Solution { public:int numSquares(int n) {vector<int> dp(n 1, INT_MAX);dp[0] 0;for(int i 1; i * i < n; i){for(int j i * i; j < n; j){dp[j] min(dp[j …...
python爬虫--某动漫信息采集
python爬虫--tx动漫 一、采集主页信息二、采集详情页信息三、代码供参考一、采集主页信息 略。 二、采集详情页信息 如上图所示,使用xpath提取详情页的标题、作者、评分、人气、评论人数等数据。 三、代码供参考 import csv import time import random import requests fr…...
使用Rollup.js快速开始构建一个前端项目
Rollup 是一个用于 JavaScript 项目的模块打包器,它将小块代码编译成更大、更复杂的代码,例如库或应用程序。Rollup 对代码模块使用 ES6 模块标准,它支持 Tree-shaking(摇树优化),可以剔除那些实际上没有被…...
10.15学习
1.程序开发的步骤 定义程序的目标→设计程序→编写代码(需要选择语言,一种语言对应一种编译器)→编译→运行程序→测试和调试程序→维护和修改程序 2.ANSI/ISO C标准 1989年ANSI批准通过,1990年ISO批准通过,因此被称…...
mongodb-7.0.14分片副本集超详细部署
mongodb介绍: 是最常用的nosql数据库,在数据库排名中已经上升到了前六。这篇文章介绍如何搭建高可用的mongodb(分片副本)集群。 环境准备 系统系统 BC 21.10 三台服务器:192.168.123.247/248/249 安装包:…...
C++运算出现整型溢出
考虑如下代码: int aINT_MAX; int b 1; long c ab; 这段代码没有编过! 原因是a和b都是int型,相加之后会溢出。 请记住,c语言没有赋值,只有表达式,右侧会存在一个暂存的int保存ab的值,而明…...
LeetCode岛屿数量
题目描述 给你一个由 1(陆地)和 0(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外,你可以假设该网…...
Karmada核心概念
以下内容为翻译,原文地址 Karmada 是什么? | karmada 一、Karmada核心概念 一)什么是Karmada 1、Karmada:开放,多云,多集群Kubernetes业务流程 Karmada (Kubernetes Armada)是一个Kubernetes管理系统&…...
Rust 与生成式 AI:从语言选择到开发工具的演进
在现代软件开发领域,Rust 语言正在逐步崭露头角,尤其是在高性能和可靠性要求较高的应用场景。与此同时,生成式 AI 的崛起正在重新塑造开发者的工作方式,从代码生成到智能调试,生成式 AI 的应用正成为提升开发效率和质量…...
Python爬虫高效数据爬取方法
大家好!今天我们来聊聊Python爬虫中那些既简洁又高效的数据爬取方法。作为一名爬虫工程师,我们总是希望用最少的代码完成最多的工作。下面我ll分享一些在使用requests库进行网络爬虫时常用且高效的函数和方法。 1. requests.get() - 简单而强大 requests.get()是我们最常用的…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
