Разделы портала

Онлайн-тренинги

.
Как организовать большой проект в Cypress
14.02.2024 00:00

Автор: Филип Рик (Filip Hric)
Оригинал статьи
Перевод: Ольга Алифанова

Cypress из коробки дает вам структуру проекта, но по мере его роста в нем появляются различные файлы, нуждающиеся в своем месте. К тому же идет вечный спор, использовать ли Page Object, а если не использовать, то где альтернатива? В этой статье я хочу поделиться своим видением создания и структурирования успешного проекта. Статья основана на моем почти семилетнем опыте создания различных проектов с Cypress.

Основы и принципы

Начнем с ряда принципов, на которых базируются мои мысли. Эти принципы помогали мне принимать решения в прошлых проектах, они жизненно важны для проектного успеха. Иными словами, не все тут упомянутое применимо к любому проекту. Это, конечно, аксиома, но все равно хочу это подчеркнуть – в основном, чтобы избежать ответов «для нас это не сработает». Вот они.

QA-автоматизация должна быть частью репозитория исходного кода. Недавно я проводил опрос на LinkedIn и выяснил, что у 45% ответивших набор тестов находится в другом репозитории. Я считаю, что код тест-автоматизации (особенно при использовании Cypress) не нужно отделять от исходного кода тестируемого приложения. Таким образом все тесты и все ветки синхронизированы с разработкой, что упрощает непрерывную поставку. Это также означает, что разработчики так же, как и тестировщики, вовлечены в создание и поддержку тест-автоматизации.

Читабельность – (наиболее) важный фактор принятия решений. Упавшего теста может быть недостаточно для понимания, что именно пошло не так. Тестировщики – это провайдеры информации. Это значит, что если упавший тест дает недостаточно информации о том, что пошло не так и почему, тестировщик не сделал свою работу. Создавая тест, принимайте любые решения о тест-дизайне в пользу читабельности этого теста.

Тестирование должно повышать скорость поставки. Нашим пользователям наплевать, насколько классные у вас тесты – им важна ценность, которую дает им продукт. Успешная компания должна предоставить им эту ценность как можно быстрее. Что это значит для тестирования? Оно должно начинаться рано, а тест-автоматизация должна быть как можно быстрее. Медленный дебаг и тестирование – это медленная поставка, и поэтому тест-автоматизация должна быть оптимизирована по скорости.

Время человека дороже машинного. Сокращение издержек при помощи непрерывной интеграции – это, в целом, хорошо, и тест-автоматизация может ускорить процесс. Но у времени, которое стоит на это тратить, есть пределы. Важно не распыляться по пустякам и убедиться, что автоматизация действительно сбережет вам время. Все, что вы создаете, нуждается в поддержке, и об этом важно задуматься – особенно решая, создавать инструмент с нуля или заплатить за готовое решение.

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

ВАЖНАЯ ЗАМЕТКА: несмотря на вышеизложенное, эти принципы не выбиты для меня в камне. С течением времени и наработкой опыта я корректирую свои взгляды и применяю новые принципы. Мы можем обсудить это в Discord, буду рад узнать, что вы об этом думаете.

BDD без Cucumber

Тесты, которые я пишу, часто отражают конкретное поведение или описывают использование конкретной фичи. Когда я начинал работу над тест-автоматизацией, мы с коллегой записали 15 наиболее важных сценариев и соревновались, кто завершит их быстрее (она работала вручную, а я – при помощи тест-автоматизации). По сути это сделало нашу автоматизацию управляемой через поведение, хотя мы не принимали решения перейти на синтаксис Gherkin или фреймворк Cucumber. Простота команд Cypress – достаточно хорошее для нас решение, явно демонстрирующее, чем занят тест. Я довольно рано нашел твит от Кента С. Доддса, который мы взяли в качестве миссии:

Чем больше ваши тесты похожи на реальное использование ПО, тем больше уверенности они принесут.

- Кент С. Доддс (@kentcdodds) March 23, 2018

Мы хотели, чтобы наши тесты следовали определенному сценарию, а затем сгруппировали сценарии в фичи. В результате тест-структура выглядела примерно так:

cypress
|-- e2e
|    |-- board
|    `-- list
|-- fixtures
`-- support

Папки – это какая-либо фича, а файлы спецификаций внутри этих папок отражают поведение или юзер-стори, покрытые этим сценарием. Как уже упоминалось, эти сценарии отражают реальное поведение пользователя, но не написаны в синтаксисе Gherkin (Если, Когда, Тогда) и не используют фреймворк Cucumber. Сообщество Cypress-разработчиков открытого ПО создало предварительный обработчик Cucumber, позволяющий так писать тесты, я в целом не сторонник такого решения.

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

Несмотря на это, BDD-подход ставит во главу угла поведение пользователя, и с этим я определенно согласен. Я считаю, что команды Cypress уже сконцентрированы на поведении, и большая их часть читается, как обычное предложение:

1    cy.visit('/my-page')
2    cy.get('#element').click()

Настрой, действуй, проверь

Вместо подхода «Если – Когда – Тогда» я предпочитаю «Настрой – Действуй – Проверь» (Arrange – Act – Assert). По своей сути они очень схожи, но второй вариант, по моему мнению, точнее определяет цель тестирования. Ключевое слово «Когда» в синтаксисе Gherkin кажется слегка двусмысленным – не всегда ясно, к чему оно относится, к действию или к состоянию. Паттерн «Настрой – Действуй – Проверь» говорит об этом явно.

1  before( () => {
2        // arrange
3        cy.request('POST', '/api/lists', { name: 'new list' })
4  })
5  it('creates an item', () => {
6         // act
7         cy.visit('/')
8        cy.get('#create').type('list item{enter}')
9        // assert
10        cy.get('[data-cy=item]').should('be.visible')
11  })

Как правило, часть «Настрой» осуществляется при помощи вызовов API или настройки базы данных, а не через пользовательский интерфейс. Как правило, этот шаг идет в хуках before() или beforeEach().

Паттерн «Настрой – Действуй – Проверь» помогает принять решение, нужно ли проводить часть теста через UI или через API. Все, что делается через UI – часть шага «Действуй». Все, что до него, идет в часть «Настрой» и не выполняется через UI.

В ходе end-to-end теста шаги «Действуй» и «Проверь» можно проводить неоднократно.

Аннотирование тестов

Как упоминалось в начале, читабельность – это очень важно. Она упрощает навигацию по тестам. Возможно, это кажется мелочью, но на самом деле это очень ценно при дебаге теста. При создании блока it() имя теста должно давать вам достаточно информации о тест-сценарии. Плохие и хорошие примеры:

❌ не делайте так

1  it('board is visible', () => {})
2  it('works in edge cases', () => {})
3  it('handles input', () => {})

✅ так намного лучше

1  it('creates a board and navigates to board detail', () => {})
2  it('throws error when trying to access private board', () => {})
3  it('shows a warning message when input is empty', () => {})

В идеале заголовок теста должен быть таким, чтобы без вникания в содержание теста было ясно, что именно он делает. Как-то раз коллега рекомендовал мне писать аннотации так, чтобы it и имя теста читались, как предложение. Мне очень нравится этот подход, хотя в некоторых случаях это контрпродуктивно и приведет к странным именам тестов.

Не усложняйте ничего без нужды, если правило нужно нарушить – нарушайте.

Еще один полезный способ сделать тесты читабельнее – это добавить собственные кастомные логи. У Глеба Бахмутова есть удобный плагин для логирования в терминал, который точно может помочь с аннотированием тестов. Лично я предпочитаю добавлять в тесты шаги для аннотирования сценария. Я написал плагин, который делает вот что:

  • Каждая команда cy.step() описывает шаг в тесте.
  • Каждая команда cy.step() автоматически нумеруется.
  • Когда тест падает, к сообщению об ошибке прикрепляется нумерованный список.
  • Сообщение об ошибке выводится в терминал вместе со скриншотом падения.


Файлы спецификаций

Каждый файл спецификации должен содержать лишь часть тестов. End-to-end тесты, как правило, длиннее обычных, то есть содержат больше строк кода. Когда в файле спецификации несколько длинных сценариев, в нем становится сложнее ориентироваться.

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

Однако этот подход приводит к проблемам при попытке нажать кнопку “Run all specs” в открытом режиме Cypress. Этот режим по сути создает единую спецификацию из множества файлов, а, следовательно, ваши хуки before() и beforeEach() конкантенируются, что приводит к неожиданным результатам. Это нужно держать в уме. Однако при работе с большим проектом я практически никогда не прогоняю все спецификации в открытом режиме.

Селекторы

В прошлом я пробовал разные подходы, но остановился на рекомендации Cypress и добавил в приложение селекторы data-cy. Опыт показал, что этот вариант наиболее стабилен. Расчет на имена классов всегда приводил к случайным падениям. Это особенно верно сейчас, когда разработчики опираются на UI-библиотеки вроде material design или bootstrap. Их обновление часто вызывает изменения классов и ломает тесты.

Знание, что выбирать

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

1  <button disabled>
2         <span>Click me!</span>
3  </button>

В тесте вы хотите кликнуть по этому элементу. Заметьте, что у кнопки есть свойство disabled, что означает, что реальный пользователь не сможет кликнуть по ней. Это значит, что очень важно правильно нацелиться на элемент:

1  // тест пройдет, но по клику ничего не произойдет
2  cy.get('span').click()
3
4  // тест упадет, потому что кнопка заблокирована
5  cy.get('button').click()

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

Я также посоветовал бы добавлять атрибуты data-* самостоятельно, даже если вы тестировщик или не заняты в разработке проекта. Вы лучше поймете структуру приложения, а также познакомитесь с различными фреймворками. К тому же, по моему опыту, перекладывание задачи по добавлению селектора data-* на разработчиков не очень-то полезно. Это увеличивает петлю обратной связи и бессмысленно увеличивает расходы.

Устранение двойственности, повышение читабельности

Другое преимущество добавления своих собственных атрибутов селекторов в исходный код – это устранение двойственности, появляющейся при использовании Page Object. Это также относится к хранению ваших элементов в отдельном файле. Об этом тоже нужно позаботиться – легко поймать рассинхрон между реальным приложением и тестами.

Добавление data-* селекторов также поможет улучшить читабельность ваших тестов – вы можете добавить что угодно, имеющее смысл для создаваемого вами теста. Ряд моих собеседников обеспокоен соглашением о наименованиях, но я бы этим не парился. Два одинаковых атрибута data-* - это не проблема, если они не находятся в одном и том же тесте или на одном и том же экране. Я настоятельно советую использовать селекторы, описывающие, что видит пользователь, и не пытаться изобрести соглашение о наименованиях, которое охватит все до единого селекторы в приложении.

1  // ❌ считаю, что это слишком сложно
2  cy.get('[data-cy=account-screen-sidemenu-settings-modal]')
3  // ✅ намного лучше
4  cy.get('[data-cy=settings-modal]')

Стратегии селекторов, имеющие смысл

Обоснованное беспокойство на предмет использования атрибутов data-* - это ситуация, в которой атрибут изменен или удален. По моему опыту, при таком подходе это происходит реже. В основном это связано с тем, что эти атрибуты никогда не меняются/удаляются случайно, из-за обновления фреймворка или другого стороннего изменения. Если атрибут удален, то обычно вместе с элементом. В этом случае ваш тест должен упасть. Если атрибут переименован, это решается простым «найти и заменить».

Другой часто возникающий вопрос – это использование testing-библиотеки Cypress. Лично я не особенно ей пользуюсь, но признаю ее полезность. Эта библиотека часто заставляет вас полагаться на атрибуты доступности, и в результате вы тестируете две вещи одновременно – не только функциональность приложения, но и его доступность.

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

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

Кастомные команды

Кастомные команды – одна из наиболее мощных функциональностей Cypress. Возможность расширять библиотеку дает экосистеме Cypress потрясающую гибкость. Я, как правило, использую три категории кастомных команд

  • Вспомогательные команды
  • Вызовы API
  • Последовательности действий

Кастомные действия API

При помощи Cypress вы зачастую вызываете API для отправки данных или каких-либо действий в приложении. Так как не всегда желательно вызывать cy.request() и предоставлять авторизационные данные, заголовки или тело запроса, хорошей идеей будет создание кастомной команды. Вы можете создать функцию, которая разберется со значениями по умолчанию, или же передавать в нее различные аргументы, изменяющие поведение. Использованные в вызове API данные можно позднее задействовать в тесте или обработать их в нем.

Вспомогательные команды

Если вы пользуетесь селекторами data-*, создание команды cy.getByDataCy() может вам пригодиться. Вспомогательные команды обычно имеют дело с каким-либо частным случаем внутри приложения. Примеры таких команд - cy.getClipboard(), cy.getTooltip(), и т. д.

Последовательности действий

Последовательности действий больше всего похожи на традиционный Page Object. Это серия UI-шагов, которых нельзя избежать путем вызова API. В большинстве случаев они задействованы в ситуациях, требующих множества шагов и критически важных для тест-потока. Примерный вариант:

1  Cypress.Commands.add('pickSidebarItem', (item: 'Settings' | 'Account' | 'My profile' | 'Log out') => {
2
3        cy.get('[data-cy=hamburger-menu]')
4           .click()
5
6        cy.contains('[data-cy=side-menu]', item)
7          .click()
8
9  })

Организация кастомных команд

Мое главное правило – размещать каждую кастомную команду в ее собственном отдельном файле, а затем добавить их в отдельную папку проекта Cypress:

big-project
|-- cypress
|   |-- commands
|   |-- e2e
|   |-- fixtures
|   `-- support
|-- .gitignore
`-- cypress.config.ts

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

Каждая команда лежит в своем собственном файле и содержит как команду, так и определение TypeScript. Вот пример такой команды:

1  declare global {
2    namespace Cypress {
3      interface Chainable {
4        addBoardApi: typeof addBoardApi;
5      }
6    }
7  }
8
9  /**
10   * Creates a new board using the API
11   * @param name name of the board
12   * @example
13   * cy.addBoardApi('new board')
14   *
15   */
16  export const addBoardApi = function(this: any, name: string): Cypress.Chainable<any> {
17
18    return cy
19      .request('POST', '/api/boards', { name })
20      .its('body', { log: false }).as('board');
21
22  };

Документация Cypress рекомендует создавать централизованный файл index.d.ts, содержащий определения типов для всех команд. Лично я больше склоняюсь к вышеописанному подходу, так как в этом случае определение типа находится в том же файле, что и сама команда. Это снижает путаницу и упрощает поддержку.

У каждой команды есть собственный JSDoc-комментарий, дающий дополнительную информацию о том, что делает эта команда. Это очень полезно для командных новичков, а также делает код самодокументирующимся и указывает на полезные ссылки – скажем, на внутреннюю вики.

Вместо использования Cypress.Commands API каждая команда пишется как функция, а затем импортируется в файл cypress/support/e2e.ts.

cypress/support/e2e.ts
1  import { addBoardApi } from '../commands/addBoardApi'
2
3  Cypress.Commands.addAll({ addBoardApi })

Другой используемый мной способ – это создать файл index.ts, добавляющий все импорты из папки cypress/commands, и импортировать его в cypress/support/e2e.ts. Полезно, если вы решили переместить свое приложение в монорепозиторий и добавить кастомные команды в отдельную библиотеку, чтобы пользоваться ими в разных проектах.

monorepo-project
|-- node_modules
|-- packages
|   |-- commands       // library
|   |-- trelloapp      // app
|   `-- trelloapp-e2e// tests
|-- tools
|-- .editorconfig
|-- .eslintrc.json
|-- .gitignore
|-- .prettierignore
|-- .prettierrc
|-- nx.json
|-- package-lock.json
`-- package.jso
TypeScript

Все мои проекты используют TypeScript. Внедрение TypeScript в существующий JS-проект – очень простая задача, которую можно выполнять ступенчато. Ошибки TypeScript, как правило, не влияют на ваши тесты, но могут помочь найти проблемы. TypeScript руководит вами в ходе написания тестов, предлагая автодополнение, проверяя передаваемые в команды параметры, и т. д.

TypeScript также отлично работает с кастомными командами. Один из способов использования TypeScript в кастомных командах – это повторное использование типов исходного кода в тестах:

1  import Board from '@/src/models'
2
3  cy.request<Board>('POST', '/api/boards', { name: 'new board' })

Вышеприведенный пример кода – команда cy.request(), возвращающая типы из интерфейса Board, импортированного из исходного кода. Если у вас есть вот такой интерфейс:

1  interface Board {
2    id: number;
3    starred: boolean;
4    name: string;
5    created: string;
6    user: number;
7  }
8
9  export default Board;

…то вы заметите ошибку TypeScript, если решите написать тест на что-то, что не входит в интерфейс Board.

1  import Board from '@/src/models'
2
3  cy.request<Board>('POST', '/api/boards', { name: 'new board' })
4    .then(({ body }) => {
5      // the "key" will be underlined in editor
6      expect(body.key).to.be.a('number')
7    })

В дополнение к проверке кода в редакторе вы можете настроить lint-проверку, убеждающуюся, что в базе кода нет ошибок TypeScript:

package.json
1  "scripts": {
2    "lint": "tsc --noEmit"
3  }

Запуск команды npm run lint поможет убедиться, что все TypeScript-ошибки, внедренные при последних изменениях, будут отловлены заранее. Этот lint-шаг может быть хуком перед коммитом и даже предотвращать коммиты такого кода. Проверка займет всего несколько секунд.

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

Приятный бонус TypeScript  - способность определяет пути, что устраняет головняки с разрешением относительных путей в проекте. Допустим, у вас есть заданный в tsconfig.json путь.

tsconfig.json
1  {
2    "compilerOptions": {
3      "target": "es5",
4      "lib": ["es5", "dom"],
5      "types": ["cypress","node"],
6      "baseUrl": "./",
7      "paths": {
8        "@fixtures/*": [
9          "cypress/fixtures/*"
10        ]
11      },
12      "resolveJsonModule": true,
13    }
14  }

Файл фикстуры можно импортировать в ваш тест:

1  import boardSchema from '@fixtures/boardSchema.json'
2
3  it('board returns proper JSON schema', () => {
4
5    cy.api({
6      url: `/api/boards/1`
7    }).its('body')
8    .should('jsonSchema', boardSchema)
9
10  })

Покрытие кода

Ранее я уже объяснял, как работает покрытие кода. Это действительно мощный инструмент. Конечно, некоторые команды стремятся к стопроцентному покрытию кода, но я не считаю это чем-то полезным. Отчет о покрытии кода может послужить картой «пространства» приложения и указать вам на неисследованные области.

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

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

Отчеты о покрытии можно сохранять, как артефакты, или использовать сервисы вроде Codecov, делающие потрясающие выводы на основании вашего покрытия кода. Если хотите взглянуть на живой пример такого отчета, это можно увидеть в моем примере репозитория с Trello-приложением.

Утилиты

Все проекты уникальны, но тем не менее подвержены ряду нуждающихся в решении общих проблем. Чтобы не разбираться с одним и тем же многократно, я складываю все свои утилиты в папку cypress/utils. Там лежат штуки вроде generateRandomUser(), getAuthorization(). Обычно я импортирую их напрямую в тест, а не включаю в файл поддержки. Как правило, их немного, так как в Cypress есть встроенная библиотека lodash, в которой много полезных утилит.

1  // imports lodash from Cypress
2  const { _ } = Cypress
3
4  // generates number between 0 and 10
5  const randomNumber = _.random(10)

Глобальные хуки

В моих проектах, как правило, настроен ряд глобальных хуков. Типичный сценарий использования – это необходимость разбираться с согласием пользователя. Добавление глобального хука beforeEach() настраивает все важные куки и предотвращает появление этого сообщения в тестах.

cypress/e2e.ts
1  beforeEach(() => {
2    cy.setCookie('user_consents', '{"marketing":false,"essential":true}')
3  })

Всегда можно воспользоваться командой cy.clearCookies() для удаления кукис в тесте, тестирующем этот запрос согласия.

Тэги тестов

По мере роста проекта прогон всех тестов для каждого коммита становится почти невозможной задачей. Разделения тестов на категории легко добиться при помощи плагина @cypress/grep. Он позволяет запускать подмножество тестов на основании их имен или тэгов.

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

1  it('creates a new board', { tags: ['@smoke'] }, () => {
2    // test
3  })

У теста может быть множество тэгов, чтобы он запускался в зависимости от целей тестирования. К примеру, тэг @email – для прогона всех тестов с валидацией электронной почты, @mobile для всех мобильных тестов, @visual – для всех тестов с визуальной валидацией. Люблю размышлять о различных ситуациях, в которых понадобится прицел на определенную область приложения. К примеру, если изменился CSS, нам могут понадобиться все @visual-тесты, а если почтовый тест-сервис не работает, то набор @email, возможно, нужно временно исключить.

В CLI это можно сделать этой командой:

1         npx cypress run --env grepTags='@smoke'

Переключение конфигураций

Важно, чтобы тесты срабатывали в множестве различных окружений. Чтобы упростить задачу, я обычно создаю конфигурационную папку с .json-файлами, содержащими все специфичные для окружения переменные вроде baseUrl, url API, иной информации, которая может понадобиться в ходе теста. Они скармливаются объекту env из файла .json, и их легко можно получить через Cypress.env().

Подобная настройка разберется с передачей верной информации в проект:

cypress.config.ts
1  import { defineConfig } from 'cypress'
2
3  export default defineConfig({
4    // other config attributes
5    setupNodeEvents(on, config) {
6      // если версия не определена, используем local
7      const version = config.env.version || 'local'
8      // загрузка env из json
9      config.env = require(`./cypress/config/${version}.json`);
10      // изменение baseUrl
11      config.baseUrl = config.env.baseUrl
12
13      return config
14    }
15  })

Запуская тест с другой конфигурацией, нужно всего лишь сделать так:

1  npx cypress open --env version="production"

и Cypress загрузит все нужные переменные.

Помимо настройки конфигурации в отдельном .json, есть информация, которую нельзя передавать в репозитории – пароли, ключи API, и т. д. Обычно это часть окружения, передающаяся через CLI.

Чтобы упростить задачу, я пользуюсь пакетом dotenv, управляющим переменными окружения через использование файла .env.

.env
1  ADMIN_KEY="1234-5678-abcd-efgh"

⚠️ Всегда проверяйте, что файл .env добавлен в .gitignore – в противном случае вы рискуете закоммитить секретную информацию в публичный доступ.

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

cypress.config.ts
1  import { defineConfig } from 'cypress'
2  import 'dotenv/config'
3
4  export default defineConfig({
5    // другие атрибуты настройки
6    setupNodeEvents(on, config) {
7      // прочитать ADMIN_KEY из файла .env
8      config.env.ADMIN_KEY = process.env.ADMIN_KEY
9      return config
10    }
11  })

Скрипты Node

Файл cypress.config.ts может быстро распухнуть – особенно при настройке задач или решении конфигурационных вопросов. Поэтому я начал создавать для этого отдельные файлы в папке scripts.

scripts
|-- codeCoverage.ts
`-- resolveGoogleVars.ts

Таким образом основной файл конфигурации остается чистым и читабельным. Таким образом также проще управляться с множеством команд cy.task().

Документация

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

Как правило, документация состоит из трех важных частей:

  1. Установка проектов.
  2. Объяснения, рекомендации, примеры.
  3. Правила pull-реквестов (их можно добавить к используемой платформе).

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

Так как все проекты уникальны, важно объяснить все особенности. Каковы соглашения в проекте? Как вы решаете распространенные проблемы? Каковы соглашения для репозитория? Ответы на все эти вопросы должны находиться в документации. Основная цель этого документа – упростить вам жизнь, поэтому в идеале он должен быть читабельным и при необходимости разделенным на несколько файлов.

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

cypress
|-- commands
|-- config
`-- docs
|-- best-practices.md
`-- installation.md

Заключение

Большие проекты редко крутятся вокруг Cypress-команд – тут важнее тест-дизайн и дизайн проекта. У меня, конечно, есть идеи, что считать лучшими практиками, но большая часть моих проектов – живые организмы, которые меняются и развиваются с течением времени и изменением потребностей. Моя текущая структура выглядит примерно так:

big-project
|-- cypress
|   |-- commands
|   |-- config
|   |-- docs
|   |-- downloads
|   |-- e2e
|   |-- fixtures
|   |-- screenshots
|   |-- scripts
|   |-- support
|   |-- utils
|   `-- videos
`-- cypress.config.ts

Надеюсь, что это было для вас полезным.

Обсудить в форуме