JavaScript

Cypress для начинающих: начало работы со сквозным тестированием

Spread the love

Перевод: Valentino GagliardiCypress Tutorial for Beginners: Getting started with End to End Testing

Базовый требования

Чтобы продолжить, вам понадобится рабочая установка Node.js в вашей системе. Кроме того, полезно иметь базовое понимание нового синтаксиса JavaScript.

Что такое Cypress? Что такое сквозное тестирование?

Сквозное тестирование (End to End Testing), или тестирование пользовательского интерфейса, — это один из многих подходов к тестированию веб-приложений.

Сквозной тест проверяет, работает ли веб-приложение должным образом, путем тестирования так называемого пользовательского потока — user flow.

Важно ли сквозное тестирование? Да, это так. Но никто не любит тесты E2E. Они могут быть медленными, громоздкими и дорогими в написании.

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

Настройка проекта

Создадим новую папку cypress-tutorial, зайдем в нее и инициализируем новый проект JavaScript:

mkdir cypress-tutorial && cd $_
npm init -y

Внутри этой папки создадим два новых файла.

HTML-документ index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Cypress tutorial for beginners</title>
  </head>
  <body>
    <main>
      <form>
        <div>
          <label for="name">Name</label>
          <input type="name" required name="name" id="name" />
        </div>
        <div>
          <label for="email">Email</label>
          <input type="email" required name="email" id="email" />
        </div>
        <div>
          <label for="message">Your message</label>
          <textarea id="message" name="message" required></textarea>
        </div>
        <div>
          <button type="submit">SEND</button>
        </div>
      </form>
    </main>
  </body>
  <script src="form.js"></script>
</html>

Это HTML-форма с набором входных данных и текстовым полем textarea.

Затем создайте файл JavaScript form.js с минимальной логикой для обработки отправки формы:

const form = document.forms[0];

form.addEventListener("submit", event => {
  event.preventDefault();
});

Обратите внимание, что я не добавил стили для простоты примера. Теперь, мы готовы установить Cypress.

Установка Cypress

Чтобы установить Cypress, все еще находясь в папке проекта, запустите:

npm i cypress --save-dev

Дайте ему минуту (ему нужно скачать двоичный файл), а затем запустите:

node_modules/.bin/cypress open

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

Закроем окно и перейдем к следующему разделу.

Запуск проекта

Для запуска проекта на локальном компьютере убедитесь, что у вас установлена новая версия Node.js, а затем запустите команду:

npx serve

Она запустит сервер разработки по адресу http://localhost:5000/. Перейдя по ссылке вы увидите нашу форму:

Теперь пора написать наш первый тест!

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

Создайте новый файл в cypress/integration/form.spec.js и напишите свой первый блок:

describe("Form test", () => {
  //
});

describe — это метод Cypress (заимствованный из Mocha) для хранения одного или нескольких связанных тестов. Каждый раз, когда вы начинаете писать новый набор функциональных тестов, оборачивайте его в блок describe.

Как видите, он принимает два аргумента: строку для описания набора тестов и функцию обратного вызова для обертывания фактического теста.

Далее мы встретимся с другой функцией, называемой it, которая является фактическим тестовым блоком:

describe("Form test", () => {
  it("Can fill the form", () => {
    //
  });
});

Если вы знаете Jest, вы можете вспомнить, что он использует it или test взаимозаменяемо. В случае с Cypress дело обстоит иначе. it единственный возможный блок.

А теперь время для дымового теста (smoke test)! В блоке it напишите:

describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");
  });
});

Здесь cy — сам Cypress. visit — это метод Cypress для перехода по заданному пути.

get — это метод выбора элементов на странице. С помощью этого кода мы говорим Cypress «иди, и возьми форму на странице».

Через минуту мы увидим Cypress в действии, но сначала немного настроек!

Настройка Cypress

Чтобы немного упростить работу, мы собираемся настроить Cypress. Для начала откройте package.json и создайте скрипт с именем e2e, указывающий на двоичный файл Cypress:

  "scripts": {
    "e2e": "cypress open"
  },

Команда open запускает визуальное отображение прохождения тестов (в браузере видимое для программиста). Есть еще команда run которая запускает прохождение тестов в консоле.

Затем откройте cypress.json и настройте базовый URL:

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

С помощью этой опции мы говорим Cypress посещать только наш URL-адрес разработки. (5000 — порт по умолчанию для пакета serve

Теперь мы готовы запустить ваш первый тест!

Запускаем тест

Готовы? Когда сервер разработки все еще работает в терминале:

npx serve

откройте другой терминал и запустите:

npm run e2e

Вы должны увидеть, как Cypress откроет браузер и отобразить страницу. После запуска теста на странице будет что то типа такого:

Это был ваш первый запуск теста! И visit, и get — это команды Cypress, которые также действуют как неявные утверждения (implicit assertions), то есть, если элемент находится на странице, Cypress будет считать, что тест пройден.

Теперь давайте продолжим расширять наш тест, чтобы увидеть, может ли пользователь заполнить форму:

describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");

    cy.get('input[name="name"]').type("Molly");
  });
});

Вот еще одна команда Cypress: type, которая, что неудивительно, вводит в наш input элемент указанный текст. Также обратите внимание на селектор CSS для получения элемента ввода.

А пока давайте добавим еще одну команду: should. Эта команда создает утверждение и используется, например, для проверки того, обновляет ли input элемент свое состояние должным образом:

describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");

    cy.get('input[name="name"]')
      .type("Molly")
      .should("have.value", "Molly");
  });
});

Обратите внимание на have.value. Если вы новичок в этой концепции, вы можете узнать больше об утверждениях здесь.

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

Больше тестов и Submit

Чтобы продолжить наш тест, мы можем проверить ввод электронной почты:

describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");

    cy.get('input[name="name"]')
      .type("Molly")
      .should("have.value", "Molly");

    cy.get('input[name="email"]')
      .type("molly@dev.dev")
      .should("have.value", "molly@dev.dev");
  });
});

Также мы можем проверить текстовую область textarea:

describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");

    cy.get('input[name="name"]')
      .type("Molly")
      .should("have.value", "Molly");

    cy.get('input[name="email"]')
      .type("molly@dev.dev")
      .should("have.value", "molly@dev.dev");

    cy.get("textarea")
      .type("Mind you if I ask some silly question?")
      .should("have.value", "Mind you if I ask some silly question?");
  });
});

Если вы оставили Cypress открытым, тест должен отслеживать ваши изменения и запускаться автоматически:

Как мило! В качестве вишенки на торте давайте протестируем отправку формы с помощью submit:

describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");

    cy.get('input[name="name"]')
      .type("Molly")
      .should("have.value", "Molly");

    cy.get('input[name="email"]')
      .type("molly@dev.dev")
      .should("have.value", "molly@dev.dev");

    cy.get("textarea")
      .type("Mind you if I ask some silly question?")
      .should("have.value", "Mind you if I ask some silly question?");

    cy.get("form").submit();
  });
});

Тест должен пройти без проблем. Вы можете заметить одну вещь: все используемые команды с самоописанием: type, submit. Это простой английский.

Теперь давайте немного пофантазируем в следующем разделе с тестированием запросов XHR.

Заглушка запросов XHR с помощью Cypress

Помимо всего прочего, Cypress также может перехватывать запросы AJAX и имитировать ответы. Этот подход известен как stubbing (заглушка).

Чтобы понять разницу между mocking и stubbing, прочтите этот пост.

Stubbing удобен при разработке, когда вам нужен возврат фальшивого ответа на ваши запросы AJAX.

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

describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");
    // ...
    // опущено для краткости
    // ...
    cy.server();
    cy.route({
      url: "/users/**",
      method: "POST",
      response: { status: "Saved", code: 201 }
    });

    cy.get("form").submit();
  });
});

Здесь cy.server запускает «виртуальный» сервер, в то время как cy.route настраивает поддельную конечную точку API.

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

Использование stubbing полезно, потому что мы можем полностью обойти настоящее API в процессе разработки. Давайте расширим тест с помощью cy.contains:

describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");
    
    // ...

    cy.server();
    cy.route({
      url: "/users/**",
      method: "POST",
      response: { status: "Form saved!", code: 201 }
    });

    cy.get("form").submit();

    cy.contains("Form saved!");
  });
});

Ожидается, что тест завершится неудачно, потому что нет логики для отправки формы в API. В следующем разделе мы поправим тест.

Отправка данных формы в API

На момент написания Cypress не мог перехватывать запросы Fetch. Начиная с версии 4.9.0 Cypress имеет экспериментальную поддержку Fetch stubbing. Чтобы включить его, настройте experimentalFetchPolyfill в cypress.json:

{
  "baseUrl": "http://localhost:5000",
  "experimentalFetchPolyfill": true
}

В этом примере, написанном до 4.9.0, мы будем использовать XMLHttpRequest. Откройте form.js и реализуйте следующую логику:

const form = document.forms[0];

form.addEventListener("submit", event => {
  event.preventDefault();
  new FormData(form);
});

document.addEventListener("formdata", event => {
  const body = Object.fromEntries(event.formData.entries());
  const jsonBody = JSON.stringify(body);
  const request = new XMLHttpRequest();
  request.open("POST", "https://jsonplaceholder.typicode.com/users/");
  request.send(jsonBody);
});

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

В прослушивателе событий мы создаем объект с fromEntries (ECMAScript 2019). Далее мы отправляем данные в API.

Чтобы пройти тест, нам также необходимо получить ответ от API и сохранить его в документе. Для этого мы можем прослушивать событие onload XMLHttpRequest:

// omit
document.addEventListener("formdata", event => {
  const body = Object.fromEntries(event.formData.entries());
  const jsonBody = JSON.stringify(body);
  const request = new XMLHttpRequest();
  request.open("POST", "https://jsonplaceholder.typicode.com/users/");
  request.send(jsonBody);
  // получение ответа
  request.onload = function() {
    const jsonResponse = JSON.parse(this.response);
  };
});

Наконец, мы можем что опасно (но мы это сделаем просто для простоты) сохранить ответ на странице (пожалуйста, не делайте этого в вашей кодовой базе):

// ...
  request.onload = function() {
    const jsonResponse = JSON.parse(this.response);
    document.body.innerHTML += `Response from the server: ${jsonResponse.status}`;
  };

Пришло время посмотреть на прохождение теста!

Заглушка запросов XHR с помощью Cypress: успешный тест

Подведем итоги полного теста в cypress/integration/form.spec.js:

describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");

    cy.get('input[name="name"]')
      .type("Molly")
      .should("have.value", "Molly");

    cy.get('input[name="email"]')
      .type("molly@dev.dev")
      .should("have.value", "molly@dev.dev");

    cy.get("textarea")
      .type("Mind you if I ask some silly question?")
      .should("have.value", "Mind you if I ask some silly question?");

    cy.server();
    cy.route({
      url: "/users/**",
      method: "POST",
      response: { status: "Form saved!", code: 201 }
    });

    cy.get("form").submit();

    cy.contains("Form saved!");
  });
});

Вот полный код для form.js:

const form = document.forms[0];

form.addEventListener("submit", event => {
  event.preventDefault();
  new FormData(form);
});

document.addEventListener("formdata", event => {
  const body = Object.fromEntries(event.formData.entries());
  const jsonBody = JSON.stringify(body);
  const request = new XMLHttpRequest();
  request.open("POST", "https://jsonplaceholder.typicode.com/users/");
  request.send(jsonBody);
  // получение ответа
  request.onload = function() {
    const jsonResponse = JSON.parse(this.response);
    document.body.innerHTML += `Response from the server: ${jsonResponse.status}`;
  };
});

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

На данный момент у нас все в порядке, и если вы оставили Cypress открытым, вы уже должны увидеть успешное прохождение теста:

Вы можете увидеть раздел маршрутов вверху слева и заглушку XHR в тестовых выходных данных, это означает, что Cypress перехватил запрос POST.

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

Данной заглушкой можно закончить урок. Прекрасная работа!

Выводы

Я надеюсь, что вы узнали что-то новое из этого урока, и вы примените эти концепции в своем следующем проекте! Тестирование важно!

Сквозное тестирование не должно быть трудным: Cypress делает его приятным и приятным. Команда Cypress действительно справилась с этим.

Кроме того, есть очень подробная документация: Cypress Docs наполненная передовыми практиками и примерами.

Спасибо за прочтение!

Была ли вам полезна эта статья?
[9 / 4]

Spread the love
Editorial Team

View Comments

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

  • На одном из скринов есть несовпадение с последующими скринами что даёт ошибку в тестах. Сначала идёт response: {status: "Saved" ... }, а на следующих эта же строка выглядит так: response:{ status: "Form saved!" ... }
    Несмотря на это спасибо большое за перевод статьи!

Recent Posts

Vue 3.4 Новая механика v-model компонента

Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование​ v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…

12 месяцев ago

Анонс Vue 3.4

Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…

12 месяцев ago

Как принудительно пере-отобразить (re-render) компонент Vue

Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…

2 года ago

Проблемы с установкой сертификата на nginix

Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…

2 года ago

Введение в JavaScript Temporal API

Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…

2 года ago

Когда и как выбирать между медиа запросами и контейнерными запросами

Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…

2 года ago