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

в 9:46, , рубрики: appium, diy или сделай сам, framework, java, java 8, selenium, webdriver, велосипединг, Тестирование IT-систем

Привет!

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

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

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

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

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

1. Основные фичи (краткий обзор)

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

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

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

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

Итак.

Кроссбраузерность

От велосипеда к…
Я думаю этот пункт подразумевается как нечто само собой разумеющееся. Но упомянуть надо.

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

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

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

Это возможно за счет интенсивного использования возможностей 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<Handle>{

    @FindBy(className = "someClass1")
    @AndroidFindBy(id = "bbc.mobile.news.ww:id/articleWrapper")
    private List<RemoteWebElement> 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<Handle> {

    @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<WebElement> 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<WebElement> 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'ах — автоматизированы. Так что можно полностью сосредоточиться на описании бизнес-логики!

Архитектура.

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

В современных условиях, мое имхо, решает не монолитная архитектура, а модульная или «прозрачная». Я постарался реализовать все так, чтобы можно было использовать как стандартные решения 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(c); 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(c); 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. Как развивалось (лирическое отступление).

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

Большую часть своей профессиональной биографии я занимался автоматизацией тестирования десктопного софта, главным образом с помощью 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# проект. Но! Все это имеет смысл, если базовая функциональность нужна. Всегда рад пулл-реквестам!

До встречи!

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

Автор: Sergey_Tikhomirov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js