Тестируем HTTP-заголовки ответов сервера в Selenium и BrowserMob-Proxy в автотестах на Java

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

Одним из таких тестов может стать проверка ответа сервера на наличие и значение некоторого HTTP-заголовка (например, вам может понадобиться проверить корректно ли значение кодировки, декларируемое сервером в соответствующем заголовке).

Сам по себе Selenium не поддерживает такой тип проверок, однако, интегрировав его с прокси сервером BrowserMob-Proxy подобные сценарии станет возможно реализовать. Об этом мы сегодня и поговорим.

Описание примера: Проверяем заголовок в ответе сервера

Наш план - создать тест, открывающий веб-страницу и проверяющий, что кодировка, декларируемая сервером в качестве кодировки контента, соответствует кодировке UTF-8. Для этого нам потребуется распарсить содержимое заголовка Content-Type, приходящего нам с сервера в ответ на обращение браузера. Вот некоторые моменты, которые необходимо принять во внимание прежде чем мы продолжим:

  1. Нам необходимо сконфигурировать браузер таким образом, чтобы обращения к тестируемому сайту шли через прокси-сервер

  2. Нам необходимо сконфигурировать прокси таким образом, чтобы заголовки ответов сохранялись где-то для последующей проверки

  3. Когда мы обращаемся к некоторой странице из браузера, браузер также загружает все ресурсы, используемые в коде страницы (изображения, javascript, CSS, и т.д.). Таким образом вызов одной страницы приводит ко множественным обращениям к серверу.

Не забудьте добавить зависимость BrowserMob-Proxy в ваш проект (либо добавьте соответствующую библиотеку в classpath проекта):

<dependency>
    <groupId>net.lightbody.bmp</groupId>
    <artifactId>browsermob-core</artifactId>
    <version>2.1.5</version>
    <scope>test</scope>
</dependency>

Здесь я также предполагаю, что вы уже знакомы с тем как запустить BrowserMob-Proxy из кода теста и связать с ним ваш WebDriver. Если вы пока не умеете этого делать, рекомендую обратиться к другим моим статьям на данную тему (например через хэштег #browsermob-proxy).

Детали реализации

Учитывая, что браузер может инициировать несколько вызовов к серверу для одного обращения к веб-странице, мы будем хранить заголовки всех таких вызовов. Для хранения этих данных мы воспользуемся следующей структурой:

Map<String, Map<String,String>>

Другими словами мы создадим Map, который будет хранить соответствия URL каждого ресурса, загружаемого страницей, и еще один Map, который будет содержать пары "имя заголовка:значение заголовка". Взглянем на наш план имплементации:

  1. Сперва реализуем метод, возвращающий значение заголовка из описанной структуры данных

  2. Затем реализуем метод, выделяющий значение кодировки из заголовка Content-Type, а также сравнивающий это значение с ожидаемым значением

  3. Далее мы реализуем листенер внутри прокси, который будет выделять заголовки из ответов сервера и заполнять данными нашу структуру

  4. Наконец, мы реализуем небольшой тест, который всё это запустит

Извлекаем значение заголовка из структуры данных

Принимая во внимание вышеизложенное, мы реализуем метод, принимающий на вход коллекцию описанного типа, URL, значение заголовка для которого мы хотим получить и имя самого заголовка. Метод будет возвращать значение для заголовка, удовлетворяющего указанным критериям.

private String getHeaderValue(Map<String, Map<String,String>> allHeaders,
                              String headerName,
                              String url){
    if(!allHeaders.containsKey(url)){
        throw new NoSuchElementException("Headers for requested URL have not been found...");
    }
    return allHeaders.get(url).get(headerName);
}

Реализуем проверку заявленной кодировки для контента

Ниже представлен упрощенный алгоритм, проверяющий кодировку контента, заявляемую сервером. Этот алгоритм предусматривает только одно возможное представление свойства charset (свойство идёт последним в списке, отделено ведущим пробелом и не обрамлено кавычками).

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

Итак, логика метода будет такой:

private boolean testContentEncoding(String contentTypeValue, String expectedCharset){
    return contentTypeValue.toLowerCase().endsWith(" charset=" + expectedCharset.toLowerCase());
}

Реализуем листенер в прокси

Здесь мы реализуем оставшуюся часть нашего решения. Эта часть будет представлять собой некоторый псевдо-тест, выводящий результат проверки в консоль.

@Test
public void testHeaders(){

    Map<String, Map<String, String>> urlToHeaders = new HashMap<>();

    browserMobProxy.addResponseFilter((response, contents, messageInfo) -> {
        Map<String, String> headers = new HashMap<>();
        for(Map.Entry<String, String> httpHeader: response.headers()){
            headers.put(httpHeader.getKey(), httpHeader.getValue());
        }
        urlToHeaders.put(messageInfo.getOriginalUrl(), headers);
    });

    driver.get("https://google.com");

    System.out.println(
        testContentEncoding(
            getHeaderValue(
                urlToHeaders,
                "Content-Type",
                "https://google.com/"),
            "UTF-8")
    );
}

Ключевая часть теста - подготовка прокси к перехвату ответов сервера и вычленение из этих ответов всех заголовков. Эта же логика наполняет выделенными заголовками подготовленную структуру данных:

browserMobProxy.addResponseFilter((response, contents, messageInfo) -> {
    Map<String, String> headers = new HashMap<>();
    for(Map.Entry<String, String> httpHeader: response.headers()){
        headers.put(httpHeader.getKey(), httpHeader.getValue());
    }
    urlToHeaders.put(messageInfo.getOriginalUrl(), headers);
});

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

urlToHeaders.clear();

Таким образом, мы научились извлекать заголовки из ответов, возвращаемых браузеру тестируемым приложением. Мы также определили некоторые важные моменты, на которые следует обращать внимание при реализации подобных сценариев в автоматизированных тестах на Selenium.

В заключение, хочу добавить, что очень ценю обратную связь, получаемую от вас. Прошу оставлять отзывы, пожелания и предложения, используя эту форму обратной связи. Опираясь на ваши пожелания, я формирую будущие темы, а также исправляю существующие статьи.