Межбраузерние кодовані тести UI із застосуванням Visual Studio 2013

  1. Нова Visual Studio
  2. Тестована система
  3. перед кодуванням
  4. Стрибаємо в код
  5. Робота з будь-яким браузером
  6. Тестування, тестування і ще раз тестування
  7. Що буде якщо…
  8. Висновок

Веб-рішення стали популярними в останні кілька років, тому що вони забезпечують простий доступ для користувачів по всьому світу. Вони подобаються користувачам і за зручність. Користувачам не потрібно встановлювати окремий додаток; за допомогою браузерів вони можуть підключатися до своїх облікових записів з будь-якого пристрою, сполученого з Інтернетом. Однак для розробників і тестерів той факт, що користувач може вибрати будь-який веб-браузер, створює проблему: рішення доводиться тестувати з безліччю браузерів. У цій статті я продемонструю простий спосіб вирішення цього завдання, створивши на C # кодовані UI-тести, здатні виконуватися в будь-якому сучасному браузері.

Нова Visual Studio

Кілька років тому, коли була випущена Visual Studio 2010 однією з її найцікавіших особливостей була можливість тестувати UI веб-рішень. Однак в той час існували деякі обмеження на використання цієї технології; наприклад, єдиним підтримуваним веб-браузером був Internet Explorer. Більш того, тестування UI спиралося на запис дій користувача на веб-сайті та подальше їх відтворення для імітації реальних операцій, що багато розробників знаходили неприйнятним.

Нова Visual Studio 2013, доступна у вигляді кандидата на випуск (RC), вносить масу удосконалень і нововведень в багатьох областях - від нових засобів IDE до розширеної інфраструктури тестування (довгий список змін в RC-версії см. За посиланням bit.ly/1bBryTZ ). З моєї точки зору, особливий інтерес представляють два нових засоби. По-перше, тепер можна тестувати UI не тільки в Internet Explorer (включаючи Internet Explorer 11), а й у всіх інших сучасних браузерах, таких як Google Chrome і Mozilla Firefox. По-друге, з'явилося те, що з точки зору розробки тестів навіть важливіше, а саме (як кажуть в Microsoft): «конфігуруються властивості для кодованих тестів UI в браузері». По суті, ця нова функціональність визначає набір критеріїв пошуку для UI-елементів. Я опишу ці кошти докладніше далі в цій статті.

Тестована система

На основі цих двох нових засобів я створю межбраузерние, повністю кодовані тести UI (coded UI tests). Для своєї тестованої системи (system under test, SUT) я хочу використовувати загальнодоступне, добре відоме веб-додаток, тому я вибрав Facebook. Мені потрібно охопити два основних користувальницьких сценарію. Перший - позитивний (чистий) тестовий сценарій (positive test case), який буде показувати сторінку профілю після успішного входу. Другий - негативний (брудний) тестовий сценарій (negative test case), в якому я вводжу неправильні посвідчення користувача і намагаюся увійти. В цьому випадку я очікую будь-яке повідомлення про помилку у відповіді користувачу.

Мені доведеться вирішити кілька проблем. По-перше, потрібно запускати правильний браузер (на основі конфігурації тесту), і він повинен надавати доступ по конкретному URL. По-друге, в період виконання від HTML-документа повинен бути отриманий певний елемент управління, щоб забезпечити введення для імітованого користувача. Скрізь, де потрібно, в цей елемент управління потрібно вводити значення і клацати правильні кнопки для передачі HTML-форми сервера. Код також повинен бути здатний обробляти відповідь від сервера, перевіряти його і, нарешті, закривати браузер, коли тестові сценарії закінчуються (в методі очищення тесту).

перед кодуванням

Перш ніж почати кодувати, необхідно підготувати середу, що робиться досить просто. Насамперед скачайте Visual Studio 2013 RC по посиланню bit.ly/137Sg3U . За замовчуванням Visual Studio 2013 RC дозволяє створювати кодовані тести UI тільки для Internet Explorer, але це не те, в чому я зацікавлений; я хочу створити тести для всіх сучасних браузерів. Звичайно, ніяких помилок компіляції не буде, поки я вказую в своєму коді, що тести повинні запускатися стосовно браузерам, відмінним від Internet Explorer, але в період виконання буде згенеровано необроблюваних виняток. Трохи пізніше я покажу і то, як змінювати браузер. Щоб уникнути проблем при кодуванні, потрібно завантажити і встановити розширення Visual Studio під назвою «Selenium components for Coded UI Cross-Browser Testing» ( bit.ly/Sd7Pgw ), Яке дозволить виконувати тести в будь-якому браузері, встановленому на комп'ютері.

Стрибаємо в код

Тепер я можу продемонструвати, як створити новий проект Coded UI. Відкрийте Visual Studio 2013 і виберіть File | New Project | Templates | Visual C # | Tests | Coded UI Test Project. Введіть ім'я проекту і клацніть OK, щоб побачити нове рішення, як показано на рис. 1.

Мал
Мал. 1. Створення нового Coded UI Test Project

Рішення, по суті, ділиться на три тісно пов'язані групи (рис. 2). Перша група містить простір імен Pages, куди входить клас BasePage, від якого успадковуються ProfilePage і LoginPage. Ці класи надають властивості і логіку для операцій над сторінкою, яка відображається в даний момент в браузері. Такий підхід допомагає відокремити реалізації тестових сценаріїв від специфічних для браузера операцій, таких як пошук елемента управління по Id. Тестові сценарії безпосередньо маніпулюють властивостями і функціями, наданими класами сторінки.

Мал
Мал. 2. Схема рішення Coded UI Test

До другої групи я розміщую всі розширення (UIControlExtensions), селектори (SearchSelector) і загальні для браузерів класи (BrowserFactory, Browser). Це підмножина об'єктів містить логіку реалізації для механізму пошуку HTML-елементів (про це - пізніше). Крім того, я додав власні об'єкти, які стосуються браузеру, - вони допомагають у виконанні тестових сценаріїв з використанням правильного веб-браузера. Остання група включає файл тесту (FacebookUITests) з реалізаціями тестових сценаріїв. Ці тестові сценарії ніколи не працюють з браузером безпосередньо; замість цього вони використовують класи панелей (panel classes).

Важлива частина мого проекту - механізм пошуку HTML-елементів управління, тому в першу чергу я створюю статичний клас UIControlExtensions, що містить логіку реалізації для пошуку і отримання певних елементів управління від поточної відкритої в браузері сторінки. Щоб полегшити кодування і отримати можливість повторного використання, мені потрібно позбутися від ініціалізації екземплярів цього класів при кожному його використанні. Для цього я маю намір реалізувати його як метод розширення, який буде розширювати вбудований тип UITestControl. Крім того, всі реалізовані мною функції розширення будуть узагальненими. Вони повинні успадковуватися від HtmlControl (базового класу для всіх елементів управління UI в Coded UI Test Framework) і містити конструктор за замовчуванням без параметрів. Я хочу, щоб ця функція була узагальненої тому, що збираюся шукати тільки певні типи елементів управління (список доступних HTML-типів см. За посиланням bit.ly/1aiB5eW ).

Я буду передавати критерії пошуку в свою функцію через параметр selectorDefinition типу SearchSelector. Клас SearchSelector є простим, але дуже корисним типом. Він надає ряд властивостей, таких як ID або Class, які можна встановлювати з іншої функції і згодом перетворювати, використовуючи відображення, в клас PropertyExpressionCollection ( bit.ly/18lvmnd ). Потім цей набір властивостей можна застосовувати як фільтр для вилучення тільки невеликої підмножини HTML-елементів управління, які підходять зазначеним критеріям. Надалі згенерований набір властивостей призначається властивості SearchProperties ( bit.ly/16C20iS ) Узагальненого об'єкта, який дозволяє викликати властивість Exists і функцію FindMatchingControls. Врахуйте, що алгоритми Coded UI Test Framework за замовчуванням не будуть шукати зазначені елементи управління у всій сторінці і оброблятимуть всі дочірні і «внучаті» елементи тільки в розширеному UITestControl. Це допомагає збільшити продуктивність тестових сценаріїв, оскільки критерії пошуку можна застосовувати лише до невеликого підмножині HTML-документа, наприклад до всіх дочірнім елементам якогось DIV-контейнера, як показано на рис. 3.

Мал. 3. Пошук HTML-елементів управління

private static ReadOnlyCollection <T> FindAll <T> (this UITestControl control, SearchSelector selectorDefinition) where T: HtmlControl, new () {var result = new List <T> (); T selectorElement = new T {Container = control}; selectorElement.SearchProperties.AddRange (selectorDefinition.ToPropertyCollection ()); if (! selectorElement.Exists) {Trace.WriteLine (string.Format ( "Html {0} element does not exist for given selector {1}.", typeof (T) .Name, selectorDefinition), "UI CodedTest") ; return result.AsReadOnly (); } Return selectorElement .FindMatchingControls () .Select (c => (T) c) .ToList (). AsReadOnly (); }}

Я реалізував основну частину механізму пошуку, але функція FindAll <T> вимагає безлічі коду і знання того, як домогтися її належного функціонування, - ви повинні вказувати параметри пошуку, перевіряти, чи існує елемент, і т. Д. Ось чому я вирішив зробити її закритою і надавати замість неї дві інші функції:

public static T FindById <T> (this UITestControl control, string controlId) where T: HtmlControl, new () public static T FindFirstByCssClass <T> (this UITestControl control, string className, bool contains = true) where T: HtmlControl, new ( )

Ці узагальнені методи набагато корисніше, так як кожен з них виконує по одному завданню і тим самим скорочує список введення в прості типи. За лаштунками обидві функції викликають функцію FindAll <T> і оперують її результатом, але ця реалізація прихована.

Робота з будь-яким браузером

Я вже виконав якусь частину роботи з пошуку і отриманню елементів управління, але, щоб протестувати правильність реалізації функцій, мені потрібно запустити веб-браузер. Запуск конкретного браузера так само простий, як і будь-яка інша операція, що відноситься до браузеру. Я вже згадував, що хочу помістити все пов'язані з браузерами операції в класи, що відносяться до сторінки. Однак запуск браузера не є частиною тестування - це обов'язкова вимога. Виходячи з рекомендацій, я вирішив створити клас BasePage (рис. 4), що містить загальні операції для всіх похідних класів сторінки (включаючи запуск браузера), щоб уникнути будь-якої надмірності.

Мал. 4. Клас BasePage

public abstract class BasePage: UITestControl {protected const string BaseURL = "https://www.facebook.com/"; /// <summary> /// Отримуємо URL поточної сторінки /// </ summary> public Uri PageUrl {get; protected set;} /// <summary> /// Зберігаємо кореневий елемент управління для сторінки /// </ summary> protected UITestControl Body; /// <summary> /// Отримуємо вікно поточного браузера /// </ summary> protected BrowserWindow BrowserWindow {get; set; } /// <summary> /// Конструктор за замовчуванням /// </ summary> public BasePage () {this.ConstructUrl (); } /// <summary> /// Формуємо URL похідною сторінки на основі BaseURL /// і URL конкретної сторінки /// </ summary> /// <returns> A specific URL for the page. </ Returns> protected abstract Uri ConstructUrl (); /// <summary> /// Перевіряємо правильно відображення похідної сторінки /// </ summary> /// <returns> True if validation conditions passed. </ Returns> public abstract bool IsValidPageDisplayed (); }

Статична узагальнена функція Launch <T> також є частиною класу BasePage. У тілі цієї функції инициализируется новий екземпляр типу конкретної сторінки (похідного від BasePage) за допомогою конструктора за замовчуванням без параметрів. Далі в коді задається цільовий веб-браузер на основі значення параметра browser ( «ie» для Internet Explorer, «chrome» для Google Chrome і т. Д.). Цей параметр вказує браузер, в якому буде виконуватися поточний тест. Наступний крок - перехід по якомусь URL в обраному браузері. Ця операція обробляється в BrowserWindow.Launch (page.ConstructUrl ()), де ConstructUrl - конкретна функція для кожної похідної сторінки. Після запуску вікна браузера і переходу по конкретному URL я зберігаю HTML body у властивості BasePage і розгортаю вікно браузера (це не обов'язково, але бажано, оскільки елементи управління сторінки можуть перекриватися і автоматизовані UI-операції можуть завершитися невдачею). Потім я очищаю файли cookie, так як кожен тест повинен бути незалежним. Нарешті, в функції Launch (рис. 5) я перевіряю, чи є правильною поточна відображається сторінка, тому викликаю IsValidPageDisplayed, яка виконується в контексті узагальненої сторінки. Ця функція знаходить всі необхідні HTML-елементи управління (login, password, submit) і перевіряє, чи існують вони в сторінці.

Мал. 5. Функція Launch

public static T Launch <T> (Browser browser = Browser.IE, bool clearCookies = true, bool maximized = true) where T: BasePage, new () {T page = new T (); var url = page.PageUrl; if (url == null) {throw new InvalidOperationException ( "Unable to find URL for requested page."); } Var pathToBrowserExe = FacebookCodedUITestProject .BrowserFactory.GetBrowserExePath (browser); // Задаємо поточний браузер для тесту BrowserWindow.CurrentBrowser = GetBrowser (browser); var window = BrowserWindow.Launch (page.ConstructUrl ()); page.BrowserWindow = window; if (window == null) {var errorMessage = string.Format ( "Unable to run browser under the path: {0}", pathToBrowserExe); throw new InvalidOperationException (errorMessage); } Page.Body = (window.CurrentDocumentWindow.GetChildren () [0] as UITestControl) as HtmlControl; if (clearCookies) {BrowserWindow.ClearCookies (); } Window.Maximized = maximized; if (! page.IsValidPageDisplayed ()) {throw new InvalidOperationException ( "Invalid page is displayed."); } Return page; }

Веб-браузери постійно розвиваються, і ви можете втратити момент, коли щось змінюється. Іноді це означає, що в новій версії браузера деякі засоби більше не доступні, що в свою чергу призводить до провалу якихось тестів, навіть якщо раніше вони проходили. Ось чому важливо відключати автоматичне оновлення браузера і чекати, поки в компонентах Selenium для Coded UI Cross-Browser Testing чи не з'явиться підтримка нової версії. Інакше в період виконання можуть виникати несподівані винятки, як показано на рис. 6.

Мал
Мал. 6. Виключення після оновлення веб-браузера

Тестування, тестування і ще раз тестування

Нарешті, я напишу логіку для тестів. Як згадувалося, мені потрібно тестувати два основних користувальницьких сценарію. Перший - це позитивний процес входу (positive login process) (другий, негативний тестовий сценарій доступний у вихідному коді проекту, який можна завантажити за посиланням archive.msdn.microsoft.com/mag201312Testing ). Щоб виконати цей тест, я повинен створити клас конкретної сторінки, похідний від BasePage (рис. 7). У закриті поля нового класу я розміщую всі значення констант (імена елементів управління, ідентифікаторів і CSS-класу) і створюю виділені методи, які використовують ці константи для отримання певних UI-елементів від поточної сторінки. Крім того, я створюю функцію TypeCredentialAndClickLogin (string login, string password), яка повністю інкапсулює операцію входу (login operation). У період виконання вона знаходить всі необхідні елементи управління, імітує введення значень, переданих як параметри, а потім «натискає» кнопку Login, ініціюючи подія клацання лівої кнопки миші.

Мал. 7. Сторінка Login

public class LoginPage: BasePage {private const string LoginButtonId = "u_0_1"; private const string LoginTextBoxId = "email"; private const string PasswordTextBoxId = "pass"; private const string LoginFormId = "loginform"; private const string ErrorMessageDivClass = "login_error_box"; private const string Page = "login.php"; /// <summary> /// Формуємо URL для сторінки /// </ summary> /// <returns> Uri of the specific page. </ Returns> protected override Uri ConstructUrl () {this.PageUrl = new Uri ( string.Format ( "{0} / {1}", BasePage.BaseURL, LoginPage.Page)); return this.PageUrl; } /// <summary> /// Перевіряємо, чи правильна сторінка відображається /// </ summary> public override bool IsValidPageDisplayed () {return this.Body.FindById <HtmlDiv> (LoginTextBoxId)! = Null; } /// <summary> /// Отримуємо кнопку Login від сторінки /// </ summary> public HtmlInputButton LoginButton {get {return this.Body.FindById <HtmlInputButton> (LoginButtonId); }} /// <summary> /// Отримуємо текстове поле входу від сторінки /// </ summary> public HtmlEdit LoginTextBox {get {return this.Body.FindById <HtmlEdit> (LoginTextBoxId); }} /// <summary> /// Отримуємо текстове поле пароля від сторінки /// </ summary> public HtmlEdit PasswordTextBox {get {return this.Body.FindById <HtmlEdit> (PasswordTextBoxId); }} /// <summary> /// Отримуємо діалогове вікно помилки, якщо увійти не вдалося /// </ summary> public HtmlControl ErrorDialog {get {return this.Body.FindFirstByCssClass <HtmlControl> ( "* login_error_box"); }} /// <summary> /// Вставляємо логін і пароль в поля введення /// і клацаємо кнопку Login /// </ summary> public void TypeCredentialAndClickLogin (string login, string password) {var loginButton = this.LoginButton; var emailInput = this.LoginTextBox; var passwordInput = this.PasswordTextBox; emailInput.TypeText (login); passwordInput.TypeText (password); Mouse.Click (loginButton, System.Windows.Forms.MouseButtons.Left); }}

Після створення необхідних компонентів можна створити тестовий сценарій (test case). Ця функція тесту буде перевіряти успішне виконання операції входу. На початку тестового сценарію я запускаю сторінку Login за допомогою статичної функції Launch <T>. Всі необхідні значення я передаю в поля введення логіна і пароля, а потім імітують клацання кнопки Login. Після закінчення операції я перевіряю, чи є нова відображається панель сторінкою Profile:

[TestMethod] public void FacebookValidLogin () {var loginPage = BasePage.Launch <LoginPage> (); loginPage.TypeCredentialAndClickLogin (fbLogin, fbPassword); var profilePage = loginPage.InitializePage <ProfilePage> (); Assert.IsTrue (profilePage.IsValidPageDisplayed (), "Profile page is not displayed."); }

В процесі пошуку елемента управління з певним CSS-класом я помітив, що в Coded UI Test Framework може виникнути проблема. В HTML елементи управління можуть мати більше одного імені класу в атрибуті class, і це, звичайно ж, впливає на інфраструктуру, з якої я працюю. Якщо, наприклад, поточний веб-сайт містить елемент DIV з атрибутом class «ABC» і для пошуку всіх елементів управління з CSS-класом «B» використовується властивість SearchSelector.Class, то я можу не отримати ніякого результату - адже «ABC» не тотожне «B». Щоб усунути цю проблему, я ввів нотацію зірочки «*», яка змінює очікування класу з «equals» на «contains». Таким чином, щоб цей приклад працював, потрібно змінити клас «B» на «* B».

Що буде якщо…

Іноді тести провалюються, і ви ставите питанням - чому. У багатьох випадках відповідь на це питання дає аналіз журналу тесту. Але не завжди. У Coded UI Test Framework новий засіб повинен за вимогою надати додаткову інформацію.

Припустимо, тест провалився тому, що відображається сторінка відрізняється від очікуваної. У журналах я бачу, що деякі запитані елементи управління не знайдені. Це хороша інформація, але вона не дає повної відповіді. Однак за допомогою нового засобу я можу отримати знімок поточного екрану. Щоб задіяти цей засіб, потрібно просто додати його і запрограмувати спосіб його збереження в методі очищення тесту, як показано на рис. 8. Тепер я отримаю детальну інформацію про те, чому провалився тест.

Мал. 8. Метод очищення тесту

[TestCleanup] public void TestCleanup () {if (this.TestContext.CurrentTestOutcome! = Null && this.TestContext.CurrentTestOutcome.ToString () == "Failed") {try {var img = BrowserWindow.Desktop.CaptureImage (); var pathToSave = System.IO.Path.Combine (this.TestContext.TestResultsDirectory, string.Format ( "{0} .jpg", this.TestContext.TestName)); var bitmap = new Bitmap (img); bitmap.Save (pathToSave); } Catch {this.TestContext.WriteLine ( "Unable to capture or save screen."); }}}

Висновок

У цій статті я показав, наскільки легко і швидко приступити до використання нової інфраструктури Coded UI Test Framework в Visual Studio 2013 RC. Звичайно, я описав лише базове застосування цієї технології, включаючи управління різними браузерами і підтримку безлічі операцій пошуку, отримання і маніпулювання HTML-елементами управління. Однак функціональність цієї інфраструктури набагато ширше, і її варто досліджувати.