0,0
рейтинг
13 января 2014 в 12:10

Разработка → SWD.Starter: Быстрый старт автоматизации тестирования UI на C# + Selenium WebDriver + PageObjects tutorial

WTF Logo
Статья расскажет о том, как настроить фреймворк автоматизированного тестирования пользовательского интерфейса на языке C#, вместе с Selenium WebDriver и паттерном PageObjects.

Стартовый набор с открытым исходным кодом – SWD.Starter – поможет написать и запустить ваш первый тест в течении 10 минут. Кроме этого, предлагая архитектуру фреймворка, основанную на хороших практиках автоматизации тестирования.
Весь код SWD.Starter может быть полностью настроен под ваши задачи.


Что такое SWD.Starter?


SWD.Starter – это стартовый набор для вашего фреймворка автоматизации тестирования. Весь исходный код доступен на GitHub: dzhariy/SWD.Starter, а лицензия проекта (unlicense), позволяет вам использовать исходный код как угодно, хоть продавать.

SWD.Starter – это уже настроенный проект, содержащий весь необходимый инфраструктурный код для начала создания и запуска тестов пользовательского интерфейса через Selenium WebDriver.

SWD.Starter настойчиво рекомендует использование паттерна PageObjects. И в случае использования этого паттерна, вы сможете писать новый код авто-тестов действительно быстро, при этом, сохраняя красивую архитектуру и читабельность кода.

Что необходимо для начала


Для запуска проекта, вам будет необходимо следующее программное обеспечение:

  1. Visual Studio Express 2013 Desktop Edition (также, теоретически, поддерживаются VS2010 и VS2012)
  2. Git для выкачки проекта из Github
  3. Дополнительные драйвера браузеров Selenium WebDriver, которые можно скачать с официальной страницы проекта

Для быстрой и удобной установки ПО, я рекомендую использовать пакетный менеджер для Windows – Chocolatey.
Согласно инструкциям на главной странице, откройте cmd.exe, и в консольном окне, просто выполните следующий код:

@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%systemdrive%\chocolatey\bin

А далее, в том же консольном окне, выполните следующие команды:
  • cinst VisualStudioExpress2013WindowsDesktop
  • cinst git

Теперь, в консольном окне (например в cmd.exe или Far Manager), выберете папку, куда вы хотите клонировать SWD.Starter – и запустите команду:
git clone https://github.com/dzhariy/SWD.Starter.git


Это обязательный шаг, иначе проект не скомпилируется:
Скопируйте chromedriver.exe и IEDriverServer.exe в папку SWD.Starter\webdrivers

А вот видео полной установки на чистую виртуалку (которую я скачал с modern.ie )
Следуй за розовым покемоном, Нео!

На всякий случай замечу, на видео видно, что Windows на виртуальной машине требует активацию.
Cогласно лицензионным условиям modern.ie я имею право использовать такие образы легально в тестовых целях. В пользовательском соглашении сказано, что я не должен активировать Windows в этом случае.


Что такое PageObjects и почему это настолько важно?


Если говорить просто, то подход в автоматизированном тестировании, с использованием PageObjects, заключается в том, что вы просто выносите весь код низкоуровневой работы со страницей (например, набор текста и нажатия мышкой по элементам) в отдельные классы.

Теперь ваши тесты не работают со страницей напрямую, вызывая низкоуровневые методы WebDriver, а используют более высокоуровневые операции, специфичные для каждой страницы.

Это сокращает количество строк кода в тестах, тем самым делая код более читабельным, понятным и надёжным.
Подход PageObjects – это альтернатива бот-стилю – вызову методов WebDriver из тестов напрямую.
В самом начале, бот-стиль кажется проще и понятнее чем использование PageObjects. Но, это огромное заблуждение, которое может привести ваш проект автоматизации к краху.

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

Тесты в бот-стиле подобны огромной не отсортированной куче книг. Когда ваша «куча» состоит всего из 10-ти книг, в ней можно разобраться без особых трудностей.
Но, что вас ждёт, когда количество книг возрастёт до 100? Поверьте, я вам не завидую. Просто потому, что сам через это уже прошёл.
Heap of books

С другой стороны, при использовании PageObjects, можно разложить все книги по полочкам. В книжных магазинах и на складах, содержится огромное количество книг. Тем не менее, продавцы могут быстро найти то, что вам нужно.
PageObject-класс – это книжная полка, позволяющая удобно организовать код работы с веб-страницей. А популярные языки программирования и IDE предоставляют значительно больше возможностей при использовании объектно-ориентированного программирования.

Book shelf

Тесты в бот-стиле


Основное преимущество тестов в бот-стиле то, что вы можете их «записать даже не зная языка программирования», при помощи таких инструментов, как Selenium IDE и Selenium Builder.

В результате, может получится нечто такое:
Очень длинная простыня кода типа: driver.FindElement(By.Id(ConfirmPassword)).SendKeys(pass);
class BrittleTest
{
    [Test]
    public void Can_buy_an_Album_when_registered()
    {
        var driver = Host.Instance.Application.Browser;
        driver.Navigate().GoToUrl(driver.Url);
        driver.FindElement(By.LinkText("Admin")).Click();
        driver.FindElement(By.LinkText("Register")).Click();
        driver.FindElement(By.Id("UserName")).Clear();
        driver.FindElement(By.Id("UserName")).SendKeys("HJSimpson");
        driver.FindElement(By.Id("Password")).Clear();
        driver.FindElement(By.Id("Password")).SendKeys("!2345Qwert");
        driver.FindElement(By.Id("ConfirmPassword")).Clear();
        driver.FindElement(By.Id("ConfirmPassword")).SendKeys("!2345Qwert");
        driver.FindElement(By.CssSelector("input[type=\"submit\"]")).Click();
        driver.FindElement(By.LinkText("Disco")).Click();
        driver.FindElement(By.CssSelector("img[alt=\"Le Freak\"]")).Click();
        driver.FindElement(By.LinkText("Add to cart")).Click();
        driver.FindElement(By.LinkText("Checkout >>")).Click();
        driver.FindElement(By.Id("FirstName")).Clear();
        driver.FindElement(By.Id("FirstName")).SendKeys("Homer");
        driver.FindElement(By.Id("LastName")).Clear();
        driver.FindElement(By.Id("LastName")).SendKeys("Simpson");
        driver.FindElement(By.Id("Address")).Clear();
        driver.FindElement(By.Id("Address")).SendKeys("742 Evergreen Terrace");
        driver.FindElement(By.Id("City")).Clear();
        driver.FindElement(By.Id("City")).SendKeys("Springfield");
        driver.FindElement(By.Id("State")).Clear();
        driver.FindElement(By.Id("State")).SendKeys("Kentucky");
        driver.FindElement(By.Id("PostalCode")).Clear();
        driver.FindElement(By.Id("PostalCode")).SendKeys("123456");
        driver.FindElement(By.Id("Country")).Clear();
        driver.FindElement(By.Id("Country")).SendKeys("United States");
        driver.FindElement(By.Id("Phone")).Clear();
        driver.FindElement(By.Id("Phone")).SendKeys("2341231241");
        driver.FindElement(By.Id("Email")).Clear();
        driver.FindElement(By.Id("Email")).SendKeys("chunkylover53@aol.com<script type="text/javascript">
/* <![CDATA[ */
(function(){try{var s,a,i,j,r,c,l,b=document.getElementsByTagName("script");l=b[b.length-1].previousSibling;a=l.getAttribute('data-cfemail');if(a){s='';r=parseInt(a.substr(0,2),16);for(j=2;a.length-j;j+=2){c=parseInt(a.substr(j,2),16)^r;s+=String.fromCharCode(c);}s=document.createTextNode(s);l.parentNode.replaceChild(s,l);}}catch(e){}})();
/* ]]> */
</script>");
        driver.FindElement(By.Id("PromoCode")).Clear();
        driver.FindElement(By.Id("PromoCode")).SendKeys("FREE");
        driver.FindElement(By.CssSelector("input[type=\"submit\"]")).Click();
 
        Assert.IsTrue(driver.PageSource.Contains("Checkout Complete"));
    }
}



Такой подход может быть оправдан при выполнении одноразовых задач. Например, если вам необходимо создать 1000 пользователей через интерфейс приложения – достаточно записать создание одного, и с минимальными изменениями поместить код в цикл.
Такой подход будет губительным, если вы надеетесь на автоматизацию тестирования в долгосрочной перспективе.
Вот один небольшой пример:

Предположим, доступ к одной странице продукта осуществляется из 30-ти тестов. В один прекрасный день, программисты принимают решение поменять вёрстку страницы:
Теперь и элементы называются по другому, и «логика кликов» меняется.
В этом случае, вам будет необходимо внести изменение в 30 тестов, вместо того, чтобы сделать это в одном классе.
Как вы думаете, сколько времени эта интереснейшая работа займёт?

Bot style

Тесты с использованием PageObject-классов


Если просто вынести куски кода и организовать все виде нескольких PageObject-классов, то с кодом теста происходят чудесные превращения: он стает понятным, появляются действия, которые можно переиспользовать в других тестах, вместо того чтобы копи-пастить вызовы WebDriver.
Обратите внимание, что в коде теста стало больше строк… Но, это ведь только за счёт комментариев и пояснений, которые важны для демонстрации в этой статье, но необязательны в вашем реальном коде.
Уберите все комментарии и пустые строки – и код все равно останется читабельным и сократится по количеству строк.

class PageObjectTest
{
    [Test]
    public void Can_buy_an_Album_when_registered()
    {
        // Обычно, конструкторы PageObject объектов не выполняют действий на странице. 
        // Они необходимы лишь для получения ссылки на объект.
        var registerUserPage = new RegisterUserPage();
        
        // Просто открывает страницу регистрации, при этом, 
        // кликая на все нужные ссылки по пути
        registerUserPage.Invoke();

        // Этот класс используется для передачи данных. 
        // Некоторые данные могут быть заполнены «по умолчанию», но об этом – позже        
        var newUserFromData = new UserFromDataData()
        {
            UserName = "HJSimpson",
            Password = "!2345Qwert",
        };

        // Момент заполнения и отправки формы
        registerUserPage.FillForm(newUserFromData);
        registerUserPage.Submit();
        
        // А следующий код выбирает товар из витрины, добавляет его в корзину 
        // и переходит на страницу оформления заказа. 
        var showCasePage = new ShowCasePage();
        showCasePage.Goto("Disco");
        showCasePage.SelectProduct("showCasePage");
        showCasePage.AddToCard();
        showCasePage.Checkout();

        var checkOutForm = new CheckOutForm();

        // .DefaultValues возвращает класс с уже заполненными данными по умолчанию. 
        // Если нас что-то не устраивает – всегда можно заменить. 
        var checkoutFromData = UserCheckoutFromData.DefaultValues;


        // Вот как раз это и не устраивает! А давайте JavaScript инъекцию добавим!
        checkoutFromData.Email = @"chunkylover53@aol.com<script type=""text/javascript"">
                                   /* <![CDATA[ */
                                   (function(){try{var s,a,i,j,r,c,l,b=document.getElementsByTagName(""script"");l=b[b.length-1].previousSibling;a=l.getAttribute('data-cfemail');if(a){s='';r=parseInt(a.substr(0,2),16);for(j=2;a.length-j;j+=2){c=parseInt(a.substr(j,2),16)^r;s+=String.fromCharCode(c);}s=document.createTextNode(s);l.parentNode.replaceChild(s,l);}}catch(e){}})();
                                    /* ]]> */
                                   </script>";

        CheckoutCompletePage  checkoutCompletePage = checkOutForm.Submit();

        Assert.IsTrue(checkoutCompletePage.GetPageTitle().Contains("Checkout Complete"));
    }
}



Page Object
Ну что? Хотите создавать тесты, используя PageObject?

Первый Smoke-тест в SWD.Starter


Если вы задаётесь вопросом: с чего начать автоматизацию тестирования? То, у меня для вас есть очень простой ответ, который подойдёт в 99% случаев.
Начните со смоук тестов для каждой страницы приложения.

Рецепт:
  1. Взять страницу любого уровня вложенности
  2. Открыть страницу
  3. Проверить, что все важные элементы – присутствуют на странице.

И в результате мы получим легковесный тест, который в случае успешного прохода говорит:
Что все важные элементы отдельной страницы до сих пор не изменились
То, что наши PageObject классы по прежнему соответствуют актуальной странице
То, что путь из точки А. (главная страница) в точку Б. (любая другая страница) – возможен для конечно пользователя приложения.
И все это работает в разных браузерах.

А теперь, давайте напишем первый тест для страницы регистрации нового пользователя Хабрахабр:


Покрыв все страницы приложения такими тестами – вы будете приятно удивлённы: метрики покрытия покажут покрытие больше 50%. Конечно же, мы понимаем, что метрика покрытия кода – не самая основная, но согласитесь, это – хороший результат.

Кроме того, в SwdBrowser.cs есть метод HandleJavaScriptErrors(). В данной реализации, его нужно просто почаще вызывать, например, в каждом .Invoke(). И тогда, этот метод сможет отловить возможные неожиданные ошибки JavaScript.

Я надеюсь, что в ходе просмотра видео, вы заметили несколько интересных вещей?
Например, что в проекте уже готова инфраструктура для смоук-тестов PageObject-классов?..
И чтобы добавить тест – необходимо просто его записать, сгенерировать код… и следовать инструкциям в сгенерированном коде.
А в самом начале, мы видим строку кода:


[TestMethod]
public void S01_First_Step_Run_WebDriver_with_Firefox()
{
    SwdBrowser.Driver.Url = "http://swd-tools.com";
}



которая: открывает браузер, переходит по нужному URL… и закрывает браузер.
Не много ли это для одной строки?
И почему открылся именно FireFox, а что если я хочу Internet Explorer?
Об этом и многом другом – ниже.

Хорошие практики в автоматизации тестирования


Вы знаете, опасно называть практики «лучшими», и поэтому, оставим просто «хорошими».
Время от времени, я описываю такие практики в виде небольших заметок, которые иллюстрируют конкретное решение, но, к сожалению, не показывают общей картины.

Для того, чтобы показать, как хорошие практики работают вместе, я и начал работу над SWD.Starter.
Вот, например, по статье Автоматическое создание Браузера и инициализация PageObject как раз и был реализован SwdBrowser. А PageObject классы, унаследованные от CorePage – умеют самостоятельно инициализировать веб-элементы.
А в заметке WebDriverWait и PageObject, я рассказываю, как добавить «умные» методы ожидания элементов для PageObject, по типу WebDriverWait для обычных элементов.

Все это уже вошло в SWD.Starter. И если вас интересует решение конкретной проблемы – просто посмотрите код, а я, со временем, сделаю так, чтобы в нем можно было легко разобраться. Уже сейчас, некоторые классы в достаточной мере документированы, например – Swd.Core.Configuration.Config Class. А комментарии для некоторых классов уже есть в коде, но пока ещё не мигрировали в Doxygen.

Структура проекта SWD.Starter


Ядро SWD.Starter – это Swd.Core. В нем содержатся такие интересные штуки как: Solution
  • Класс Swd.Core.Configuration → Config, который читает настройки фреймворка из внешнего файла Config.config. Именно в этом файле можно выбрать запускаемый браузер, а также добавить свои настройки.
  • Класс Swd.Core.WebDriver → SwdBrowser – уже упоминался. Он управляет жизнью браузера. А рядом, в том же пространстве имен, находятся полезные методы и классы, упрощающие работу с браузером.
  • В пространстве имен Swd.Core.Pages, живут базовые классы для PageObject'ов.


В Swd.Core расположен только общий код, который, в дальнейшем, можно расширить в дочерних проектах по тестированию.

Пример такого тестового проекта – DemoProject.
Тестовый проект состоит из двух основных подпроектов:

  • Demo.TestModel – содержит декларации PageObject-классов, кастомизированные базовые классы, необходимые данные, кусочки логики работы приложения, и другие библиотечные функции, специфичные для конкретного тестируемого приложения.
    Обычно, для отдельного тестируемого приложения должна быть лишь одна библиотека Модели.
  • Demo.TestProject – проект, содержащий наборы тестов. Таких тестовых проектов может быть несколько. Вот, например, Demo.Tutorial – это тоже проект с тестами, и он также как Demo.TestProject использует библиотеку Модели (Demo.TestModel ).
  • Demo.Tutorial – попытка создания руководства по работе с Swd.Starter. Пока ещё не совсем законченный, но уже сейчас, можно читать файл «Ch00Introduction.cs» и пробовать запускать тесты.


Обратная связь, лицензия и сотрудничество


Лицензия проекта позволяет вам производить любые действия с кодом проекта, которые может ограничить лишь ваша фантазия. (http://unlicense.org/)
Код можно видоизменять, использовать в коммерческих целях, выкладывать на торренты и майнить биткоины, если хотите.

Но, мне бы было очень полезно получить от вас обратную связь. Оставить комментарии можно как тут, так и на странице проекта на Github.
А лучше всего, если вы отправите реальный чёткий пацанячий pull-request в репозиторий на github.
Но, если это будет огромное изменение с перелапачиванием половины кода, то, неплохо было бы вначале его обсудить.

Над чем можно работать? – Там поле почти не паханное:
  • Документация
  • Туториал
  • Новый полезный код, решающий реальные проблемы
  • Новые демонстрационные проекты
  • Инструкции


И ещё. 28-го февраля 2014 в Киеве, я планирую выступать с докладом на конференции Selenium Camp 2014. Доклад будет посвящён проекту SWD Page Recorder, но и проекту SWD.Starter будет посвящено не мало времени. А после, запись доклада появится в разделе архива материалов, через 3-4 месяца после конференции.
Я буду доступен все два дня, и буду готов пообщаться «в живую» как после моего доклада, так и в течении всего времени конференции.

Полезные материалы



Успешной вам автоматизации.



P.S.: SWD расшифровывается как Selenium WebDriver
Это было полезно?

Проголосовало 118 человек. Воздержалось 29 человек.

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

Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (8)

  • 0
    Спасибо, любопытно. Концепция высокоуровневой команды Страница.ВыполнитьДействие, хоть и напрашивалась, но всё равно — прогресс.
  • 0
    А как правильно при таком подходе проверять переходы на другую страницу? Есть метод Page1.GoToPage2(). Как после этого проверить, что Page2 загрузилась так как мы ожидали?
    • 0
      Обычно, такой проверки на то, что Page2 загрузилась не требуется.
      Например, в следующем коде:

      var page1 = new Page1;
      var page2 = page1.GoToPage2(); // ①
      page2.DoSomeStuff(); // ② 
      


      При условии того, что Page2 не была загружена, строка ② просто упадёт. Если тесты снимают скриншот страницы автоматом при падении, то по нему будет видно, что страница 2 небыла загружена.

      Второй вариант, это введение отдельного метода IsDisplayed, как в примере CreateNewAccountPage.cs для каждой страницы.

      public override bool IsDisplayed()
      {
          return txtCaptcha.Displayed;
      }
      


      Смысл метода в том, что он определяет открыта ли страница по некоторому уникальному условию. В примере – это условия отображения контрола txtCaptcha на странице.
      Для каждой страницы проверка на существование должно быть уникальным.
      • 0
        Проблема в том, что мы должны проверить наличие элемента через Page1, которого на Page1 быть не должно. То есть наша капча должна быть определена в Page2 (в Page1 она смысла не имеет).
        Можно ли как-то дать понять что мы должны были оказаться на Page2 и вызвать myPage2.VerifyExpectedElementsAreDisplayed()?
        • +1
          Мне коротко объяснить всю эту штуку сложно, но я попробую.

          В общем, если элемент txtCaptcha находится на физической странице Page 2, то и объявить его необходимо в классе Page2. Это вы верно говорите.

          Для того, чтобы выяснить из метода Page1.GotoPage2(), отображается ли уже Page2 на экране или ещё нет, мы вызываем Page2.IsDisplayed().

          В случае, если Page2.IsDisplayed() вернёт true – ничего делать не нужно. Страница Page2 уже открыта.
          В случае false – нам необходимо проделать некоторые действия на странице Page1 для того, чтобы появилась Page2.

          У меня есть доклад, в котором я менее скомканным образом рассказываю про эту тему.
          Там же есть пример кода взаимодействия между страницами:

          blog.zhariy.com/2013/02/atdays-pageobject.html

          Посмотрите пожалуйста, если будет что-то не ясно, то я придумаю как продемонстрировать все наглядней.
  • +1
    Со временем, когда количество тестов будет расти, в случае использования бот-стиля, вы будете тратить все больше и больше времени на их поддержку.

    Подтверждаю. Стоимость внесения изменений растет экспоненциально. За изменение некоторых тестов даже не хочется браться.
  • 0
    Встретился с задачей автоматизирования веб-приложения на C# + WebDriver. Всё хорошо, кроме ожиданий. Нашёл эту статью, полазил по исходникам и возник вопрос:

    В Вашем примере используется System.Threading.Thread.Sleep(100); для реализации эксплицитных ожиданий.

    Насколько я знаю, это яркий пример 'Worst Practices' работы с вебдрайвером. Почему тут используется именно Thread.Sleep()?
    • 0
      Сам Thread.Sleep ничем не плох и не хорош.
      В моем коде, Thread.Sleep(100) используется для задержки на 100 миллисекунд в цикле, ожидания элемента для того, чтобы не напрягать браузер частыми запросами.

      Т.е. в худшем случае, вы потеряете 1/10-ю секунды при ожидании одного элемента. Все относительно.
      Для WebDriver тестов — это мало. Для юнит тестов и торговле на бирже ценных бумаг — это много.

      Плохой практикой Thread.Sleep() становится, когда люди указывают константное время, например, 5 секунд для ожидания элемента.
      Потом оказывается, что в IE, нужно подождать 10 секунд…
      Тогда меняют Thread.Sleep(5000) на Thread.Sleep(10000)… и теперь тесты во всех браузерах работают со скоростью самого медленного.

      Вот это — плохая практика.

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