Для тех кто сталкивался с необходимостью реализовать автоматизированные тесты для нескольких браузеров, знакома проблема неодинакового взаимодействия с элементами страницы.
Например, в Firefox вам удается просто кликнуть на элемент через FindElement().Click(),
а для IE приходится использовать javascript() или кликать два раза или еще что-нибудь.
Один из выходов - использовать If или switch. Но если для 5 браузеров у нас разная реализация, то решение получается не красивым.
Таким образом, возникает задача, реализовать связку - браузер-действие, при чем желательно чтобы система сама понимала какой браузер сейчас вызван.
Click(), MoveToElementAndClick() и ExecuteJavaScript()
Класс назван CommonActions, чтобы не путать его с Actions.
Так же, у нас есть класс страницы или какой-нибудь хелпер, где эти методы используются
В классе реализован метод, который закрывает какое-то сообщение, нажимая на кнопку с Id “elementId”.
Хорошо, если такой метод работает для всех браузеров, а что если в IE Webdriver не “хочет” нажимать на кнопку и никакие workarounds кроме нажатия через javascript не подходят?
Получается, что у нас метод “ClickCloseButton” имеет разные реализации для IE и например chrome, другими словами, метод зависит от браузера.
назовем их
ChromeActions, IeActions и DefaultActions
В IE ClickCloseButton специфично, следовательно
Реализуем Interface IBrowserActions
Но как же интерфейс поймет, какую реализацию ему сейчас использовать? Нам нужно задать соответствие м/у браузером и реализацией. Одним из решений служит использование IoC.
Вот как реализовано у меня (за основу взято с этого сайта )
В методе UnityDependencyResolver и задается соответствие м/у интерфейсами, реализацией и браузером.
Когда IoC реализован, вернемся к DependentActions. Добавим экземпляр IBrowserActions с замаппированным контейнером в конструктор класса.
Теперь нужно вызвать IoC вместе с драйвером приложения
IoC.Initialize(new UnityDependencyResolver());
Например, в Firefox вам удается просто кликнуть на элемент через FindElement().Click(),
а для IE приходится использовать javascript() или кликать два раза или еще что-нибудь.
Один из выходов - использовать If или switch. Но если для 5 браузеров у нас разная реализация, то решение получается не красивым.
Таким образом, возникает задача, реализовать связку - браузер-действие, при чем желательно чтобы система сама понимала какой браузер сейчас вызван.
Обрисовка проблемы
У нас есть класс которые содержит самые простые действия, такие какClick(), MoveToElementAndClick() и ExecuteJavaScript()
public class CommonActions { protected void ExecuteJavaScript(string script) { } protected void MoveToElementAndClick(By by) { } protected void Click(By by) { } }
Класс назван CommonActions, чтобы не путать его с Actions.
Так же, у нас есть класс страницы или какой-нибудь хелпер, где эти методы используются
public class TestPage : CommonActions { public void CloseAnyMessage() { ClickCloseButton(By.Id(“elementId”)) } private void ClickCloseButton(By by) { Click(by) } }
В классе реализован метод, который закрывает какое-то сообщение, нажимая на кнопку с Id “elementId”.
Хорошо, если такой метод работает для всех браузеров, а что если в IE Webdriver не “хочет” нажимать на кнопку и никакие workarounds кроме нажатия через javascript не подходят?
Получается, что у нас метод “ClickCloseButton” имеет разные реализации для IE и например chrome, другими словами, метод зависит от браузера.
Решение.Создание зависимого класса
Следовательно, нам нужно создать класс, где у нас будут лежать все “зависимые” действия. Назовем его DependentActionspublic class DependentActions: CommonActions { protected void ClickCloseButton() { …........ } }И сделаем так, чтобы класс TestPage наследовался уже от DependentActions
public class TestPage : DependentActions { … }Теперь займемся реализацией метода ClickCloseButton для IE и Chrome
Решение.Разделение действий для браузеров
Каждый набор действий для отдельного браузера будет лежать в своем классе. Если действие одинаковое для двух браузеров и отличное для третьего, то оно помещается в отдельный класс.назовем их
ChromeActions, IeActions и DefaultActions
public class DefaultActions: CommonActions { } public class IeActions : DefaultActions { } public class ChromeActions: DefaultActions { }Класс DefaultActions знает о всех CommonActions. Методы в классе виртуальные для того, чтобы потом их можно было переопределить.
Решение. Реализация DefaultActions
Реализация методов в DefaultActionspublic virtual void ClickCloseButton() { Click(By.Id(“elementId”)) }Если для Chrome реализация действия не специфично, то в ChromeActions метод ClickCloseButton не переопределяется.
В IE ClickCloseButton специфично, следовательно
public class IeActions : DefaultControls { public override void ClickCloseButton() { ExecuteJavaScript(“javascript”) } }Теперь осталось собрать реализации в одном месте.
Решение. Сбор реализаций
Чтобы собрать все реализации я использовал interfacesРеализуем Interface IBrowserActions
public interface IBrowserActions { void ClickCloseButton(); }И добавим в наши классы браузеров наследование от интерфейса
public class DefaultActions: CommonActions, IBrowserActions { } public class ChromeActions: DefaultControls, IBrowserActions { }В класс DefaultControls от интерфейса не наследуется, так как он не зависит от браузеров.
Но как же интерфейс поймет, какую реализацию ему сейчас использовать? Нам нужно задать соответствие м/у браузером и реализацией. Одним из решений служит использование IoC.
Решение.Реализация IoC
Для реализации IoC используется Unity. Как их связать можно найти в интернете (или написать самому)Вот как реализовано у меня (за основу взято с этого сайта )
public class UnityDependencyResolver : IDependencyResolver { private readonly IUnityContainer _container; public UnityDependencyResolver() { _container = new UnityContainer(); _container.RegisterType<IControls, IeControls>(BrowserNames.InternetExplorer); _container.RegisterType<IControls, ChromeControls>(BrowserNames.Chrome); _container.RegisterType<IControls, FirefoxControls>(BrowserNames.Firefox); _container.RegisterType<IControls, SafariControls>(BrowserNames.Safari); } public T Resolve<T>() { return _container.Resolve<T>(); } public T Resolve<T>(string name) { return _container.Resolve<T>(name); } }
В методе UnityDependencyResolver и задается соответствие м/у интерфейсами, реализацией и браузером.
Когда IoC реализован, вернемся к DependentActions. Добавим экземпляр IBrowserActions с замаппированным контейнером в конструктор класса.
Решение.Реализация зависимости от браузера
public class DependentActions: CommonActions { private readonly IBrowserActions _browserActions; protected DependentActions() { _browserActions = IoC.Resolve<IBrowserActions>(BrowserName); } protected void ClickCloseButton() { _browserActions.ClickCloseButton(); } }Теперь у нас в DependentActions есть метод, который работает по разному в зависимости от текущего браузера.
Теперь нужно вызвать IoC вместе с драйвером приложения
IoC.Initialize(new UnityDependencyResolver());
Выводы.
Реализация тестов для нескольких браузеров - интересная и творческая задача. На самом деле, нужно стремиться уменьшать количество "зависимых" действий, искать более универсальные способы взаимодействия с контролами.
Лично для меня эта задача была очень интересна в плане изучения нового (IoC).
И я хочу поблагодарить за помощь в реализации моего коллегу Авдеева Владимира.
Комментариев нет:
Отправить комментарий