Проблема
В предыдущих частях туториала (GET, POST, PUT, PATCH и DELETE) вы, возможно, заметили, что в Playwright нет специального метода логирования записи запросов и ответов. В Rest Assured есть метод log().all()
, который используется для протоколирования запросов и ответов, однако в Playwright нет подобного метода. Но есть метод text()
в интерфейсе APIResponse
, который можно использовать для извлечения текста ответа.
Примечание. В настоящее время Playwright не дает возможности получить доступ к телу запроса и заголовкам запроса при выполнении API-тестирования. Поскольку при тестировании важно знать, какие заголовки и тела были переданы в запросе, было бы неплохо добавить такую функцию. Проблема уже обсуждалась на GitHub, поэтому давайте добавляйте свои upvote к этой issue.
В этом блоге мы научимся извлекать ответ и создавать кастомный логгер для подробного протоколирования ответов в любых API-тестах Playwright на Java.
Прежде чем приступить к кодированию и реализации, обсудим зависимости и конфигурации.
Настройки
Поскольку мы работаем с Maven, будем использовать зависимость Log4J2 Maven для логирования деталей ответа. Также будет использоваться зависимость Jackson Databind
для разбора ответа в JSON-формате.
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>${log4j-api-version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>${log4j-core-version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson-databind-version}</version> </dependency>
В качестве хорошей практики, версии этих зависимостей будут добавлены в блок Properties
, так как это позволит пользователям удобнее проверять и обновлять версии зависимостей в проекте.
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <log4j-core-version>2.24.1</log4j-core-version> <log4j-api-version>2.24.1</log4j-api-version> <jackson-databind-version>2.18.0</jackson-databind-version> </properties>
Следующим шагом будет создание файла log4j2.xml
в папке src/main/resources
. В этом файле хранится конфигурация логов — глубина протоколирования, куда должны выводиться логи (в консоль или в файл), шаблон разметки лога и т.д.
Раздел <Appenders>
содержит информацию, связанную с выводом лога, и формат. Раздел <Loggers>
содержит информацию об уровне детализации. В файле может быть несколько блоков <Loggers>
для разных уровней, типа «info«, «debug«, «trace» и т. д.
Репозиторий проекта на GitHub.
Реализация
Создается новый java-класс Logger
для реализации методов логирования деталей ответа.
public class Logger { private final APIResponse response; private final org.apache.logging.log4j.Logger log; public Logger (final APIResponse response) { this.response = response; this.log = LogManager.getLogger (getClass ()); } //... }
В этом классе глобально объявлены интерфейс APIResponse
из Playwright и интерфейс Logger
из Log4j. Это сделано для того, чтобы не дублировать объявление этих классов в методе и эффективнее их использовать.
Конструктор класса Logger
используется для создания объектов классов этих интерфейсов. Интерфейс APIResponse
добавляется в качестве параметра, так как нам нужно предоставить объект ответа этому классу для записи в лог.
Метод logResponseDetails()
реализует функцию для записи в лог информации ответа.
public void logResponseDetails() { this.log.info ("Logging Response Details....."); this.log.info ("Response Headers: \n{}", this.response.headers ()); this.log.info ("Status Code: {}", this.response.status ()); if (this.response.text () != null && !this.response.text () .isEmpty () && !this.response.text () .isBlank ()) { this.log.info ("Response Body: \n{}", prettyPrintJson (this.response.text ())); } this.log.info ("End of Logs!"); }
Первое утверждение в коде — информационное, в нем будет выведено упоминание о том, что началась генерация лога регистрации ответа.
Собственно печать логов начинается со второго утверждения в методе. Заголовки ответа будут выведены после выполнения второго оператора. Далее в лог будет выведен статус-код ответа.
Детали ответа возвращаются соответствующим методом, например post()
, put()
, get()
и т. д. интерфейса APIResponse
. (Однако есть случаи, например, запрос DELETE, когда тело ответа не отправляется).

Условие if()
добавлено сюда для обработки случая, когда тело ответа не отправлено. Это условие проверяет, что ответ, возвращаемый функцией response.text()
, не является null
, empty
или blank
, тогда в лог будет занесен только ответ.
Возвращаемый ответ не выводится в «красивом виде», то есть JSON отображается в String
, в виде длинных строк.
Поэтому, чтобы вывести ответ в нормальном виде, мы создаем метод prettyPrintJson()
, который принимает ответ в формате String
и возвращает его в удобочитаемом формате.
private String prettyPrintJson (final String text) { String prettyPrintJson = ""; if (text != null && !text.isBlank() && !text.isEmpty()) { try { final ObjectMapper objectMapper = new ObjectMapper (); final Object jsonObject = objectMapper.readValue (text, Object.class); prettyPrintJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject); return prettyPrintJson; } catch (final JsonProcessingException e) { this.log.error ("Error Printing Pretty Json : {}", e.getMessage ()); } } this.log.info ("No response body generated!"); return null; }
Этот метод принимает параметр String
, в котором будет находиться объект ответа. Объявляется новая String-строка prettyPrintJson
и инициализируется с пустым значением.
Далее выполняется проверка с помощью условия if()
, что предоставленный текст не является null
, не является blank
и не является empty
. Если условие выполняется, то инстанцируется класс ObjectMapper
из зависимости Jackson Databind
.
Далее считывается текстовое значение ответа, которое преобразуется и возвращается в удобочитаемом JSON-формате с помощью методов writerWithDefaultPrettyPrinter()
и writeValueAsString()
класса ObjectMapper
.

Если ответ оказывается null, empty или blank, он выводит в лог сообщение «No response body generated!!!
» и вернет null
, что означает, что ничего не будет возвращено и метод будет завершен.
Практика
Класс Logger
должен быть инстанцирован и его соответствующие методы должны быть вызваны, чтобы получить детали ответа, выводимые во время выполнения тестов.
Нам нужно убедиться, что мы не дублируем код в тестах для получения деталей ответа в логгер. Для этого используем класс BaseTest
и создаем новый метод logResponse(APIResponse response)
.
Этот метод будет принимать ответ в качестве параметра, а logResponseDetails()
будет вызываться после инстанцирования класса Logger
.
public class BaseTest { //... protected void logResponse (final APIResponse response) { final Logger logger = new Logger (response); logger.logResponseDetails (); } }
Поскольку класс BaseTest
расширяется на все классы Test
, становится проще вызывать методы непосредственно в тестовом классе.
Класс HappyPathTests
, который мы использовали для добавления happypath-тестов в предыдущих частях туториала, расширяет класс BaseTest
.
Давайте выведем логи ответов для теста API-запросов POST и GET.
testShouldCreateNewOrders()
проверяет, что новые заказы успешно созданы. Добавим метод logResponse()
к этому тесту и получим ответ, выведенный в лог.
public class HappyPathTests extends BaseTest{ @Test public void testShouldCreateNewOrders() { final int totalOrders = 4; for (int i = 0; i < totalOrders; i++) { this.orderList.add(getNewOrder()); } final APIResponse response = this.request.post("/addOrder", RequestOptions.create() .setData(this.orderList)); logResponse (response); //... // Assertion Statements... } }
Метод logResponse()
будет вызван после отправки POST-запроса. Это позволит нам узнать, какой ответ был получен, прежде чем мы начнем выполнять ассерты.
Тест TestShouldGetAllOrders()
проверяет API-запрос GET /getAllOrder
. Добавим метод logResponse()
к этому тесту и проверим, какие логи ответов будут выведены.
public class HappyPathTests extends BaseTest{ @Test public void testShouldGetAllOrders() { final APIResponse response = this.request.get("/getAllOrders"); logResponse (response); final JSONObject responseObject = new JSONObject(response.text()); final JSONArray ordersArray = responseObject.getJSONArray("orders"); assertEquals(response.status(), 200); assertEquals(responseObject.get("message"), "Orders fetched successfully!"); assertEquals(this.orderList.get(0).getUserId(), ordersArray.getJSONObject(0).get("user_id")); assertEquals(this.orderList.get(0).getProductId(), ordersArray.getJSONObject(0).get("product_id")); assertEquals(this.orderList.get(0).getTotalAmt(), ordersArray.getJSONObject(0).get("total_amt")); } }
Метод logResponse()
вызывается после отправки GET-запроса и выводит логи в консоль.
Выполнение тестов
Тесты будут выполняться последовательно: сначала будет выполнен POST-запрос (чтобы создались новые заказы), затем GET-запрос. Для этого будет использован файл testng-restfulecommerce-postandgetorder.xml
.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <suite name="Restful ECommerce Test Suite"> <test name="Testing Happy Path Scenarios of Creating and Updating Orders"> <classes> <class name="io.github.mfaisalkhatri.api.restfulecommerce.HappyPathTests"> <methods> <include name="testShouldCreateNewOrders"/> <include name="testShouldGetAllOrders"/> </methods> </class> </classes> </test> </suite>
При выполнении testng-restfulecommerce-postandgetorder.xml
выполняются POST- и GET-запросы, а ответ выводится в консоль:

Логи ответов POST:


Логи ответов GET:


Ответ корректно выводится в лог, теперь мы можем точнее оценить результаты выполнения своего теста.
Резюме
Кастомный логгер в проекте может помочь во многих отношениях. Он предоставляет более полную информацию о тестовых данных и результатах выполнения, обеспечивая полный контроль над тестами. Также помогает решать проблемы с упавшими тестами.