- Подготовка, установка и настройка
- Тестовый сценарий 1 — Добавление заказов
- Сценарий 2 — Некорректная нагрузка в запросе
Подготовка
Прежде чем приступить к написанию тестовых сценариев опишем, что нужно поставить и настроить.
Ставим и настраиваем
Ниже перечислены необходимые условия, которые должны быть установлены на машине.
- Java JDK 17
- IntelliJ IDE или любая другая IDE
- Maven
Создание нового проекта
Будем создавать Maven-проект с помощью IntelliJ IDE. Шаги создания Maven-проекта:
- Откройте IntelliJ, перейдите в меню Файл >> Новый >> Проект:

- В окне Новый проект введите:
- Название проекта
- Путь, где будет сохранен проект
- Выберите версию JDK — я использую JDK 17
- Archetype — введите слово «quickstart» и выберите maven-archetype-quickstart из результатов
- Нажмите на кнопку Создать, чтобы создать проект

После этого проект Maven должен быть создан в IntelliJ IDE.
Если вы новичок в IntelliJ, вот ютуб по созданию Maven-проекта (на английском).
Обновляем зависимости
После успешного создания проекта нужно добавить в pom.xml следующие зависимости и плагины:
- Playwright — Java
- TestNG
- Lombok — для использования паттерна builder и генерации POJO во время выполнения
- Gson — для сериализации
- Datafaker — для генерации тестовых данных во время выполнения
- Плагин Maven Surefire
- Плагин Maven Compiler.
Файл: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>io.github.mfaisalkhatri</groupId> <artifactId>api-testing-playwright</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>api-testing-playwright</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <playwright-version>1.47.0</playwright-version> <testng-version>7.10.2</testng-version> <lombok-version>1.18.34</lombok-version> <datafaker-version>2.3.1</datafaker-version> <gson-version>2.11.0</gson-version> <maven-compiler-plugin-version>3.13.0</maven-compiler-plugin-version> <maven-surefire-plugin-version>3.3.1</maven-surefire-plugin-version> <java-release-version>17</java-release-version> <suite-xml>test-suite/testng.xml</suite-xml> </properties> <dependencies> <dependency> <groupId>com.microsoft.playwright</groupId> <artifactId>playwright</artifactId> <version>${playwright-version}</version> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>${testng-version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok-version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>net.datafaker</groupId> <artifactId>datafaker</artifactId> <version>${datafaker-version}</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>${gson-version}</version> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven-compiler-plugin-version}</version> <configuration> <release>${java-release-version}</release> <encoding>UTF-8</encoding> <forceJavacCompilerUse>true</forceJavacCompilerUse> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${maven-surefire-plugin-version}</version> <executions> <execution> <goals> <goal>test</goal> </goals> </execution> </executions> <configuration> <suiteXmlFiles> <suiteXmlFile>${suite-xml}</suiteXmlFile> </suiteXmlFiles> <argLine>-Dfile.encoding=UTF-8 -Xdebug -Xnoagent</argLine> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <version>${maven-enforcer-version}</version> <executions> <execution> <id>enforce-maven</id> <goals> <goal>enforce</goal> </goals> <configuration> <rules> <requireMavenVersion> <version>3.0</version> </requireMavenVersion> </rules> </configuration> </execution> </executions> </plugin> </plugins> </pluginManagement> </build> </project>
Обновление версий зависимостей в блоке Properties является хорошей практикой и помогает проверять и обновлять версии maven в проекте.
После создания проекта Maven и обновления зависимостей в pom.xml проект будет готов.
Тестируемое приложение
Мы будем использовать бесплатные API restful-ecommerce (ссылка). Он предоставляет список API управления заказами, связанных с электронной коммерцией, для создания, получения, обновления и удаления заказов. Кроме того, API предоставляет JWT-токен, который можно использовать в качестве инструмента безопасности при обновлении и удалении записей заказов.
Этот проект может быть установлен на локальной машине с помощью NodeJS или с помощью Docker. Более подробно об этом здесь.
Что такое POST-запрос
Прежде чем мы погрузимся в мир автоматизации тестирования API, давайте немного освежим знания о HTTP-методе POST.
Запрос POST используется для отправки данных на сервер для создания новой записи в системе. В идеале POST-запрос предполагает предоставление некоторых данных вместе с запросом. Тело этих данных зависит от заголовка Content-Type. Чаще всего для REST API для передачи данных в POST-запросе используется JSON.
Однако в зависимости от Content-Type для передачи данных в POST-запросе также могут использоваться XML и другие типы данных. POST-запросы обычно возвращают в ответ код состояния 201.
В следующем примере API restful-ecommerce, POST-запрос /addOrder отправляет данные на сервер для создания нового заказа (заказов).

После успешного выполнения POST-запроса в системе создаются необходимые данные. В ответ возвращается код состояния 201, вместе с подтверждением и информацией о созданном в системе заказе.

POST API в Playwright/Java
Мы должны быть знакомы с некоторыми интерфейсами и классами Playwright, которые могут применяться для автоматизации тестирования API. В данном случае мы будем использовать API restful-ecommerce для написания тестов API по счастливым и печальным сценариям. Будем последовательно выполнять код и комментировать детали, чтобы убедиться, что все понятно.
Тестовый сценарий 1 — Добавление заказов
- Запустите службу restful-ecommerce
- Сгенерируйте тестовые данные во время выполнения, с помощью Lombok и DataFaker
- Создайте 4 различных заказа в системе с помощью API-запроса POST /addOrder, предоставив необходимый набор данных в формате JSON
- Убедитесь, что в ответ получен статус-код 201
- Убедитесь, что данные заказа сгенерированы правильно, как указано в POST-запросе

Реализация теста
Для реализации тестового сценария мы создадим новый пакет в папке src/test/ и назовем его «restfulecommerce«. В дальнейшем будем использовать этот пакет для создания тестовых классов и управления тестовыми данными.
Первым шагом на пути к реализации будет написание класса BaseTest, который будет заниматься установкой и настройкой Playwright. Этот класс BaseTest будет реюзабельным (повторно использоваться) в тестовых классах, чтобы избежать дублирования кода и упростить сопровождение тестовых сценариев.
Написание BaseTest
Создадим новый java-класс с именем BaseTest внутри пакета «restfulecommerce«. Интерфейсы Playwright и APIRequestContext будут объявлены в этом классе глобально, поскольку нам нужно будет вызывать эти интерфейсы в методах setup() и tearDown() в одном и том же классе.
public class BaseTest { protected Playwright playwright; protected APIRequestContext request; private static final String BASE_URL = "http://localhost:3004"; //... }
Переменная BASE_URL будет объявлена как static final глобально, так как она будет оставаться постоянной на протяжении всего цикла выполнения тестов. URL = localhost, так как приложение restful-ecommerce будет запускаться локально, а тесты будут выполняться на соответствующих API.
Два новых метода — createPlaywright() и createAPIRequestContext() — создаются с модификатором доступа private, так как эти методы будут использоваться только в этом классе BaseTest.
private void createPlaywright() { playwright = Playwright.create(); }
private void createAPIRequestContext() { Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/json"); request = playwright.request().newContext(new APIRequest.NewContextOptions() .setBaseURL(BASE_URL) .setExtraHTTPHeaders(headers)); }
Метод createPlaywright() создаст новый экземпляр Playwright. Метод createAPIRequestContext() задаст конфигурацию запроса API, например, установит общие заголовки запроса и базовый url. Он также создаст новый экземпляр APIRequestContext, который будет использоваться в тестах.
Создается новый метод setup() с аннотацией @BeforeTest, который будет запущен до начала любого из тестов. Он вызовет методы createPlaywright() и createAPIRequestContext(), которые в конечном итоге подготовят сцену для запуска API-тестов.
@BeforeClass public void setup() { createPlaywright(); createAPIRequestContext(); }
Еще два созданных метода — closePlaywright(), который закрывает сессию Playwright, и disposeAPIRequestContext(), который отбрасывает все ответы, возвращенные APIRequestContext.get() и другими подобными методами, которые хранятся в памяти.
Эти методы имеют модификаторы доступа private, поскольку они предназначены только для этого класса BaseTest.
private void closePlaywright() { if (playwright != null) { playwright.close(); playwright = null; } } private void disposeAPIRequestContext() { if (request != null) { request.dispose(); request = null; } }
Эти два метода будут вызваны в методе tearDown(), который имеет аннотацию @AfterClass TestNG. Этот метод вызывает методы closePlaywright() и disposeAPIRequestContext() и будет выполнен после завершения теста, что позволит плавно закрыть сессию Playwright.
Написание теста
Создадим новый java-класс — HappyPathTests внутри существующего пакета «restfulecommerce«. Этот класс HappyPathTests будет расширять класс BaseTest, поскольку он позволит повторно использовать публичные методы и переменные, определенные в классе BaseTest.
public class HappyPathTests extends BaseTest{ private List<OrderData> orderList; //.. } @BeforeClass public void testSetup() { this.orderList = new ArrayList<>(); }
Метод testSetup() инстанцирует List<OrderData> для создания массива данных о заказах (тело POST-запроса должно быть предоставлено в формате JSON Array), он остается глобальным, так как мы будем использовать его в различных тестовых методах для проверки заказов.
Теперь мы создадим новый тестовый метод testShouldCreateNewOrders() для проверки POST-запроса на создание новых заказов.
@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)); final JSONObject responseObject = new JSONObject(response.text()); final JSONArray ordersArray = responseObject.getJSONArray("orders"); assertEquals(response.status(), 201); assertEquals(responseObject.get("message"), "Orders added successfully!"); assertNotNull(ordersArray.getJSONObject(0).get("id")); 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")); }
Мы добавим в список заказов в общей сложности 4 заказа. Эти заказы будут предоставлены в качестве тела метода запроса /addOrderPOST.
Ниже приведено тело запроса, которое нужно предоставить в запросе для генерации заказов.
[{ "user_id": "1", "product_id": "1", "product_name": "iPhone", "product_amount": 500.00, "qty": 1, "tax_amt": 5.99, "total_amt": 505.99 }, { "user_id": "1", "product_id": "2", "product_name": "iPad", "product_amount": 699.00, "qty": 1, "tax_amt": 7.99, "total_amt": 706.99 }]
Чтобы сгенерировать данные для заказа, нам нужно создать класс POJO с точными именами полей, которые требуются для объекта заказа. Класс OrderData поможет в создании POJO.
@Getter @Builder public class OrderData { @SerializedName("user_id") private String userId; @SerializedName("product_id") private String productId; @SerializedName("product_name") private String productName; @SerializedName("product_amount") private int productAmount; private int qty; @SerializedName("tax_amt") private int taxAmt; @SerializedName("total_amt") private int totalAmt; }
Поскольку имена полей JSON содержат символ «_» между ними, используется аннотация @SerializedName из библиотеки Gson, которая присоединит имя переменной, созданной в POJO, к сериализованному имени.
Обратите внимание, что мы не создаем никаких методов типа Getter и Setter в этом классе, поскольку мы используем зависимость Lombok. Он автоматически создаст необходимые Getter-методы, поскольку мы использовали аннотацию @Getter из Lombok. Аналогично используется аннотация @Builder, которая позволяет использовать класс в качестве шаблона, избавляя от необходимости добавлять параметры в конструкторы класса.
Данные заказа будут генерироваться с помощью статического метода getNewOrder(). Этот метод getNewOrder() берется из класса OrderDataBuilder, который доступен в пакете restfulecommerce/testdata.
public class OrderDataBuilder { private static final Faker FAKER = new Faker(); public static OrderData getNewOrder() { int userId = FAKER.number().numberBetween(2, 4); int productId = FAKER.number().numberBetween(331,333); int productAmount = FAKER.number().numberBetween(400, 903); int quantity = FAKER.number().numberBetween(1, 5); int taxAmount = FAKER.number().numberBetween(10,50); int totalAmount = (productAmount*quantity)+taxAmount; return OrderData.builder() .userId(String.valueOf(userId)) .productId(String.valueOf(productId)) .productName(FAKER.commerce().productName()) .productAmount(productAmount) .qty(quantity) .taxAmt(taxAmount) .totalAmt(totalAmount) .build(); } }
Тестовые данные, необходимые для заказов, будут генерироваться во время выполнения теста с помощью библиотеки Datafaker (вот она), зависимость для которой уже добавлена в файл pom.xml.
Процесс довольно прост: создаем объект класса Faker, предоставляемый DataFaker, и используем его для вызова нужного метода данных и генерации данных во время выполнения теста.
Мы будем добавлять 4 заказа в POST-запрос, следовательно, нам нужно вызвать метод getNewOrder() 4 раза в тесте. Для этого в тестовом методе будем использовать цикл For.

Далее для выполнения POST-запроса будут использованы следующие строки кода:

response — это переменная, определенная для типа APIResponse, которая хранит результат запроса API.
this.request.post(«/addOrder», …) отправит POST-запрос на эндпойнт «/addOrder». Этот эндпойнт будет обрабатывать добавление заказа.
RequestOptions.create() создаст объект, который будет содержать различные параметры запроса, такие как заголовки запроса, тело и т. д.
.setData(this.orderList) содержит список заказов, сохраненных в переменной orderList, всего 4 заказа, добавленных в цикле, который мы предоставили. Эти данные отправляются в качестве тела POST-вызова API-запроса.
Следующая часть теста заключается в выполнении утверждений (assertions, здесь подробно) для проверки того, что отправленный POST-запрос успешно создал заказы в системе. Также необходимо проверить целостность данных о заказах, которые были созданы с помощью библиотеки Datafaker.

Первый assertion — проверка того, что в ответе получен правильный статус-код, то есть 201.
В следующем утверждении мы проверяем, что поле «message» в объекте ответа содержит текст «Orders added successfully!!!«. Важным утверждением является проверка того, что идентификатор заказа успешно сгенерирован для каждого заказа. Поскольку мы не уверены в идентификаторе order id, т. е. сгенерированном id-номере заказа, мы будем проверять, что id не является нулевым значением.
Нам нужно распарсить (разобрать) ответ и получить массивы JSON и объект JSON, чтобы получить поля для утверждения с соответствующими значениями.
Класс JSONObject из библиотеки org.json используется для разбора текста ответа, а класс JSONArray из той же библиотеки — для разбора массивов JSON из ответа. Эти объекты используются для получения необходимых данных из ответа для выполнения утверждений.
Наконец, выполняются проверки целостности данных, чтобы детали заказа, такие как user_id, product_id, total_amt и т. д., были заполнены правильно и отображались так, как они были отправлены в запросе.
Выполнение теста
Перед выполнением теста нам нужно убедиться (сверяя шаги здесь), что приложение restful-ecommerce запущено и работает.
На следующем скриншоте IntelliJ IDE показано, что тест выполнен успешно и заказы были созданы в соответствии с POST-запросом.

Сценарий 2 — Некорректная нагрузка в запросе
- Запустить службу restful-ecommerce
- Выполнить POST-запрос с некорректной нагрузкой в запросе, когда поле product_id отсутствует в теле заказа
- Убедиться, что в ответ получен статус-код 400
- Убедиться, что в ответ получено сообщение «Each order must have user_id, product_id, product_name, product_amount, qty, tax_amt, and total_amt!«, и заказ не создан в системе.
Реализация теста
Создадим новый java-класс SadPathTests в существующем пакете «restfulecommerce«. Этот класс будет реализовывать все тестовые сценарии, связанные с печальными путями API restful-ecommerce.
Под «печальными путями» вообще подразумеваются тестовые сценарии, в которых должно быть выдано внятное сообщение об ошибке, что улучшает пользовательский опыт.
Например, тестовый сценарий, который мы только что рассмотрели, будет отправлять статус-код 400 в ответ, если в запросе отправлена неправильная нагрузка. Это позволит конечному пользователю понять, что он допустил ошибку при отправке POST-запроса, и соответствующим образом исправить свою ошибку.
В тестовом классе SadPathTests создан новый метод testShouldNotCreateOrder_WhenProductIdFieldIsMissing().
@Test public void testShouldNotCreateOrder_WhenProductIdFieldIsMissing() { List<OrderData> orderList = new ArrayList<>(); orderList.add(getOrderDataWithMissingProductId()); final APIResponse response = this.request.post("/addOrder", RequestOptions.create().setData(orderList)); final JSONObject responseObject = new JSONObject(response.text()); assertEquals(response.status(), 400); assertEquals(responseObject.get("message"), "Each order must have user_id, product_id, product_name, product_amount, qty, tax_amt, and total_amt!"); }
Реализация тестового сценария довольно проста: он создает новый список заказов ArrayList, чтобы обеспечить передачу тела запроса в формате Array.
В классе OrderDataBuilder создается новый метод «getOrderDataWithMissingProductId()» для обработки тела запроса, которое мы должны отправить в POST-запросе.
public static OrderData getOrderDataWithMissingProductId() { int userId = FAKER.number().numberBetween(2, 4); int productAmount = FAKER.number().numberBetween(400, 903); int quantity = FAKER.number().numberBetween(1, 5); int taxAmount = FAKER.number().numberBetween(10,50); int totalAmount = (productAmount*quantity)+taxAmount; return OrderData.builder() .userId(String.valueOf(userId)) .productName(FAKER.commerce().productName()) .productAmount(productAmount) .qty(quantity) .taxAmt(taxAmount) .totalAmt(totalAmount) .build(); }
Следует отметить, что мы не указали поле product_id в вышеуказанном методе.
POST-запрос выполняется, добавляя заказ с отсутствующим product_id, и, наконец, выполняются утверждения для проверки того, что в ответе получен статус-код 400 вместе с сообщением «Each order must have user_id, product_id, product_name, product_amount, qty, tax_amt, and total_amt!«.
Выполнение теста
Скриншот выполнения теста с помощью IntelliJ IDE показывает, что тест выполнен успешно.

Резюме
POST-запросы создают новый ресурс в системе и в идеале возвращают в ответе статус-код 201. Важной частью POST-запроса является тело запроса, которое должно быть представлено в требуемом формате в соответствии с заголовком Content-Type. В теле запроса был использован формат JSON, так как он был необходим приложению restful-ecommerce.