Статьи

Объекты страницы, улучшающие автоматизацию пользовательского интерфейса

Если вы читали некоторые из моих предыдущих постов, скорее всего, вы проверили некоторые из моих статей о  шаблонах проектирования в 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);
}

Page Объекты Плюсы

Pros

Одним из самых больших преимуществ предлагаемых идей является единственная ответственность классов страницы.

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

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

Исходный код

Вы можете скачать полный исходный код из моего  репозитория Github .

Если вам понравились мои публикации, не стесняйтесь  ПОДПИСАТЬСЯ 

Кроме того, нажмите эти кнопки обмена. Спасибо!  

Ссылки: