От Зефира в коробке к Structure Zephyr или как тест-менеджеру перекроить внут...
Appium для народа
1. Черемушкин Дмитрий
инженер
по автоматизации тестирования ПО
Scalable eCommerce Platform Solutions
Обо мнеОбо мне
6+ лет в тестировании ПО
ручное | автоматизированное
настольное | веб | мобильное
автоматизация рутинных
действий в QA-процессах,
интеграция инструментов
Черемушкин Дмитрий
1
2. 2
Постановка задачиПостановка задачи
Имеется: фреймворк,
основанный на стеке технологий:
+ автоматизированные тесты для eCommerce веб-сайта
~ 2000 тесткейсов • Firefox, Chrome, IE • локальный и удаленный запуск
3. 3
1 Запустить имеющиеся тесты
(в Android Browser и Mobile Safari)
Разработать новые тесты
(мобильная версия сайта + Android-приложение)
2
Необходимо:
на мобильных ОС Android и iOS
Постановка задачиПостановка задачи
4. 4
Этапы решения задачиЭтапы решения задачи
11
устранение
проблем
+
завершение
интеграции
в фреймворк
создание
proof of
concept
+
выявление
проблем
выбор
инструмента
тестирования
на мобильных
платформах
22 33
5. 5
Выбор инструментаВыбор инструмента
Требования:
open-source решение;
поддержка Java и WebDriver API;
поддержка ОС Android и iOS;
автоматизация приложений и браузеров;
работа на эмуляторах и физических устройствах;
активное развитие, наличие документации
10. 10
Appium: преимуществаAppium: преимущества
лёгкость внесения модификаций в серверную часть
работает без «агентов» в приложении
большой спектр поддерживаемых языков
распределённый запуск тестов (SeleniumGrid)
кросс-платформенность тестов
12. 12
Proof of concept: первый запускProof of concept: первый запуск
Вручную: вернуть исходное состояние системы
Вручную: посмотреть Appium-логи
report
stories
mvn clean test
Вручную: запустить Appium
Вручную: Узнать UDID (для iOS-устройств)
13. 13
Appium: общие проблемыAppium: общие проблемы
✗
в гибридных приложениях
нужно переключаться между native и webview частями
✗
между тест-кейсами
cookies браузера не очищаются
✗ Android: js-метод `click()` не работает
✗ iOS: не снимаются скриншоты
✗ нет отката к “чистому” состоянию
14. 14
Appium: проблемы интеграцииAppium: проблемы интеграции
✗ Maven не запускает Appium автоматически
✗ разные “логи” у Maven и Appium
✗ iOS–устройства: нужно указывать UDID при запуске
✗ Android: Appium не запускает GenyMotion–эмулятор
17. 17
Примеры решений:
гибридные приложения
public class ShopScreen extends CommonMobileScreen {
private ElementLocator txtScreenTitle = new ElementLocator
("Screen Title", "NATIVE_APP", By.xpath("//*[@id='title']");
public ShopScreen(WebDriverProvider driverProvider) {
super(driverProvider);
addElements(new MobileElement[]{txtScreenTitle, lnkShopCategory});
}
}
public class ShopScreen extends CommonMobileScreen {
private ElementLocator txtScreenTitle = new ElementLocator
("Screen Title", "NATIVE_APP", By.xpath("//*[@id='title']");
public ShopScreen(WebDriverProvider driverProvider) {
super(driverProvider);
addElements(new MobileElement[]{txtScreenTitle, lnkShopCategory});
}
}
private MobileElement txtScreenTitle =
new MobileElement ("Screen Title | xpath=//*[@id='title'] | NATIVE_APP");
private MobileElement lnkShopCategory =
new MobileElement ("Category link | xpath=//div[text()='%s'] | WEBVIEW");
Native и Webview элементы – в разных фреймах✗
18. 18
Примеры решений:Примеры решений:
гибридные приложениягибридные приложения
public class CommonMobileMethods extends WebDriverPage {
private List<ElementLocator> elements = new ArrayList<ElementLocator>();
public ElementLocator getElementLocatorByName(String name) {
for (MobileElement element : getElements()) {
if (element.getName().equals(name)) {
this.switchTo().window(element.getType());
return element;
}
}
fail("[ERROR] Element '" + name + "' is not defined on '"
+ MobileScreens.getCurrentScreen() + '" screen.");
return;
}
}
public class CommonMobileMethods extends WebDriverPage {
private List<ElementLocator> elements = new ArrayList<ElementLocator>();
public ElementLocator getElementLocatorByName(String name) {
for (MobileElement element : getElements()) {
if (element.getName().equals(name)) {
this.switchTo().window(element.getType());
return element;
}
}
fail("[ERROR] Element '" + name + "' is not defined on '"
+ MobileScreens.getCurrentScreen() + '" screen.");
return;
}
}
this.switchTo().window(element.getType());
public MobileElement getElementLocatorByName(String name) {
19. 19
Примеры решений:Примеры решений:
замена javascript `click()` на Androidзамена javascript `click()` на Android
public class CommonMethods extends WebDriverPage {
public void jsClickElementByLoc(By loc) {
if (isLocatorPresentOnPage(loc)) {
WebElement element = findElement(loc);
if (isBrowser("android")) {
new TouchActions(getdriver()).singleTap(element).perform();
} else {
JavascriptExecutor js = (JavascriptExecutor) getDriver();
js.executeScript("arguments[0].click()", element);
}
}
}
public class CommonMethods extends WebDriverPage {
public void jsClickElementByLoc(By loc) {
if (isLocatorPresentOnPage(loc)) {
WebElement element = findElement(loc);
if (isBrowser("android")) {
new TouchActions(getdriver()).singleTap(element).perform();
} else {
JavascriptExecutor js = (JavascriptExecutor) getDriver();
js.executeScript("arguments[0].click()", element);
}
}
}
if (isBrowser("android")) {
new TouchActions(getdriver()).singleTap(element).perform();
Android: JavaScript-клик не работает✗
20. 20
Примеры решений:Примеры решений:
снимки экрана на iOSснимки экрана на iOS
static class ScreenshootingRemoteWebDriver extends RemoteWebDriver
implements TakesScreenshot {
private static final String IOS_SCREENSHOT_CMD = "mobile :getScreenshot”;
public <X> X getScreenshotAs(OutputType<X> target)
throws WebDriverException {
String base64 = "";
if (isBrowser("ios")) {
base64 = execute(IOS_SCREENSHOT_CMD).getValue().toString();
} else {
base64 = execute(DriverCommand.SCREENSHOT).getValue().toString();
}
return target.convertFromBase64Png(base64);
}
static class ScreenshootingRemoteWebDriver extends RemoteWebDriver
implements TakesScreenshot {
private static final String IOS_SCREENSHOT_CMD = "mobile :getScreenshot”;
public <X> X getScreenshotAs(OutputType<X> target)
throws WebDriverException {
String base64 = "";
if (isBrowser("ios")) {
base64 = execute(IOS_SCREENSHOT_CMD).getValue().toString();
} else {
base64 = execute(DriverCommand.SCREENSHOT).getValue().toString();
}
return target.convertFromBase64Png(base64);
}
if (isBrowser("ios")) {
base64 = execute(IOS_SCREENSHOT_CMD).getValue().toString();
} else {
base64 = execute(DriverCommand.SCREENSHOT).getValue().toString();
}
private static final String IOS_SCREENSHOT_CMD = "mobile :getScreenshot”;
iOS: стандартная функция не снимает скриншоты✗
21. 21
Примеры решений:Примеры решений:
удаление приложенийудаление приложений
public class BaseStoriesRunner extends JUnitStories {
protected static final String PROPERTY_MOBILE = "mobile”;
@Test
public void run() throws Throwable {
try {
beforeRun();
getEmbedder().runStoriesAsPaths(storyPath);
} finally {
afterRun();
}
}
protected void afterRun() {
if (StringUtils.isNotBlank(System.getProperty(PROPERTY_MOBILE))) {
uninstallMobileApps();
}
}
}
public class BaseStoriesRunner extends JUnitStories {
protected static final String PROPERTY_MOBILE = "mobile”;
@Test
public void run() throws Throwable {
try {
beforeRun();
getEmbedder().runStoriesAsPaths(storyPath);
} finally {
afterRun();
}
}
protected void afterRun() {
if (StringUtils.isNotBlank(System.getProperty(PROPERTY_MOBILE))) {
uninstallMobileApps();
}
}
}
protected void afterRun() {
if (StringUtils.isNotBlank(System.getProperty(PROPERTY_MOBILE))) {
uninstallMobileApps();
}
}
afterRun();
Нет отката к исходному состоянию✗
22. 22
Примеры решений:Примеры решений:
удаление приложенийудаление приложений
public class MobileUtils {
public static void uninstallMobileApps() {
executeShCommand(UNINSTALL_APPS_COMMAND);
}
private static void RunCommand(String command) {
try {
String line;
Process p = new ProcessBuilder(command).start();
BufferedReader input = new BufferedReader(
new InputStreamReader(p.getInputStream()));
while ((line = input.readLine()) != null) {
System.out.println(line);
}
input.close();
} catch (Exception err) { err.printStackTrace(); }
}
}
public class MobileUtils {
public static void uninstallMobileApps() {
executeShCommand(UNINSTALL_APPS_COMMAND);
}
private static void RunCommand(String command) {
try {
String line;
Process p = new ProcessBuilder(command).start();
BufferedReader input = new BufferedReader(
new InputStreamReader(p.getInputStream()));
while ((line = input.readLine()) != null) {
System.out.println(line);
}
input.close();
} catch (Exception err) { err.printStackTrace(); }
}
}
public static void uninstallMobileApps() {
if isBrowser("ios") { RunCommand(IOS_UNINSTALL_APPS); }
if isBrowser("android") { RunCommand(IOS_UNINSTALL_APPS_COMMAND); }
}
Process p = new ProcessBuilder(command).start();
28. 28
ЗаключениеЗаключение
запуск существующих веб-тестов
в мобильных браузерах
разработка новых кросс-платформенных тестов
для мобильных сайтов и приложений
11
использование принятых на проекте практик
в мобильном тестировании
22
33
Возможности полученного решения:
29. 29
Перспективы развитияПерспективы развития
расширение корпоративной инфраструктуры
SeleniumGrid нодами для мобильного тестирования
реализация решения в виде Maven-плагина
для интеграции в другие проекты
добавление возможности мобильного тестирования
на ОС Windows Phone и Windows 8
увеличение количества мобильных браузеров,
поддерживаемых фреймворком