Вступление
Снова здравствуйте! Представляю вам вторую часть статьи на тему автоматизированного тестирование веб-приложения на 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/
Добавить комментарий