Всем привет. Немного ранее мы рассмотрели использование фреймворка TestStack White для автоматизации десктопного приложения, а в частности калькулятора Windows.
В этой статье мы пойдем далее и создадим основу поддерживаемого проекта.
Сразу привожу скриншот проекта в Visual Studio:
Он состоит из двух подпроектов – основы фреймворка автоматизации и самих тестов.
Основным классом проекта является класс CalcFactory, который используя паттерн Singleton, объявляет свойство Instance, которое в свою очередь мы будем использовать при доступе к методам этого класа. Например, к методу запуска приложения (калькудятора) Launch().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
using Calculator.Automation.Framework.Internals; using System; using TestStack.White; using TestStack.White.UIItems.WindowItems; namespace Calculator.Automation.Framework { /// <summary> /// Calculator application factory /// </summary> public class CalcFactory : IDisposable { /// <summary> /// instance variable for singleton /// </summary> private static CalcFactory _instance; /// <summary> /// singleton property /// </summary> public static CalcFactory Instance { get { return _instance ?? (_instance = new CalcFactory()); } } /// <summary> /// calc app path /// </summary> private const string CalculatorExecutablePath = "calc.exe"; /// <summary> /// <see cref="TestStack.White.Application"/> /// </summary> private Application _calcApp; /// <summary> /// Initializes application /// </summary> public void Launch() { if (_calcApp != null) return; _calcApp = Application.Launch(CalculatorExecutablePath); } /// <summary> /// Gets window object /// </summary> /// <typeparam name="T">Type of window</typeparam> /// <returns>returns instance of an object</returns> public T GetWindow<T>() { Window whiteWin = _calcApp.GetWindow(typeof(T).GetWindowInfo().Title); var window = (T)Activator.CreateInstance(typeof(T), new object[] { whiteWin }); return window; } /// <summary> /// Closes calc /// </summary> public void Dispose() { _calcApp.Close(); _calcApp.Dispose(); _calcApp = null; _instance = null; } } } |
Вторым по важности классом является класс WindowBase, который предоставляет методы для доступа к контролам приложения, например, по Name, Id или по обеим сразу.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
using System.Collections.Generic; using System.Linq; using TestStack.White.UIItems; using TestStack.White.UIItems.Finders; using TestStack.White.UIItems.WindowItems; namespace Calculator.Automation.Framework.Mappings { /// <summary> /// Base class for window objects /// </summary> public abstract class { /// <summary> /// <see cref="TestStack.White.Window"/> /// </summary> private Window _window; /// <summary> /// Collection of items in a window /// </summary> private List<IUIItem> _uiItems; /// <summary> /// Flag to determine if ui items are initialized /// </summary> private bool _initilized; /// <summary> /// Initializes instance of WindowsBase class /// </summary> /// <param name="window">Window</param> protected WindowBase(Window window) { _window = window; } /// <summary> /// <see cref="TestStack.White.Window"/> /// </summary> protected Window Window { get { return _window; } } /// <summary> /// Gets UI element by name /// </summary> /// <typeparam name="T">Type of ui element</typeparam> /// <param name="name">name of ui element</param> /// <returns>instnace of ui element</returns> protected T GetByName<T>(string name) where T : IUIItem { Initialize(); return (T)_uiItems.SingleOrDefault(item => item.AutomationElement.Current.Name == name); } /// <summary> /// Gets UI element by name /// </summary> /// <typeparam name="T">type of ui element</typeparam> /// <param name="id">id of element</param> /// <returns>instance of ui element</returns> protected T GetById<T>(string id) where T : IUIItem { Initialize(); return (T)_uiItems.SingleOrDefault(item => item.AutomationElement.Current.AutomationId== id); } /// <summary> /// Gets ui element by name and id /// </summary> /// <typeparam name="T">type of ui element</typeparam> /// <param name="name">name of ui element</param> /// <param name="id">id of ui element</param> /// <returns>instance of ui element</returns> protected T GetByNameAndId<T>(string name, string id) where T : IUIItem { Initialize(); return (T)_uiItems.SingleOrDefault(item => item.AutomationElement.Current.Name == name && item.AutomationElement.Current.AutomationId == id); } /// <summary> /// Initialies collection of ui elements /// </summary> private void Initialize() { if (_initilized) return; _uiItems = _window.GetMultiple(SearchCriteria.All).ToList(); _initilized = true; } } } |
Класс MainWindow, который наследуется от предыдущего класса, непосредственно относится к автоматизируемому приложению. В нем объявляются свойства типа Button, MenuBar, над которыми мы и будем совершать действия.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
using Calculator.Automation.Framework.Attributes; using TestStack.White.UIItems; using TestStack.White.UIItems.WindowItems; using TestStack.White.UIItems.WindowStripControls; namespace Calculator.Automation.Framework.Mappings { /// <summary> /// Main window /// </summary> [WindowInfo(Title = "Calculator")] public class MainWindow : WindowBase { /// <summary> /// Initializes MainWindow class /// </summary> /// <param name="window">Window</param> public MainWindow(Window window) : base (window) { } /// <summary> /// Gets divide button /// </summary> public Button Divide { get { return GetByNameAndId<Button>("Divide", "91"); } } /// <summary> /// Gets multiply button /// </summary> public Button Multiply { get { return GetByNameAndId<Button>("Multiply", "92"); } } /// <summary> /// Gets add button /// </summary> public Button Add { get { return GetByNameAndId<Button>("Add", "93"); } } /// <summary> /// Gets subtract button /// </summary> public Button Subtract { get { return GetByNameAndId<Button>("Subtract", "94"); } } /// <summary> /// Gets menu bar /// </summary> public MenuBar MenuBar { get { return Window.MenuBar; } } /// <summary> /// Gets order of y root button /// </summary> public Button YRoot { get { return GetByNameAndId<Button>("Order of y root", "96"); } } /// <summary> /// Gets sine button /// </summary> public Button Sine { get { return GetByNameAndId<Button>("Sine", "102"); } } /// <summary> /// Gets cosine button /// </summary> public Button Cosine { get { return GetByNameAndId<Button>("Cosine", "103"); } } /// <summary> /// Gets equals button /// </summary> public Button EqualsButton { get { return GetByNameAndId<Button>("Equals", "121"); } } /// <summary> /// Gets result value /// </summary> public double Result { get { return double.Parse(GetById<Label>("150").Text); } } /// <summary> /// Gets digit button /// </summary> /// <param name="digit">digit</param> /// <returns>instance of a button</returns> public Button GetDigitButton(char digit) { return GetByNameAndId<Button>(digit.ToString(), (130 + char.GetNumericValue(digit)).ToString()); } /// <summary> /// sets digit by clicking buttons /// </summary> /// <param name="digit"></param> public void SetDigitByButtonClick(double digit) { foreach (char d in digit.ToString()) { GetDigitButton(d).Click(); } } } } |
Остальные два класса являются вспомогательными и предназначены лишь для того, чтобы переключатся между диалоговами окнами приложения, используя их атрибуты.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
using Calculator.Automation.Framework.Attributes; using System; namespace Calculator.Automation.Framework.Internals { /// <summary> /// Extension methods /// </summary> internal static class Extensions { /// <summary> /// Gets <see cref="Calculator.Automation.Framework.Attributes.WindowInfoAttribute"/> /// </summary> /// <param name="type">type</param> /// <returns><see cref="Calculator.Automation.Framework.Attributes.WindowInfoAttribute"/></returns> internal static WindowInfoAttribute GetWindowInfo(this Type type) { var windowInfo = (WindowInfoAttribute)Attribute.GetCustomAttribute(type, typeof(WindowInfoAttribute)); if (windowInfo == null) throw new ArgumentException(string.Format("{0} type doesnot contain WindowInfoAttribute", type.ToString())); return windowInfo; } } } |
Обратите внимание на атрибут, который мы будем использовать, – название (Title).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
using System; namespace Calculator.Automation.Framework.Attributes { /// <summary> /// Provides additional info about window object /// </summary> [AttributeUsage(AttributeTargets.Class)] public class WindowInfoAttribute : Attribute { private string _title; public WindowInfoAttribute() { } public WindowInfoAttribute(string title) { _title = title; } public string Title { get { return _title; } set { _title = value; } } } } |
В следующей статье мы рассмотрим написание и выполнения автотестов. Если возникли какие-то вопросы или нужно более детальное описание, спрашивайте, не стесняйтесь