От велосипеда к…

Привет! Этот небольшой очерк адресован QA — специалистам и в большей степени разработчикам, которые привлечены к автоматизации тестирования вэб и мобильных приложений. Те, кто просто интересуется open source' ом — тоже welcome.

Здесь я хочу развить мысли, высказанные год назад в статье «Про Selenium и один «велосипед».

План:1. Основные фичи (краткий обзор)2. Как развивалось (лирическое отступление)3. Заключение.

Вы можете сразу ознакомиться с решением. Но если интересно сначала прочитать статью — поехали!

e510d710e158495d96ed3cbcb18ebd7d.jpg

1. Основные фичи (краткий обзор)2940f52f601843ca909217fc6bcfe495.pngКак вы уже поняли, решение написано на Java 8. Основными компонентами являются Selenium (для обеспечения взаимодействия с десктопными браузерами), Appium (java-client, для обеспечения взаимодействия с мобильными браузерами и приложениями).

Если говорить о принципе и способе работы с Selenium Webdriver, то он в чем-то похож на то, что предлагают такие решения как Html elements от Яндекс и Thucydides. Но все это несколько пересмотрено.

То, что получилось, у меня вызывает ассоциации с каком-то членистоногим существом. Учитывая, что Selenium и Appium находят свое применение в автоматизации тестирования + мы все знаем перевод слова bug, то получилось такое название для решения — Arachnidium (лат. «паукообразный»).

Итак.

Кроссбраузерность c01a3ec1d321475ca1cedfc06379656f.jpgЯ думаю этот пункт подразумевается как нечто само собой разумеющееся. Но упомянуть надо.Поддерживаются: — Firefox; — Chrome; — Internet Explorer; — Safari; — PhantomJS.— Удаленный запуск перечисленных выше браузеров.От поддержки HtmlUnitDriver и OperaDriver пришлось отказаться. Первый не является наследником RemoteWebDriver и его поддержка чревата костылями, второй устарел (например, он не запускает Opera на Windows 8/8.1). Но есть актуальная замена: — Chrome для Android. Чуть позже хочу добавить поддержку родного браузера Android и Chromium— Mobile Safari для iOS

Поддержка автоматизации взаимодействия с UI нативных и гибридных мобильных приложений. c2763283486e438ca8b72ede05ce7514.jpgЭто возможно за счет интенсивного использования возможностей Appium.

Но, даже не это, на мой взгляд, самое интересное.

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

Для своих тестов на Android я использую приложение BBC News. Его UI очень похож на UI сайта по визуальному составу элементов. Предположим, что нужно протестировать как сайт, так и приложение для Android. Тогда можно описать пользовательский интерфейс так.

Список и просмотр новостей:

Код под катом /** * Imagine that we have to check browser and Android versions * How?! See below. */ @IfBrowserURL (regExp = «http://www.bbc.com/news/») @IfMobileContext (regExp = «NATIVE_APP») @IfMobileAndroidActivity (regExp = «HomeWwActivity») public class BBCMain extends FunctionalPart{

@FindBy (className = «someClass1») @AndroidFindBy (id = «bbc.mobile.news.ww: id/articleWrapper») private List articles;

@FindBy (className = «someClass2») @AndroidFindBy (id = «bbc.mobile.news.ww: id/articleWebView») private RemoteWebElement currentArticle;

@FindBy (className = «someClass3») @AndroidFindBy (id = «bbc.mobile.news.ww: id/optMenuShareAction») private RemoteWebElement share;

@FindBy (className = «someClass4») @AndroidFindBy (id = «bbc.mobile.news.ww: id/optMenuWatchListenAction») private RemoteWebElement play;

@FindBy (className = «someClass5») @AndroidFindBy (id = «bbc.mobile.news.ww: id/optMenuEditAction») private RemoteWebElement edit;

@FindBy (className = «someClass6») @AndroidFindBy (uiAutomator = «new UiSelector ().resourceId» + »(\«bbc.mobile.news.ww: id/optMenuRefreshAction\»)») private RemoteWebElement refresh;

protected BBCMain (Handle context) { super (context); load (); }

@InteractiveMethod public int getArticleCount (){ return articles.size (); }

//some more staff //… }

Формочка выбора новостей по категориями:

Код под катом /** * Imagine that we have to check browser and Android versions * How?! See below. */ @IfBrowserURL (regExp = «http://www.bbc.com/news/») @IfMobileContext (regExp = «NATIVE_APP») @IfMobileAndroidActivity (regExp = «PersonalisationActivity») public class TopicList extends FunctionalPart {

@CacheLookup @FindBys ({@FindBy (linkText = «someLink»), @FindBy (linkText = «someLink2»), @FindBy (linkText = «someLink2»)}) @AndroidFindBys ({@AndroidFindBy (id = «bbc.mobile.news.ww: id/personalisationListView»), @AndroidFindBy (className = «android.widget.LinearLayout»), @AndroidFindBy (uiAutomator = «new UiSelector ()»+ ».resourceId (\«bbc.mobile.news.ww: id/feedTitle\»)»)}) private List titles;

@CacheLookup @FindBys ({@FindBy (linkText = «someLink3»), @FindBy (linkText = «someLink4»), @FindBy (linkText = «someLink5»)}) @AndroidFindBys ({@AndroidFindBy (id = «bbc.mobile.news.ww: id/personalisationListView»), @AndroidFindBy (className = «android.widget.LinearLayout»), @AndroidFindBy (uiAutomator = «new UiSelector ().»+ «className (\«android.widget.CheckBox\»)»)}) private List checkBoxes;

@AndroidFindBy (id = «bbc.mobile.news.ww: id/personlisationOkButton») private WebElement okButton;

protected TopicList (Handle context) { super (context); load (); }

//some more staff //… } Тест (самый упрощенный вид):

Android

Код под катом @Test public void androidNativeAppTest () { Configuration config = Configuration .get («android_bbc.json»); Application bbc = MobileFactory.getApplication ( Application.class, config); try { BBCMain bbcMain = bbc.getPart (BBCMain.class); Assert.assertNotSame (0, bbcMain.getArticleCount ()); bbcMain.selectArticle (1); Assert.assertEquals (true, bbcMain.isArticleHere ());

bbcMain.edit ();

TopicList topicList = bbcMain.getPart (TopicList.class); topicList.setTopicChecked («LATIN AMERICA», true); topicList.setTopicChecked («UK», true); topicList.ok ();

bbcMain.edit (); topicList.setTopicChecked («LATIN AMERICA», false); topicList.setTopicChecked («UK», false); topicList.ok (); } finally { bbc.quit (); } } Браузер (десктопный/мобильный)

Код под катом @Test public void webTest () { Configuration config = Configuration .get («android_some_browser.json»); Application bbc = WebFactory.getApplication ( Application.class, config, urlToBBCNews); //does the same О некоторых вещах я расскажу чуть позже.

Я постарался реализовать универсальную (скорее — условно универсальную) модель, чтобы разработчик фрэймворка для автотестов (а я вижу себя в этой роли, для себя я плохо не сделаю :)) не множил код, а смог сделать его независимым от окружения (я понимаю под этим то, как тест выполняется — в браузере или это нативный контент/html контент гибридного приложения).

Используется дизайн — паттерн Page Object. Мой вариант предполагает, что страницы/скрины могут быть описаны как целиком, так и по частям, если есть повторяющиеся виджеты или наборы элементов. Можно даже заставить целое приложение вести себя как Page Object!

Еще одной особенностью является то, что многие технические нюансы, связанные с необходимостью управления экземпляром WebDriver’а в тех ситуациях, когда одновременно присутствует несколько окон браузера (или контекстов, если мобильное приложение) и часть контента размещена в ifram’ах — автоматизированы. Так что можно полностью сосредоточиться на описании бизнес-логики!

Архитектура. d28c0497850b47e19a1a31dee4c6ce09.JPGВ современных условиях, мое имхо, решает не монолитная архитектура, а модульная или «прозрачная». Я постарался реализовать все так, чтобы можно было использовать как стандартные решения Selenium и Appium (этот способ декорирования элементов в моем решении используется по умолчанию), для которых я постарался предусмотреть удобные способы работы, так и, теоретически — решения сторонних разработчиков.

Здесь примеры:

— совместной работы с HtmlElements от Яндекса. Ссылка.— совместной работы с Selenide от Сodeborne. Ссылка.— использования Thucydides. Ссылка. Кому интересно — отчет для web (GoogleDrive) и отчет для Android (BBC News, виртуальная машина Genymotion, эмулирующая Android-планшет). Приятного просмотра и не забудьте распаковать. Хочу позже сделать похожий сэмпл для Allure.

Способ настройки В данном случае я подразумеваю передачу и хранение параметров для запуска браузеров и мобильных приложений (так задумывалось с самого начала). Подробно описано здесь. Но как уж и быть. Приведу пример.Пусть есть общая настройка, хранящаяся в файле settings.json, приложенном к проекту.

JSON с дефолтными параметрами { «settingA»: { «aValue»:{ «type»: «STRING», «value»: «AAA» } }, «settingB»: { «bValue»:{ «type»: «STRING», «value»: «bbb» } }, «settingC»: { «cValue»:{ «type»: «STRING», «value»: «C» } }, «settingD»: { «dValue»:{ «type»: «STRING», «value»: «D…» } } } И есть файл, названный по другому, содержащий такие данные, которые как бы перекрывают данные из примера выше.

JSON с кастомными параметрами { «settingB»: { «bValue»:{ «type»: «INT», «value»:»1» } }, «settingC»: { «cValue»:{ «type»: «BOOL», «value»: «true» } } } Код ниже

код import com.github.arachnidium.util.configuration.Configuration; import org.junit.Before; import org.junit.Test;

public class DemoTest { Configuration testConfig; private String aGroup = «settingA»; private String bGroup = «settingB»; private String cGroup = «settingC»; private String dGroup = «settingD»;

private String aValue = «aValue»; private String bValue = «bValue»; private String cValue = «cValue»; private String dValue = «dValue»;

@Before public void setUp () throws Exception { testConfig = Configuration.get («src/test/resources/test.json»); }

@Test public void test () {

Object a = Configuration.byDefault.getSettingValue (aGroup, aValue); Object b = Configuration.byDefault.getSettingValue (bGroup, bValue); Object c = Configuration.byDefault.getSettingValue (cGroup, cValue); Object d = Configuration.byDefault.getSettingValue (dGroup, dValue);

System.out.println (a); System.out.println (a.getClass ()); System.out.println (b); System.out.println (b.getClass ()); System.out.println©; System.out.println (c.getClass ()); System.out.println (d); System.out.println (d.getClass ());

System.out.println (); System.out.println (); System.out.println («Showtime! Customized setting see below.»); System.out.println (); System.out.println ();

a = testConfig.getSettingValue (aGroup, aValue); b = testConfig.getSettingValue (bGroup, bValue); c = testConfig.getSettingValue (cGroup, cValue); d = testConfig.getSettingValue (dGroup, dValue);

System.out.println (a); System.out.println (a.getClass ()); System.out.println (b); System.out.println (b.getClass ()); System.out.println©; System.out.println (c.getClass ()); System.out.println (d); System.out.println (d.getClass ()); } } дает такой вывод на консоль

то, что вывела консоль AAA class java.lang.String bbb class java.lang.String C class java.lang.String D… class java.lang.String

Showtime! Customized setting see below.

AAA class java.lang.String 1 class java.lang.Integer true class java.lang.Boolean D… class java.lang.String Таким образом, предполагается наличие общей настройки, в которой указаны дефолтные данные, и набора таких конфигураций, которые частично перекрывают или уточняют эти данные. При этом если данные не указаны в таком кастоме — будет использована общая информация.

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

На этом я заканчиваю свой обзор. Здесь есть и другие интересные вещи. Может быть, о них я расскажу в другой статье или в комментариях к этой.

2. Как развивалось (лирическое отступление). 3c91603b5eba45c5b300f9ec631be5fe.jpgБольшую часть своей профессиональной биографии я занимался автоматизацией тестирования десктопного софта, главным образом с помощью Test Complete. Тут было много всего интересного и нетривиального. Но я устал. Потянуло на какое-то творчество.

Позже я узнал про Selenium Webdriver (а кто сейчас не знает?). Сначала были просто эксперименты. Потом стали появляться идеи. Хотя… Я их брал из накопленной практики. Например, такое понятие как Page Object и примеры реализации не вызвали у меня Wow — эффекта. Нечто подобное приходилось делать для десктопных приложений.

Описанный в главе выше эксперимент был прекращен и возобновлен спустя несколько месяцев.

Далее я узнал про Appium. Мне даже довелось поучаствовать в этом проекте! Участие началось спонтанно — с репортинга багов и того, что мне казалось проблемным. Позже появилась клиентская библиотека для java: java-client. Тут вклад серьезнее. Мне удалось реализовать фичи для работы с PageFactory и помочь с редизайном библиотеки, в результате которого появились AndroidDriver и IOSDriver и появилась гибкость, если понадобится добавить поддержку Firefox OS и Windows Mobile. Надеюсь, это помогло многим другим людям по всему миру.

3. Заключение. Буду рад общению в комментариях.Этот эксперимент я довел до такого состояния, что не стыдно сказать, что билды доступны в maven central. Пока я предложил бы с ними поиграть или попробовать автоматизировать какой-нибудь несложный тест-кейс.

Если кто-то найдет баг — это круто! Прошу описать его здесь. А в комментариях было бы здорово почитать критику, идеи, если кто-то похвалит — это тоже хорошо.

На описанном останавливаться не хочется. Есть идея продолжить банкет — реализовать плагины для JUnit, TestNG и Jbehave. Можно сделать плагин, например, для Eclipse IDE, но я с трудом представляю пока его функции. Кроме того — существует пока еще пустой C# проект. Но! Все это имеет смысл, если базовая функциональность нужна. Всегда рад пулл-реквестам!

До встречи!

c8fc015d8c104b62bcc94c45e664b023.jpg

© Habrahabr.ru