Статья расскажет о том, как настроить фреймворк автоматизированного тестирования пользовательского интерфейса на языке 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. И в случае использования этого паттерна, вы сможете писать новый код авто-тестов действительно быстро, при этом, сохраняя красивую архитектуру и читабельность кода.
Что необходимо для начала
Для запуска проекта, вам будет необходимо следующее программное обеспечение:
- Visual Studio Express 2013 Desktop Edition (также, теоретически, поддерживаются VS2010 и VS2012)
- Git для выкачки проекта из Github
- Дополнительные драйвера браузеров 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? Поверьте, я вам не завидую. Просто потому, что сам через это уже прошёл.
С другой стороны, при использовании PageObjects, можно разложить все книги по полочкам. В книжных магазинах и на складах, содержится огромное количество книг. Тем не менее, продавцы могут быстро найти то, что вам нужно.
PageObject-класс – это книжная полка, позволяющая удобно организовать код работы с веб-страницей. А популярные языки программирования и IDE предоставляют значительно больше возможностей при использовании объектно-ориентированного программирования.
Тесты в бот-стиле
Основное преимущество тестов в бот-стиле то, что вы можете их «записать даже не зная языка программирования», при помощи таких инструментов, как Selenium IDE и Selenium Builder.
В результате, может получится нечто такое:
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 тестов, вместо того, чтобы сделать это в одном классе.
Как вы думаете, сколько времени эта интереснейшая работа займёт?
Тесты с использованием 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"));
}
}
Ну что? Хотите создавать тесты, используя PageObject?
Первый Smoke-тест в SWD.Starter
Если вы задаётесь вопросом: с чего начать автоматизацию тестирования? То, у меня для вас есть очень простой ответ, который подойдёт в 99% случаев.
Начните со смоук тестов для каждой страницы приложения.
Рецепт:
- Взять страницу любого уровня вложенности
- Открыть страницу
- Проверить, что все важные элементы – присутствуют на странице.
И в результате мы получим легковесный тест, который в случае успешного прохода говорит:
Что все важные элементы отдельной страницы до сих пор не изменились
То, что наши 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. В нем содержатся такие интересные штуки как:
- Класс 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 месяца после конференции.
Я буду доступен все два дня, и буду готов пообщаться «в живую» как после моего доклада, так и в течении всего времени конференции.
Полезные материалы
- SWD Tools – страница проектов Page Recorder и Starter. Содержит (или будет содержать) все необходимые ссылки, касательно проектов
- GitHub SWD Starter
- GitHub SWD Page Recorder
- SWD Page Recorder: Записывает PageObject-классы для Selenium WebDriver – Обзор SWD Page Recorder на Хабрахабр
- Вебинар: Основы использования паттерна Page Object вместе с Selenium WebDriver
- Подборка свежих заметок и статей по автоматизации тестирования – если вам интересны советы по автоматизации тестирования
- Слайды/Видео к моему докладу на #atdays: За пределами PageObject
- Maintainable Automated UI Tests – отличная статья, с современным взглядом на автоматизацию тестирования
Успешной вам автоматизации.
P.S.: SWD расшифровывается как Selenium WebDriver
Автор: Dmitry_Zhariy