Cypress: кастомные команды от А до Я

от автора

Автоматизация тестирования с Cypress становится всё популярнее, а кастомные команды — одним из ключевых инструментов для повышения читаемости, переиспользуемости и поддерживаемости тестов. В этой статье разберём, что такое кастомные команды, почему Cypress рекомендует отказаться от классического Page Object Model (POM), а также рассмотрим, как грамотно организовать и структурировать свои команды.

Что такое кастомные команды в Cypress?

Кастомные команды — это пользовательские функции, которые расширяют базовый набор команд Cypress (cy.*). Они позволяют инкапсулировать часто повторяющиеся действия и операции с UI в удобные вызовы.

Пример базовой кастомной команды:

Cypress.Commands.add('login', (username: string, password: string) => {   cy.visit('/login');   cy.get('#username').type(username);   cy.get('#password').type(password);   cy.get('button[type=submit]').click(); }); 

Теперь в тестах можно просто вызывать:

cy.login('user', 'password123'); 

Это упрощает тесты и делает код более декларативным.

Почему Cypress рекомендует отказаться от классического POM?

В официальном блоге Cypress есть статья Stop Using Page Objects and Start Using App Actions, где подробно объясняется, почему классический Page Object Model (POM), унаследованный от Selenium, не подходит для Cypress:

  • POM нарушает цепочку Cypress-команд. Методы классов обычно не возвращают команды cy.*, из-за чего теряется встроенный механизм ожиданий, логирования, автоматического повторения (retry) и time-travel.

  • Усложняет отладку и снижает надёжность тестов. Так как действия вне cy не попадают в логи и не контролируются Cypress.

  • Создаёт лишние абстракции и перегружает тесты. Cypress ориентирован на простоту и прямое взаимодействие с приложением.

Вместо этого Cypress предлагает использовать кастомные команды и App Actions, которые полностью интегрируются в цепочку Cypress и используют преимущества его асинхронной модели.

Как организовать кастомные команды?

1. Где хранить кастомные команды?

По умолчанию Cypress предлагает размещать кастомные команды в одном файле:
cypress/support/commands.ts

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

2. Что делать при большом количестве команд?

В небольших проектах одного файла может быть достаточно, но при росте проекта такой подход приводит к «хаосу» и потере структуры:

  • Трудно понять, какая команда за что отвечает.

  • Появляются дублирующие и плохо документированные команды.

  • Возможны конфликты имён, так как все команды глобальные.

Рекомендуемый подход — разделять команды по смысловым модулям:

cypress/support/commands/ ├─ auth.commands.ts ├─ cart.commands.ts ├─ profile.commands.ts └─ index.ts 

В index.ts импортируются все модули команд:

import './auth.commands'; import './cart.commands'; import './profile.commands'; 

И index.ts подключается в cypress/support/commands.ts или напрямую из support/index.ts.

3. Как именовать команды?

Чтобы избежать конфликтов и улучшить читаемость, полезно использовать префиксы:

  • cy.authLogin()

  • cy.cartAddItem()

  • cy.profileUpdateName()

Минусы кастомных команд

Хотя кастомные команды — мощный инструмент, нужно помнить о некоторых рисках:

  • Глобальность. Все команды регистрируются глобально, что может привести к перезаписи существующих или встроенных команд. Особенно если все плохо с документацией, над проектом работает много людей или «текучка» в коллективе.

  • Потеря контроля. При большом количестве команд становится сложно отследить, где и как они используются.

  • Трудности с отладкой. Если команда содержит сложную логику, это усложняет диагностику проблем.

Поэтому важно поддерживать документацию, придерживаться модульной структуры и не перегружать команды лишней логикой.

Типизация кастомных команд в TypeScript

Чтобы получить автодополнение и проверку типов в кастомных командах, можно расширить глобальный интерфейс Cypress. Это делается в отдельном файле, например, cypress/support/index.d.ts:

// cypress/support/index.d.ts declare namespace Cypress {   interface Chainable<Subject = any> {     /**      * Логин под пользователем      * @example cy.authLogin('user1', 'pass123')      */     authLogin(username: string, password: string): Chainable<void>;      /**      * Добавить товар в корзину по ID      * @example cy.cartAddItem('product123')      */     cartAddItem(productId: string): Chainable<void>;   } } 

Теперь при использовании команд IDE будет подсказывать их сигнатуры, а TypeScript — контролировать правильность аргументов.

Best practice по организации тестов и команд

  • Разделяйте команды и тесты по функциональным областям. Это улучшает навигацию и поддержку.

  • Избегайте длинных цепочек команд с побочными эффектами внутри одной команды. Лучше разбивать действия на логические шаги.

  • Документируйте команды. Используйте JSDoc, чтобы описывать назначение и параметры.

  • Пишите тесты, используя только кастомные команды. Это упрощает поддержку тестов и повышает их читаемость.

  • Периодически рефакторьте команды. Убирайте дублирование, упрощайте логику.

Полезные утилиты для кастомных команд

Название

Назначение

Импорт

Пример использования

@faker-js/faker

Генерация фейковых данных для форм и API

import { faker } from '@faker-js/faker'

faker.internet.email()

qs

Формирование query-параметров

import qs from 'qs'

qs.stringify({ page: 1, filter: 'active' })

uuid

Генерация уникальных ID

import { v4 as uuidv4 } from 'uuid'

uuidv4()

dayjs

Работа с датами

import dayjs from 'dayjs'

dayjs().subtract(20, 'years').format('YYYY-MM-DD')

@testing-library/cypress

Семантические селекторы для улучшенной доступности

import '@testing-library/cypress/add-commands'

cy.findByRole('button', { name: /сохранить/i })

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

Заключение

Кастомные команды в Cypress — эффективный способ сделать тесты лаконичнее и понятнее, сохранив преимущества асинхронной модели и мощных возможностей Cypress. Официальная рекомендация — отказаться от классического POM в пользу App Actions и кастомных команд.

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


ссылка на оригинал статьи https://habr.com/ru/articles/930600/