Тестирование пользовательского интерфейса веб-приложения - один из самых неприятных аспектов веб-разработки. Тесты обычно нестабильны, их сложно написать и еще сложнее отладить, когда что-то не работает. Большинство современных решений, таких как Protractor и Nightwatch, основаны на Selenium. Это добавляет поддержку сетки Selenium или использования сторонней сетки в список вещей, которые нужно отслеживать и поддерживать. Это не весело для всех, кто участвует.

Входит Сайпресс.

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

Хотя Cypress великолепен, когда я настраивал свою реализацию, я обнаружил, что существует множество различных ресурсов о том, как настроить части фреймворка. Если бы я хотел увидеть, как добавить Typescript, мне нужно было перейти в одно место, затем для огурца в другое место, а другое - для всего, что я хотел добавить. Иногда разные настройки конфликтовали, и в итоге я тратил много времени на отладку проблем с моей настройкой, которых можно было избежать.

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

Установить Cypress

Прежде чем что-либо делать, нам нужно установить Cypress. Я начну с совершенно нового приложения Angular, но я не буду показывать что-либо конкретное для Angular в этом пошаговом руководстве, поэтому вы можете использовать Angular, React или просто проект npm, чтобы продолжить.

➜ npm install -D cypress

Это может занять несколько минут, поскольку он установит пакет Javascript и двоичный файл Cypress, необходимые для выполнения тестов. При установке не создается структура папок, которая нам нужна для Cypress, вместо этого это происходит при первом запуске Cypress. Итак, давайте продолжим и добавим сценарий npm, который мы будем использовать в этом посте для запуска Cypress. Продолжайте и запустите его, когда закончите.

"cypress:open": "$(npm bin)/cypress open"

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

Файловая структура

После инициализации появится папка cypress, которая будет инициализирована внутри корневого каталога вашего приложения. Внутри этой папки также будут созданы еще четыре.

- cypress
 |- fixtures
 |- integration
 |- plugins
 |- support

светильники

Папка fixtures - это место, где вы можете хранить статические данные, используемые в ваших тестах. Это могут быть тестовые данные или фиктивные ответы. Мы не будем тратить много времени на разговоры об этой части Cypress, но если вам интересно узнать больше, я бы начал здесь.

интеграция

В этой папке будут находиться все ваши тесты. Все образцы тестов, с которыми вы экспериментировали ранее, будут созданы здесь.

плагины

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

поддержка

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

Напишите свои тесты на машинописном тексте

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

Чтобы написать наши тесты на Typescript, нам нужно воспользоваться возможностями плагина Cypress. Мы собираемся добавить файловый препроцессор, который будет преобразовывать любые файлы Typescript, которые у нас есть, в Javascript перед запуском тестов. Для этого нам нужно будет установить несколько зависимостей.

➜ npm install -D ts-loader @cypress/webpack-preprocessor @babel/core @babel/preset-env babel-loader webpack typescript

Возможно, вы уже установили некоторые из них или они могут существовать как часть временной зависимости внутри вашего проекта.

Затем мы создадим файл конфигурации webpack внутри нашей папки cypress со следующим содержимым:

// cypress/webpack.config.js
module.exports = {
  resolve: {
    extensions: [".ts", ".js"]
  },
  node: { fs: "empty", child_process: "empty", readline: "empty" },
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: [/node_modules/],
        use: [
          {
          loader: "ts-loader"
          }
        ]
      }
    ]
  }
};

Здесь мы скажем webpack использовать пакет ts-loader для транспилирования всех файлов .ts. Есть и другие возможные загрузчики, которые вы можете использовать здесь, если хотите, однако эта настройка будет работать как есть.

Нам также нужно добавить tsconfig.json файл в наш каталог cypress. Это базовый вариант, который вы можете использовать. Их самая важная часть - это cypress внутри массива типов, так как именно так ваша IDE сможет использовать intellisense. Если вы хотите сохранить согласованность с остальной частью вашего приложения Typescript, вы можете расширить свою базу tsconfig.json и просто добавить сюда типы Cypress.

{
  "compilerOptions": {
  "strict": true,
  "baseUrl": "../node_modules",
  "target": "es5",
  "lib": ["es5", "dom"],
  "types": ["cypress"]
  },
  "include": [
    "**/*.ts"
  ]
}

Теперь нам нужно указать Cypress, что нужно предварительно обработать наши тестовые файлы с помощью webpack и нашей конфигурации webpack. Как вы, возможно, догадались из более ранних объяснений файловой структуры, это происходит внутри каталога плагинов в файле index.js. Я собираюсь создать новый файл в каталоге плагинов с именем preprocess.js, внутри которого я буду выполнять всю необходимую нам логику предварительной обработки. Нет необходимости создавать отдельный файл, но это помогает упростить index.js файл.

Вот содержимое этого файла:

const webpack = require('@cypress/webpack-preprocessor')
 
const options = {
 webpackOptions: require("../webpack.config.js")
};
 
module.exports = webpack(options)

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

const preprocess = require('./preprocess');
 
module.exports = (on, config) => {
 on("file:preprocessor", preprocess);
}

Вот и все. Теперь у нас должна быть возможность писать наши тесты на Typescript. Но не верьте мне на слово. Мы должны добавить образец теста, чтобы убедиться, что все работает правильно. Удалите все тесты, созданные в процессе инициализации. Создайте папку с именем google и файл внутри с именем search.spec.ts с этим содержимым.

// cypress/google/search.spec.ts
describe('When I visit Google', () => {
  beforeEach(() => {
    cy.visit('https://google.com/imghp')
  });
  
  it('I should be able to search', () => {
    cy.get('input[title="Search"]')
      .type('cat pictures{enter}');
  })
});

Это очень простой тест, который переходит на страницу Картинок Google и выполняет поиск изображений кошек, потому что кто не любит хорошие картинки с кошками. Чтобы проверить это, запустите в своем терминале npm run cypress:open - команду, которую мы настроили ранее. Средство запуска тестов Cypress должно открыться, и вы сможете выбрать наш новый тест и запустить его. Он должен пройти. Если по какой-то причине это не так, вернитесь и дважды проверьте, что вы все добавили правильно. Я знаю по собственному опыту, что легко сделать небольшую опечатку, которая все отбрасывает.

Отдельные конфигурации среды

У большинства известных приложений есть как минимум две среды: место для тестирования нового кода (QA) и затем приложение, ориентированное на клиента (prod). В некоторых приложениях их может быть больше, иногда до семи, и важно иметь возможность запускать тесты во всех необходимых вам средах.

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

Мы начнем с создания наших файлов конфигурации. В отличие от большинства папок, с которыми мы работали до сих пор, не существует установленного стандарта для их размещения. Поскольку мы расширяем Cypress, у нас есть возможность разместить их где угодно. В этом примере я собираюсь создать в каталоге плагинов папку с именем config. Мы будем настраивать три среды: local, qa и prod. Итак, давайте создадим три файла local.js, qa.js и prod.js и вставим их в каждый из них.

// cypress/plugins/config/{prod|qa|local}.js
module.exports = {
 baseUrl: '',
 env: {}
};

Это базовый файл конфигурации, настроенный в формате Cypress. Поле baseUrl здесь представляет URL-адрес тестируемого сайта. Это значение используется несколькими командами Cypress, в первую очередь .visit(). Установив baseUrl в конфигурации, вы можете перейти к маршруту в приложении, просто выполнив cy.visit('/route'), вместо того, чтобы вводить полный URL-адрес приложения. Это позволяет нам сделать нашу среду тестирования независимой.

Я продолжу пример Google, который мы создали ранее, и буду использовать https://google.com в качестве baseUrl в файле конфигурации prod. В своем приложении вы можете поместить URL-адрес для собственных сред приложений. Параметр env можно использовать для установки переменных среды для ваших тестов, которые должны быть доступны. Это удобно для паролей или других тестовых данных, которые также могут быть переданы извне. Для целей этой настройки я не буду добавлять никаких файлов.

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

const preprocess = require('./preprocess');
 
module.exports = (on, config) => {
 
  on("file:preprocessor", preprocess);
  
  const targetEnv = config.env.TARGET_ENV || 'qa';
  
  const environmentConfig = require(`./config/${targetEnv}`);
  
  return {
  ...config,
  ...environmentConfig,
  };
}

Мы добавили в файл всего несколько строк, но они делают многое. Первая новая строка, которую мы видим, это

const targetEnv = config.env.TARGET_ENV || 'qa';

Здесь мы устанавливаем переменную targetEnv на основе значения переменной среды, которую мы будем передавать в тесты с именем TARGET_ENV. Мы также по умолчанию используем qa, если вы забудете его передать.

const environmentConfig = require(`./config/${targetEnv}`);

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

return {
  ...config,
  ...environmentConfig,
};

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

Чтобы запустить тесты и передать переменную TARGET_ENV, запустите созданный ранее сценарий npm следующим образом: npm run cypress:open -- -e TARGET_ENV=prod.

Пользовательские команды

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

Ранее я упоминал, что каталог поддержки был удобным местом для настройки наших пользовательских команд, и этим мы займемся сейчас. Во-первых, давайте создадим в каталоге поддержки папку с именем commands и index.ts файл внутри нее. Внутри файла index.ts добавьте export * from './search-google';. Это будет то, что мы называем нашим файлом с помощью специальной команды. Для любых будущих команд, которые вы добавляете, вы захотите также экспортировать их в этот файл.

В этом примере я собираюсь создать настраиваемую команду для ввода текста в поле поиска на странице поиска Google. Потребитель этой команды должен иметь возможность вызывать cy.searchGoogle('text to search'). В каталоге команд создайте наш search-google.ts файл и вставьте в него следующий код (не волнуйтесь, я объясню, что происходит).

export function searchGoogle(searchText: string): void {
  cy.get('input[title="Search"]')
    .type(`${searchText}{enter}`);
}
 
Cypress.Commands.add('searchGoogle', searchGoogle);
 
declare global {
  namespace Cypress {
    interface Chainable {
      searchGoogle: typeof searchGoogle 
    }
  }
}

Сначала мы определяем функцию searchGoogle, которая принимает строку, вводит ее во вход поиска и нажимает клавишу ВВОД для поиска. Здесь нет ничего, что сильно отличалось бы от того, как вы бы написали это в обычном тесте.

В следующей строке мы сообщаем Cypress о нашей функции и делаем ее командой. Здесь функция Cypress.Commands.add принимает два аргумента. Первое - это имя команды, именно так вы хотите, чтобы команда вызывалась. Второй аргумент - это функция, которая будет вызываться при вызове команды. После этой строки ваша команда готова к использованию, однако, если вы используете Typescript (а я надеюсь, что да), ваша IDE не узнает о команде.

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

Настройка огурца

Как мы уже видели, Cypress дает нам возможность писать сквозные тесты аналогично тому, как мы пишем модульные тесты. Это один из способов написания тестов, однако многие из вас могут использовать Cucumber в своих сквозных тестах. Это не то, что Cypress поддерживает из коробки, однако, подобно тому, как мы настраиваем Typescript, мы можем использовать препроцессор, чтобы включить использование Cucumber. Если вы до сих пор следили за этим, удалите папку google, которую мы создали ранее, так как мы будем настраивать тесты для огурца немного по-другому.

Перво-наперво, давайте установим необходимый нам препроцессор.

➜ npm install -D cypress-cucumber-preprocessor

Есть три элемента конфигурации, которые нам нужно сделать, прежде чем мы сможем начать писать наши файлы функций и определения шагов. Сначала измените файл cypress.json, чтобы он выглядел так:

{
 "testFiles": "**/*.feature"
}

Это сообщает Cypress, где найти наши тестовые файлы. Затем нам нужно настроить препроцессор для использования неглобальных определений шагов. Это одна из замечательных особенностей реализации Cypress cucumber. Возможность иметь определения шагов, которые не являются глобальными, значительно упрощает написание наших файлов функций и тестов, поскольку нам не нужно беспокоиться о коллизиях. Для этого добавьте в свой package.json следующее:

"cypress-cucumber-preprocessor": {
  "nonGlobalStepDefinitions": true
}

Мы помещаем конфигурацию сюда, потому что препроцессор cypress-cucumber-preprocessor использует cosmiconfig, который извлекает информацию из package.json.

Наконец, поскольку мы используем webpack для предварительной обработки наших файлов, мы добавим несколько строк в нашу конфигурацию webpack, и мы будем готовы к работе. Добавьте этот объект в массив правил файла webpack.config.js:

{
  test: /\.feature$/,
  use: [
    {
      loader: "cypress-cucumber-preprocessor/loader"
    }
  ]
}

А теперь давайте напишем тесты!

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

Еще раз, мои тестовые примеры будут включать поиск в Google. Вот мой search.feature файл и определение шага, которое я создал в папке поиска.

# cypress/integration/search.feature
Feature: Google Search

  Test searching google
  
  Scenario: Search for cats
    Given I open Google home page
    Then I search for cats
// cypress/integration/search/search.spec.ts
import { Given, Then } from 'cypress-cucumber-preprocessor/steps';

Given('I open Google home page', () => {
  cy.visit('/')
});

Then(/^I search for (.*)$/, (text: string) => {
  cy.searchGoogle(text);
});

Если вы знакомы с огурцом, то здесь нет ничего шокирующего. Когда вы запустите npm run cypress:open -- -e TARGET_ENV=prod, вы должны увидеть возможность запустить наш search.feature файл.

Теперь предположим, что мы хотим добавить еще один файл функции для тестирования поиска в Картинках Google. Вот мой файл функций и определение шага:

# cypress/integration/images.feature
Feature: Google Images

  Test searching google images
  
  Scenario: Search for cat pictures
    Given I open Google images page
    Then I search for cat pictures
// cypress/integration/images/images.spec.ts
import { Given, Then } from 'cypress-cucumber-preprocessor/steps';

Given('I open Google images page', () => {
  cy.visit('/imghp')
});

Then(/^I search for (.*)$/, (text: string) => {
  cy.searchGoogle(text);
});

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

К счастью, препроцессор cypress-cucumber позволяет нам создать папку integration/common, в которую мы можем поместить определения шагов, общие для нескольких файлов функций. Затем мы можем полностью удалить дублированное определение шага и поместить его в файл внутри этой папки, и наши тесты все равно должны пройти.

Время праздновать! Были сделаны!

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

Ресурсы

  1. Кипарис без огурца. Исходный код
  2. Кипарис с исходным кодом огурца
  3. Кипарисовая документация
  4. Препроцессор Webpack
  5. Препроцессор огурца

Первоначально опубликовано на https://fullstackhq.io 20 ноября 2019 г.