Автоматизированное тестирование веб-приложения (MS Unit Testing Framework + Selenium WebDriver C#). Часть 3: WebPages — описываем страницы

в 6:45, , рубрики: Microsoft Unit Testing Framework, q&a, Selenium WebDriver C#, автоматизированное тестирование, тестирование, метки: , , ,

WebPages

Введение

Приветствую всех, кому предыдущие статьи оказались полезными или были просто интересны. В прошлой части мы разобрались с Selenium WebDriver, и теперь, перед тем, как приступать к написанию тестов, мы должны описать страницы тестируемого приложения. В превью перечислю основные моменты

  • Описания всех страниц будут находиться в отдельном проекте, я назвал его Autotests.WebPages
  • Для каждой страницы будет создан отдельный класс, располагающийся в отдельном файле
  • Названия классов будут совпадать с названиями web-страниц, например описание страницы MySite/Home/Help будет храниться в классе с названием Help
  • Структура расположения этих файлов иерархическая и будет повторять дерево web-страниц. Например, страница LogOn будет иметь путь RootHomeHelp.cs относительно проекта и располагаться в пространстве имен (namespace) Autotests.WebPages.Root.Home
  • Для доступа к страницам будет разработан некоторый фасад (Facade), статический класс Pages
  • На практике страницы могут иметь много общего, поэтому вполне логично использовать наследование и вынесение некоторого функционала в отдельные хелперы

Ссылки

Часть 1: Введение
Часть 2.1: Selenium API wrapper — Browser
Часть 2.2: Selenium API wrapper — WebElement
Часть 3: WebPages — описываем страницы

Класс PageBase

Вполне логично начать с проектирования базового класса. Что же будет общего у наших страниц? Как минимум:

  • получение Url страницы
  • открытие страницы
  • получение имени страницы
  • навигация по ссылкам

В добавок к этому, класс PageBase будет содержать общие для всех страниц методы, которые зависят от конкретного приложения. Например, если на всех страницах сайта может выскакивать кастомное всплывающее окно, то мы напишем метод ClosePopup(). Или, например, на всех страницах присутствует меню сайта. Тогда это меню можно описать в базовом классе. В примере я это опущу.

namespace Autotests.WebPages
{
    public abstract class PageBase
    {
        public Uri Url
        {
            get 
            {
                Browser.WaitAjax();

                return GetUriByRelativePath(RelativePath);
            }
        }

        public void Open()
        {
            Contract.Requires(Url != null);
            Contract.Ensures(Browser.Url == Url, string.Format("{0} != {1}", Browser.Url, Url));

            Browser.WaitAjax();

            if (Browser.Url == Url) return;

            Browser.Navigate(Url);
        }

        public Type PageName()
        {
            return GetType();
        }

        protected void Navigate(Uri url)
        {
            Contract.Requires(url != null);

            Browser.Navigate(url);
        }

        protected static Uri GetUriByRelativePath(string relativePath)
        {
            Contract.Requires(!string.IsNullOrEmpty(relativePath));

            return new Uri(string.Format("{0}{1}", SharedSettings.TestEnvironmentUrl, relativePath));
        }

        private string RelativePath
        {
            get
            {
                const string rootNamespaceName = "Autotests.WebPages.Root";
                const string stringToDelete = "Page";
                var fullName = GetType().FullName;

                Contract.Assume(fullName != null);

                return fullName.Replace(rootNamespaceName, "").Replace(".", "/").Replace(stringToDelete, "");
            }
        }
    }
}

RelativePath получает относительный путь к web-странице, исходя из названия и расположения класса, который ее описывает. Удаление магического слова «Page» — это костыль на случай, когда название страницы сайта совпадает с зарезервированными или занятыми названиями. В таком случае к названию класса описания страницы просто приписывается слово «Page».
SharedSettings.TestEnvironmentUrl — адрес тестируемого сайта. Может храниться в константах или в настройках проекта.

Описание страницы LogOn

Теперь, чтобы попасть на сайт, нам необходимо пройти авторизацию (кстати, все знают чем авторизация отличается от аутентификации?).
Опишем страницу LogOn:

public class LogOn : PageBase
    {
        #region Elements

        private static readonly WebElement LoginEdit = new WebElement().ById("Login");
        private static readonly WebElement PasswordEdit = new WebElement().ById("Password");
        private static readonly WebElement RememberMeCheckbox = new WebElement().ById("Remember");
        private static readonly WebElement ValidationSummary = new WebElement().ByClass("ValidationSummary");

        #endregion

        public void DoLogOn(UserLoginInfo userLoginInfo)
        {
            LoginEdit.Text = userLoginInfo.Login;
            PasswordEdit.Text = userLoginInfo.Password;
            RememberMeCheckbox.SetCheck(userLoginInfo.RememberMe);

            Browser.ExecuteJavaScript("$('#LogOnForm').submit()");

            if (Browser.Url == Main.Url) return;
            if (ValidationSummary.Exists()) throw new LogOnValidationException();

            throw new Exception("Unknown logon exception.");
        }
    }

Итак, на странице у нас есть два поля ввода (логин и пароль), чекбокс «Запомнить меня» и кнопка «Войти». Я умышленно не стал описывать эту кнопку, а нажимаю на нее через Browser.ExecuteJavaScript, чтобы показать и такой способ манипуляции с элементами.
Метод DoLogOn принимает некоторую структуру данных, заполняет и отправляет форму. Если после этого отобразилась главная страница Browser.Url == Main.Url, то метод успешно завершает свою работу. Если вход не удался, и показана ошибка ValidationSummary.Exists(), то метод выбрасывает исключение LogOnValidationException. Во всех остальных (непредусмотренных) случаях выбрасывается исключение с сообщением «Unknown logon exception.»
Класс LogOn унаследовал от PageBase некоторые методы и свойства, например метод Open(). Бывают ситуации, когда страницу необходимо открыть с какими-либо параметрами. Для этого придется написать в классе LogOn перегрузку для Open:

public void Open(int userId)
{
	var url = new Uri(string.Format("{0}?userId={1}", Url, userId));
	
	Navigate(url);
}

В этом методе мы формируем новый Url (добавляем id пользователя) и вызываем метод Navigate базового класса.

Заметьте, что нет никакой скрытой инициализации (вспомните PageFactory), элементы будут искаться в момент доступа к ним.

Фасад Pages

В принципе, можно и не использовать фасад. Мне так было удобно, поскольку классы страниц не статические, и необходимо создавать экземпляры. Для примера (см. картинку сверху), класс Pages будет выглядеть вот так:

namespace Autotests.WebPages
{
    public static class Pages
    {
        public static LogOn LogOn = new LogOn();         

        public static class Home
        {
            public static Help Help = new Help ();
            public static Main MainLogOut  = new MainLogOut ();
        }
    }
}

Таким образом, из тестов мы сможем работать со страницами следующим образом:

Pages.LogOn.DoLogOn(...);
Pages.Home.Help.Open();

Это весьма удобно, иерархия страниц повторяет иерархию сайта.

Заключение

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

Автор: natexriver

Источник


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


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