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

    Привет!

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

    До встречи!

    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 11
    • 0
      Я б покатался =)
      • 0
        Автор статьи сделал очередное «гусям ярмо» или что?

        Не из заголовка, не из вступления, не из заключения не понятно что вообще предлагается. Что за продукт или сервис?
        • 0
          Вообще, задумывал рассказать о некоем решении, которое попробовал сделать в качестве эксперимента, который меня увлек. Это набор библиотек Java 8, которые должны упрощать работу с Selenium и Appium. Постольку поскольку я это задумал как сиквел — получилось так. Но не поздно — могу исправить :)

          В первой статье (она во вступлении) — идеи (местами довольно сырые)
          Здесь — результат
        • +1
          Какая то сложная статья, или я ничего не понял. Куда что применять. Чем переключение браузера тут лучше чем в предыдущей статье?
          • +1
            На самом деле есть серьезный нюанс.

            Раньше я пытался привязать Page Object к окну. Теперь можно принять в учет ссылкам (не к одной ссылке как у thucydides, наприер), если страницу или какой-то виджет можно найти только по этим ссылкам. Их можно задавать рег. выражениями. Так же можно принять в учет индекса окна и заголовок для усиления.

            Аналогично для мобильных приложений. Имя контекста, индекс (первый второй третий, т.к. может быть несколько WebView), активити для Android (можно и целый набор в виде регекспов).

            Перечисленные выше моменты можно совместить и получить один универсальный page/screen object, если на данном конкретном приложении это реально.
            • +1
              Все это можно передавать в виде параметров в определенные методы, а можно так аннонтировать классы-пейдж обжекты.

              Эти правила будут использованы при автопереключении.
          • +2
            Сергей, не в обиду, но я читал вашу статью год назад. И через год… ваш стиль написания статьи полон сумбура.
            Вы пытаетесь охватить всё на свете в одной теме. Становится непонятно, для кого эта статья написана.

            Если вы хотите, чтобы начинающие автоматизаторы попробовали ваш фреймворк, то я рекомендую начать с более простой обзорной статьи и видео демонстрации работы, развивая более глубокие темы в следующих.
            • +1
              Да я и не обиделся. Спасибо за замечание. Я рад.

              Ок. Тогда. возможно что скоро я так и сделаю. Дело в том, что я хочу сделать публикации (заметьте, я уже принял ваше замечание) в каком-нибудь англоязычном комьюнити. А потом здесь будут теже статьи идти как перевод (если там хорошо примут) :)

              Да, над стилем надо работать + найти больше времени. И вы правы, даже если это и хобби, объем радоты на одного человека немалый. Подсознательно пытаешься ничего не упустить

              • +1
                Перевести ваши статьи я могу, если нужна помощь в этом.
            • +2
              После комментариев Dmitry_Zhariy и armid я подумал и решил добавить опрос.

              Т.к. если это интересно, но сумбурно описано, то надо будет немного постараться. Но нужно, чтобы был в этом смысл.
              Буду считать, что прошлогодняя статья была о набросоке, эта — я объявил о результате и она вводная перед циклом небольших тъюториалов.
              • +2
                Очень рекомендую записать скринкаст с демонстрациией возможностей фреймворка.
                Для начала, простой «Getting Started… „

                Только не в виде “вебинара», которые очень скучные и длинные, а в стиле Pluralsite или Lynda.

                Я заметил, что запись моего «кодирования» можно ускорить в 2-4 раза, при этом, видео не потеряет в качестве. А всякие длинные установки, билды и скачивания можно и вовсе вырезать.

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

                Удачи вам, Сергей, с проектом и последующим маркетингом :D

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.