Если вы читали некоторые из моих предыдущих постов, скорее всего, вы проверили некоторые из моих статей о шаблонах проектирования в Automation Testing . Одним из наиболее заметных шаблонов, о которых я писал в блоге, является шаблон объекта страницы . Это настолько важно для создания быстрой, надежной и удобочитаемой автоматизации пользовательского интерфейса, что я даже посвятил ей вторую статью . В моей команде мы применяли его в течение нескольких лет в различных проектах. В ходе этого процесса мы предложили несколько различных вариантов объектов страницы. Я полагаю, что некоторые из них приводят к более читабельному и более понятному коду по сравнению с ранее представленным
Объекты страницы v.2.0 — интерфейсы
Мне настолько нравится этот шаблон, что я даже создал свободный API для объектов страницы . Тем не менее, я думаю, что его использование немного хлопотно, по крайней мере, для меня. Оказалось, что я не большой поклонник интерфейса Fluent . Если вы прочитали мою статью на эту тему, возможно, вы узнали, что я предложил использовать эти страницы в качестве одиночных . Тем не менее, я думаю, что есть более «чистые» способы сделать это. Класс базового синглтона и все другие базовые универсальные страницы делают код более нечитаемым и не таким простым для понимания. Мы решили эту конкретную проблему с помощью контейнера IoC . К сожалению, дерево наследования объектов страницы все еще было относительно сложным из-за двух общих параметров.
Контейнер IoC — Предыдущая версия
public class BasePage<M>
where M : BasePageElementMap, new()
{
protected readonly string url;
public BasePage(string url)
{
this.url = url;
}
public BasePage()
{
this.url = null;
}
protected M Map
{
get
{
return new M();
}
}
public virtual void Navigate(string part = "")
{
Driver.Browser.Navigate().GoToUrl(string.Concat(url, part));
}
}
public class BasePage<M, V> : BasePage<M>
where M : BasePageElementMap, new()
where V : BasePageValidator<M>, new()
{
public BasePage(string url) : base(url)
{
}
public BasePage()
{
}
public V Validate()
{
return new V();
}
}
После пары дискуссий о методах утверждения, должны ли они быть частью объектов страницы, я начал думать о том, как их улучшить. С одной стороны, я хотел, чтобы утверждения использовались повторно, с другой стороны, они как-то не должны были быть непосредственно частью объектов страницы. Я думал, что потому что они были причиной существования второго базового класса с дополнительным универсальным параметром. Также появилось новое требование — страницы должны быть взаимозаменяемыми. Таким образом, если у вас есть страница A, она позже устареет, и ее заменой будет страница A1. Должно быть легко обмениваться обеими реализациями, не нарушая весь окружающий код.
Интерфейсы — улучшенная версия
public class BingMainPage : BasePage<BingMainPageMap>, IBingMainPage
{
public BingMainPage(IWebDriver driver)
: base(driver, new BingMainPageMap(driver))
{
}
public override string Url
{
get
{
return @"http://www.bing.com/";
}
}
public void Search(string textToType)
{
this.Map.SearchBox.Clear();
this.Map.SearchBox.SendKeys(textToType);
this.Map.GoButton.Click();
}
}
Чтобы решить последнюю упомянутую проблему, мы представили нового участника интерфейса страницы Page Object Pattern. Он определяет, какие действия должна выполнять страница.
public interface IBingMainPage : IPage
{
void Search(string textToType);
}
Теперь весь код, который зависит от страницы, может использовать страницу в качестве интерфейса. Это означает, что если вам нужно заменить его, новая версия должна реализовать только тот же интерфейс.
Второе улучшение, которое мы сделали, было связано с ранее названными классами Validators. В общем, они держали методы утверждений, поэтому первое, что мы решили сделать, это переименовать их, чтобы они заканчивались суффиксом Asserter. Мы сделали это, потому что в производственном коде валидаторы предназначены для другой работы, например, для проверки ввода пользователя, а не для утверждения вещей.
После этого еще один большой рефакторинг, который мы применили, заключался в том, что методы Asserter использовались в качестве методов расширения интерфейса страницы. В результате они могут использоваться как обычные методы, предоставляемые конкретной страницей.
public static class BingMainPageAsserter
{
public static void AssertResultsCountIsAsExpected(this IBingMainPage page, int expectedCount)
{
Assert.AreEqual(page.GetResultsCount(), expectedCount, "The results count is not as expected.");
}
}
Единственный недостаток представленной реализации состоит в том, что вам всегда нужно создавать метод-оболочку утвержденного элемента, поскольку у вас нет прямого доступа к карте элементов страницы через ее интерфейс. Однако эти идеи привели к исключению второй базовой страницы и дополнительного универсального параметра.
public abstract class BasePage<TMap>
where TMap : BaseElementMap
{
private readonly TMap map;
protected IWebDriver driver;
public BasePage( IWebDriver driver, TMap map)
{
this.driver = driver;
this.map = map;
}
internal TMap Map
{
get
{
return this.map;
}
}
public abstract string Url { get; }
public virtual void Open(string part = "")
{
this.driver.Navigate().GoToUrl(string.Concat(this.Url, part));
}
}
Как видно из примера, модель наследования упрощена по сравнению с предыдущими версиями.
Использование предоставленного решения просто.
[TestClass]
public class BingTests
{
private IBingMainPage bingMainPage;
private IWebDriver driver;
[TestInitialize]
public void SetupTest()
{
driver = new FirefoxDriver();
bingMainPage = new BingMainPage(driver);
}
[TestCleanup]
public void TeardownTest()
{
driver.Quit();
}
[TestMethod]
public void SearchForAutomateThePlanet()
{
this.bingMainPage.Open();
this.bingMainPage.Search("Automate The Planet");
this.bingMainPage.AssertResultsCountIsAsExpected(264);
}
}
Тесты используют страницы как в ранее представленных версиях. Единственная тонкая деталь здесь заключается в том, что если вы хотите использовать методы утверждения расширения, вам нужно добавить оператор using для пространства имен, в котором находится их класс.
Страница объектов v.2.1 — открытая карта, пропустить интерфейсы
Как указано в подзаголовке, следующее «поколение» объектов страницы предоставляет свою карту элементов для кода, который использует страницу. Кроме того, мы решили, что создание интерфейса для каждой конкретной страницы — непростая задача. Более того, мы выяснили, что в большинстве случаев интерфейсы замещающих страниц должны отличаться от старых, поскольку к страницам применяются разные изменения. Итак, первая тонкая настройка, которую мы сделали, была в классе базовой страницы, помечающем свойство Map как public.
public abstract class BasePage<TMap>
where TMap : BaseElementMap
{
private readonly TMap map;
protected IWebDriver driver;
public BasePage(IWebDriver driver, TMap map)
{
this.driver = driver;
this.map = map;
}
public TMap Map
{
get
{
return this.map;
}
}
public abstract string Url { get; }
public virtual void Open(string part = "")
{
this.driver.Navigate().GoToUrl(string.Concat(this.Url, part));
}
}
Второе чередование было связано с классами Asserter. Теперь они не расширяют интерфейсы страниц, а сами страницы.
public static class BingMainPageAsserter
{
public static void AssertResultsCountIsAsExpected(this BingMainPage page, int expectedCount)
{
Assert.AreEqual(page.Map.ResultsCountDiv.Text, expectedCount, "The results count is not as expected.");
}
}
Единственное отличие использования в тестах состоит в том, что теперь вы можете получить доступ к элементам карты непосредственно в тестах.
[TestMethod]
public void SearchForAutomateThePlanet()
{
this.bingMainPage.Open();
this.bingMainPage.Map.FeelingLuckyButton.Click();
this.driver.Navigate().Back();
this.bingMainPage.Search("Automate The Planet");
this.bingMainPage.AssertResultsCountIsAsExpected(264);
}
На мой взгляд, это хорошая идея, только если логика фреймворка скрыта за объектами страницы. Я имею в виду такие действия, как очистка ввода текста перед вводом текста, выполнение вызовов JavaScript и т. Д.
Страница объектов v.2.3 — частичные страницы
Что мне больше всего нравится в объектах страницы, которые мы разработали, так это то, что элементы, логика страницы и утверждения помещаются в разные файлы. Я считаю, что это делает объекты страницы более понятными и читаемыми. Кроме того, это снижает связанные с этим затраты на ремонтопригодность.
Чтобы еще больше упростить дерево наследования, мы решили использовать частичные классы вместо общих базовых классов. С небольшой настройкой, делающей свойство Map общедоступным, мы подумали, что это будет отличной идеей размещения элементов карт прямо на странице. Однако, если бы они были объединены в одном файле, дизайн имел бы те же недостатки, что и у WebDriver. Это привело бы к большим файлам, где элементы были бы смешаны с методами обслуживания страницы. Мы этого не хотели. Решением было оставить элементы в отдельном файле, но теперь это частичный класс класса главной страницы.
public partial class BingMainPage : BasePage
{
public IWebElement SearchBox
{
get
{
return this.driver.FindElement(By.Id("sb_form_q"));
}
}
public IWebElement GoButton
{
get
{
return this.driver.FindElement(By.Id("sb_form_go"));
}
}
public IWebElement ResultsCountDiv
{
get
{
return this.driver.FindElement(By.Id("b_tween"));
}
}
}
Так выглядит новая карта страницы. Он использует экземпляр драйвера, определенный в классе главной страницы.
public partial class BingMainPage : BasePage
{
public BingMainPage(IWebDriver driver) : base(driver)
{
}
public override string Url
{
get
{
return @"http://www.bing.com/";
}
}
public void Search(string textToType)
{
this.SearchBox.Clear();
this.SearchBox.SendKeys(textToType);
this.GoButton.Click();
}
}
Также есть небольшие изменения в классе главной страницы. Теперь он наследует более простую версию BasePage, которая не требует общего параметра карты элемента.
Использование новых объектов страницы идентично ранее представленным, с той лишь разницей, что к элементам страницы можно получить доступ непосредственно из экземпляра страницы.
[TestMethod]
public void SearchForAutomateThePlanet_Second()
{
this.bingMainPage.Open();
this.bingMainPage.SearchBox.Clear();
this.bingMainPage.SearchBox.SendKeys("Automate The Planet");
this.bingMainPage.GoButton.Click();
this.bingMainPage.AssertResultsCountIsAsExpected(264);
}
Pros
Одним из самых больших преимуществ предлагаемых идей является единственная ответственность классов страницы.
В объектно-ориентированном программировании принцип единой ответственности гласит, что каждый класс должен нести ответственность за одну часть функциональности, и эта ответственность должна быть полностью инкапсулирована классом.
В представленной реализации класс карты отвечает только за расположение элементов, класс asserter для подтверждения вещей и саму страницу для предоставления сервисных методов.
Исходный код
Вы можете скачать полный исходный код из моего репозитория Github .
Если вам понравились мои публикации, не стесняйтесь ПОДПИСАТЬСЯ
Кроме того, нажмите эти кнопки обмена. Спасибо!