вторник, 11 декабря 2012 г.

Реализация собственных действий для каждого браузера при авто-тестировании

Для тех кто сталкивался с необходимостью реализовать автоматизированные тесты для нескольких браузеров, знакома проблема неодинакового взаимодействия с элементами страницы.
Например, в 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, другими словами, метод зависит от браузера.

Решение.Создание зависимого класса

Следовательно, нам нужно создать класс, где у нас будут лежать все “зависимые” действия. Назовем его DependentActions
public 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

Реализация методов в DefaultActions
public 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). 
И я хочу поблагодарить за помощь в реализации моего коллегу Авдеева Владимира.

Комментариев нет:

Отправить комментарий