Как правильно запускать браузер, часть 2

Чуть более года тому назад я написал статью, в которой рассказал, какими свойствами должен обладать “правильный” механизм запуска браузера. Эта статья должна была иметь продолжение, в котором я планировал описать реализацию такого механизма. Но желания не всегда совпадают с возможностями, и продолжение статьи тогда так и не появилось. Но некоторое время тому назад мы организовали рассылку “Сотня полезных советов про Selenium”, в рамках этой рассылки среди прочих была написана и заметка про то, как правильно запускать браузер, и мы решили опубликовать её как самостоятельную статью.

Ниже описана реализация универсального механизма запуска браузера, обладающего следующими возможностями:

  • запуск браузера “по требованию”, в момент первого использования,
  • повторное использование уже запущенного браузера,
  • автоматический перезапуск браузера, если требуется браузер с другими характеристиками,
  • автоматический запуск нового браузера, если предыдущий экземпляр недоступен,
  • автоматический перезапуск браузера после заданного количества использований,
  • автоматический останов браузера в конце, при завершении работы виртуальной машины Java.

К статье прилагается готовый проект, который содержит класс WebDriverFactory, реализующий этот механизм запуска браузера, а также ряд примеров его использования.

Со временем из этого примера выросла вполне самостоятельная и зрелая библиотека, читайте про неё в продолжении этой статьи

Сначала я вкратце опишу, как это работает (подробности смотрите в коде :)), а потом прокомментирую примеры.

Глядя на название класса WebDriverFactory вы можете предположить, что он реализует шаблон проектирования Object Factory. Это не совсем так, реализация не соответствует в точности ни одному из шаблонов, описанных в знаменитой книге “банды четырёх” Design Patterns.

Реализация класса WebDriverFactory представляет собой нечто среднее между фабрикой и синглетоном. Может быть это даже больше похоже на “пул объектов”, но маленький такой, одноместный пул. Но я буду далее употреблять слово “фабрика”, потому что оно красивое, и потому что все эти шаблоны проектирования так или иначе всё равно являются реализациями единой идеи.

Класс WebDriverFactory содержит метод getDriver, который по запросу создаёт экземпляр типа WebDriver, соответствующий переданному параметру capabilities. Ссылка на созданный объект сохраняется внутри фабрики, и при следующем обращении, если затребован драйвер с теми же самыми capabilities, используется ранее созданный экземпляр. Но если пришёл запрос на драйвер с другими capabilities, тогда предыдущий браузер останавливается, драйвер уничтожается и создаётся новый, с запрошенными параметрами.

Кроме того, WebDriverFactory автоматически останавливает браузер при завершении работы Java-машины.

Таким образом, вам вообще больше нет необходимости заботиться об остановке браузера, это происходит само либо тогда, когда вам понадобился новый браузер, либо в конце выполнения тестов.

Но это ещё не все возможности WebDriverFactory.

Если пришёл запрос, в ответ на который нужно вернуть уже имеющийся драйвер, фабрика проверяет, что браузер доступен – выполняет метод getCurrentUrl, и если он отработал без ошибок, считается, что с браузером всё в порядке. Если же браузер недоступен (завершился аварийно, или был закрыт вручную, или случайно был закрыт предыдущим тестом), создаётся новый драйвер, который запускает новый браузер.

И последняя возможность, весьма актуальная, если вы запускаете тесты для сложных приложений в браузере Firefox – периодический перезапуск браузера. Ни для кого не секрет, что Firefox “теряет память”, то есть в процессе работы занимаемая процессом память увеличивается, браузер работает всё медленнее и медленнее, а при достижении некоторого предела может даже упасть. Конечно, можно было бы как-то проверять объём занимаемой памяти, но WebDriverFactory устроен проще – он перезапускает браузер после заданного количества “использований”, то есть обращений к методу getDriver. Ну а частоту перезапуска вы можете подобрать сами экспериментальным путём.

Всё это достаточно хорошо видно, если посмотреть на код метода getDriver:

public static WebDriver getDriver(String hub, Capabilities capabilities) {
    count++;
    // 1. WebDriver instance is not created yet
    if (driver == null) {
      return newWebDriver(hub, capabilities);
    }
    // 2. Different flavour of WebDriver is required
    String newKey = capabilities.toString() + ":" + hub;
    if (!newKey.equals(key)) {
      dismissDriver();
      key = newKey;
      return newWebDriver(hub, capabilities);
    }
    // 3. Browser is dead
    try {
      driver.getCurrentUrl();
    } catch (Throwable t) {
      t.printStackTrace();
      return newWebDriver(hub, capabilities);
    }
    // 4. It's time to restart
    if (count > restartFrequency) {
      dismissDriver();
      return newWebDriver(hub, capabilities);
    }
    // 5. Just use existing WebDriver instance
    return driver;
  }

А теперь краткое пояснение к примерам.

TestNgWebDriverSample показывает типовой способ использования фабрики с фреймворком TestNG. Инициализация браузера, то есть вызов getDriver в фабрике, выполняется в @BeforeMethod, а остановка браузера – в @AfterSuite. TestNG представляет достаточно богатые возможности управления инициализацией, есть из чего выбирать – можно выполнить некоторый код перед каждым тестовым методом, перед всеми методами класса, перед группой методов, помеченных некоторым тегом, перед тестовым набором (то есть фактически один раз в самом начале выполнения тестов). То же самое относится и к завершающему коду. Но выбор здесь сделан неслучайно. Инициализация драйвера выполняется в @BeforeMethod, чтобы фабрика перед каждым тестовым методом проверяла, доступен ли браузер, и при необходимости перезапускала его, а также чтобы увеличивался счётчик количества использований и происходил периодический перезапуск браузера. А остановка выполняется в самом конце, в @AfterSuite, да и та не нужна, можно смело удалить её, потому что фабрика сама остановит браузер.

JUnitWebDriverSample представляет собой пример использования фабрики с фреймворком JUnit. Возможности этого фреймворка гораздо беднее, чем у TestNG, фактически можно лишь определить инициализацию, которая будет выполняться перед каждым тестовым методом. Но при использовании фабрики этого достаточно. Код инициализации выполняется перед каждым тестовым методом, и несмотря на это будет использоваться один и тот же драйвер, об этом позаботится фабрика. Код для остановки браузера отсутствует, потому что он не нужен, всё происходит автоматически.

Этот пример показывает, что использование фабрики позволяет вам выбрать даже “плохой” фреймворк, который не позволяет гибко управлять кодом инициализации и завершения, и при этом вы всё равно получаете возможность повторного использования браузера и автоматической остановки в конце выполнения тестов. Из этого также следует, что можно переписать фабрику на другой язык программирования и её можно будет использовать практически с любым фреймворком тестирования, сколь бы ни был он беден возможностями.

NoInitWebDriverSample является модификацией предыдущего примера, в нём нет методов инициализации – обращение к фабрике выполняется ровно в том месте, где вам потребовался браузер, то есть в непосредственно в тестовом методе. Опять таки нигде нет кода для остановки браузера, фабрика делает это сама.

VariousBrowsersWebDriverSample демонстрирует автоматический перезапуск браузера, когда затребован браузер другого типа.

RestartingWebDriverSample показывает, как настроить фабрику для периодического перезапуска браузера. В примере содержится три тестовых метода, а частота перезапуска указана равной двум. В результате после второго теста браузер будет остановлен, а третий тест будет выполняться в новом экземпляре браузера.

Ну и ещё раз ссылка на проект, содержащий класс WebDriverFactory и примеры его использования