Создаем простейший API и тестируем его с помощью Playwright + TS (2024)

Создаем простейший API и тестируем его с помощью Playwright + TS (1)

Краткое содержание

Что будет выполнено в ходе данной статьи:

  1. Будет создан простейший API сервер на NodeJS для запуска локально.

  2. Будут написаны автотесты, на Playwright + Typescript, покрывающие простые запросы GET, POST, PUT, PATCH, DELETE.

  3. Выполнены негативные тесты с получением ошибок, последующим анализом и устранением.

1. Подготовка среды для тестирования - создание API сервера

Основой для выполнения тестов будет примитивный API сервер на NodeJS содержащий объекты в JSON с несколькими свойствами.

Для примера создадим папку для сервера и назовем ее cars-api
Далее следует сделать этот каталог рабочим при помощи консоли cd cars-apiили например открыть каталог программой VSC.

Убедитесь что NodeJS установлен проверив версию node -v , при необходимости установите NodeJS.

Инициализируйте проект и установите фреймворк express
- npm init -y
- npm install express

Следующим шагом создайте файл cars.js в директории cars-api

const express = require('express');const path = require('path'); // Import path moduleconst app = express();const PORT = process.env.PORT || 3000;// Middleware to parse JSON requestsapp.use(express.json());// Serve static files from the "public" directoryapp.use(express.static('public'));// Root route to serve index.htmlapp.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); });// Sample cars datalet cars = [ { id: 1, brand: 'Subaru', model: 'Impreza WRX', color: 'Blue'}, { id: 2, brand: 'Nissan', model: 'Skyline', color: 'Black'}, { id: 3, brand: 'Toyota', model: 'Supra', color: 'Yellow'}];// GET all carsapp.get('/api/cars', (req, res) => { res.json(cars);});// GET car by IDapp.get('/api/cars/:id', (req, res) => { const car = cars.find(i => i.id === parseInt(req.params.id)); if (car) { res.json(car); } else { res.status(404).json({ message: 'car not found' }); }});// POST a new carapp.post('/api/cars', (req, res) => { const newcar = { id: cars.length + 1, brand: req.body.brand, model: req.body.model, color: req.body.color, }; cars.push(newcar); res.status(201).json(newcar);});// PUT (update) an car by IDapp.put('/api/cars/:id', (req, res) => { const car = cars.find(i => i.id === parseInt(req.params.id)); if ('engine' in req.body) { return res.status(501).json({ message: 'Not Implemented' });}if (car) { car.brand = req.body.brand; car.model = req.body.model; car.color = req.body.color; res.json(car); } else { res.status(404).json({ message: 'car not found' }); }});// DELETE a car by IDapp.delete('/api/cars/:id', (req, res) => { cars = cars.filter(i => i.id !== parseInt(req.params.id)); res.status(204).end();});// PATCH (partial update) a car by IDapp.patch('/api/cars/:id', (req, res) => { const car = cars.find(i => i.id === parseInt(req.params.id)); if (car) { // Only update fields that are provided in the request body if (req.body.brand) { car.brand = req.body.brand; } if (req.body.model) { car.model = req.body.model; } if (req.body.color) { car.color = req.body.color; } res.json(car); } else { res.status(404).json({ message: 'Item not found' }); }});// Status endpointapp.get('/api/status', (req, res) => { res.json({ status: 'Server is running' });});// Start the serverapp.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}/`);});

В данном файле мы описываем правила нашего API.
Объектом будет автомобиль с несколькими свойствами (Производитель, модель и цвет). По список будет иметь всего 3 объекта.

let cars = [ { id: 1, brand: 'Subaru', model: 'Impreza WRX', color: 'Blue'}, { id: 2, brand: 'Nissan', model: 'Skyline', color: 'Black'}, { id: 3, brand: 'Toyota', model: 'Supra', color: 'Yellow'}];

Методы используемые на сервере
- GET (ALL) получить весь список объектов
- GET (ID) получить данные конкретного объекта по ID
- POST создать новый объектов
- PUT заменить конкретный объект по ID
- DELETE удалить конкретный объект по ID
- PATCH заменить часть свойства конкретного объекта по ID

По умолчанию сервер будет запущен на порте 3000 http://localhost:3000/

Далее необходимо создать index.html файл в под каталоге public

<!-- public/index.html --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>API Server is Running</title> <style> body { font-family: Arial, sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; margin: 0; } header { background-color: #4CAF50; color: white; padding: 15px; width: 100%; text-align: center; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .name { color: black; }.doc {text-align: left;margin-left: 30px;} </style></head><body> <header> <h1>API Server <a class="name">Cars</a> is running</h1> </header><br><h3>Add more data if you needed(e.g. API documentation).</h3> <div class ="doc"><h3>GET ALL </h3><p>fetch('http://localhost:3000/api/cars/')<br> &nbsp.then(response => response.json())<br> &nbsp.then(data => console.log('GET full Car list:', data))<br> &nbsp.catch(error => console.error('Error:', error));<br></p> </div></body></html>

Данные из этого файла будут отображаться на главной странице.

Конечная структура сервера должна выглядеть так

Создаем простейший API и тестируем его с помощью Playwright + TS (2)

Запускаем сервер командой node cars.js и проверяем открыв ссылку в браузере http://localhost:3000/

Создаем простейший API и тестируем его с помощью Playwright + TS (3)

Проверяем API на работоспособность. Открываем консоль в dev tools - F12, копируем со страницы запрос GET ALL и жмем Enter.

Создаем простейший API и тестируем его с помощью Playwright + TS (4)

Результат - мы получили список автомобилей, которые хранятся на нашем сервере.

Для простейшего понимания функционала представим что сервер это гараж с автомобилями а методы GET - посмотреть/воспользоваться авто, POST - приобрести новый авто, DELETE - продать авто, PATCH - тюнинговать/модернизировать авто и PUT - получить авто на подмену.

2. Написание авто тестов Playwright + TS

Для фреймворка Playwright необходимо создать новую директорию, например test_cars_api

  1. Откройте командную строку и перейдите в рабочий каталог или откройте каталог редактором кода (например VSC).

  2. Установите фреймворк Playwright выполнив командуnpm init playwright@latestи выбрав необходимые опции (язык typescript, каталог для тестов и т.п.)

  3. Установите дополнительные библиотеки при необходимости
    npm install typescript

  4. Создать новый файл тестов в папке test / test_cars_api.spec.ts

import {test, expect, request} from '@playwright/test'test.describe('API test Cars', () => { let baseURL = 'http://localhost:3000/api/cars';})

Первоначально добавим импорт необходимых элементов из playwright и базовую URL.

В файле настроек playwright.config.ts можно поправить конфиг для выполнения тестов только в одном браузере например name: 'chromium', остальные закомментировать.

Тест 1 - Get All проверка получения полного списка

Отправляем GET запрос на весь список, проверяем что статус и количество объектов в списке. Для перепроверки выводим список в консоль.

 test('Get All Cars', async ({ request }) =>{ const response = await request.get(baseURL); expect(response.status()).toBe(200); const cars = await response.json(); expect(cars.length).toBe(3); console.log(cars); })

Переменной response присваивается значение запрос get по базовой URL, которая уже была создана. Далее проверяется статус, значение равно 200 (ok). Затем переменной cars назначается ответ в json формате. В конце проверяется что полученный список содержит 3 автомобиля length = 3.

Запускаем тест npx playwright test

Результат мы видим список автомобилей с их свойствами, тест пройден успешно.

Создаем простейший API и тестируем его с помощью Playwright + TS (5)

Тест 2 - Get by ID. Получения данных о конкретном объекте

Отправляем GET запрос на первый объект с id = 1, проверяем что статус равен 200 и что результат в свойствах объекта соответствует ожидаемым.

 test('Get Car by ID', async ({request}) => { const carID = 1; const response = await request.get(`${baseURL}/${carID}`); expect(response.status()).toBe(200); const car = await response.json(); expect(car).toEqual({ id:1, brand: 'Subaru', model: 'Impreza WRX', color: 'Blue' }); console.log(car); })

В данном тесте в GET запросе мы используем модифицированный базовый URL для получения данных конкретного автомобиля из списка - переменная carID. Последняя операция проверяет полученные свойства объекта с ожидаемыми.

Запускаем второй тест по названию npx playwright test -g "Get Car by ID"

Создаем простейший API и тестируем его с помощью Playwright + TS (6)

Результат - тест успешно пройден данные соответствуют.

Тест 3 - POST. Создание нового объекта

Отправляем POST запрос и создаем новый объект, 4й в нашем списке.

 test('POST new Car', async ({request}) =>{ const newCar = { brand: 'Honda', model: 'NSX', color: 'Yellow' }; const response = await request.post(baseURL, {data: newCar}); expect(response.status()).toBe(201); const createdCar = await response.json(); expect(createdCar).toMatchObject(newCar); console.log(createdCar); })

В данном тесте объявляем новую переменную newCar и при помощи POST запроса передаем данные на сервер по базовой URL. Проверяем что сервер вернул нам статус 201 (created) и сравниваем данные объекта чтобы убедиться в корректности данных.

Запускаем тест по названию npx playwright test -g "POST new Car"

Создаем простейший API и тестируем его с помощью Playwright + TS (7)

Результат - тест успешно пройден, новые данные добавлены на сервер.
Отправив запрос GET ALL, сервер вернет расширенный список содержащий новым объект.

Создаем простейший API и тестируем его с помощью Playwright + TS (8)

Тест 4 - PUT. Замена объекта

Отправляем PUT запрос и передаем объект с 2 свойствами из 3х.

test('PUT existing car', async({request}) => { const carID = 2; const updatedCar = { model: '240 SX', color: 'Green' }; const response = await request.put(`${baseURL}/${carID}`, {data: updatedCar}); expect(response.status()).toBe(200); const car = await response.json(); expect(car).toMatchObject(updatedCar); console.log(car); })

В данном тесте используем переменную carID чтобы сервер понимал какой из объектов будет заменен. Объявляем переменную updatedCar, которая имеет 2 свойства из 3х базовых (*для примера) и при помощи PUT запроса передаем данные на сервер. Проверяем что сервер вернул нам статус 200 (ok) и сравниваем данные объекта чтобы убедиться в корректности данных.

Запускаем тест по названию npx playwright test -g "PUT existing car"

Создаем простейший API и тестируем его с помощью Playwright + TS (9)

Результат - тест выполнен успешно. Если выполнить запрос GET ALL, то мы увидим что в объекте №2 отсутствует свойство brand.

Создаем простейший API и тестируем его с помощью Playwright + TS (10)

Тест 5 - PATCH. Обновление свойства объекта

Отправляем PATCH запрос и передаем данные для замены 1 из свойств объекта.

 test('PATCH property of existing Car', async ({request}) =>{ const carID = 3; const updatedCar = { color: 'Red' }; const response = await request.patch(`${baseURL}/${carID}`, {data: updatedCar}); expect(response.status()).toBe(200); const car = await response.json(); expect(car).toMatchObject(updatedCar); console.log(car); })

В данном тесте используем переменную carID чтобы сервер понимал в каком из объектов будет выполнены изменения. Объявляем переменную updatedCar, которая имеет 1 свойство из 3х базовых и при помощи PATCH запроса передаем данные на сервер. Проверяем что сервер вернул нам статус 200 (ok) и сравниваем данные объекта чтобы убедиться в корректности данных.

Запускаем тест по названию npx playwright test -g "PATCH property of existing Car"

Создаем простейший API и тестируем его с помощью Playwright + TS (11)

Результат - тест выполнен успешно. Если выполнить запрос GET ALL, то мы увидим что объект №3 имеет все 3 свойства и было обновлено только одно - color. Теперь мы видим наглядную разницу между PUT и PATCH.

Создаем простейший API и тестируем его с помощью Playwright + TS (12)

Тест 6 - DELETE. Удаление объекта

Отправляем DELETE запрос и удаляем объект.

 test('DELETE car by ID', async ({request}) => { const carID = 4; const response = await request.delete(`${baseURL}/${carID}`); expect(response.status()).toBe(204); const verifyResponse = await request.get(`${baseURL}/${carID}`); expect(verifyResponse.status()).toBe(404); })

В данном тесте используем переменную carID чтобы сервер понимал в какой из объектов следует удалить. Отправляем запрос DELETE и проверяем, что статус 204(No content). Следующей операцией отправляем GET запрос на URL удаленного объекта и проверяем что статус равен 404(Not found).

Запускаем тест по названию npx playwright test -g "DELETE car by ID"

Создаем простейший API и тестируем его с помощью Playwright + TS (13)

Результат - тест выполнен успешно. Если выполнить запрос GET ALL, то мы увидим что объект №4 был удален из списка.

Создаем простейший API и тестируем его с помощью Playwright + TS (14)

3. Негативные сценарии и анализ ошибок

Тест 7 - GET. Запрос на не существующий объект

 test('Negative Get Car by ID', async ({request}) => { const carID = 4; const response = await request.get(`${baseURL}/${carID}`); expect(response.status()).toBe(404); })})

По умолчанию сервер содержит 3 объекта. Отправим GET запрос для не существующего 4-го объекта. В результате мы получим статус равный 404. Данный тест содержит те практически те же условия что и Тест 6, но для того чтобы убедиться в том что тест работает корректно заменим значение carID на 1.

Результат - ошибка полученный статус не соответствует ожидаемому. Так как мы заменили carID на существующий объект сервер вернул нам статус 200.

Создаем простейший API и тестируем его с помощью Playwright + TS (15)

Тест 8 - PUT. Запрос на добавление не существующего свойства

 test('Negative PUT non-existing property', async ({request}) => { const carID = 3; const updatedCar = { engine: 'Turbo 3.0' }; const response = await request.put(`${baseURL}/${carID}`, {data: updatedCar}); expect(response.status()).toBe(200); })

На пример мы хотим изменить 3й объект добавить свойство engine,так как у нас нет доступа к документации мы ожидаем положительный результат и статус 200.

В результате тест не пройден, получена ошибка статус 501 вместо ожидаемого 200.

Создаем простейший API и тестируем его с помощью Playwright + TS (16)

Обновим тест с учетом специального кейса валидации для метода PUT, который оказывается есть на сервере. При отправке свойства engine мы ожидаем статус 501c описанием ошибки - Not Implemented.

 test('Negative PUT non-existing property', async ({request}) => { const carID = 3; const updatedCar = { engine: 'Turbo 3.0' }; const response = await request.put(`${baseURL}/${carID}`, {data: updatedCar}); expect(response.status()).toBe(501); const responseBody = await response.json(); expect(responseBody.message).toBe('Not Implemented'); })

Результат - тест выполнен успешно.

Создаем простейший API и тестируем его с помощью Playwright + TS (17)

Тест 9 - POST .Специальный кейс некорректная логика на сервере

Для этого теста необходимо внести изменения на сервере - сделать ошибку в обработке POST запроса. Поменяем местами brand и color.

 const newcar = { id: cars.length + 1, brand: req.body.color, model: req.body.model, color: req.body.brand, };

Перезапускаем сервер. Запускаем уже созданный ранее Тест 3 - POST new Car.
npx playwright test -g "POST new Car"

Как результат мы видим не соответствия данных при проверке свойств объекта.

Создаем простейший API и тестируем его с помощью Playwright + TS (18)

На этом все. Надеюсь материал был написан максимально прост к пониманию и повторению практических задач.

Создаем простейший API и тестируем его с помощью Playwright + TS (2024)

References

Top Articles
Latest Posts
Article information

Author: Geoffrey Lueilwitz

Last Updated:

Views: 6756

Rating: 5 / 5 (60 voted)

Reviews: 83% of readers found this page helpful

Author information

Name: Geoffrey Lueilwitz

Birthday: 1997-03-23

Address: 74183 Thomas Course, Port Micheal, OK 55446-1529

Phone: +13408645881558

Job: Global Representative

Hobby: Sailing, Vehicle restoration, Rowing, Ghost hunting, Scrapbooking, Rugby, Board sports

Introduction: My name is Geoffrey Lueilwitz, I am a zealous, encouraging, sparkling, enchanting, graceful, faithful, nice person who loves writing and wants to share my knowledge and understanding with you.