視覺回歸測試最常見的情況是使用基線圖像進行測試。然而,視覺測試的不同方面也值得討論。我們將介紹模板匹配(使用OpenCV)、布局測試(使用Galen)和OCR(使用Tesseract),并展示如何將這些工具無縫集成到現有的Appium和Selenium測試中。
我們使用JAVA(以及OpenCV和Tesseract的Java包裝器),但類似的解決方案也可以通過其他技術堆棧實現。
這篇文章是2020年9月在新加坡的Taqelah和2020年Selenium會議期間(以較短的形式)發表的快速演講的配套文章。有關完整功能的演示和更多詳細信息,請參閱 http://www.justtestlah.qa/
我希望這個總結能幫助你選擇對你的用例最有影響的工具,并給你一些關于如何將它們集成到你自己的工具箱中的想法。
模板匹配
模板匹配的任務是在當前屏幕上找到給定的圖像(模板)。

Waldo在哪里?

對于移動測試,Appium在其1.9版本中以圖像定位器策略的形式添加了此功能。(更多信息可以在文檔和早期教程中找到)其思想是將圖像的Base64編碼字符串表示傳遞給WebDriver。
- 早期教程:https://appiumpro.com/editions/32-finding-elements-by-image-part-1
使用圖像定位器(image locator),你可以像任何其他WebElement一樣與結果元素交互。例如:
WebElement element =
driver.findElementByImage(base64EncodedImageFile);
element.click();
或
By image = MobileBy.image(base64EncodedImageFile);
new WebDriverWait(driver, 10).until(ExpectedConditions.presenceOfElementLocated(image)).click();
開發人員采用的方法是將功能添加到Appium服務器的一部分,并使用OpenCV(這將成為運行Appium服務器的實例的依賴項)來增強實際的圖像識別能力。
有趣的是,客戶端與服務器之間的流程如下所示:
- 從Appium服務器請求截圖。
- 將屏幕截圖和模板都發送到Appium服務器進行匹配。
這感覺并不完美,尤其是如果我們想在同一個屏幕上匹配多個模板。
當我在2018年首次實現模板匹配時(當時還不知道Appium團隊已經在開發模板匹配),我也選擇了OpenCV,而是在客戶端運行了它。使用OpenCV Java包裝器,我的代碼要點如下所示:
Mat result = new Mat(resultRows, resultCols, CvType.CV_32FC1);
Imgproc.matchTemplate(image, templ, result, Imgproc.TM_CCOEFF_NORMED);
MinMaxLocResult match = Core.minMaxLoc(result);
if (match.maxVal >= threshold) {
// found
}
這種方法不需要向上述Appium服務器發出額外的請求。實際上,除了屏幕截圖的功能外,它不需要WebDriver的任何功能。它還可以與Selenium和Appium一起使用。也就是說,這也增加了對OpenCV的依賴,這次是對運行測試執行的實例的依賴。
我將以上兩種方法(客戶端和服務器端執行)都包裝到TemplateMatcher接口中,以展示其用法(將其視為PoC)。
你可以在JustTestLah中找到更多詳細信息和示例!
- JTL測試框架:https://justtestlah.qa/#template-matching
布局測試
另一種視覺測試類型涉及驗證頁面或屏幕的布局。你可以通過圖像比較來做到這一點,圖像比較也會隱式檢查布局。一種更簡單的方法是使用像Galen這樣的專用布局測試工具(在我看來,這是最被低估的UI測試框架之一)。
Galen使用每個屏幕的規范來定義屏幕上的所有(重要)元素及其大小以及它們之間的絕對或相對位置。
讓我們以google搜索頁為例:

我們可以使用以下規范表示它:
SEARCH_FIELD:
below LOGO
centered horizontally inside viewport
visible
LOGO:
above SEARCH_FIELD
centered horizontally inside viewport
width < 100% of SEARCH_FIELD/width
visible
SEARCH_BUTTON:
near LUCKY_BUTTON 20px left
visible
注意,上面使用的是JustTestLah!框架的語法(通過在頁面對象的YAML文件中定義的唯一鍵引用UI元素)。在純Galen中,這些需要在spec文件的頂部定義:
@objects
LOGO id hplogo
SEARCH_FIELD css input[name=q]
...
有多種執行這些檢查的方法。我更喜歡將verify方法作為BasePage抽象類的一部分:
private T verify() {
String baseName = this.getClass().getSimpleName();
String baseFolder = this.getClass().getPackage().getName().replaceAll("\.", File.separator);
String specPath = baseFolder
+ File.separator
+ configuration.getPlatform()
+ File.separator
+ baseName
+ ".spec";
galen.checkLayout(specPath, locators);
return (T) this;
}
這樣,每當我們第一次與屏幕交互時,我們都可以輕松地從測試中調用驗證(順便說一句,我使用類似的方法來集成Applitools進行視覺測試):
public class GoogleSteps extends BaseSteps {
private GooglePage google;
@Given("I am on the homepage")
public void homepage() {
google.verify().someAction().nextAction();
}
}
光學字符識別(OCR)
視覺斷言的另一種形式是光學字符識別,其首字母縮寫為OCR。每當由于某種原因將文本渲染為圖像并且無法使用標準測試工具進行驗證時,此功能將非常有用。
對于那些使用Selenium進行Web抓取而不是進行測試的用戶來說,這可能也很有趣,因為這是網站開發人員采取的反措施之一,以使其變得更加困難。
我們使用Tesseract(一種最初由HP在1980年代開發,目前由Google贊助的OCR工具)。
我們的示例不是最實際的示例,而是要展示Tesseract在檢測不同類型的字體方面的強大功能:我們將驗證Google徽標是否確實拼寫出“ Google”:
public class GooglePage extends BasePage<GooglePage> {
@Autowired private OCR ocr;
...
public String getLogoText() {
return ocr.getText($("LOGO"));
}
}
public class GoogleSteps extends BaseSteps {
private GooglePage google;
...
@Then("the Google logo shows the correct text")
public void checkLogo() {
assertThat(google.getLogoText()).isEqualTo("Google");
}
}
使用的OCR服務如下所示:
public class OCR implements qa.justtestlah.stubs.OCR {
private Logger LOG = LoggerFactory.getLogger(OCR.class);
private TakesScreenshot driver;
private Tesseract ocr;
@Autowired
public OCR(Tesseract ocr) {
this.ocr = ocr;
}
/**
* @param element {@link WebElement} element to perform OCR on
* @return recognised text of the element
*/
public String getText(WebElement element) {
return getText(element.getScreenshotAs(OutputType.FILE));
}
/** @return all text recognised on the screen */
public String getText() {
return getText(getScreenshot());
}
private String getText(File file) {
LOG.info("Peforming OCR on file {}", file);
try {
return ocr.doOCR(file).trim();
} catch (TesseractException exception) {
LOG.warn("Error performing OCR", exception);
return null;
}
}
/**
* Usage:
*
* <pre>
* new OCR().withDriver(driver);
* </pre>
*
* @param driver {@link WebDriver} to use for capturing screenshots
* @return this
*/
public OCR withDriver(WebDriver driver) {
this.driver = (TakesScreenshot) driver;
return this;
}
/**
* Usage:
*
* <pre>
* new OCR().withDriver(driver);
* </pre>
*
* @param driver {@link TakesScreenshot} to use for capturing screenshots
* @return this
*/
public OCR withDriver(TakesScreenshot driver) {
this.driver = driver;
return this;
}
private File getScreenshot() {
return driver.getScreenshotAs(OutputType.FILE);
}
@Override
public void setDriver(WebDriver driver) {
this.driver = (TakesScreenshot) driver;
}
}
這要求在運行測試的實例上安裝Tesseract。有關完整的源代碼和演示,請查看JustTestLah!測試框架。
- http://www.justtestlah.qa/