В мире много различных хороших практик в различных областях и сферах. Не является исключением и тестирование программного обеспечения. Сегодня мы с вами рассмотрим использование Page Object и Page Factory.

Я не буду сейчас рассказывать, что такое Page Object (паттерн/шаблон проектирования, который используется в автоматизированном тестировании) или Page Factory (класс из библиотеки Selenium), так как в интернете много информации о них. В данной статье я постараюсь на примерах показать, как с ними необходимо работать, чтобы вы взяли и начали сразу применять, то о чём мы сегодня будем говорить.

Для чего я стал использовать Page Object при написании автоматических тестов? Всё просто — есть элементы сайта, которые мной используются многократно в разных тестах. Если разработчик изменит элемент, а он используется мной в 100 тестах, то мне придётся править 100 мест в коде. При использовании Page Object мне достаточно исправить код в одном месте.

Рассмотрим на простом примере. Есть на странице результатов поиска Google логотип, ссылка которого имеет определённый id. Предположим, что каждый из 100 тестов я заканчиваю переходом на главную страницу кликая по логотипу сайта:

driver.FindElement(By.CssSelector("a#logo")).Click();

Если в определённый момент разработчики изменят id у логотипа, то все 100 тестов будут заканчиваться неудачей и мне надо будет править все 100 тестов. В этом случае нам на помощь приходит Page Object. Мы создадим класс, описывающий главную страницу сайта, а также класс описывающий страницу результатов поиска и в них будут перечислены все элементы страниц, к которым мы потом будем обращаться из всех 100 тестов. И когда разработчики изменят id у логотипа или ещё у какого-то из сотен элементов, то мы исправим его в одном месте, а сами тесты не будем трогать.

Рассмотрим сказанное выше на примере. Я не буду описывать как настроить Visual Studio для написания автоматических тестов на Selenium WebDriver об этом вы можете прочитать в соответствующей статье.

Итак, среда для тестов у нас подготовлена. CS-файл, в котором содержатся тесты я назвал «Test.cs». Теперь добавим в проект папку «pages», в которой у нас будут находиться страницы содержащие объекты страниц (объектами страниц будем называть элементы страниц: кнопки, ссылки и прочее.) В Solution Explorer правой кнопкой мыши на проекте и «Add -> New Folder»:

Page Object и Page Factory или просто о простом

Теперь добавим в эту папку CS-файлы назвав их «PageHome.cs» (там будут объкты главной страницы) и «PageResultSearch.cs» (там будут объекты страницы результатов поиска):

Page Object и Page Factory или просто о простом

Их рассмотрим позже, а сейчас рассмотрим обычный тест поиска в Google и возврата обратно на главную страницу:

[Test]
public void Test_1()
{
	driver.Navigate().GoToUrl("https://www.google.ru");
	driver.FindElement(By.CssSelector("input[title='Поиск']")).Click(); // кликаем в поле ввода поисковой фразы
	driver.FindElement(By.CssSelector("input[title='Поиск']")).SendKeys("В чём сила брат?"); // вводим поисковую фразу
	driver.FindElement(By.CssSelector("input[value='Поиск в Google']")).Click(); // нажимаем на кнопку "Поиск в Google"
	Thread.Sleep(5000); // пауза за наблюдением результата поиска, для наглядности
	driver.FindElement(By.CssSelector("a#logo")).Click(); // возвращаемся на главную страницу кликнув по логотипу
	Thread.Sleep(5000); // пауза за наблюдением перехода на главную, для наглядности
}

Знакомо и так вы писали до текущего момента. Теперь разберём написание теста с использованием Page Object. Для этого в созданные нами файлы описания объектов страниц добавим нужные нам объекты, с которыми в процессе тестов вы будем с вами работать.

PageHome.cs:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;

namespace AutoTest.pages
{
    class PageHome
    {
        /// <summary>Строка для ввода текста поиска</summary>
        [FindsBy(How = How.CssSelector, Using = "input[title='Поиск']")]
        public IWebElement TxtSearchForm { get; set; }

        /// <summary>Кнопка Поиск в Google</summary>
        [FindsBy(How = How.CssSelector, Using = "input[value='Поиск в Google']")]
        public IWebElement BtnSearchSubmit { get; set; }
    }
}

PageResultSearch.cs:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;

namespace AutoTest.pages
{
    class PageResultSearch
    {
        /// <summary>Логотип-ссылка</summary>
        [FindsBy(How = How.CssSelector, Using = "a#logo")]
        public IWebElement LnkLogo { get; set; }
    }
}

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

Разберём «PageResultSearch.cs». В обычном тесте мы обращались к логотипу следующим образом:

driver.FindElement(By.CssSelector("a#logo"))

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

В «PageResultSearch.cs» этот поиск элемента описан следующим образом:

[FindsBy(How = How.CssSelector, Using = "a#logo")]
// искать элемент по селектору (How = How.CssSelector), который равен "a#logo" (Using = "a#logo")

Ну и конечно же задаётся его имя, по которому мы с ним будем работать:

public IWebElement LnkLogo { get; set; }

Сравните данные и вы поймёте принцип.

А как работать с элементами? Для этого напишем второй тест, который уже будет использовать при тестировании Page Object. Не забудьте в файле, где находятся сами тесты подключить «namespace» страниц «Page*.cs» в моём случае так:

using AutoTest.pages;

Сам тест:

[Test]
public void Test_2()
{
	driver.Navigate().GoToUrl("https://www.google.ru");
	PageHome pageHome = new PageHome(); // чтобы могли обращаться к объектам из PageHome.cs
	PageFactory.InitElements(driver, pageHome); // инициализация элементов Page Object из PageHome.cs
	pageHome.TxtSearchForm.Click(); // кликаем в поле ввода поисковой фразы
	pageHome.TxtSearchForm.SendKeys("В чём сила брат?"); // вводим поисковую фразу
	pageHome.BtnSearchSubmit.Click(); // нажимаем на кнопку "Поиск в Google"
	Thread.Sleep(5000); // пауза за наблюдением результата поиска, для наглядности
	PageResultSearch pageResult = new PageResultSearch(); // чтобы могли обращаться к объектам из PageResultSearch.cs
	PageFactory.InitElements(driver, pageResult); // инициализация элементов Page Object из PageResultSearch.cs
	pageResult.LnkLogo.Click(); // возвращаемся на главную страницу кликнув по логотипу
	Thread.Sleep(5000); // пауза за наблюдением перехода на главную, для наглядности
}

Запомните, что PageFactory инициализирует объекты/элементы страницы и обращается к ним, только когда в коде есть обращение к ним. Т.е. если в коде вы ни разу не обратились к какому-то объекту страницы, то его инициализация не производится хоть он и прописан в классе описания страницы. Эта важная особенность PageFactory. Можно работать без PageFactory, но тогда там происходит сразу инициализация всех объектов, находящихся в классе и если, инициализируя объект он не будет найден на открытой странице, то WebDriver выдаст исключение, что данный объект не найден на странице. Примеры без PageFactory я не разбираю, потому что работа без PageFactory доставляет массу неудобств.

Сравнив оба теста, вы увидите, что тест увеличился на 4 строки + появилось два файла, в которых также появился код. Если у вас десяток тестов и каждый уникален, то вам может не понадобится Page Object, однако если у вас сотни тестов и многие из них работают с одними и теми же элементами, то проектируйте тесты сразу используя Page Object, иначе после того как разработчики изменят несколько элементов вам придётся править не один десяток строк. В случае с Page Object достаточно будет поправить одну строку в классе описания объекта страницы. В нашем случае, если разработчики Google исправят id логотипа-ссылки на «logotwo», то мы поправим строку в «PageResultSearch.cs» на:

[FindsBy(How = How.CssSelector, Using = "a#logotwo")]

И не будет правиться сотня тестов, так как в них обращение к элементу идёт следующим образом:

pageResult.LnkLogo.Click();

Многие в классах описания страниц прописывают не только объекты страницы, с которыми надо будет работать, но и различные методы, чтобы в тестах было меньше кода, однако я сторонник того, чтобы в классах Page Object описывать объекты, а всю работу с ними описывать уже в самих тестах, тогда тесты становятся понятными и легко читаются.

Ещё рекомендую в классах описания страниц оставлять комментарии ко всем объектам, тогда при написании тестов вы в любой момент сможете прочесть, что за элемент в списке и облегчите себе и другим жизнь. Пример:

Page Object и Page Factory или просто о простом
Page Object и Page Factory или просто о простом

Надеюсь объяснил доступно. Далее вы можете экспериментировать как вам угодно. Прикладываю готовый проект с описанными выше примерами.