• 支付春节大型活动中会对接上百个页面。如何能在活动过程中持续的对这些外部对接的页面来进行自动化测试和验证,保障活动的稳定运营。
• QQ支付维护着多个项目,而开发的人力却非常有限。我们需不断地探求更好的方案来保证线上的质量。
• 我们需在测试部署,和功能上线几个阶段中,分别对页面功能进行自动化验证,让上线的代码符合预期。
综上,我们去思考,有别于前端和服务端的监控,如何建设出一套无需业务代码改动,自动化多场景多维度的自动化测试系统。
一. 通用能力
这里的重点在于通用任何页面,页面无需任何改动,极低成本即可接入自动化监控的能力,包括:
1. 对页面的白屏监控。
2. 监控页面加载过程中出现的错误。包括html加载错误,静态资源加载错误,未捕获的js报错。
3. 对页面中指定DOM元素是否存在的监控
二. 页面定制自动化脚本
上面功能只是满足页面能最基本的要求 而真正保证页面具体每个功能点符合预期还是需要靠手动编写UI自动化测试的代码,而如何降低自动化代码成本成了我们探索的重点
三. 多环境支持
通常,我们的服务具备测试环境和体验环境。在功能发布上线后再做自动化测试可能为时已晚,让自动化测试支持测试环境和体验环境也是我们追求的目标。
在了解业内知名自动化方案如 Selenium, nightmare, cypress, puppeteer 之后,我们最终决定通过pupeteer来满足我们的需求。
pupeteer有对我们有如下几个切实的优点:
• Chromium 团队维护,可以通过配置变为 Chromium行为,且拥有更好的兼容性和前景
• 支持 headless 模式,启动速度更快且运行效率更高
• Recorder特性可以生成pupeteer脚本
更多pupeteer相关介绍和文档可以很方便地搜索到,我们不再赘述,
**下面我们针对之前描述的功能,分别描述下原理和实现 ↓ **
白屏监控依赖两个能力pupeteer的的截图能力和node环境下的canvas。在通过page.goto[1]加载页面后,我们可以通page.screenshot[2]功能来对当前页面截图,其后通过canvas[3]来提取每个每个像素点的数据,通过当前页面中色值相同的像素点的比例来判断当前是否白屏。
我们可以通过一个简单流程图来描述这个流程
在色值相同的像素点达到某个比例时,我们可以通过机器人发起通知:
我们页面的错误大体分为如下几种情况
1. html本身的请求失败
2. html内引用的各类静态资源加载失败
3. 页面相关请求没有收到响应,如超时
4. 页面出现未被捕获的js错误
puppeteer所提供的如下三个事件可以达到我们的目的
最终我们可以把上面的逻辑补充为如下
同样我们也可以收集错误信息通知告警出来
这个是最容易做到的点,我们只要在当前页面中判断指定的dom元素是否存在即可。
Chrome已经支持了Recorder功能,可以把用在浏览器上面的操作转化为puppeteer脚本,这大大简化了我们编写测试脚本所需要的工作,也成为了我们确定方案的重要依据之一
脚本录制的实现原理不复杂,却是UI自动化测试中比较核心的能力之一。目前比较通用的录制方式有如下两种:
• headless-recorder[4] 开源Chrome扩展,录制的脚本简单清晰,便于修改
• Chrome Recorder[5] Chrome内置功能,录制后需调整的代码相对多一些
上述两种方式原理相似,在使用体验和生成的脚本有一定差异,大家可以分别尝试和使用。
最终我们的方案和步骤如下:
1. 通过上述两种工具录制生成脚本
2. 对脚本做修改和调整,对需要验证的地方增加断言或判断
3. 保存脚本,并在测试配置中指定页面需要执行的脚本
4. 系统执行脚本,搜集错误并通知
以我们实现的配置方式举例:
通过配置checker.recorder来指定需要执行的的测试脚本
{
"title": "实名认证-个人信息",
"description": "实名认证-个人信息",
"url": "https://h5.qianbao.qq.com/auth/userinfo?frompage=index",
"fullPage": true,
"checker": {
"recorder":[{
"description":"chrome-recorder功能测试",
"filePath":"auth-userinfo/chrome-recorder.js"
},{
"description":"headless-recorder功能测试",
"filePath":"auth-userinfo/headless-recorder.js"
}]
}
},
脚本格式
录制的脚本统一保存在recorder-scripts目录下,如下为脚本例子
module.exports.play = async (page) => {
// TODO 执行UI某些操作
// 不满足条件时 抛出错误
throw new Error('Could not find element for selectors');
};
于是,我们补充上了如下流程
同样的,我们可以把脚本执行过程中的错误抛出来
前端通常通过指定域名对应的ip和端口来指定环境,有时对所测试的页面会有相对复杂的代理配置需求。Whistle强大的功能和对linux友好的支持成了第一时间想到的选择。
在linux上和whistle的结合有如下几个问题需要解决:
1. 证书问题,涉及到如何安装并信任证书
2. whistle并不支持命令行方式开启https的代理,我们如何开启https功能
3. 如何做到每个页面具有不同的whistle规则,且相互独立不互相影响
4. puppeteer如何指定whistle代理
下面我们对几个问题逐一解决
这里有两种方式解决此问题
1. 提前生成好证书,并通过whistle指定自定义的证书,系统信任自定义根证书。
2. 在首次执行whistle时,会在whistle配置目录中自动生成证书,我们可以把此证书加到系统信任根证书内
下述代码我们采用第二种方式
RUN npm install -g whistle && \
npm install && \
npm run build && \
chmod -R 755 /usr/lib/node_modules && \
w2 start && w2 stop && \
cp /root/.WhistleAppData/.whistle/certs/root.crt /usr/local/share/ca-certificates/ && \
echo "root.crt" >> /etc/ca-certificates.conf && \
update-ca-certificates
如果信任根证书后仍存在证书错误,我们可以在puppeteer中通过如下代码忽略证书错误
const browser = await puppeteer.launch({
headless: IS_PRODUCTION || IS_TEST,
devtools: IS_DEVELOPMENT,
args: launchArgs,
// whistle使用的自签证书,需要忽略证书错误
ignoreHTTPSErrors: true,
});
whistle并不提供cli的方式直接开启https的抓包,这里面同样有两种方式
1. 通过对whistle ip端口发起请求,通过/cgi-bin/init请求获取到clientId,用clientId发起/cgi-bin/intercept-https-connects请求开启https的抓包。
2. whistle在首次启动后,会生成配置目录,配置目录中包含配置关于intercept-https-connects的选项。我们可以直接编辑这个文件,来实现https抓包的开启
配置文件保存在.WhistleAppData/.whistle/properties,内容如下
{"filesOrder":["latestVersion"],"Custom1":"Custom1","Custom2":"Custom2","interceptHttpsConnects":true}
Dockerfile命令如下
RUN cp -f Rainbow/properties /root/.WhistleAppData/.whistle/properties/
whistle对于代理规则只提供了add/use的能力,用来应用指定的规则,但并没有提供删除或关闭指定代理的能力 这里面我们也探索出两个方案:
1. 每次使用新的规则前,清空whistle代理规则配置目录,并重启whistle
2. 让规则的名字相同,这样每次use后就是最新的,而没有配置规则的页面可以用use空规则
我们采用第二种,所以配置方式如下:
通过配置whistleFilePath字段指向whistle-rules目录内的规则文件。
{
"title": "钱包首页",
"description": "钱包首页h5部分",
"url": "https://m.qianbao.qq.com/pages/qianbaoHome",
"whistleFilePath":"qianbao-home.whistle.js"
}
如下是qianbao-home.whistle.js规则文件例子,保存在whistle-rules目录下
exports.name = 'supernova';
exports.rules = `
m.qianbao.qq.com 11.160.161.108
`;
我们可以在启动browser的时候增加启动参数来开启
const browser = await puppeteer.launch({
args: [
`--proxy-server=127.0.0.1:${WHISTLE_PORT}`,
],
ignoreHTTPSErrors: true,
});
在结合whistle后,我们最终得到了如下流程图
我们的方案是和QQLogin的同学针对几个固定的号码开通白名单 在页面自动跳转到qqlogin登入页面时,自动输入账户密码来登入
在linux中使用canvas和pupeteer时,可能会遇到某个lib缺少或不满足版本要求的情况 我们的方案是通过直接基于社区Debian最新稳定版来构建镜像且安装如下包
FROM mirrors.tencent.com/moggyteam/debian:stable-slim
USER root
RUN apt-get update && \
apt-get install -y curl && \
curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && \
apt-get install -y psmisc nodejs wget sudo procps telnet vim inetutils-ping net-tools htop libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libgbm1 libasound2 libpangocairo-1.0-0 libxss1 libgtk-3-0
brew install pkg-config cairo pango libpng jpeg giflib librsvg
通常是因为linux缺少中文字体的原因
COPY ./fonts /usr/share/fonts/mac
通过之前的描述我们已经完成了前端自动化测试中三个纬度的基本能力。而如何更好地和每个项目结合在一起是我们下一步探索的方向。
目前规划中的能力包括:
• domdiff算法的引用,对比前后dom树结构差异来判断页面是否异常。
• 流水线的结合,可让每个项目自行选择触发检查的阶段。
• 性能上的分析和优化,分析每个CPU核心的利用,尝试更好地分配进程。
• 更加可视化的结果汇总和图表。
[1]
page.goto: https://zhaoqize.github.io/puppeteer-api-zh_CN/#?product=Puppeteer&version=v13.6.0&show=api-pagegotourl-options[2]
page.screenshot: https://zhaoqize.github.io/puppeteer-api-zh_CN/#?product=Puppeteer&version=v13.6.0&show=api-pagescreenshotoptions[3]
canvas: https://www.npmjs.com/package/canvas[4]
headless-recorder: https://github.com/checkly/headless-recorder[5]
Chrome Recorder: https://developer.chrome.com/docs/devtools/recorder/