年后回来第二周正式开始了2018年度的第一个sprint(敏捷开发流程概览见下图),这也是转入开发团队参加的第一个sprint,前一周周五的会议上将这个sprint的任务清单安排的满满当当,当时心里还在想: 这么多需求,10天能做完吗?!
周一上午每日站会上,领了个“难啃”的任务: 爬取某度页面的指数数据,某度反爬技术果真不是盖的,想通过接口拉数据?no way,不操作页面无数据请求,且接口返回的是加密的数据片段图片,在前端页面再解密组合成数据图片展示的(见下图),这肿么搞。。。。
为什么要领这个任务?以前UI自动化测试做过那么多,有这方面基础,虽然图表的自动化测试一般都是直接跳过,不过这次想尝试挑战一下;其实这种验证或开发思路很明确,分2步:
- 在图表上移动鼠标指针到对应的点,出现数据浮标,截图保存;
- 对截图做OCR识别,提取出图片中的数据;
思路是不是很简单的感觉?可做起来就不是特么的那么回事了,中间有好几个大坑等着你跳,下面我把一些技术难点(大坑)分享出来,对做图表方面的UI自动化测试可以参考下:
- 第一个坑就是鼠标移动了,用过WebDriver的都知道可以用Actions,可是这个Action是基于那个元素或控件呢?
一般第一直觉大家都会选图表对于的标签元素(如<chart>、<svg>、<graph>、<rect>、<canvas>等等),很坑,用这些标签去定位WebDriver识别不了。。。。根本找不到对应的元素(高版本没有去尝试了不知道会不会好些,我这里用的2.53.1版本),这时候就要找这个图表元素外层的第一个div标签(通常图表外面都会包一层div),鼠标移动Action的代码我也贴一下:
private void move(WebElement element, int x, int y) {
Actions action = new Actions(driver);
try {
//操作
if (x == 0) {
action.moveToElement(element, 20, y).perform();
} else {
action.moveToElement(element, x, y).build().perform();
}
} catch (MoveTargetOutOfBoundsException e) {
System.out.println(String.format("point(%d, %d) is out of range", x, y));
}
System.out.println(String.format("move the cursor to point(%d,%d)", x, y));
sleep(1000);
}
- 第二个坑,移动鼠标并不能每次都完美移到位,所以就要加一个数据浮标出来了才算成功,否则就要在当前坐标的前后小范围内尝试;
move(chart, x, y);
File snapshot = snapshotAndSave(chart, genSnapshotName(x, y));
boolean isSuccess = templateMatcher.isMatch(snapshot.getPath(), templateImgFile);
int counter = 0;
int tempX = x > 0 ? x - offset : 10;
while (!isSuccess) {//如果未出现预期pop,则在区间[x-10, x+10]内重试5次
if (counter > 4) {
break;
}
snapshot.delete();//删除无效图片
move(chart, tempX, y);
snapshot = snapshotAndSave(chart, genSnapshotName(tempX, y));
isSuccess = templateMatcher.isMatch(snapshot.getPath(), templateImgFile);
tempX += offset / 2; //坐标移动offset/2位
counter++;
}
- 区域截图?
WebDriver截图通常都是截取全浏览器的,我现在就只想接图表区域那部分,how to do? 相信你们也很想知道,直接看到代码,注释很清晰了:
private File snapshotAndSave(WebElement element, String fileName) {
WrapsDriver wrapsDriver = (WrapsDriver) element;
File scrFile = ((TakesScreenshot) wrapsDriver.getWrappedDriver()).getScreenshotAs(OutputType.FILE);//截图整个页面
try {
BufferedImage img = ImageIO.read(scrFile);
// 获得元素的高度和宽度
int width = element.getSize().getWidth();
int height = element.getSize().getHeight();
// 创建一个矩形使用上面的高度,和宽度
Rectangle rect = new Rectangle(width, height);
// 得到元素的坐标
Point p = element.getLocation();
BufferedImage dest = img.getSubimage(p.getX(), p.getY(), rect.width, rect.height);
//存为png格式
ImageIO.write(dest, "png", scrFile);
File file = new File(fileName);
FileUtils.copyFile(scrFile, file);
return file;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
- 第4个大坑,怎么验证那个浅黑色的数据浮标(popup)?
这个坑比上面都要难,刚开始我想到的是用sikuli来处理,确实效果很好,识别的很准确,可是。。。我后来一问,代码规范不允许maven项目直接引用jar包,我去。。。。搜索了google半天也没找到sikuliX.jar的maven依赖方式,没办法从头再来,后面想偷点懒,想找一些现成的图片验证方案来处理,找了几个github上star比较多的项目尝试了下都不理想,跟我这个需求不匹配,那些处理都是将背景灰化,可是我就是想要验证这一层浮标的背景,灰化之后就没差异性了。所以还是得自己动手丰衣足食,先确定好思路:
1> 截图的时候尽量只截取图表区域,排除一些无关干扰,这个可以去调整坑3中的截图坐标和高度,就会得到你满意的效果;
2> 颜色验证方案如何做? 看过我文章的童鞋应该还记得分享过的UiAutomator2.0移动端的颜色验证方案,这次思路就是来自那里,并做了算法改进:
- 由于浮标可以理解是一层悬浮在页面的div,那么它跟页面肯定会有交集,如果交集区域页面也有颜色就会造成很大的干扰,那么我们就直接取像素点的颜色rgb值验证就好了,千万别灰化,用那个就GG;
private boolean assertColor(int rgb) {
//容错处理,正常浮标数据的色度在[-12170676或-11907766]=RGB(74,74,74),所以base取74
int base = 74;
int r = (rgb >> 16) & 0xff;
int g = (rgb >> 8) & 0xff;
int b = rgb & 0xff;
boolean isMatch =
Math.abs(r - base) * 1.0 / base < 0.2 && Math.abs(g - base) * 1.0 / base < 0.2
&& Math.abs(b - base) * 1.0 / base < 0.2;
return isMatch;
}
- 考虑到很多干扰,最后我决定就只找浮标的四个顶点,遍历图片的像素点(所以截图不能太大,图片较大的话压缩一下尺寸后再遍历)验证四个顶点存在即浮标出现;
BufferedImage sourceImage = readImage(String.valueOf(sourceFile));
int sourceHeight = sourceImage.getHeight();
int sourceWidth = sourceImage.getWidth();
BufferedImage cutImage = null;
Integer counter = 1;
Map<Integer, List<Integer>> points = new LinkedHashMap<Integer, List<Integer>>();//存放顶点坐标
for (int i = 0; i < sourceWidth; i++) {
if (counter > 4) {
break;
}
for (int j = 0; j < sourceHeight; j++) {
if (isColorRangeMatch(sourceImage, i, j, counter)) {
if (counter > 4) {
break;
}
points.put(counter, Arrays.asList(new Integer[] { i, j }));
counter++;
}
}
}
- 四个顶点的查找和验证,我这里使用了一些小算法,比如如何确定是一个顶点?干扰点如何排除?采用了构造3*3矩形点阵、矩形面积和十字交叉验证等方法;
//构造(x,y)坐标的矩形点阵
int[][] points = new int[][] { { x - 1, y - 1 }, { x - 1, y }, { x - 1, y + 1 },
{ x, y - 1 }, { x, y }, { x, y + 1 }, { x + 1, y - 1 }, { x + 1, y },
{ x + 1, y + 1 } };
如上图,一个坐标,构造对应坐标的3x3矩形点阵,红色点表示颜色匹配的,其他点颜色不匹配,如果符合这个条件就可以基本断定这是一个顶点了,但是由于界面上还有其他颜色交叉干扰,所以就还需要矩形面积(其实思路也跟上面类似,基于疑似顶点+方向构造对应的10x10矩阵,计算面积内的点都符合即进一步判断是顶点)和十字交叉验证(基于疑似顶点的x轴和y轴各上下移动一位得到4个点坐标,其中按照点的方向判断哪些点颜色符合即可判断是不是顶点);
最后按照这样跑完及匹配后切图,就会得到原图(x.png)和切出来的数据图(data*.png),如下:
后面再对切出来的数据图做OCR文字识别就可以将数据都提取出来了,OCR识别这样的图片可以说是毫无压力了,要是原图那就乱惨不忍睹了,OCR文字识别这部分可以引用开源的框架或开放的OCR接口,比如百度的OCR文字识别API(有兴趣可以自己去了解了,文档很详细,步骤很简单)。
原文来自下方公众号,转载请联系作者,并务必保留出处。
想第一时间看到更多原创技术好文和资料,请关注公众号:测试开发栈