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. Все случаи, которые у меня возникали, можно было покрыть текущими инструментами, без костылей и дополнительных плагинов. А некоторые функции, как, например, автоматическая запись тестов, даже приятно удивили.

Комментарии

Популярные сообщения из этого блога

Стайлгайд и компонентная разработка

Погружение в React Native: навигация, работа оффлайн, пуш нотификации

z-index