本文介绍了我们在UI自动化测试领域应用GenAI的一次尝试。我们基于gpt-4-turbo和Appium开发了AI Testing Tool来进行概念验证,它可以基于文本测试用例对安卓系统进行UI自动化测试。但其架构并不强依赖于gpt-4-turbo和Appium,经过重构后,它可以支持多种GenAI和自动化测试工具。
效果怎么样?
在开始讨论之前,让我们先看看效果。在下图中,左边是安卓模拟器,右边是GPT-4生成的操作指令。注意,下图是5倍速播放,原视频请戳这里。
和传统的自动化测试不同,我们不需要对测试用例进行编码或录制,只需要提供测试用例文本。在上面的演示中,我们使用的测试用例如下
When you add a google account in Passwords & accounts, username is abc@gmail.com, password is 123456. Then you should see an error "Couldn't find your Google Account".
在测试开始后,GPT-4能够自动推理出以下步骤来完成测试
- 在没有看到Passwords & account时,向上滑动
- 在仍然没有看到Passwords & account时,继续向上滑动
- 在看到Passwords & account时,点击Passwords & account
- 点击Add account按钮,添加账号
- 点击Google,添加Google账号
- 在看到Checking info页面时,等待5秒钟
- 在Email or phone中输入 abc@gmail.com
- 点击Next按钮,准备输入密码
- 在看到Couldn't find your Google Account时,结束测试
如何设计?
架构拓扑
整个架构包含以下部分
- AI Testing Tool: 架构核心,负责任务管理、报告生成、GenAI适配、自动化测试工具适配和流程调度。
- GPT-4:负责根据任务信息和当前页面信息推理出下一步操作。可用其他GenAI来替代。
- Appium:负责执行操作,获取页面信息。可用其他自动化测试工具来替代。
注意,在AI Testing Tool的上下文里,任务和测试用例是一个概念,目前我们还没有发现区分他们的必要。
交互流程
在收到用户提交的任务后,AI Testing Tool会做如下操作
- 从Appium获取当前页面截图和页面元素。
- 对页面截图和元素进行预处理以减少token数量。
- 将提示、任务、页面截图、页面元素和历史操作发送给GPT-4以推理下一步操作。
- 在收到下一步操作后,针对不同的操作执行不同的逻辑
- 如果是点击操作,在计算出点击位置或元素后,调用Appium执行点击操作。
- 如果是输入操作,在计算出输入位置或元素后,调用Appium执行输入操作。
- 如果是滑动操作,在计算出开始和结束位置后,调用Appium执行滑动操作。
- 如果是等待操作,等待指定的时间。
- 如果是错误操作,记录错误信息,结束任务。
- 如果是结束操作,记录结束信息,结束任务。
- 生成任务报告
界面操作
我们需要对界面操作进行建模,这样我们的工具可以具备以下优点:
- 和测试对象无关。它既可以用于Andorid、iOS测试,也可以用于Web测试,只需要适配不同的自动化测试工具即可。
- 和GenAI模型无关。只要支持图片输入,它既可以用GPT-4,也可以用其他模型。
- 方便进行调试和问题定位。每一个操作都可用作为一个观察点,它既可以被自动化测试工具反复执行,也可以被GPT-4反复生成。
这里我们采用了JSON格式来对界面操作进行建模,所有操作都有两个必填字段
action
: 操作的类型,主要用于识别不同的操作。explanation
: GPT-4对当前操作的解释,主要用于调试和问题定位。
为了进行快速验证,目前我们只对6种移动端操作进行了建模。
点击
在我们操作界面时,最常见的操作就是点击页面。有时我们是点击某个控件,有时我们是点击某个区域,所以我们定义了两种点击操作。
一种是通过页面区域进行点击。获取区域坐标的方法有两种
- GPT-4根据自动化测试工具提供的页面元素进行定位。例如,Appium会提供每个元素的区域信息(bounds)。我们在提示中将其作为获取坐标的首选方案,但它过于依赖自动化测试工具,未来我们需要寻找更优方案。
- GPT-4根据图片进行定位。它完全依赖于GPT-4的视觉定位(Visual Grounding)能力,我们试验了三种方式,效果都不太理想,后文会单独讨论。所以,我们将其作为获取坐标的备选方案,并没有在提示中使用,但从长期看它有成为最优方案的可能。
{"action": "tap","bounds": "[22,1117][336,1227]", "explanation": "I need to tap the Battery button to check battery details. I can see the bounds of the button is [22,1117][336,1227] in the page source, So I will use [22,1117][336,1227] to find the button and tap it"}
另外一种是通过控件进行点击。它需要GPT-4结合页面截图和页面元素识别出控件的XPath。虽然在页面元素复杂时,GPT-4有可能给出错误的XPath,但其正确率仍然比视觉定位要高。所以,我们在自动化测试工具不能提供区域信息时使用该方法获取要点击的控件。
{"action": "tap","xpath": "//[@text='Battery']", "explanation": "I need to tap the Battery button to check battery details. I can see the xpath of the button is //[@text='Battery'], So I will use it to find the button and tap it"}
输入
当我们想要输入信息时就需要输入操作,例如输入用户名,密码等。它和点击操作类似,只是多了字段value
来存储需要输入的信息。
通过页面区域进行输入
{"action": "input","bounds": "[22,1117][336,1227]", "value": "test user name","explanation": "I need to input the username to sign in. I can see the bounds of the user input box is [22,1117][336,1227], So I will [22,1117][336,1227] to find the user input box and input the username"}
通过控件进行输入
{"action": "input","xpath": "//[@id='user']", "value": "test user name","explanation": "I need to input the username to sign in. I can see the xpath of the user input box is //[@id='user'], So I will it to find the user input box and input the username"}
滑动
当我们想要查看更多信息,返回或前进时就需要滑动操作。该操作需要三个信息,滑动的起点,滑动终点,和滑动的持续时间。这里起点和终点的定位方法和点击操作中的一样,通过自动化测试工具返回的元素区域信息进行定位。未来我们需要使用视觉定位对其进行扩展,以应对自动化测试工具无法返回元素区域信息的情况。
{"action": "swipe", "swipe_start_x": 10,"swipe_start_y": 30,"swipe_end_x": 20,"swipe_end_y": 30, "duration": 500,"explanation": "I want to see more settings. So, I will retrieve the start position and end position according to the bounds of elements in the page source, and return them as (swipe_start_x, swipe_start_y) and (swipe_end_x, swipe_end_y)."} // Example for horizontal swipe, Duration in milliseconds
等待
当页面正在加载或某些操作之后页面反应迟钝时,我们需要等待操作。它只包含了等待的时间。GPT-4会根据页面截图和历史操作记录来判断是否需要等待。
{"action": "wait","timeout": 5000,"explanation": "I can see that there is no meaningful content, So I will wait a moment for content loading"} // Timeout in milliseconds
错误
严格来说,错误不是一种操作,而是任务的一种结果,指当前任务因为错误而结束。GPT-4会根据任务要求,历史操作和页面截图来判断是否出现了错误。
{"action": "error","message": "there is an unexpected content","explanation": "I saw an unexpected content"}
完成
完成也是任务的一种结果,指当前任务正常结束。GPT-4会根据任务要求,历史操作和页面截图来判断任务是否已经完成。
{"action": "finish","explanation": "I saw the expected content"}
提示
系统提示
角色和任务
这里,我们让GPT-4以自动化测试助手的身份,结合当前的任务推理出下一步操作。我们将所有可用操作限定在上节给出的操作中并给出了具体示例。需要注意的是,示例中的explanation
会影响GPT-4对下一步操作的推断,需要反复调试。
指令
首先,我们会告诉GPT-4,它会拿到哪些数据以及这些数据可以用来做什么。然后,我们给出了下图的PlantUML来指导其推理下一步操作。注意,这个流程图是我们调试的重点。
特别强调
为了能够让GPT-4只输出JSON格式的操作,并且使用页面元素区域(bounds)来推理滑动操作的起点和终点,我们在最后做了特别强调。
用户提示
在推理每一个操作时,我们不仅要向GPT-4输入系统提示,而且还需要输入任务信息,历史操作,页面元素和页面截图。具体请求数据如下
[
{
"role": "system",
"content": "<系统提示>"
},
{
"role": "user",
"content":[
{
"type": "text",
"text": "# Task \n <任务信息>"
},
{
"type": "text",
"text": "# History of Actions \n <历史操作>"
},
{
"type": "text",
"text": "# Source of Page \n ```yaml\n <页面元素> \n```"
},
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,<页面截图>"
}
]
}
]
如何使用?
运行
我们可以执行下面的命令来启动工具
OPENAI_API_KEY=<openai api key> python ai-testing-tool.py <系统提示文件> <任务文件> --appium=<appium 服务器地址>
任务文件的格式如下
[
{
"name": "<任务名称>",
"details": "<任务描述>",
"skip": "<true/false, 是否跳过该任务>"
}
]
工具默认会在./reports
文件夹下输出任务报告,其结构如下
reports
|-<task name>
|-<date time>
|-step_<index>.png // step_<index> 执行操作后的页面截图
|-step_<index>.jpg // step_<index> 处理后的页面截图
|-step_<index>.json // step_<index> 执行的操作
|-step_<index>.xml // step_<index> 执行操作后的页面元素
|-step_<index>.yaml // step_<index> 处理后的页面元素
|-step_<index>_prompt.md // 推断step_<index> 操作所需要的用户提示
|-task.json // 任务信息
启动后,工具会自动执行所有任务并打印执行任务过程中的所有操作,例如
调试
考虑到GPT-4的价格,我们无法通过不断的调用API来对代码和提示进行调试。为了应对这个问题,我们在工具中增加了调试模式
python ai-testing-tool.py <系统提示文件> <任务文件> --appium=<appium 服务器地址> --debug
在该模式下,工具不会调用API,而是将用户提示保存到step_<index>_prompt.md
文件,然后等待用户手动输入step_<index>
所要执行的操作。为了输入该操作,我们需要将系统提示,step_<index>_prompt.md
中的内容和step_<index-1>.jpg
一起发给ChatGPT,由其生成该操作。这样我们就可以节省一些费用。
踩过哪些坑?
视觉定位(Visual Grounding)
最初,我们希望GPT-4能够直接告诉我们页面的点击位置,这样我们就可以不依赖自动化测试工具,像人一样进行视觉定位和设备操作。但在实际测试过程中我们发现,GPT-4会对图片进行处理,要求最长边不超过2048,最短边不超过768。这样,我们就不能直接使用图片像素进行定位了。于是,我们进行了三种尝试来解决这个问题,但效果并不理想。所以,要实现准确的视觉定位还是要依赖模型本身的改进。
对GPT-4给出的像素坐标进行修正
我们将点击操作设计成如下格式,让GPT-4在给出像素坐标的同时,给出图片的宽度和高度。
{"action": "tap", "tap_x_on_screenshot": 150, "tap_y_on_screenshot": 300, "width_of_screenshot": 781, "height_of_screenshot": 1024, "explanation": "I need to open the naviagtion menu, I will use its pixel coordinates to generate tap action"}
在得到坐标后,我们可以根据页面截图的实际大小做如下修正
在实际测试过程中,我们无法验证GPT-4返回的width_of_screenshot
和height_of_screenshot
是否正确,这依赖于它的图片处理逻辑。从测试结果看,有时坐标是正确的,有时却会有比较大的误差,我们没能总结出规律来进行进一步修正。
在图片中添加自定义坐标系
考虑到GPT-4可能对图片上的内容有更强的识别能力,我们尝试在图片中添加自定义坐标系。
它不依赖于GPT-4的图片处理逻辑,我们可以对坐标进行快速验证。从测试结果看,坐标出现过向左下角、右下角偏移的情况,我们无法确定是否有规律。
在图片中添加有标签的网格
为了进一步帮助GPT-4进行视觉定位,我们用网格覆盖图片并且为每一个网格打上标签。这里我们的假设是GPT-4能够准确的识别出离目标最近的网格标签。
我们并不清楚字母和数字是否会对识别有影响,所以准备了两种标签。一种是以数字为标签。
另外一种是以字母为标签。
但从实际测试效果看,GPT-4对两种标签的定位能力并没有明显的差别和提高。
这其实是我们最早发现的定位方法,还有一种技术是通过不断迭代带标签的网格来缩小定位区域,最后定位到目标。考虑到其复杂性,目前我们还没有尝试。
XPath
我们尝试让GPT-4给出目标元素的XPath,发现其在某些情况下它会给出错误答案。例如,当我们想在页面上点击Game
元素时,它有时会给出//[resouse-id="GameItem" and content-desc="Game"]
这样的错误答案。
<a resource-id="GameItem" clickable="true">
<b content-desc="Game" resouce-id="GameItemDescription" clickable="false"/>
</a>
不过其准确率仍然要比视觉定位高很多,而且通过对提示的进一步优化,是有可能获得更高准确率的,所以当我们在页面元素中找不到区域信息时会使用该方法获取元素。
响应时间
我们在测试时,每一个操作的推理大概需要3~10秒钟,这就意味着当测试到一些有时间限制的功能时,GPT-4就显得无能为力了。例如,在测试视频暂停时,有的App会在暂停按钮显示3秒后自动隐藏,这就意味着当GPT-4推理出点击操作时控件已经不存在了。
费用
我们一开始没有意识到这个问题,所有代码和提示的调试都是直接调用的API,充进去的钱很快就被花完了。经过计算,系统提示和图片的token数量并不高,在2000左右,但页面元素有6000甚至更高的token数量。后来,我们对图片进行了预处理,将图片大小控制在781x2048范围内,并采用JPEG格式以减少token数量和图片传输时间。同时,我们将页面元素从XML格式转换为YAML格式并过滤掉一些不需要的节点属性,这样大概能减少30%的token数量。
经过上述处理后,用于开头演示的测试用例花费了0.17$,但这对于动辄有成千上万个测试用例的自动化测试来说,费用可能还是太高了。寻找更加经济的解决方案也许是在自动化测试领域应用GenAI的一个重要方向。
还能做什么?
支持更多的自动化测试工具
目前该工具只集成了Appium,但其架构并不限制其他自动化测试工具的使用,例如Selenium、WebdriverIO等。我们可以把自动化测试工具改为可配置的,同时将提示调试成不依赖于任何自动化测试工具的,这意味着我们要减少对页面元素区域(bounds)的依赖,更多的使用视觉定位和XPath。
探索测试用例编写的最佳实践
目前我们还没有对测试用例提出任何格式建议,例如Given-When-Then,因为我们还不清楚不同的格式会对测试造成哪些影响,这需要不断的试验才能够总结出来。例如,我们发现简短的命令可能比Given-When-Then效果更好,GPT-4会自己发现执行任务过程中的错误。
探索更加经济的解决方案
如果将来GPT-4可以支持视觉定位或者我们可以找到更加有效的辅助定位方法,那么我们将不再需要向GPT-4发送页面元素,可以大幅减少token数量。
也许我们并不需要每次都调用GPT-4来推理下一步操作。例如,我们可以用某种方法对操作进行缓存,当上下文符合某些条件时就使用缓存中的操作。或者,我们可以让GPT-4在推理下一步操作时生成自动化测试工具的代码,这样后续的重复测试就可以直接运行自动化测试工具来执行。再或者,我们可以将GPT-4和录制工具结合起来,不再使用像素对比来判定测试结果,而是让GPT-4来判定,以此来获得更加稳定高效的自动化测试。
巨人的肩膀
我们主要参考了QA GPT,对其提示和代码进行了重构和优化,支持更多的操作和更灵活的任务配置,增加了报告生成、调试模式、页面截图和页面元素预处理等功能。
我们从GridGPT中学习到了通过带标签的网格进行定位的技术,还从chatGPT_Vision_To_Coords中学习到了通过不断迭代带标签的网格来缩小定位区域,最后定位到目标的技术。
我们从论文DroidBot-GPT: GPT-powered UI Automation for Android中得到了一些灵感,但对该论文的代码DroidBot-GPT还没有深入学习。从其示例看,它应该可以完成与本文类似的测试用例,但实现逻辑有所不同,它使用的是gpt-3.5-turbo
模型,只通过页面元素就可以推理出下一步操作。
总结
通过AI Testing Tool的概念验证,我们认为在UI自动化测试领域应用GenAI是可行的。随着模型的不断迭代,视觉定位技术的不断增强,费用的持续降低和集成方案的不断创新,大规模使用也并非是不可能的。想象一下,当我们可以直接用故事卡作为输入进行UI自动化测试时,我们将不再需要维护庞杂的自动化测试代码,我们的精力将主要集中在对需求的理解上,它有可能成为在手动测试,编码测试和录制测试之后的另外一种测试方法。