Интеграционное тестирование web-приложения с Selenium WebDriver

    Интеграционное тестирование (в отличие от Unit- или модульного тестирования) это тестирование не отдельных атомарных компонентов системы (классов) а результата их взаимодействия между собой в какой-либо среде.

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

    О том, как можно Unit-тестировать JavaScript я писал ранее, сейчас же расскажу о процессе интеграционного тестирования, применяемого в команде.

    Selenium


    С давних времен известен инструмент тестирования веб-приложений/страниц в браузере — Selenium. В плане его применения есть два основных пути, а именно:
    1. написание TestSuite в SeleniumIDE и прогон их через SeleniumTestRunner, или
    2. использование WebDriver

    WebDriver это новая «фишка» Selenium, появившаяся во второй ветке продукта. Основная его суть — можно гонять тесты, описанные в коде (C#, Java, Python, Ruby), в разных браузерах и/или в виртуальной среде исполнения.

    WebDriver


    Selenium WebDriver это набор «биндингов» к разным языкам (C#, Java), позволяющий отдавать различные команды «подчиненному» браузеру.

    Для каждого браузера имеется своя реализация WebDriver (FireFoxDriver, InternetExplorerDriver, ChromeDriver — сейчас включены в поставку, OperaSoftware разработали OperaDriver). Существует также «виртуальный» HtmlUnitDriver. В отличии от «браузерных» реализаций он не требует установленного браузера и за счет этого работает быстрее и платформонезависим, но есть и минусы — HtmlUnitDriver имеет «свою» реализацию JavaScript и потому поведение «богатых» веб-приложений может в нем отличаться. Для своих задач мы используем «браузерные» реализации, это позволяет проверить приложение именно в той среде, в которой оно будет исполняться впоследствии.

    Кратко общая суть работы с WebDriver может быть описана так:
    • реализуется код, использующий какую-либо имплементацию WebDriver. Данный код выполняет какие-либо действия с веб-страницей и сравнивает результат с эталонным
    • WebDriver транслирует команды в запущенный браузер (при использовании «браузерной» реализации) и сообщает результаты «обратно в код»

    Что умеет WebDriver


    Ниже рассматривается «браузерная» реализация, суть расширение класса RemoteWebDriver (реализует интерфейс WebDriver).
    • поиск элементов: findElement(s)By*
      • CssSelector
      • ClassName
      • Id
      • LinkText
      • TagName
      • XPath
    • загрузка страницы, получение контента страницы
    • исполнение произвольного JavaScript
    • осуществление операций Drag-n-Drop

    C «найденными» элементами (интерфейс WebElement)
    • получение текста (text)
    • получение значения (value)
    • click по элементу
    • ввод с клавиатуры (клавиша, сочетание клавиш, последовательность клавиш/сочетаний)

    Среда исполнения тестов


    В качестве языка для написания тестов была выбрана Java. Среда для исполнения — JUnit4.

    DISCLAIMER: Не претендую на звание крутого джависта, посему если старшие коллеги найдут огрехи и всякие прочие «антипаттерны» — с удовольствием выслушаю в комментариях.

    Базовый абстрактный класс веб-тестов.
    @Ignore
    abstract public class AbstractWebTest {
    
        protected static RemoteWebDriver _driver;
        // расположение тестовой страницы
        private String testPageLocation =
                    String.format(
                            "http://%s:%s/test.html",
                            System.getProperty("test.httproot"),         // Web-сервер ...
                            System.getProperty("test.httpport", "80")   // и порт
                    );
        // Используемая имплементация WebDriver
        private static String driverName =
                    System.getProperty(
                            "test.driver",
                            "org.openqa.selenium.firefox.FirefoxDriver");
    
        /**
         * Перед каждым набором тестов - создаем инстанс драйвера.
         * Это автоматически запустит браузер
         */
        @BeforeClass
        public static void setUpDriver()
                    throws ClassNotFoundException,
                              IllegalAccessException,
                              InstantiationException {
            _driver = (RemoteWebDriver) Class.forName(driverName).newInstance();
        }
    
        /**
         * Перед каждым тестом - открываем тестовую страницу
         */
        @Before
        public void setUp() {
            _driver.get(testPageLocation);
        }
    
        /**
         * После каждого набора тестов - закрываем инстанс дарйвера (закрываем браузер)
         */
        @AfterClass
        public static void tearDown() {
            _driver.close();
        }
    }

    Конкретный класс с набором тестов (для простоты убраны некоторые проверки, например на то, что элемент по CSS-селектору действительно найден и доступен на странице)
    public class TestMoneyField extends AbstractWebTest {
    
        /**
         * При рендеринге поле ввода денежной суммы должно показать 0.00
         */
        @Test
        public void testRendering() {
            WebElement content =
               _driver.findElementByCssSelector("#FieldMoney .input-text-field");
            Assert.assertEquals("0.00", content.getValue());
        }
    
        /**
         * Проверим форматирование "триад"
         */
        @Test
        public void testInputWithoutDot() {
    
            WebElement content =
               _driver.findElementByCssSelector("#FieldMoney .input-text-field");
            content.sendKeys("999999");
            Assert.assertEquals("999 999.00", content.getValue());
        }
    }

    Все тесты запускаются с помощью отдельного таска Ant-билда:
    <target name="integrationtest" depends="init, buildtests, deploytests">
       <junit haltonfailure="false">
          <sysproperty key="test.driver" value="org.openqa.selenium.firefox.FirefoxDriver" />
          <classpath>
             <pathelement location="${path.to.tests.jar}"/>
          </classpath>
          <batchtest>
             <fileset dir="${path.to.compiled.test.classes}">
                <include name="**/tests/Test*.class" />
             </fileset>
          </batchtest>
       </junit>
    
       <junit haltonfailure="false">
          <sysproperty key="test.driver" value="org.openqa.selenium.ie.InternetExplorerDriver" />
          <classpath>
             <pathelement location="${path.to.tests.jar}"/>
          </classpath>
          <batchtest>
             <fileset dir="${path.to.compiled.test.classes}">
                <include name="**/tests/Test*.class" />
             </fileset>
          </batchtest>
       </junit>
    </target>

    Данный таск прогонит все известные тесты из классов, чьи имена начинаются с Test под браузерами Firefox и InternetExplorer. В зависимостях таски с базовой инициализацией, компиляцией и выгрузкой скомпилированных тестов на тестовую площадку.

    Фишки-плюшки


    Некоторые «браузерные» реализации (Firefox, Opera, Chrome) поддерживают снятие скриншотов. Это может быть полезно дабы зафиксировать визуальное состояние, в котором пребывала тестовая страница в момент, когда тест не прошел. Для этого подойдет функционал JUnit4 — TestWatchman.
    @Ignore
    abstract public class AbstractWebTest {
    
        // Папка для скриншотов
        private String screenshotDir =
                System.getProperty("test.screenshotDir", "");
    
        @Rule
        public MethodRule watchman = new TestWatchman() {
    
            /**
             * Будет вызван при каждом "проваленном" тесте
             * @param e Брошенное тестом исключение
             * @param method Тест-метод
             */
            @Override
            public void failed(Throwable e, FrameworkMethod method) {
                if(_driver instanceof TakesScreenshot && !screenshotDir.equals("")) {
                    String browserName = _driver.getClass().getName();
                    String testSuiteName = method.getMethod().getDeclaringClass().getName();
                    browserName =
                            browserName.substring(browserName.lastIndexOf('.') + 1);
                    testSuiteName =
                            testSuiteName.substring(testSuiteName.lastIndexOf('.') + 1);
    
                    byte[] screenshot =
                            ((TakesScreenshot)_driver).getScreenshotAs(OutputType.BYTES);
                    try {
                        FileOutputStream stream =
                                new FileOutputStream(
                                        new File(
                                            String.format("%s/screenshot_%s_%s_%s.png",
                                                    screenshotDir,
                                                    browserName,
                                                    testSuiteName,
                                                    method.getName())));
                        stream.write(screenshot);
                        stream.close();
                    } catch (IOException e1) {
                        e1.printStackTrace(System.out);
                    }
                }
            }
        };
        // все остальное...
    
    }

    Добавим переменную с путем к папке со скриншотами в Ant-билд
       <junit haltonfailure="false">
          <sysproperty key="test.driver" value="org.openqa.selenium.firefox.FirefoxDriver" />
          <sysproperty key="test.screenshotDir" value="${screenshotsDir}" />
          <classpath>
             <pathelement location="${path.to.tests.jar}"/>
          </classpath>
          <batchtest>
             <fileset dir="${path.to.compiled.test.classes}">
                <include name="**/tests/Test*.class" />
             </fileset>
          </batchtest>
       </junit>

    Интеграция


    В текущей реализации Ant-билд гоняется через Jetbrains TeamCity. Запуск билда настроен на сброс кода в SVN. Интеграционные тесты — часть общей процедуры тестирования. При провале любого из интеграционных тестов снимается скриншот и публикуется как «артефакт» билда — можно видеть не только какие тесты «отъехали» после сброса в транк какого-либо функционала, но и увидеть «как» они «отъехали».

    В настоящее время используется тестирование под IE и Firefox, Chrome не подключен по причине некоторых трудностей с интеграцией (судя по всему в ChromeDriver присутствуют некоторые ошибки, не позволяющие нормально искать элементы на странице в некоторых случаях — по состоянию на 2.0b1, сейчас доступна 2.0b2 но работу с ней пока не пробовали)
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 22
    • 0
      Спасибо за замечательную статью! Буквально 2 недели назад у себя в конторе рассказывал о Selenium как средстве Unit тестирования нашего UI, и о том что его достаточно просто полноценно цеплять к TeamCity (в отличии от Sahi, который мы использовали). Но при более глубоком знакомстве возникли вопросы по поводу тестирования AJAX, и RIA web приложений, написанных на ExtJS. У вас случаем нет паттернов или хороших практик по тестированию сложных Web приложений с Ajax (чтоб без delay и sleep через строчку)? Было бы очень интересно узнать ваш опыт, как вы обходите подводные камни, думаю для многих он окажется ценным (особенно для меня).

      Ещё раз спасибо за статью!
      • +1
        Некоторые (но не все) проблемы «delay и sleep через строчку» мы решили выключением анимации в jQuery (в базе у нас именно он). Соответственно некоторые вещи типа плавного отображения окна и т.п. стали происходить «сразу». Благо анимация в jQ отключается одной командой и не влияет на общую работоспособность.

        AJAX запросов и общей готовности страницы мы по прежнему дожидаемся. Я сделал некоторую обвязку над WebDriver чтобы можно было делать не

        WebElement element = _driver.getElementById(...);
        

        а

        WebElement element = Wait.forElementPresence("ById", "...");
        

        Примерно так, общая суть ясна.
        • 0
          Я делал то же самое (waitForElement). Но буквально две-три недели назад на Хабре проскочила статья, как заставить Селениум ждать исполнения всех AJAX запросов, что в большинстве случаев делает ожидание элементов ненужным, по крайней мере, в моем продукте.
          • 0
            А у Вас случайно ссылки не осталось на эту статью??? Сейчас как раз есть проблема ожидания пока все загрузится, приложение Rich Ajax и мне было бы очень полезно получить любую полезную информацию по данному вопрос.
            • 0
              К сожалению, нет, ссылки не осталось. Если я правильно помню (а было это давно), то было предложено решение типа вот такого: sysmagazine.com/posts/111649

              Если продраться через сильно неродной для автора английский и/или смотреть только на код, то становится понятно, куда копать. Надеюсь, вам поможет.
      • +1
        А в чем отличие от Selenium RC?
        • +1
          WebDriver = Selenium 2
          ;)
          • 0
            Насколько я понимаю в SeleniumRC нужен отдельный сервер (как минимум п/о) который будет получать команды «из кода» и транслировать их «подчиненным» браузерам.

            Здесь же «подключенны» браузер определяется подключенным «драйвером». + не нужен отдельный сервер.
            • +2
              Selenium 2.0 = Selenium 1.0 + WebDriver

              Так что всё, что было — осталось. Но добавился новый движок для управления браузером, через нативные API. Рекомендуется использовать там, где Se 1.0 не справляется.
              • 0
                У них на сайте написано что WebDriver является чем-то вроде расширения Remote Control, которое исправляет недостатки последнего.
                У нас кстати вполне нормально для тестов используется Selenium RC + python bindings.
                • 0
                  Да, да. Я вот тоже пользуюсь RC + (Java||Python). Интересно, есть ли смысл попробовать WebDriver.
                  • 0
                    Вот здесь есть мануал: http://code.google.com/p/selenium/wiki/PythonBindings
                    • 0
                      Я еще заметил, что сервер второй версии быстрее работает с IE 8.
                    • +1
                      Если возможностей 1.0 хватает — пока можете попробовать только из любопытства. Продуктивные тесты переводить на WebDriver ещё рано.

                      Движок для IE переписывается в каждой очередной бете чуть менее чем полностью. Движок для Chrome косячит при заполнении форм. Движок для Safari вообще ещё не сделан. Движок для Opera не запускается на Vista и Win7. В общем, всё ещё сырое. Ну, если только на Firefox надо тестировать — тогда можно, там всё уже достаточно стабильно работает.
                • 0
                  Selenium RC (remote control) — сервер запускается отдельно, на любой машине, локальной или удаленной (что удобно для проверки на разных платформах), который запускает браузер и прогоняет тесты. В самом же скрипте теста указывается к какому серверу ему подключаться, какой браузер запускать и тд.
                  • 0
                    Ещё бы такие драйвера для Flash и Silverlight :(
                    • 0
                      у меня для ASP.NET отлично прижилась конфигурация TeamCity + Selenium RC + CassiniDev
                      • 0
                        Очень в тему, спасибо!
                        • 0
                          Пока используем WebDriver для основных сценариев. Всё-таки пока ещё очень и очень сыро, особенно в контексте сложных AJAX решений. Даже в IE порой сложно добиться каких-то вещей. Бывает, что в FF работает, а в IE — наотрез, и наоборот. Хром после первой попытки — в сторону отставили.

                          Будем ждать, альтернатив практически нет или в таком же состоянии.
                          • +2
                            Та же ерунда с поддержкой браузеров. Сейчас гоняем в IE и FF. В Chrome злые баги в базовом функционале. В Opera даже смотреть боюсь, хотя, кстати, реализация под Opera не от Selenium, а от самих Opera Software.
                          • –1
                            Хм, а почему а так упорно повторяется «Java и С#»? В FAQ написано
                            Q: What about support for languages other than Java?
                            A: Python and Ruby support are already available. Support for C# is planned, and we will be announcing this on the mailing list as they are checked in. There is also a Javascript API (JS API) which is available on Firefox.

                            • 0
                              Пардон, исправлю.

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