基于Jmeter和Selenium的WebUI自动化测试服务实现思路

背景

快到年底了,没啥东西要做了,搞一把 WebUI 自动化测试作为 2023 年收官之战,以下仅介绍技术实现思路,也欢迎大家有更好的想法在评论区交流。

需求

实现一个面向普通用户的 WebUI 自动化测试服务,实现效果可以参考MeterSphere的 UI 自动化测试功能,当然这玩意儿是要收费才能用,可以申请个账号去体验下。注意:不是在给 MeterSPhere 打广告,只是懒得写需求而已。

技术选型

虽然现在也有一些开源的 WebUI 自动化测试平台,但是调研了之后发现并不好用,比如 LuckyFrame,代码都是写死的,没啥扩展性,不是说这个项目不好,而是不满足我们的需求。所以最后选择了半天,使用如下技术栈来实现:
Jmeter, Jmeter-plugins-webdriver, Springboot

  1. 以 Jmeter为底层用例的执行引擎,配合 Jmeter 丰富的组件,可以实现复杂的用例步骤。
  2. Jmeter-plugins-webdriver是一个开源的 Jmeter 插件,底层基于 Selenium 开发,支持 Jmeter 实现 UI 自动化测试。
  3. 简单易上手的 web 框架自然是 Springboot 了。

实现流程

image.png

以上是大概的实现流程,整体思路为:

  1. 前端将用户操作封装为后端接口能处理的 json。
  2. 解析 json,将用户操作识别出来,然后生成代码,原理类似于 Selenium-IDE 中代码生成的逻辑。
  3. 生成一个空的 HashTree,默认填充 TestPlan 和 ThreadGroup 组件,并设置好属性。
  4. 按照用户的每一步的操作,分别生成一个 WebDriverSampler 组件,这个组件就是Jmeter-plugins-webdriver提供的采样器组件。
  5. 将第2步生成好的代码,填充到 WebDriverSampler 组件的 script 属性中。
  6. 拼装完整的 HashTree,并添加一个自己开发的后台监听器组件(继承AbstractBackendListenerClient即可,不知道的可以自己百度)。
  7. 将最后生成的 HashTree,交给 Jmeter 执行,注意,为了保证不同测试用例能够独立执行,每次执行新用例,必须要重新 new 一个 StandardJmeterEngine。

遇到的坑

  • Jmeter-plugins-webdriver 插件有个 bug,就是在执行WebDriverSampler.sample方法时,如果用例中有主动关闭浏览器的步骤,就会导致采样器报错,原因是:在脚本执行结束后,需要对采样器结果进行封装,会将页面源码封装为 SampleResult 对象的 ResposeData,将当前页面的 URL 设置为 SampleResult 对象的 URL,但是因为浏览器已经被主动关闭了,这个时候再也无法获取到页面源码和 url,就会导致采样器结果标记为异常。
    image.png

  • WebDriverSampler的 sample方法中,并未设置采样器开始和结束时间,导致自己开发的后台监听器中,无法取到用例的执行时长。
    image.png

  • 我用的 Jmeter 版本是 5.4.1,其中 beanshell 版本是 Jmeter 自带的 2.0b6 版本,在这个版本中,使用 Java 编写代码时,会将 null 转为 void,详情可见这几个bug:https://github.com/apache/jmeter/issues/3411
    https://github.com/apache/jmeter/issues/6110
    解决方案就是把 beanshell 升级到 2.1.1

  • 在服务器上部署时,需要安装浏览器运行环境,我这边用的 chrome,但是在测试时发现打开的页面要么中文是方框,要么页面默认是英语。记得安装中文语言包,并设置服务器默认语言为中文,不知道怎么设置就去问 gpt 吧,懒得写了。

  • 服务器上运行 chrome 可能会出现 chrome 启动失败,记得增加如下配置到 ChromeOptions 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* @author: wick
* @date: 2023/12/24 20:09
* @description: chrome options
*/

public class ChromeOptionConstant {
/**
* 允许跨域资源共享(CORS)请求。*表示接受所有来源的请求。
*/
public static final String REMOTE_ALLOW_ORIGINS = "--remote-allow-origins=*";

/**
* 无痕模式
*/
public static final String INCOGNITO = "--incognito";

/**
* 禁用沙箱
*/
public static final String NO_SANDBOX = "--no-sandbox";

/**
* 禁用 GPU,服务器上没有 GPU
*/
public static final String DISABLE_GPU = "--disable-gpu";

/**
* 禁用共享内存,解央 DevToolsActivePort file doesn't exist 异常
*/
public static final String DISABLE_DEV_SHM_USAGE = "--disable-dev-shm-usage";

/**
* 浏览器语言-中文
*/
public static final String LANG_ZH_CN = "--accept-lang=zh-CN";

/**
* 所有配置
*/
public static final String OPTIONS = String.join(" ", REMOTE_ALLOW_ORIGINS, INCOGNITO, NO_SANDBOX, DISABLE_GPU, DISABLE_DEV_SHM_USAGE, LANG_ZH_CN);
}
  • 无法并行执行用例,这个坑爹的问题困扰了我很久,最后发现是我在 ChromeOptions 中加了一个配置 “–remote-debugging-port=9222”,这个配置会让 WebDriver 在指定端口启动,多个用例执行时会创建多个 session,但是 9222 端口被占用了,导致其他 session 创建失败,或者出现session 串掉的情况,千万别加这个配置,尤其是你需要并行执行用例的时候。

总结

这个 WebUI 自动化技术实现上没啥难度,难的地方在于如何做技术选型,因为绝大多数人做 UI 自动化都是想着用框架写代码实现,但是很少有人会去挑战通过代码生成的方式来做,只要把实现思路理清楚,其实写代码也就那么回事了。对了,我fork 了这个Jmeter-plugins-webdriver插件到 gitee 了,把上面两个问题处理了一下,有兴趣的可以直接用我改过的代码:https://gitee.com/linvaux/jmeter-plugins-webdriver