понедельник, 30 мая 2011 г. - www.msmirnov.ru

Создание тестов для Selenium на C# для web-приложений на ASP.NET


Как я и обещал ранее здесь, привожу пример разработки тестовых сценариев для автоматизированного тестирования web-приложений на C# с использованием библиотеки Selenium 2.0b3.

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

Далее по шагам рассмотрим пример создания тестового сценария проверки логина на тестируемый сайт.
  1. В Visual Studio.NET cоздаем новый проект – библиотеку классов на C#.
    В данную библиотеку помещаем ссылки на библиотеки Selenium:
    • Selenium.WebDriverBackedSelenium
    • WebDriver.Common
    • WebDriver.Remote
    • WebDriver.Remote.Common
    • WebDriver.Firefox
    • WebDriver.IE

    Все эти библиотеки доступны для загрузки с сайта http://seleniumhq.org/download – Скачать необходимо Selenium WebDriver версии 2.0b3 – речь в статье пойдет о ней.

  2. Создаем класс TestFramework который будет предоставлять нам основные функции по выполнению скриптов.

       1:  public class TestFramework
       2:      {
       3:          static RemoteWebDriver _WebDriver;
       4:   
       5:          public static RemoteWebDriver WebDriver
       6:          {
       7:              get
       8:              {
       9:                  if (_WebDriver == null)
      10:                      _WebDriver = new FirefoxDriver();
      11:   
      12:                  return _WebDriver;
      13:              }
      14:          }
      15:   
      16:          public static void OpenURL (string URL)
      17:          {
      18:              WebDriver.Navigate().GoToUrl(URL);
      19:          }
      20:   
      21:          public static IWebElement FindWebElement(WebItem webItem)
      22:          {
      23:              if (webItem.ID != "")
      24:                  return WebDriver.FindElementById(webItem.ID);
      25:   
      26:              if (webItem.ClassName != "")
      27:                  return WebDriver.FindElementByClassName(webItem.ID);
      28:   
      29:              if (webItem.XPathQuery != "")
      30:                  return WebDriver.FindElementByXPath(webItem.ID);
      31:   
      32:              return null;
      33:          }
      34:   
      35:          public static void Delay(int Seconds = 10)
      36:          {
      37:              System.Threading.Thread.Sleep(Seconds * 1000);
      38:          }
      39:      }


    Основу для выполнения тестов представляет собой экземпляр класса RemoteWebDriver, который предоставляет доступ ко все основным функциям Selenium.  

    При этом, экземпляр класса RemoteWebDriver содержит ссылку на объект драйвера, реализующего работу с конкретным браузером – IE, FireFox, Chrome.

    Таким образом, меняя ссылку на конкретный драйвер мы организуем выполнение скриптов на разных браузерах. В данном примере я использую FireFox.

    Метод OpenURL,как видно из названия предназначен для открытия различного рода ссылок.

    Метод FindWebElement реализует поиск элементов на текущей странице браузера. При этом на вход ему подается экземпляр класса WebItem, который будет описан позднее. Пока лишь отмечу, что этот класс предоставляет возможность работы с элементами на странице.

    Данный метод возвращает экземпляр интерфейса IWebElement, который реализуется всеми страничными элементами Selenium.

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

    Последний метод Delay предназначен для приостановки тестов до момент окончания загрузки страниц.


  3. Создаем класс WebItem, который я упоминал выше.

       1:  public class WebItem
       2:      {
       3:          public string ID;
       4:          public string ClassName;
       5:          public string XPathQuery;
       6:   
       7:          public WebItem(string ID, string ClassName, string XPathQuery)
       8:          {
       9:              this.ID = ID;
      10:              this.ClassName = ClassName;
      11:              this.XPathQuery = XPathQuery;
      12:          }
      13:   
      14:          public void Click()
      15:          {
      16:              TestFramework.FindWebElement(this).Click();
      17:          }
      18:   
      19:          public void SetValue(string Value)
      20:          {
      21:              TestFramework.FindWebElement(this).SendKeys(Value);
      22:          }
      23:      }


    Как видим, это наш собственный класс, который предназначен для работы с элементами на веб-страницах.

    Класс содержит описание способа поиска нашего элемента, а также предоставляет нам возможность задать значение элемента и выполнить нажатие на элемент.


  4. Создаем класс LoginWebItems, который будет содержать набор элементов для работы с формой логина.

       1:  public static class LoginWebItems
       2:      {
       3:          public static WebItem LoginLink
       4:          {
       5:              get
       6:              {
       7:                  return new WebItem("ctl00_Header1_TopHeader1_ExitLinkButton", "", "");
       8:              }
       9:          }
      10:   
      11:          public static WebItem UserNameTextBox
      12:          {
      13:              get
      14:              {
      15:                  return new WebItem("ctl00_ContentPlaceHolder1_EMailTextBox", "", "");
      16:              }
      17:          }
      18:   
      19:          public static WebItem PasswordTextBox
      20:          {
      21:              get
      22:              {
      23:                  return new WebItem("ctl00_ContentPlaceHolder1_PasswordTextBox", "", "");
      24:              }
      25:          }
      26:   
      27:          public static WebItem LoginButton
      28:          {
      29:              get
      30:              {
      31:                  return new WebItem("ctl00_ContentPlaceHolder1_LoginButton", "", "");
      32:              }
      33:          }
      34:      }


    Как видим, класс содержит ссылки на следующие элементы – ссылку на страницу логина, текстовые поля для имени пользователя и пароля, а также непосредственно кнопку логина.


  5. Создадим класс PagesActions, который будет выполнять действия по открытию различных страниц нашего сайта. Он нужен нам для того, чтобы исключить жесткое упоминание ссылок в коде тестовых сценариев.

       1:  public class PagesActions
       2:      {
       3:          public static void OpenHomePage()
       4:          {
       5:              TestFramework.OpenURL("http://www.test.com/");
       6:          }
       7:      }



  6. Теперь создадим класс LoginAction, который будет нам реализовывать действия входа на сайт.

    Впоследствии это действие может быть использовано во многих тест-кейсах.

       1:  public class LoginAction
       2:      {
       3:          public static void DoLogin()
       4:          {
       5:              LoginWebItems.LoginLink.Click();
       6:   
       7:              LoginWebItems.UserNameTextBox.SetValue("test login");
       8:              LoginWebItems.PasswordTextBox.SetValue("test password");
       9:   
      10:              LoginWebItems.LoginButton.Click();
      11:   
      12:              TestFramework.Delay();
      13:          }
      14:      }


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


  7. Ну и наконец мы создаем класс тест-кейса LoginTestCase, который реализует непосредственно тестирование входа на сайт

       1:  public class LoginTestCase
       2:      {
       3:          public static void DoTestCase()
       4:          {
       5:              PagesActions.OpenHomePage();
       6:   
       7:              LoginAction.DoLogin();
       8:          }
       9:      }


Таким образом, расширяя впоследствии наши классы, мы можем описывать новые элементы страниц и сами страницы.


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


Затем, мы можем использовать наши сценарии в рамках или встроенного unit-тестирования или для тестирования посредством собственного приложения.


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


Мы можем также организовать автоматическое создание багов в TFS – о том, как это сделать я писал в посте “Как создать work item в TFS (Team Foundation Server) из письма в Outlook

P.S.
Другие мои посты на тему Selenium:
1. От QTP к Selenium
2. Создание тестов для Selenium на C# для web-приложений на ASP.NET
Мой сайт - www.msmirnov.ru

6 комментариев:

  1. Хорошо написал.
    Сделать TestFramework отвечающим только за себя (то есть, принимающим драйвер в качестве параметра конструктора) только не помешает.
    Теперь если всё это ещё и с огурцом связать - то будет и вовсе неплохо :)

    Что из этого мне НЕ нравится, так это захардкоженые автогенерённые IDшники контролов. Не нравится потому, что при изменении разметки, но не поведения (например, обернули контрол в панельку), нужно править тесты.
    Этому есть воркэраунды, например, использовать статические IDшники для контолов (4-й дотнет это позволяет "из коробки"), либо ещё встречал люди (наверное те, кто на 4-й не перешёл ещё) используют собственные атрибуты контролов (например x-control-id="loginButton") и ищут по ним.
    Всё это позволяет отвязаться от layout'а и при тестировании сосредоточиться на "логике", то есть, работать только с теми контролами, которые интересуют, вне зависимости как и куда их распихает дизайнер.

    Понятно, что в случае с ASP.NET MVC такой проблемы не стоит :)

    ОтветитьУдалить
  2. Да, со всем согласен.

    Проблема с захардкоденными IDшниками действительно есть.
    С путями ее решения тоже согласен - или собственные атрибуты или статичные IDшники.

    ОтветитьУдалить
  3. Можно еще в class="..." id-шники писать.

    ОтветитьУдалить
  4. Спасибо за статью. Со всем согласен. Приятно встретить одинаково мыслящего человека :)

    Я бы посоветовал параметры, описывающие элемент, вынести в отдельный класс.

    public class WebItemProperties
    {
    public WebItemProperties Parent; // describes properties of parent element which contains the current element
    public string Id;
    public string Name;
    public string Text;
    public string Tag;
    public string CssClass;
    }

    тогда

    public WebItem(string ID, string ClassName, string XPathQuery)

    преобразуется в

    public WebItem(WebItemProperties webItemProperties)

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

    ОтветитьУдалить
  5. Спасибо за статью. Со всем согласен. Приятно встретить одинаково мыслящего человека :)

    Я бы посоветовал параметры, описывающие элемент, вынести в отдельный класс.

    public class WebItemProperties
    {
    public WebItemProperties Parent; // describes properties of parent element which contains the current element
    public string Id;
    public string Name;
    public string Text;
    public string Tag;
    public string CssClass;
    }

    тогда

    public WebItem(string ID, string ClassName, string XPathQuery)

    преобразуется в

    public WebItem(WebItemProperties webItemProperties)

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

    ОтветитьУдалить
  6. А как получить текс из выбранного элемента?

    ОтветитьУдалить