Зарегистрируйтесь, чтобы продолжить обучение

Структура тестов Тестирование с Playwright

В этом уроке мы поговорим про то, как грамотно организовать файловую структуру тестов и не получить проблем, когда тестов станет много.

Структура каталогов

Не существует единого правила и какой-то предопределенной структуры, кроме основной директории tests. Но такая структура необходима, чтобы упростить работу. Ниже один из возможных вариантов:

  • tests/: Главная директория для тестов.
    • e2e/: Тесты end-to-end (E2E).
    • unit/: Модульные тесты.
    • integration/: Интеграционные тесты, например тесты API.

Основные тесты, находятся в директории e2e (end-to-end). Внутри могут быть как файлы так и вложенные директории, тут все зависит от количества тестов. Сами же тесты, можно раскладывать по файлам в соответствии с фичами, которые они тестируют. Один файл - одна фича, например, login.spec.js. Количество тестов внутри может быть любым. Если фича большая и у нее очень много тестов, то можно создать вложенную директорию, внутри которой уже будут отдельные файлы с тестами этой фичи.

hexlet-project-root/
├── playwright.config.js
├── package.json
├── tests/
│   ├── e2e/
│   │   ├── auth/
│   │   │   ├── login.spec.js
│   │   │   ├── logout.spec.js
│   │   ├── user/
│   │   │   ├── registration.spec.js
│   │   │   ├── profile.spec.js
│   │   ├── product/
│   │   │   ├── add-product.spec.js
│   │   │   ├── delete-product.spec.js
│   │   │   ├── update-product.spec.js
│   │   ├── cart/
│   │   │   ├── add-to-cart.spec.js
│   │   │   ├── remove-from-cart.spec.js
│   │   │   ├── checkout.spec.js
│   │   ├── utils/
│   │   │   ├── helpers.js
│   │   │   ├── constants.js

Файловая структура в Playwright тесно связана с параллелизмом. По умолчанию, тесты в рамках одного файла запускаются последовательно, но разные файлы запускаются параллельно. Playwright автоматически распределяет тесты из разных файлов между доступными ядрами процессора. Каждый тестовый файл запускается в изолированном браузерном контексте, поэтому параллельный запуск тестов из разных файлов не должен приводить к конфликтам.

Конфигурация

По умолчанию Playwright запускает браузерные тесты для всех директорий внутри tests, это определяется секцией projects в конфигурации:

export default defineConfig({
  // Весь набор тестов будет запущен для каждого проекта
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
})

Чтобы структура директорий описанная выше работала так как нам нужно, то мы должны выполнить следующие изменения:

  • Указать браузерным тестам запускаться только внутри tests/e2e
  • Добавить запуск тестов без браузера для директорий tests/integration и tests/unit

Вот как это может выглядеть:

export default defineConfig({
  // Весь набор тестов будет запущен для каждого проекта
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
      testMatch: /e2e/,
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
      testMatch: /e2e/,
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
      testMatch: /e2e/,
    },
    {
      name: 'API Tests',
      testMatch: /integration/,
    },
    {
      name: 'Unit',
      testMatch: /unit/,
    }
})

Полный запуск тестов в таком случае выглядит так:

npx playwright test

Running 7 tests using 4 workers

  ✓  1 [firefox] › e2e/todomvc.spec.js:21:5 › test with page object (1.3s)
  ✓  2 [chromium] › e2e/todomvc.spec.js:21:5 › test with page object (442ms)
  ✓  3 [firefox] › e2e/todomvc.spec.js:6:5 › test (1.0s)
  ✓  4 [chromium] › e2e/todomvc.spec.js:6:5 › test (442ms)
  ✓  5 [webkit] › e2e/todomvc.spec.js:6:5 › test (662ms)
  ✓  6 [webkit] › e2e/todomvc.spec.js:21:5 › test with page object (821ms)
  ✓  7 [API Tests] › integration/jsonapi.spec.js:5:5 › should create a bug report (250ms)

  7 passed (8.0s)

Запуск даже небольшого количества e2e-тестов занимает приличное время, поэтому, во время отладки или написания тестов, полезно запускать только тот файл, над которым сейчас ведется работа. Для этого достаточно указать путь до файла или директории после команды запуска.

npx playwright test tests/integration/jsonapi.spec.js

Running 1 test using 1 worker

  ✓  1 [API Tests] › integration/jsonapi.spec.js:5:5 › should create a bug report (102ms)

  1 passed (333ms)

Хуки

Внутри конкретного файла с тестами, обычно, находятся однотипные тесты, проверяющие какую-то фичу. В таких тестах встречаются общие части, например подготовка данных или какого-то состояния, например, залогиненности. Чтобы не дублировать эту подготовку в каждом тесте, Playwright предоставляет набор хуков, которые вызываются до и после тестов в рамках файла. Вот они:

  • test.beforeAll()
  • test.afterAll()
  • test.beforeEach()
  • test.afterEach()

Первые два запускаются один раз для всех тестов, последние два выполняются перед и после каждого теста. Но все это происходит только в рамках того файла, где они встречаются. Поэтому это еще один фактор того, как идет разделение по файлам. Если у тестов много общего с точки зрения подготовки, то, очень вероятно, что их следует поместить в один файл.

// Переменная вне beforeEach чтобы была возможность обратиться из теста
let todoMvcPage

test.beforeEach(() => {
  todoMvcPage = new TodoMVCPage(page)
  await todoMvcPage.goto()
})

test('test 1', async ({ page }) => {
  // Содержимое теста
  // Где-то тут вызов todoMvcPage
})

test('test 2', async ({ page }) => {
  // Содержимое теста
  // Где-то тут вызов todoMvcPage
})

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

Какие хуки когда стоит использовать? В большинстве случаев нужен хук beforeEach(), так как он наиболее безопасен и удобен. Хук beforeAll() это всего лишь оптимизация, там где достаточно одного вызова для всех тестов, но тут нужно действовать внимательно. Бывает, что во время теста происходят изменения, которые требуют восстановления состояния. Поэтому в случае, когда не очевидно, что использовать, лучше выбирать beforeEach().

Хуки afterEach() и afterAll() предназначены для задач, которые выполняются после тестов, например, очистки данных или восстановление какого-то состояния. Например, если тест зарегистрировал нового пользователя, то, с помощью этих хуков, мы можем удалить пользователя с сайта.

// Хук запускается даже если тест провалился
// Так повышается надежность
test.afterEach(async ({ page }) => {})

Зачем это делать? В идеальной системе, тесты никак не влияют друг на друга, а для этого система до теста и после теста должна оставаться в одном и том же состоянии. Если тест это состояние меняет, то возможны побочные эффекты, приводящие к ошибкам. В случае, регистрации пользователя, это может сломать повторные регистрации с теми же параметрами, так как пользователь уже существует. Именно поэтому after* хуки запускаются даже в том случае, когда тест завершился с ошибкой.

В теории все выглядит хорошо, но на практике, хуки after* не надежные. Они могут содержать ошибки, тесты могут прервать свое выполнение до или во время их вызова. Поэтому очистку данных и, в целом, восстановление состояние делают другими способами, которые мы разберем в следующих уроках.

Реальный же пример использования afterEach(), может быть связан с созданием скриншотов, в том случае, если тест завершился с ошибкой.

test.afterEach(async ({ page }, testInfo) => {
  if (testInfo.status !== testInfo.expectedStatus) {
    // Снимаем скриншот в случае ошибки
    await page.screenshot({
      path: `screenshots/${testInfo.title}.png`,
      fullPage: true,
    })
  }
})

То есть не очистка, а дополнительная не критичная логика, которая не влияет на последующие запуски тестов.

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff