Автоматизированное тестирование веб-приложения с нуля (MS Unit Testing Framework + Selenium WebDriver C#). Часть 2.1: Selenium API wrapper

от автора

Вступление

Selenium + C#
Снова здравствуйте! Представляю вам вторую часть статьи на тему автоматизированного тестирование веб-приложения на Selenium и C#. И если первая часть была из разряда «капитан очевидность», что вызвало негодование у читателей, то в этой части будет много кода. И так, зачем же писать обертку (wrapper) над Selenium API? На мой взгляд, пользователи могут столкнуться со следующими проблемами:

  • Интерфейс IWebDriver предоставляет довольно скудную функциональность в плане управления браузером. Поэтому все, что нам понадобится, придется писать самим
  • Описание элемента происходит одновременно с его поиском, т.е. на момент определения элемента он должен существовать в браузере. Очень часто решается путем написания getter для каждого элемента. Это накладно и плохо с точки зрения производительности
  • ISearchContext.FindElements принимает только один параметр типа OpenQA.Selenium.By, т.е. мы не можем искать сразу по нескольким свойствам. Обычно элемент ищется по первому критерию, а затем начинается отсеивание по остальным
  • Отсутствие многих, казалось бы, очевидных методов и свойств. Например: Exist, SetText, Select, SetCheck, InnerHtml и т.д. Вместо этого мы вынуждены довольствоваться Click, SendKeys и Text
  • Множество проблем на различных браузерах, например на Firefox и Chrome элемент кликается, а на IE — нет. Приходится писать special cases, «костыли»
  • Производительность. Да, драйвера работают не быстро. Впереди планеты всей как обычно IE — поиск может занимать секунды, иногда и десятки секунд

Поехали. Реализация враппера над браузером: класс Browser

Я очень надеюсь, что приведенный код не покажется вам сложным.
В примере инкапсулирован весь специфический для IWebDriver код, трусы наружу не высовываются, т.е. разработчики автотестов не будут иметь прямого доступа к драйверу. Как следствие — наличие методов, в которых вызывается метод драйвера с таким же названием.
В коде нет комментариев — это хороший тон, и как сказал один умный чел: «Комментарии в коде это как волосы в супе. Ты бы стал есть суп с волосами?!»
Я использую Microsoft Code Contracts, не пугайтесь вызовов.
Еще стоит заметить, что в тестируемом продукте подключен jquery, и некоторые действия будут производиться с его использованием.

Класс Browser поддерживает 3 браузера:

  • Firefox
  • Chrome
  • Internet Explorer

Очень жаль, что пока нет C# драйверов для Opera 🙁

Реализован набор стандартных методов и свойств вроде:

  • Start
  • Quit
  • Navigate
  • NavigateBack
  • Refresh
  • WaitReadyState
  • FindElements
  • SelectedBrowser
  • Url
  • Title
  • PageSource
  • др.

Думаю, названия вполне очевидные, поэтому идем дальше.

Также реализованы специфические функции:

  • WaitAjax
  • SwitchToFrame
  • SwitchToPopupWindow
  • AcceptAlert
  • GetScreenshot + SaveScreenshot
  • ResizeWindow
  • ExecuteJavaScript
  • DragAndDrop
  • др.

namespace Autotests.Utilities {     [Serializable]     public enum Browsers     {         [Description("Windows Internet Explorer")]         InternetExplorer,                  [Description("Mozilla Firefox")]         Firefox,          [Description("Google Chrome")]         Chrome     }      public static class Browser     {         #region Public properties          public static Browsers SelectedBrowser         {             get { return Settings.Default.Browser; }         }          public static Uri Url         {             get { WaitAjax(); return new Uri(WebDriver.Url); }         }          public static string Title         {             get             {                 WaitAjax();                 return string.Format("{0} - {1}", WebDriver.Title, EnumHelper.GetEnumDescription(SelectedBrowser));             }         }          public static string PageSource         {             get { WaitAjax(); return WebDriver.PageSource; }         }          #endregion          #region Public methods          public static void Start()         {             _webDriver = StartWebDriver();         }          public static void Navigate(Uri url)         {             Contract.Requires(url != null);              WebDriver.Navigate().GoToUrl(url);         }          public static void Quit()         {             if (_webDriver == null) return;              _webDriver.Quit();             _webDriver = null;         }          public static void WaitReadyState()         {             Contract.Assume(WebDriver != null);              var ready = new Func<bool>(() => (bool)ExecuteJavaScript("return document.readyState == 'complete'"));              Contract.Assert(Executor.SpinWait(ready, TimeSpan.FromSeconds(60), TimeSpan.FromMilliseconds(100)));         }          public static void WaitAjax()         {             Contract.Assume(WebDriver != null);              var ready = new Func<bool>(() => (bool)ExecuteJavaScript("return (typeof($) === 'undefined') ? true : !$.active;"));              Contract.Assert(Executor.SpinWait(ready, TimeSpan.FromSeconds(60), TimeSpan.FromMilliseconds(100)));         }          public static void SwitchToFrame(IWebElement inlineFrame)         {             WebDriver.SwitchTo().Frame(inlineFrame);         }          public static void SwitchToPopupWindow()         {             foreach (var handle in WebDriver.WindowHandles.Where(handle => handle != _mainWindowHandler))             {                 WebDriver.SwitchTo().Window(handle);             }         }          public static void SwitchToMainWindow()         {             WebDriver.SwitchTo().Window(_mainWindowHandler);         }          public static void SwitchToDefaultContent()         {             WebDriver.SwitchTo().DefaultContent();         }          public static void AcceptAlert()         {             var accept = Executor.MakeTry(() => WebDriver.SwitchTo().Alert().Accept());              Executor.SpinWait(accept, TimeSpan.FromSeconds(5));         }          public static IEnumerable<IWebElement> FindElements(By selector)         {             Contract.Assume(WebDriver != null);              return WebDriver.FindElements(selector);         }          public static Screenshot GetScreenshot()         {             WaitReadyState();              return ((ITakesScreenshot)WebDriver).GetScreenshot();         }          public static void SaveScreenshot(string path)         {             Contract.Requires(!string.IsNullOrEmpty(path));              GetScreenshot().SaveAsFile(path, ImageFormat.Jpeg);         }          public static void DragAndDrop(IWebElement source, IWebElement destination)         {             (new Actions(WebDriver)).DragAndDrop(source, destination).Build().Perform();         }          public static void ResizeWindow(int width, int height)         {             ExecuteJavaScript(string.Format("window.resizeTo({0}, {1});", width, height));         }          public static void NavigateBack()         {             WebDriver.Navigate().Back();         }          public static void Refresh()         {             WebDriver.Navigate().Refresh();         }          public static object ExecuteJavaScript(string javaScript, params object[] args)         {             var javaScriptExecutor = (IJavaScriptExecutor)WebDriver;              return javaScriptExecutor.ExecuteScript(javaScript, args);         }          public static void KeyDown(string key)         {             new Actions(WebDriver).KeyDown(Keys.Control);         }          public static void KeyUp(string key)         {             new Actions(WebDriver).KeyUp(Keys.Control);         }          public static void AlertAccept()         {             Thread.Sleep(2000);             WebDriver.SwitchTo().Alert().Accept();             WebDriver.SwitchTo().DefaultContent();         }          #endregion          #region Private          private static IWebDriver _webDriver;         private static string _mainWindowHandler;          private static IWebDriver WebDriver         {             get { return _webDriver ?? StartWebDriver(); }         }          private static IWebDriver StartWebDriver()         {             Contract.Ensures(Contract.Result<IWebDriver>() != null);              if (_webDriver != null) return _webDriver;              switch (SelectedBrowser)             {                 case Browsers.InternetExplorer:                     _webDriver = StartInternetExplorer();                     break;                 case Browsers.Firefox:                     _webDriver = StartFirefox();                     break;                 case Browsers.Chrome:                     _webDriver = StartChrome();                     break;                 default:                     throw new Exception(string.Format("Unknown browser selected: {0}.", SelectedBrowser));             }              _webDriver.Manage().Window.Maximize();             _mainWindowHandler = _webDriver.CurrentWindowHandle;              return WebDriver;         }          private static InternetExplorerDriver StartInternetExplorer()         {             var internetExplorerOptions = new InternetExplorerOptions                 {                     IntroduceInstabilityByIgnoringProtectedModeSettings = true,                     InitialBrowserUrl = "about:blank",                     EnableNativeEvents = true                 };              return new InternetExplorerDriver(Directory.GetCurrentDirectory(), internetExplorerOptions);         }          private static FirefoxDriver StartFirefox()         {             var firefoxProfile = new FirefoxProfile                 {                     AcceptUntrustedCertificates = true,                     EnableNativeEvents = true                 };              return new FirefoxDriver(firefoxProfile);         }          private static ChromeDriver StartChrome()         {             var chromeOptions = new ChromeOptions();             var defaultDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\..\Local\Google\Chrome\User Data\Default";              if (Directory.Exists(defaultDataFolder))             {                 Executor.Try(() => DirectoryExtension.ForceDelete(defaultDataFolder));             }              return new ChromeDriver(Directory.GetCurrentDirectory(), chromeOptions);         }          #endregion     } } 

Возможно стоит прокомментировать, что Settings.Default.Browser — это параметр, который задается в свойствах проекта, а Executor.SpinWait — некоторый хелпер, который дожидается выполнения условия с таймаутом и возвращает true / false. Так же встречаются magic strings, извиняюсь)

ссылка на оригинал статьи http://habrahabr.ru/post/180047/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *