年后回來第二周正式開始了2018年度的第一個sprint(敏捷開發(fā)流程概覽見下圖),這也是轉(zhuǎn)入開發(fā)團(tuán)隊參加的第一個sprint,前一周周五的會議上將這個sprint的任務(wù)清單安排的滿滿當(dāng)當(dāng),當(dāng)時心里還在想: 這么多需求,10天能做完嗎?!

周一上午每日站會上,領(lǐng)了個“難啃”的任務(wù): 爬取某度頁面的指數(shù)數(shù)據(jù),某度反爬技術(shù)果真不是蓋的,想通過接口拉數(shù)據(jù)?no way,不操作頁面無數(shù)據(jù)請求,且接口返回的是加密的數(shù)據(jù)片段圖片,在前端頁面再解密組合成數(shù)據(jù)圖片展示的(見下圖),這腫么搞。。。。

為什么要領(lǐng)這個任務(wù)?以前UI自動化測試做過那么多,有這方面基礎(chǔ),雖然圖表的自動化測試一般都是直接跳過,不過這次想嘗試挑戰(zhàn)一下;其實這種驗證或開發(fā)思路很明確,分2步:
- 在圖表上移動鼠標(biāo)指針到對應(yīng)的點,出現(xiàn)數(shù)據(jù)浮標(biāo),截圖保存;
- 對截圖做OCR識別,提取出圖片中的數(shù)據(jù);
思路是不是很簡單的感覺?可做起來就不是特么的那么回事了,中間有好幾個大坑等著你跳,下面我把一些技術(shù)難點(大坑)分享出來,對做圖表方面的UI自動化測試可以參考下:
- 第一個坑就是鼠標(biāo)移動了,用過WebDriver的都知道可以用Actions,可是這個Action是基于那個元素或控件呢?
一般第一直覺大家都會選圖表對于的標(biāo)簽元素(如<chart>、<svg>、<graph>、<rect>、<canvas>等等),很坑,用這些標(biāo)簽去定位WebDriver識別不了。。。。根本找不到對應(yīng)的元素(高版本沒有去嘗試了不知道會不會好些,我這里用的2.53.1版本),這時候就要找這個圖表元素外層的第一個div標(biāo)簽(通常圖表外面都會包一層div),鼠標(biāo)移動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);
}
- 第二個坑,移動鼠標(biāo)并不能每次都完美移到位,所以就要加一個數(shù)據(jù)浮標(biāo)出來了才算成功,否則就要在當(dāng)前坐標(biāo)的前后小范圍內(nèi)嘗試;
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) {//如果未出現(xiàn)預(yù)期pop,則在區(qū)間[x-10, x+10]內(nèi)重試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; //坐標(biāo)移動offset/2位
counter++;
}
- 區(qū)域截圖?
WebDriver截圖通常都是截取全瀏覽器的,我現(xiàn)在就只想接圖表區(qū)域那部分,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();
// 創(chuàng)建一個矩形使用上面的高度,和寬度
Rectangle rect = new Rectangle(width, height);
// 得到元素的坐標(biāo)
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個大坑,怎么驗證那個淺黑色的數(shù)據(jù)浮標(biāo)(popup)?
這個坑比上面都要難,剛開始我想到的是用sikuli來處理,確實效果很好,識別的很準(zhǔn)確,可是。。。我后來一問,代碼規(guī)范不允許maven項目直接引用jar包,我去。。。。搜索了google半天也沒找到sikuliX.jar的maven依賴方式,沒辦法從頭再來,后面想偷點懶,想找一些現(xiàn)成的圖片驗證方案來處理,找了幾個github上star比較多的項目嘗試了下都不理想,跟我這個需求不匹配,那些處理都是將背景灰化,可是我就是想要驗證這一層浮標(biāo)的背景,灰化之后就沒差異性了。所以還是得自己動手豐衣足食,先確定好思路:
1> 截圖的時候盡量只截取圖表區(qū)域,排除一些無關(guān)干擾,這個可以去調(diào)整坑3中的截圖坐標(biāo)和高度,就會得到你滿意的效果;
2> 顏色驗證方案如何做? 看過我文章的童鞋應(yīng)該還記得分享過的UiAutomator2.0移動端的顏色驗證方案,這次思路就是來自那里,并做了算法改進(jìn):
- 由于浮標(biāo)可以理解是一層懸浮在頁面的div,那么它跟頁面肯定會有交集,如果交集區(qū)域頁面也有顏色就會造成很大的干擾,那么我們就直接取像素點的顏色rgb值驗證就好了,千萬別灰化,用那個就GG;
private boolean assertColor(int rgb) {
//容錯處理,正常浮標(biāo)數(shù)據(jù)的色度在[-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;
}
- 考慮到很多干擾,最后我決定就只找浮標(biāo)的四個頂點,遍歷圖片的像素點(所以截圖不能太大,圖片較大的話壓縮一下尺寸后再遍歷)驗證四個頂點存在即浮標(biāo)出現(xiàn);
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>>();//存放頂點坐標(biāo)
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++;
}
}
}
- 四個頂點的查找和驗證,我這里使用了一些小算法,比如如何確定是一個頂點?干擾點如何排除?采用了構(gòu)造3*3矩形點陣、矩形面積和十字交叉驗證等方法;
//構(gòu)造(x,y)坐標(biāo)的矩形點陣
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 } };

如上圖,一個坐標(biāo),構(gòu)造對應(yīng)坐標(biāo)的3x3矩形點陣,紅色點表示顏色匹配的,其他點顏色不匹配,如果符合這個條件就可以基本斷定這是一個頂點了,但是由于界面上還有其他顏色交叉干擾,所以就還需要矩形面積(其實思路也跟上面類似,基于疑似頂點+方向構(gòu)造對應(yīng)的10x10矩陣,計算面積內(nèi)的點都符合即進(jìn)一步判斷是頂點)和十字交叉驗證(基于疑似頂點的x軸和y軸各上下移動一位得到4個點坐標(biāo),其中按照點的方向判斷哪些點顏色符合即可判斷是不是頂點);
最后按照這樣跑完及匹配后切圖,就會得到原圖(x.png)和切出來的數(shù)據(jù)圖(data*.png),如下:

后面再對切出來的數(shù)據(jù)圖做OCR文字識別就可以將數(shù)據(jù)都提取出來了,OCR識別這樣的圖片可以說是毫無壓力了,要是原圖那就亂慘不忍睹了,OCR文字識別這部分可以引用開源的框架或開放的OCR接口,比如百度的OCR文字識別API(有興趣可以自己去了解了,文檔很詳細(xì),步驟很簡單)。
原文來自下方公眾號,轉(zhuǎn)載請聯(lián)系作者,并務(wù)必保留出處。
想第一時間看到更多原創(chuàng)技術(shù)好文和資料,請關(guān)注公眾號:測試開發(fā)棧