В этом посте я расскажу о процессе добавления сквозных тестов Cypress в существующий проект.

Зачем сквозное тестирование?

У всех методов тестирования есть свои плюсы и минусы. Сквозное тестирование наиболее близко к реальному пользовательскому тестированию, что является одним из его основных преимуществ. Чем ближе тест к имитации пользователя, тем больше вероятность того, что он обнаружит проблемы, с которыми может столкнуться пользователь.

Если вы хотите, чтобы пользователь протестировал твит в Твиттере, вы можете сказать ему что-то вроде:

Перейдите на https://twitter.com и войдите в систему. Щелкните текстовое поле с текстом-заполнителем Что происходит? И введите Это тестовый твит. Нажмите кнопку с текстом Твитнуть. Теперь перейдите на страницу своего профиля и посмотрите первый твит. Текст должен иметь вид Это тестовый твит.

В идеале вы должны дать аналогичные инструкции своему исполнителю сквозных тестов.

Вместо этого вы могли бы заставить его искать элементы по именам классов или идентификаторам, но что, если имена или идентификаторы классов намеренно изменятся? Или что, если текст изменится случайно? Если вы сказали, что исполнитель тестов нажимает кнопку по имени класса, тест может пройти некорректно. Вы можете возразить:

Что делать, если вы хотите специально изменить текст? Может быть, вы хотите изменить текст кнопки на «Отправить» вместо «Твитнуть»?

Возможно, это веский аргумент, но вы также можете возразить, что действительно хотите, чтобы тест не прошел, если текст изменится. В конечном итоге вы должны спросить себя: «Если этот текст изменится, я хочу, чтобы мои тесты сломались?» В случае «Отправить» или «Твитнуть», возможно, вы не хотите, чтобы тест прервался, но, возможно, если текст был случайно удален или написан с ошибкой, вы захотите, чтобы они прервались. У вас не может быть и того, и другого, поэтому вам нужно принять лучшее решение для себя и своего приложения.

Некоторые недостатки сквозного тестирования:

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

Какой фреймворк для сквозного тестирования выбрать

Существует множество фреймворков для сквозного тестирования, и выбрать «правильный» может быть непросто. Я поделюсь своими мыслями очень кратко, хотя, по общему признанию, использовал только Cypress:

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

Puppeteer - это решение Google с открытым исходным кодом. Он кажется легким и легким в использовании. Он имеет открытый исходный код и работает на Chromium (без заголовка или без него). Puppeteer позиционируется как тестовая среда с богатой функциональностью, лучше, чем отсутствие сквозных тестов, но не полноценное решение. Также недавно они поделились, что экспериментируют с Firefox.

Cypress - удобная для разработчиков среда тестирования с открытым исходным кодом. Cypress записывает снимки и видео ваших тестов, имеет консоль для запуска тестов и бесплатен. Разработчикам и специалистам по обеспечению качества легко начать работу. В настоящее время он поддерживает только варианты Chrome, но в планах есть кроссбраузерная поддержка. У него нет встроенной поддержки iframe, но есть обходные пути. Cypress имеет свою собственную систему, основанную на обещаниях, которую вы должны использовать (нельзя использовать обещания ES6).

Вот хороший ресурс для подробного сравнения Cypress и Test Cafe: https://medium.com/yld-engineering-blog/evaluating-cypress-and-testcafe-for-end-to-end-testing- fcd0303d2103

Начиная

Я собираюсь использовать проект https://ydkjs-exercises.com. Это одностраничное веб-приложение, в котором представлены упражнения, призванные помочь пользователям проверить свои знания при чтении Вы не знаете JavaScript. Он использует React, React Router и React Context API. Существуют модульные / интеграционные тесты с использованием jest и react-testing-library. А теперь добавлю сквозное тестирование с Cypress!

Я буду отслеживать прогресс с помощью тегов, начиная с cypress-0 и увеличивая целое число на каждом шаге. Вот отправная точка.

Первый шаг - установить Cypress как devDependency:

npm install cypress --save-dev

Текущая версия Cypress - v3.1.1. В документации упоминается, что пакет Cypress npm является оболочкой для двоичного файла Cypress. И что, начиная с версии 3.0, двоичный файл загружается в каталог глобального кеша для использования в проектах.

Теперь давайте откроем Cypress. Если вы используете npm версии ›5.2, вы можете открыть его, используя:

npx cypress open

Это открывает Cypress с приветственным модальным окном, говорящим нам, что они добавили кучу файлов в наш проект:

После щелчка, чтобы закрыть модальное окно, мы видим, что есть несколько примеров тестов, и мы видим, что можем запустить их в Chrome 70. Если вы нажмете «Запускается», вы увидите, что вы можете настроить панель управления Cypress для просмотра при предыдущих прогонах. Мы не будем об этом беспокоиться, но вы, безусловно, можете попробовать эту функцию.

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

Вот текущий прогресс на данный момент.

Написание кипарисового скрипта

Мы почти готовы написать наш первый тест. Нам нужно создать каталог для хранения наших тестов Cypress: cypress/integration/ydkjs

Теперь нам нужно написать сценарий, который запустит наш сервер разработки, запустит наши тесты Cypress, а затем остановит наш сервер разработки. Этот проект был загружен с помощью приложения Create React, что означает, что у него есть scripts/start.js файл, который используется для запуска сервера. Я собираюсь скопировать оттуда код, вставить его в новый scripts/cypress.js файл и внести некоторые изменения.

Приведенный ниже фрагмент кода - это основа нашего нового файла scripts/cypress.js.

return devServer.listen(port, HOST, err => {
    if (err) {
        return console.log(err);
    }
    if (isInteractive) {
        clearConsole();
    }
    console.log(chalk.cyan('Starting the development server...\n'));
    return cypress
        .run({
            spec: './cypress/integration/ydkjs/*.js',
        })
        .then(results => {
            devServer.close();
        });
});

Он делает именно то, что мы обещали. Он запускает сервер разработки, запускает все тестовые файлы в cypress/integration/ydkjs, а затем останавливает сервер разработки.

Теперь в cypress.json мы можем добавить наш baseUrl:

{
    "baseUrl": "http://localhost:3000"
}

Теперь мы можем написать наш первый тест! Назовем его cypress/integration/ydkjs/sidebar.js, и мы будем использовать его для проверки функциональности боковой панели. А пока давайте просто напишем фиктивный тест:

/* globals context cy */
/// <reference types="Cypress" />
context('Sidebar', () => {
    beforeEach(() => {
        cy.visit('/');
    });
    
    it('does something', () => {
        cy.contains('YDKJS Exercises');
    });
});

Все, что мы здесь делаем, - это посещаем базовый URL-адрес и находим элемент, содержащий «Упражнения YDKJS». Обратите внимание, что я добавил комментарий только к первой строке, чтобы eslint не жаловался на неопределенные переменные Cypress.

Я также добавил в свой package.json новый скрипт:

"scripts": {
    ...
    "cypress": "node scripts/cypress.js",
    ...
},

Итак, теперь я могу позвонить npm run cypress, когда я хочу запустить свои сквозные тесты Cypress. Теперь, когда я выполняю эту команду в терминале, я вижу, что мой сервер запускается, тест выполняется и проходит, а затем сервер останавливается. Woohoo!

Вот код до этого момента.

Напишем настоящие тесты!

Теперь, когда у нас есть сценарий Cypress, настроенный для запуска сервера, запуска тестов и остановки сервера, мы можем начать писать тесты!

Мы уже создали sidebar.js тестовый файл, поэтому давайте напишем несколько тестов для нашей функции боковой панели. Возможно, нашим первым тестом должно быть тестирование, чтобы убедиться, что боковая панель закрывается, когда мы нажимаем кнопку X, и снова открывается, когда мы нажимаем на гамбургер.

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

beforeEach(() => {
    cy.visit('/');
    cy.contains('Progress').should('exist');
});

Теперь приступим к написанию теста. Поскольку X на самом деле является SVG, мы не можем легко сказать Cypress, чтобы он нашел его. Поэтому мы найдем его с помощью атрибута data-testid или cy.get("[data-testid=closeSidebar]").click(). Я знаю, что вы думаете…

Хорошо, я понимаю, что в этом случае нельзя использовать текст. Но зачем использовать атрибут данных? Почему бы просто не использовать имя класса или идентификатор?

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

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

Наш завершенный тест может выглядеть примерно так:

it('closes when X is clicked and reopens when hamburger is clicked', () => {
    cy.get('[data-testid=closeSidebar]').click();
    cy.contains('Progress').should('not.exist');
    cy.get('[data-testid=openSidebar]').click();
    cy.contains('Progress').should('exist');
});

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

А видео теста можно посмотреть в cypress/ydkjs/sidebar.js.mp4! Довольно аккуратно. Это очень полезно, когда ваши тесты терпят неудачу, а вы не знаете, почему.

Вам нужно быть осторожным с тем, что Cypress - это система, основанная на обещаниях. Когда вы выполняете cy.contains('Progress').should('not.exist'), Cypress не перейдет к следующей строке кода, пока эта строка не станет истинной. Если он видит элемент DOM, содержащий «Progress», он будет ждать, пока он не исчезнет или пока не истечет время ожидания и тест не пройдет.

Эта система хороша тем, что делает написание этих тестов очень быстрым и легким. Однако иногда он может вас укусить, когда вы имеете дело с асинхронными действиями. Возможно, вы хотите убедиться, что элемент DOM не отображается в результате нажатия кнопки. Вы можете просто нажать кнопку, а затем проверить, существует ли этот элемент DOM? Но что, если элемент DOM создается через секунду после нажатия кнопки? Ваш тест пройдёт, хотя должен был провалиться.

Напишем еще один тест.

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

it('navigates to /up-going when Up & Going is picked', () => {
    cy.contains(/Up & Going \(/).click({ force: true });
    cy.url().should('include', '/up-going');
    cy.contains('Chapter 1: Into Programming').should('exist'); 
    cy.contains('Chapter 2: Into JavaScript').should('exist');
});

Относительно этого теста следует отметить несколько моментов. На главной странице ydkjs-plays текст «Up & Going» находится в двух местах. Один раз на боковой панели и один раз в середине страницы. На боковой панели полный текст - «Up & Going (0/41)», что означает, что пользователь ответил на 0 вопросов из 41 возможных. На главной странице текст просто «Up & Going». Поэтому, чтобы убедиться, что мы нажимаем «Вверх и вперед» на боковой панели, я использую регулярное выражение, чтобы щелкнуть элемент, содержащий «Вверх и вперед (». Я не хочу, чтобы он включал 0 или 41, потому что эти числа могут измениться. Это может быть один из тех случаев, когда использование атрибута данных может быть лучше, чем использование текста, как в приведенном выше фрагменте кода.

Мне нужно принудительно вызвать событие щелчка, потому что тег привязки имеет текст, но он заключен в элемент элемента списка. После этого я проверяю правильность URL-адреса и правильность содержимого на странице.

Это последнее состояние кода.

Заключение

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

Как только вы освоитесь с ним, вы даже сможете сделать свой тестовый код многоразовым, создав свои собственные команды Cypress!

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

В целом, Cypress - отличный выбор, если вы хотите вывести свое тестирование на новый уровень с помощью нескольких сквозных тестов!

Удачного кодирования!