e2e тестирование nextsj приложения с playwright & msw
Серверные и клиентские компоненты в nextjs
Прежде, чем писать e2e тесты, давайте разберемся с nextjs и теми особенностями, которые он дает, а именно — что такое серверные и клиентские компоненты.
Раньше у нас был только клиентский js, и отдельно код, который запускается на сервере. Nextjs перевернул мир фронтэнда, совместив эти две концепции в один, сделав переход для разработчика от одних компонент к другим практически бесшовным. Вся разница заключается в указании «use client» в файлах, которые должны выполняться на клиенте. И это может создать путаницу, если не понимать принцип как работают оба вида компонент.
Изначально все компоненты считаются серверными и рендерятся на сервере, они имеют как преимущества, так и ряд ограничений. Например, они могут напрямую обращаться к данным, а также env-переменным и токенам доступа. Но с другой стороны не могут выполнять хуки или производить интеративные клиентские события, у них нет доступа к браузерному API.
Как проверить где выполяется компонента?
Самый простой способ — поконсольложить: написать console.log
в компоненте и посмотреть где она выведется: в браузере или же в консоле сервера.
Почему так важно понимать что мы имеем дело в двумя различными видами компонент?
Потому что теперь для тестов нам нужно будет отдельно мокать запросы как с серверной части, так и из клиентской.
Написание моков для сервера и клиента
Для моков возьмём библиотеку msw, поскольку она очень простая и хорошо документирована, также нам понадобиться playwright-msw, чтобы самим не писать интеграцию с playwright.
Мокаем клиентскую часть
Для начала замокаем запросы в файле mocks/clientHandlers.ts:
import { http, HttpResponse } from 'msw';
import { mockData } from '@/mocks/data';
const backendApiUrl = 'http://127.0.0.1:8000';
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE',
'Access-Control-Allow-Headers': '*',
};
export const handlers = [
http.get(`${backendApiUrl}/api/data`, () => {
return HttpResponse.json(
{ success: true, data: mockData },
{ headers }
);
}),
];
Можно заметить, что здесь есть ещё файл mocks/data.ts с нашими мокаными данными, который нужно заранее подготовить.
Затем создадим воркера для клиента mocks/client.ts:
import { setupWorker } from 'msw/browser';
import { handlers } from './clientHandlers';
export const worker = setupWorker(...handlers);
Мокаем серверную часть
По аналогии с клиентской, серверная часть так же будет состоять из файла с моками запросов: mocks/serverHandlers.ts и сервера mocks/mockServer.ts.
Файл с моками серверных запросов будет похож на такой же клиентский, с той лишь разницей, что будет мокать только те запросы, которые отправляются с серверной части. Поэтому они могут совпадать, но это не обязательно.
Сервер mocks/mockServer.ts будет следующим:
import { createServer } from '@mswjs/http-middleware';
import { handlers } from './serverHandlers';
const httpServer = createServer(...handlers);
httpServer.listen(8000, () => {
console.log('started mock server on port 8000');
});
Здесь я использую @mswjs/http-middleware библиотеку для запуска сервера, на который будут отправляться серверные запросы и который можно запускать командой:
npx tsx mocks/mockServer.ts
Она нам пригодится при запуске тестов.
Интеграция с Playwright
У playwright достаточно хорошая документация, чтобы не останавливаться на том, как установить, настроить и написать тесты. Вы, наверняка, заглянете в конфигурационный файл playwright.config.ts и настроете его под себя.
Интереснее, как соединить его с нашим клиентским сервером. Для этого создаем файл e2e/test.ts, в котором прописываем наши воркеры:
import { test as base, expect } from '@playwright/test';
import { http } from 'msw';
import type { MockServiceWorker } from 'playwright-msw';
import { createWorkerFixture } from 'playwright-msw';
import { handlers } from '@/mocks/clientHandlers';
const test = base.extend<{ worker: MockServiceWorker; http: typeof http; }>({
worker: createWorkerFixture(handlers),
http,
});
export { expect, test };
Далее в тестах будем пользоваться этими расширенными функциями:
import { expect, test } from '@/e2e/test';
Запуск тестов
На этом настройка закончена. Останется только запустить наши тесты. Для этого нужно сбилдить проект (в продакшен окружении):
npm run build
Запустить наш сервер для серверный запросов:
npx tsx mocks/mockServer.ts
Клиентские запросы, как мы помним, перехватываются воркерами внутри тестов. И запустить тесты:
npx playwright test
Мне нравится богатый на функциональность ui-режим, в котором можно запускать отдельные тесты, смотреть на их выполнение, быстро отлавливать ошибки (как консольные, так и от запросов), понимать в какой последовательности рендерится страница, какие запросы посылаются, что возвращают, и много всего прочего:
npx playwright test --ui
Очень рекомендую посмотреть на него — незаменимая вещь при написании тестов и отладке.
Аутентификация в приложении
Одна из обязательных вещей, с которой вы столкнётесь при написании тестов, — это аутентификация в приложении. Приятно, что playwright не стал игнорировать и оставлять разработчиков писать свои костыли, а дает свой хороший гайд. Поэтому сложностей с этим не будет.
Про важность доступности (аксессибилити)
Прежде чем писать тесты, стоит поверить ваше приложение на доступность. Это не какая-то прихоть людей с ограниченными возможностями, и не стоит воспринимать это только как правило хорошего тона, это в первую очередь об удобстве всех пользователей. Работа с клавиатуры, понятные сообщения, предсказумое поведение элементов дизайна — это всё облегчает работу с вашим приложением и помогает быстрее решить пользователю ту проблему, с которой он пришел.
При чём тут тесты, спросите вы? А при том, что выбирая элементы, проверяя текст и в целом работоспособность приложения, важно закладываться на aria-атрибуты. Playwright сам рекомендует в первую очередь использовать более явные признаки для нахождения элементов, а не полагаться на неявные, как, например, css-классы, хотя это тоже возможно. Таким образом, мы в очередной раз тестируем то, что приложение доступно и понятно пользователю.
Как писать тесты
Прежде всего изучите возможности Playwright: как выбирать элементы, как проверять результат, почитайте лучшие практики.
Чтобы избежать дублирования кода, используйте page object модели и fixtures. Изучите возможности Playwright, чтобы ваши тесты были понятными, простыми и лаконичными.
Это ваша документация, пишите её вдумчиво.
Плагин для VSCode
Понятно, что e2e тесты — это всегда о рутине прокликиваний: выбрать элемент, кликнуть, заполнить поле и прочее. Не бойтесь автоматизировать всё, что можно.
Мне нравится, что Playwright не просто предоставляет плагин для VSCode, но и объясняет как с ним работать. И он действительно может сэкономить вам кучу времени. Чего только стоит его интерактивный режим записи тестов! То есть вы запускаете режим записи и далее всё, что вы накликаете в браузере, запишется вам в виде тестов, все выбранные элементы, все взаимодействия. Вам останется только упорядочить всё, вынести в модели повторяющиеся части и написать assert'ы!
Никогда ещё тесты не было так приятно писать! Моя любовь с первого клика!)
Работа с часовыми поясами
Как и в предыдущих случаях установливать часовой пояс нужно и для сервера, и для клиента.
Для сервера в файле playwright.config.ts добавляем:
export default defineConfig({
use: {
timezoneId: 'Europe/Paris',
},
});
Для клиента устанавливаем нужную таймзону при запуске фронтэнда:
TZ='Europe/Paris' npx playwright test --ui
Выводы
Самое сложное в написании тестов — это начать. Установить, настроить окружение, разобраться как что работает и где и что нужно мокать — это действительно занимает какое-то время. Подготовить тестовые данные.
А дальше, после первого теста, понимаешь насколько правильно спроектировано приложение, насколько удобно писать тесты. Playwright со своей стороны сделал всё, чтобы можно было как можно быстрее и проще писать тесты.
Однозначно рекомендую. Пока что 10 из 10. Все случаи, которые у меня возникали, можно было покрыть текущими инструментами, без костылей и дополнительных плагинов. А некоторые функции, как, например, автоматическая запись тестов, даже приятно удивили.
Комментарии