Selenium圖表驗證實戰
年後回來第二週正式開始了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(有興趣可以自己去了解了,文件很詳細,步驟很簡單)。
原文來自下方公眾號,轉載請聯絡作者,並務必保留出處。
想第一時間看到更多原創技術好文和資料,請關注公眾號: 測試開發棧