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

в 8:10, , рубрики: .net, selenium, test automation, visual studio 2013, webdriver, автоматизация тестирования, тестирование, метки: , , , , , ,

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%chocolateybin

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

  • cinst VisualStudioExpress2013WindowsDesktop
  • cinst git

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

git clone https://github.com/dzhariy/SWD.Starter.git

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

А вот видео полной установки на чистую виртуалку (которую я скачал с 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

Автор: Dmitry_Zhariy

Источник

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


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