Визуальное тестирование в Appium

Изначально Appium создавался для автоматизации мобильных приложений на iOS и Android, со временем превратился в полнофункциональную платформу, автоматизирующую через WebDriver (для Windows-приложений).

Начиная с версии 2.0, каждый драйвер изолирован от серверных приложений Appium и может управляться самостоятельно с помощью интерфейса командной строки Appium Driver.

Что такое визуальное тестирование

Важнейший вид тестирования, который может выявлять как визуальные, так и функциональные дефекты. Хотя в целом оно дополняет функциональное тестирование, визуальное тестирование является самодостаточным.

  • Главная задача: оценивает визуальные изменения, гарантируя, что на прод передаются только нужные изменения в интерфейсе.
  • Во многих компаниях полагают, что при проведении тщательного функционального тестирования визуальное тестирование не требуется. Это ошибка.
  • Потому что визуальное тестирование находит дефекты, которые функциональное тестирование не замечает, и гарантирует, что приложение выглядит так, как должно выглядеть.
  • Визуальное регрессионное — когда нужно найти сложные дефекты, пропущенные функциональным сьютом.

Важность UX

Почему придается такое большое значение пользовательскому опыту (UX, User Experience)? 

  • Потому что независимо от того, насколько хороша основная функциональность приложения, она будет бесполезной для пользователя, если не обеспечен хороший пользовательский опыт, удобный приятный интерфейс, привлекательный визуальный дизайн.
  • Автоматизация визуальных тестов экономит средства компании, улучшая ее долгосрочную экономическую эффективность; визуальные автотесты разумеется быстрее чем ручные; в целом более точны, поскольку не опираются на человеческий фактор; визуальные автотесты — «пиксельно идеальные» (pixel-perfect).
  • Они многократно используются (реюзабельные) и их результаты понятны другим членам команды (в автоматически создаваемых репортах).

Учитывая все это, команды должны уделять визуальному тестированию значительное время и ресурсы. В сочетании с юнит-тестами визуальные тесты помогут обнаружить визуальные дефекты на ранних этапах жизненного цикла разработки.

Библиотека OpenCV

Мы знаем, как делать скриншоты в Appium. А как сравнить два скриншота, чтобы обнаружить визуальные различия?

Базовый подход к визуальному сравнению будет неправильным, потому что даже алгоритмы обработки изображений могут добавить различия. 

Но существует библиотека компьютерного зрения OpenCV с функциями для работы с изображениями, которую в данном случае можно использовать без необходимости вникать в тонкости. 

В Appium есть поддержка OpenCV, но она не включена по умолчанию, поскольку OpenCV и ее связки с Node.js — требовательны к ресурсам и должны настраиваться на каждой платформе независимо.

Чтобы подготовить библиотеку для использования в Appium, выполните команду npm install -g opencv4nodejs.

Шаги визуальной валидации

Цель: снять скриншоты каждого экрана (view), с которым мы взаимодействуем в приложении (при функциональном тестировании). Мы сравниваем каждый скриншот с предыдущим скриншотом того же экрана, с помощью приложения для анализа изображений.

В процессе проверки мы можем обнаружить визуальную регрессию, то есть если возникнут значительные нежелательные изменения. В этот момент мы можем либо прекратить выполнение теста, либо зафиксировать этот дефект в базу данных, для дальнейшей оценки командой.

  • Шаг 1: На этом этапе нам нужно протестировать приложение и сделать скриншоты.
  • Шаг 2: На этом этапе скриншоты сравниваются с базовыми (baseline) скриншотами при помощи инструмента автоматизации (в нашем случае Appium). Как правило, базовые скриншоты — это скриншоты, сделанные во время предыдущих сессий тестирования.
  • Шаг 3: После получения результатов сравнения изображений — приложение генерирует отчет (репорт) с описанием замеченных несоответствий (отличий) между изображениями.
  • Шаг 4: На последнем этапе тестировщик просматривает репорт, определяя, является ли каждое найденное отличие дефектом (чтобы исключить ложные срабатывания). Далее baseline-изображения обновляются.

Так как это первый наш практикум, у нас еще нет базовых изображений. Поэтому в качестве базовых будут использованы изображения из первого тестового прогона. Они будут сравниваться со скриншотами, начиная со следующего прогона.

Пример визуального сравнения

Будем проверять наличие знака «$» на этом изображении. Сначала делаем скриншот пользовательского интерфейса, затем в OpenCV сравниваем изображения, и если они не совпадают, то передаем баг на проверку.

Vusual comparison

Настройка тестового окружения

Установка Appium, ASDK и Java

Скачайте и установите Java (JDK) и пропишите пути к папке JDK и bin.

  • Скачайте файл «.exe» отсюда (версия: jdk1.8.0_91 или любая другая последняя).
  • Установите .exe-файл.
  • Пропишите путь к папке JDK bin в переменной окружения вашей системы.

 Загрузка Android SDK

  • Найдите по ссылке «android-sdk_r24.4.1-windows.zip» (или более свежей версии) и загрузите.
  • После загрузки zip-файла распакуйте его в папку.
  • Запустите файл «SDK Manager.exe«.
  • Откроется окно Android SDK Manager. Выберите «Tools» и Android-платформу, на которой будете тестировать.

Установка Appium

  • Откройте официальный сайт Appium.
  • Нажмите «Скачать».
  • Выберите ОС, в которой вы работаете, и загрузите соответствующую версию.
  • Распакуйте загруженную zip-папку.
  • Установите .exe-файл «appium-installer«.

Настройка Appium для визуального тестирования

Appium поддерживает OpenCV, но ее надо отдельно включать, поскольку разработка OpenCV и его привязок к Node.js должна выполняться на производительных машинах.

  • Самый простой способ подготовить все к работе — командой npm install -g opencv4nodejs.
  • Система попытается установить Node-привязки глобально, а также загрузить и собрать OpenCV на вашей машине.
  • Если это не получится, можете установить OpenCV через Homebrew, а затем установить Node-привязки с флагом окружения OPENCV4NODEJS_DISABLE_AUTOBUILD=1, чтобы заставить систему использовать установленные в ней бинарники.

После установки пакета opencv4nodejs необходимо убедиться, что он доступен Appium при запуске. Один из способов — выполнить команду npm install без параметра -g в каталоге Appium. Другой вариант — добавить глобальную папку node_modules в переменную окружения NODE_PATH.

Практикум

Тест-кейс

Выполните следующие шаги:

  1. Сначала сохраняем изображение главной страницы приложения в качестве базового (baseline) изображения.
  2. Запускаем приложение с помощью cap-файла appium.
  3. Ожидаем, пока загрузится главная страница.
  4. Нажимаем кнопку “Добавить товар в корзину”
  5. Нажимаем кнопку “Корзина”
  6. Заходим в корзину и проверяем экран корзины с базовым изображением (ScreenShot)
Homepage Add to cart

! При первом запуске он будет таким же, как и исходные изображения по заданному пути. При втором выполнении он возвращает пороговое значение совпадения (match threshold value), которое мы уже установили в нашем коде.

Дизайн фреймворка и код

Структура фреймворка:

Framework design

Тестовый сценарий

AppiumVisualTestBrowserStackAPP.java

import io.appium.java_client.MobileBy;
import io.appium.java_client.imagecomparison.SimilarityMatchingOptions;
import io.appium.java_client.imagecomparison.SimilarityMatchingResult;
import java.io.File;
import java.net.URISyntaxException;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class AppiumVisualTestBrowserStackAPP extends BaseTest {

// Give a file path where we can save the matched file
private final static String path_to_validate = "/Users/Download/bs_demo/";

private final static String CHECK_HOME = "home_screen";
private final static String CART_PAGE = "cart_page";
private final static String BASEIMAGE = "BASEIMAGE_";
private final static double Breakpoint_for_Match = 0.99; //Thresold Value
private final static By ADD_TO_CART = MobileBy.AccessibilityId("add-to-cart-12");
private final static By NAV_TO_CART = MobileBy.AccessibilityId("nav-cart");

@Override
protected DesiredCapabilities getCaps() throws URISyntaxException {
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("platformName", "Android");
capabilities.setCapability("deviceName", "Android Emulator");
capabilities.setCapability("automationName", "UiAutomator2");
capabilities.setCapability("app", getResource("apps/browserstack-demoapp.apk").toString());

//Make sure we uninstall the app before each test regardless of version
capabilities.setCapability("uninstallOtherPackages", "io.cloudgrey.the_app");

return capabilities;
}

private WebElement waitForElement(WebDriverWait wait, By selector) {
WebElement el = wait.until(ExpectedConditions.presenceOfElementLocated(selector));
try { Thread.sleep(750); } catch (InterruptedException ign) {}
return el;
}

@Test

public void testAppDesign() throws Exception {
WebDriverWait wait = new WebDriverWait(driver, 5);

// wait for an element that's on the home screen
WebElement addToCart = waitForElement(wait, ADD_TO_CART);

// now we know the home screen is loaded, so do a visual check
doVisualCheck(CHECK_HOME);

// Click on add to cart btn for adding item in card
addToCart.click();
WebElement navToCart = waitForElement(wait, NAV_TO_CART);

//click to cart btn
navToCart.click();

//Perform our second visual check, this time of the cart page
doVisualCheck(CART_PAGE);
}

private void doVisualCheck(String checkName) throws Exception {
String basematchFilename = path_to_validate + "/" + BASEIMAGE + checkName + ".png";
File basematchImg = new File(basematchFilename);

// If there is no basematch picture for this check, one should be made.
if (!basematchImg.exists()) {

System.out.println(String.format("No basematch found for '%s' check; capturing baseline instead of checking", checkName));

File newBasematch = driver.getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(newBasematch, new File(basematchFilename));
return;
}

// Otherwise, obtain the picture similarity from Appium if we discover a basematch. Obtaining the resemblance
// We also enable visualisation so that, should something go wrong, we can see what went wrong.

SimilarityMatchingOptions opts = new SimilarityMatchingOptions();
opts.withEnabledVisualization();

SimilarityMatchingResult res = driver.getImagesSimilarity(basematchImg, driver.getScreenshotAs(OutputType.FILE), opts);


// If the similarity is not high enough, consider the check to have failed
if (res.getScore() < Breakpoint_for_Match) {

File failViz = new File(path_to_validate + "/FAIL_" + checkName + ".png");
res.storeVisualization(failViz);
throw new Exception(
String.format("Visual check of '%s' failed; similarity match was only %f, and below the breakPoint of %f. Visualization written to %s.",
checkName, res.getScore(), Breakpoint_for_Match , failViz.getAbsolutePath()));

}

// Otherwise, it passed!
System.out.println(String.format("Visual check of '%s' passed; similarity match was %f",
checkName, res.getScore()));
}

}

BaseTest.java

import io.appium.java_client.android.AndroidDriver;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import org.junit.After;
import org.junit.Before;
import org.openqa.selenium.remote.DesiredCapabilities;

public class BaseTest {

AndroidDriver driver;

protected DesiredCapabilities getCaps() throws Exception {
throw new Exception("Must use getCaps");
}

@Before

public void setUp() throws Exception {
URL server_url = new URL("http://localhost:4723/wd/hub");
driver = new AndroidDriver(server_url, getCaps());
}

@After
public void tearDown() {
if (driver != null) {
driver.quit();
}
}

Path getResource(String file_name) throws URISyntaxException {
URL ref_img_url = getClass().getClassLoader().getResource(file_name);
return Paths.get(ref_img_url .toURI()).toFile().toPath();
}

private String getResourceB64(String file_name) throws URISyntaxException, IOException {

Path ref_img_path = getResource(file_name);
return Base64.getEncoder().encodeToString(Files.readAllBytes(ref_img_path ));
}

String getReferenceImageB64(String file_name) throws URISyntaxException, IOException {
return getResourceB64("images/" + file_name);
}

}

Вы можете запустить этот скрипт в main-классе.

Анализ результатов

После первого запуска мы сохранили изображения как «базовые» — главной страницы приложения и экран после добавления товаров в корзину.

Result
  • Мы добавили в код пороговое значение для сравнения изображений.
  • Если фактическое значение getScoreпревышает пороговое значение, тест-кейс красный.
  • Если значение getScoreменьше порогового, то тест пройдет.
private final static double Breakpoint_for_Match= 0.99;

if (res.getScore() < Breakpoint_for_Match) {

File failViz = new File(path_to_validate + "/FAIL_" + checkName + ".png");

res.storeVisualization(failViz);

throw new Exception(

String.format("Visual check of '%s' failed; similarity match was only %f, and below the Breakpoint_for_Match of %f. Visualization written to %s.",

checkName, res.getScore(), Breakpoint_for_Match, failViz.getAbsolutePath()));}

// Otherwise, it passed!

System.out.println(String.format("Visual check of '%s' passed; similarity match was %f",

checkName, res.getScore()));

Команды сравнения изображений

При запуске этой команды с соответствующими байтовыми массивами изображений (в данном примере img1— это «базовая линия», а img2— снепшот, который мы хотим проанализировать) и объектом опций (он должен иметь версию SimilarityMatchingOptions), создается объект SimilarityMatchingResult.

  • Самая важная функция объекта resultgetScore, которая покажет, насколько разнятся два имеющихся снепшота, от 0 до 1.
  • Мы оцениваем, превышает ли этот балл ранее заданный порог, который выбирается в зависимости от особенностей приложения.
  • Если превышает порог, то считаем, что различие большое, и создаем исключение, или засчитываем дефект.
SimilarityMatchingResult res = driver.getImagesSimilarity(baselineImg, driver.getScreenshotAs(OutputType.FILE), opts);

Советы и лучшие практики

  • Инструмент должен обрабатывать сглаживание (anti-aliasing), сдвиг пикселей и т. д.
  • Процесс можно ускорить благодаря технике моментальных снимков DOM (DOM snapshotting) и расширенным функциям параллелизации, предназначенным для масштабированного запуска сложных тестовых наборов
  • Чаще обсуждайте снепшоты, информируйте остальных членов команды о продвижении процесса.
  • Существуют инструмент наподобие Percy, который умеют игнорировать ложные срабатывания при визуальном тестировании.
  • Автоматизация процессов должна учитывать изменения условий в проекте.
  • Не опирайтесь только на числа — пороговые значения и количество ошибок. Единственное, что важно — заметит ли реальный пользователь разницу и повлияет ли она на взаимодействие с продуктом.
  • Автоматизация должна уметь оценивать структуру страницы и сравнивать макеты.
  • Тестируйте весь интерфейс страницы, а не отдельные части. Это обеспечит полный охват. Вы рискуете пропустить нестандартные дефекты, если будете проверять только отдельные компоненты.
  • Работайте в фреймворке по возможности с качественными изображениями, чтобы корректно обрабатывать пороговые значения.

Источник


Большой гайд по Appium


Божественный канал — тестирование

Какой была ваша первая зарплата в QA и как вы искали первую работу?

Мега обсуждение в нашем телеграм-канале о поиске первой работы. Обмен опытом и мнения.

Подписаться
Уведомить о
guest

0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии

Мы в Telegram

Наш официальный канал
Полезные материалы и тесты
Готовимся к собеседованию
Project- и Product-менеджмент

? Популярное

? Telegram-обсуждения

Наши подписчики обсуждают, как искали первую работу в QA. Некоторые ищут ее прямо сейчас.
Наши подписчики рассказывают о том, как не бояться задавать тупые вопросы и чувствовать себя уверенно в новой команде.
Обсуждаем, куда лучше податься - в менеджмент или по технической ветке?
Говорим о конфликтных ситуациях в команде и о том, как их избежать
$1100*
медианная зарплата в QA в июне 2023

*по результатам опроса QA-инженеров в нашем телеграм-канале

Собеседование

19%*
IT-специалистов переехало или приняло решение о переезде из России по состоянию на конец марта 2022

*по результатам опроса в нашем телеграм-канале

live

Обсуждают сейчас