React: Написание сквозного теста с помощью react cypress


Написание сквозного теста с использованием кипариса

Что такое кипарис?

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

Cypress очень мощный, вы можете создавать модульные тесты без каких-либо конфигураций или строительных лесов. Вы просто пишете код, а затем параллельно запускаете тесты с нулевыми зависимостями! Вы можете использовать cypress с другими фреймворками, такими как Angular, Vue или React Native!

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

Что такое тест e-2-e?

E-2-E тестирование — это тип тестирования, который проверяет все приложение. Его также можно назвать интеграционным тестом, поскольку он проверяет взаимодействие между различными компонентами и модулями.

Это не модульный тест (поскольку он не имеет зависимости от отдельных модулей или компонентов), а скорее сквозной.

Почему стоит выбрать Cypress?

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

Установка cypress с react

Приложение React

Чтобы настроить cypress, сначала создайте приложение react, в этом примере мы будем использовать простое приложение todo с помощью create-react-app.

Сначала создайте react-приложение в папке проекта

npx create-react-app todo
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Очистите все ненужные файлы в папке src и коды и создайте структуру папок следующим образом или вы можете использовать свою собственную структуру

в app.jsx добавьте

import { useEffect, useState } from "react";
import Addtodo from "./addtodo";
import "./app.css";
import Todo from "./todo";

const App = () => {
  const [todos, setTodos] = useState([]);
  const [keyword, setKeyword] = useState("All");
  const [filteredTodos, setFilteredTodos] = useState(todos);
  const addTodo = (todo) => {
    setTodos([todo, ...todos]);
    setKeyword("All");
  };

  const updateTodo = (id) => {
    let todosCopy = todos;
    const todoIndex = todosCopy.findIndex((el) => el.id === id);
    const todo = todosCopy.find((el) => el.id === id);
    todosCopy[todoIndex] = {
      ...todo,
      isActive: !todo.isActive,
    };
    setTodos([...todosCopy]);
  };

  const deleteTodo = (id) => {
    const newTodos = todos.filter((el) => el.id !== id);
    setTodos(newTodos);
  };

  useEffect(() => {
    filterTodos(keyword);
  }, [todos, keyword]);

  const filterTodos = (key) => {
    if (key === "All") {
      setFilteredTodos(todos);
      setKeyword("All");
      return;
    }
    if (key === "Active") {
      setFilteredTodos(todos.filter((el) => el.isActive === true));
      setKeyword("Active");
      return;
    }
    if (key === "Completed") {
      setFilteredTodos(todos.filter((el) => el.isActive === false));
      setKeyword("Completed");
      return;
    }
    if (key === "Clear") {
      setTodos(todos.filter((el) => el.isActive === true));
      setKeyword("All");
    }
  };

  return (
    <div className="App">
      <div className="container">
        <Addtodo addTodo={addTodo} />
        <ul>
          {filteredTodos.map((todo) => (
            <Todo
              todo={todo}
              key={todo.id}
              updateTodo={updateTodo}
              deleteTodo={deleteTodo}
            />
          ))}
        </ul>
        <div className="actions">
          <span>
            {todos.filter((el) => el.isActive === true).length} Active items
            left
          </span>
          &nbsp;
          <div>
            <button
              className="filter-all"
              onClick={() => {
                filterTodos("All");
              }}
            >
              All
            </button>
            &nbsp;
            <button
              className="filter-active"
              onClick={() => {
                filterTodos("Active");
              }}
            >
              Active
            </button>
            &nbsp;
            <button
              className="filter-completed"
              onClick={() => {
                filterTodos("Completed");
              }}
            >
              Completed
            </button>
            &nbsp;
            <button
              className="delete-all"
              onClick={() => {
                filterTodos("Clear");
              }}
            >
              Clear Completed
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

export default App;

Войти в полноэкранный режим Выйти из полноэкранного режима

В addTodo.jsx

import { useState } from "react";

const Addtodo = ({ addTodo }) => {
  const [todo, setTodo] = useState({
    id: "",
    title: "",
    isActive: true,
  });
  const { title } = todo;
  return (
    <div>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          todo?.title?.trim()?.length > 0 && addTodo(todo);
          setTodo({
            id: "",
            title: "",
            isActive: true,
          });
        }}
      >
        <input
          placeholder="New todo"
          value={title}
          onChange={(e) => {
            let id = new Date();
            id = id.getTime();
            if (e.target.value?.trim().length > 0)
              setTodo({
                ...todo,
                id,
                title: e.target.value,
              });
          }}
        />
        <button className="submit-button" title="Add Todo">
          <svg
            width="12"
            height="12"
            viewBox="0 0 12 12"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M6.75 5.25V0H5.25V5.25H0V6.75H5.25V12H6.75V6.75H12V5.25H6.75Z"
              fill="white"
            />
          </svg>
        </button>
      </form>
    </div>
  );
};

export default Addtodo;

Ввести полноэкранный режим Выйти из полноэкранного режима

в файле todo.tsx добавить

const Todo = ({ todo, deleteTodo, updateTodo }) => {
  const { title, id, isActive } = todo;
  return (
    <li className={isActive ? "incomplete" : "complete"}>
      <div>
        <button
          className={isActive ? "mark mark-complete" : "mark mark-incomplete"}
          onClick={() => {
            updateTodo(id);
          }}
          title={isActive ? "Mark complete" : "Mark incomplete"}
        >
          <svg
            width="13"
            height="10"
            viewBox="0 0 13 10"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M10.455 0.45498C10.6663 0.253813 10.9475 0.1426 11.2392 0.144808C11.531 0.147015 11.8104 0.262471 12.0187 0.466812C12.2269 0.671152 12.3476 0.948396 12.3553 1.24004C12.363 1.53169 12.2571 1.81492 12.06 2.02998L6.07499 9.51498C5.97208 9.62583 5.84787 9.71478 5.70979 9.77653C5.57171 9.83828 5.4226 9.87155 5.27137 9.87435C5.12014 9.87715 4.9699 9.84942 4.82963 9.79283C4.68936 9.73624 4.56194 9.65194 4.45499 9.54498L0.485992 5.57598C0.375462 5.47299 0.286809 5.34879 0.225321 5.21079C0.163833 5.07279 0.13077 4.92382 0.128105 4.77276C0.12544 4.62171 0.153227 4.47167 0.209808 4.33158C0.26639 4.1915 0.350607 4.06425 0.457435 3.95742C0.564263 3.85059 0.691514 3.76638 0.831596 3.7098C0.971678 3.65321 1.12172 3.62543 1.27278 3.62809C1.42383 3.63076 1.5728 3.66382 1.7108 3.72531C1.8488 3.7868 1.973 3.87545 2.07599 3.98598L5.21699 7.12548L10.4265 0.48798C10.4359 0.476432 10.4459 0.465414 10.4565 0.45498H10.455Z"
              fill="green"
            />
          </svg>
        </button>
        &nbsp;
        {title}&nbsp;
      </div>
      <button
        onClick={() => {
          deleteTodo(id);
        }}
        className="delete"
        title="Delete todo"
      >
        <svg
          width="16"
          height="16"
          viewBox="0 0 20 20"
          fill="none"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path
            d="M8 4H12C12 3.46957 11.7893 2.96086 11.4142 2.58579C11.0391 2.21071 10.5304 2 10 2C9.46957 2 8.96086 2.21071 8.58579 2.58579C8.21071 2.96086 8 3.46957 8 4V4ZM6 4C6 2.93913 6.42143 1.92172 7.17157 1.17157C7.92172 0.421427 8.93913 0 10 0C11.0609 0 12.0783 0.421427 12.8284 1.17157C13.5786 1.92172 14 2.93913 14 4H19C19.2652 4 19.5196 4.10536 19.7071 4.29289C19.8946 4.48043 20 4.73478 20 5C20 5.26522 19.8946 5.51957 19.7071 5.70711C19.5196 5.89464 19.2652 6 19 6H18.118L17.232 16.34C17.1468 17.3385 16.69 18.2686 15.9519 18.9463C15.2137 19.6241 14.2481 20.0001 13.246 20H6.754C5.75191 20.0001 4.78628 19.6241 4.04815 18.9463C3.31002 18.2686 2.85318 17.3385 2.768 16.34L1.882 6H1C0.734784 6 0.48043 5.89464 0.292893 5.70711C0.105357 5.51957 0 5.26522 0 5C0 4.73478 0.105357 4.48043 0.292893 4.29289C0.48043 4.10536 0.734784 4 1 4H6ZM13 10C13 9.73478 12.8946 9.48043 12.7071 9.29289C12.5196 9.10536 12.2652 9 12 9C11.7348 9 11.4804 9.10536 11.2929 9.29289C11.1054 9.48043 11 9.73478 11 10V14C11 14.2652 11.1054 14.5196 11.2929 14.7071C11.4804 14.8946 11.7348 15 12 15C12.2652 15 12.5196 14.8946 12.7071 14.7071C12.8946 14.5196 13 14.2652 13 14V10ZM8 9C8.26522 9 8.51957 9.10536 8.70711 9.29289C8.89464 9.48043 9 9.73478 9 10V14C9 14.2652 8.89464 14.5196 8.70711 14.7071C8.51957 14.8946 8.26522 15 8 15C7.73478 15 7.48043 14.8946 7.29289 14.7071C7.10536 14.5196 7 14.2652 7 14V10C7 9.73478 7.10536 9.48043 7.29289 9.29289C7.48043 9.10536 7.73478 9 8 9V9ZM4.76 16.17C4.8026 16.6694 5.03117 17.1346 5.40044 17.4735C5.76972 17.8124 6.25278 18.0003 6.754 18H13.246C13.7469 17.9998 14.2294 17.8117 14.5983 17.4728C14.9671 17.134 15.1954 16.6691 15.238 16.17L16.11 6H3.89L4.762 16.17H4.76Z"
            fill="red"
          />
        </svg>
      </button>
    </li>
  );
};

export default Todo;

Ввести полноэкранный режим Выйти из полноэкранного режима

в app.css добавьте следующие коды для пользовательского интерфейса

body {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 90vh;
  background-color: #1e272e;
  color: white;
}

.container {
  background-color: #2b343b;
  padding: 2rem;
  max-width: 450px;
  width: 90%;
  min-width: 250px;
  margin: auto;
}

form {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: 1.5rem;
}

input {
  height: 40px;
  width: calc(100% - 80px);
  background-color: #1e272e;
  border: none;
  border-bottom: 1px solid #1e272e;
  padding: 0 1rem;
  transition: 0.25s ease-out;
  color: white;
}

button {
  background: none;
  color: white;
  border: none;
}

button.submit-button {
  width: 80px;
  height: 40px;
  background-color: cadetblue;
  color: white;
  border: none;
}

input:focus {
  border: none;
  background-color: #2b343b;
  border-bottom: 1px solid grey;
}

input:focus,
button:focus {
  outline: none !important;
}

ul {
  list-style: none;
  color: white;
  padding-inline-start: 0;
  max-height: calc(100vh - 200px);
  overflow-y: auto;
}

ul::-webkit-scrollbar {
  width: 0.3rem;
}
ul::-webkit-scrollbar-track {
  background: gray;
}
ul::-webkit-scrollbar-thumb {
  background: darkgrey;
  border-radius: 0.5em;
}

ul li {
  display: flex;
  justify-content: space-between;
  background-color: #1e272e;
  padding: 0.5rem;
  margin: 0.5rem 0;
  transition: 0.22s;
}

ul li.complete {
  text-decoration: line-through;
  opacity: 0.5;
}

.actions {
  display: flex;
  justify-content: space-between;
  margin-top: 2rem;
}

.actions button {
  color: lightgray;
}

.actions button.delete-all {
  color: red;
}

Ввод полноэкранного режима Выйти из полноэкранного режима

Запустите npm start и вот как это должно выглядеть

Из этого простого приложения пользователь должен иметь возможность;

  1. Создавать новое дело и просматривать его в списке
  2. Пометить тодо как завершенное или незавершенное
  3. удалить одно дело
  4. Фильтровать все, завершенные и незавершенные тодо.
  5. Удалить все завершенные тодо

Вот что мы будем тестировать с помощью cypress

Установка кипариса

  • установите cypress в ваш проект
npm install cypress -D
Вход в полноэкранный режим Выйти из полноэкранного режима
  • Запустите cypress open для настройки cypress в вашем проекте
npx cypress open
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Откроется окно с двумя опциями, e2e тестирование и тестирование компонентов, выберите e2e тестирование.

  • Откроется страница конфигурации файлов, прокрутите ее до самого низа и продолжите, cypress создаст все те цены, которые находятся в папке вашего проекта.

  • Откроется страница выбора браузера, выберите предпочтительный браузер.

  • После конфигурирования файлов в вашем проекте будет создана следующая структура папок

  • Создайте папку e2e внутри папки cypress и создайте файл todo.cy.js внутри папки e2e и начните писать тесты.
  • Как только вы создадите этот файл, в окне cypress вы должны увидеть созданный вами файл в списке тестов e2e следующим образом

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

Напишите свои первые тесты e2e

Перед написанием тестов по любому сценарию сначала нужно изучить команды cypress, есть основные и пользовательские команды. Основные команды — это встроенные команды cypress, а пользовательские команды — это команды, которые вы можете создать. В этом посте мы изучим базовые команды, поскольку пользовательские нам не понадобятся.

Ниже приведен список некоторых базовых команд


cy.visit("url") // this launches url

cy.wait(300) // Wait for a certain time in milliseconds or for an aliased element prior to moving the following step.

cy.get("selector") // this returns single or multiple elements with provided selector i.e classname, tagname, id or title
// class selector can be written as cy.get(".classname")

cy.contains("text") // returns a single or multiple elements that contains text

cy.click() // clicks the selected element

cy.dblclick() // double clicks the selected element

cy.document() // It obtains window.document on the active page.

cy.type("text") // type text on a selected input

cy.get(".classname").eq(0) // for multiple elements you can select a specific element depending on the position of such element using eq command with element index

cy.should("exist") // checks if a selected element exists or not, there are multiple parameters thet can be used inside should for varios checks 
// i.e not.exist, be.visible, be.enabled
// it can be used with an alias .and for multiple checks ie
// cy.get(".classname").should("exist).and("be.visible")

Войти в полноэкранный режим Выйти из полноэкранного режима

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

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

npm start
Войти в полноэкранный режим Выйти из полноэкранного режима

Предполагая, что приложение работает на http://localhost:3000, перед тестированием любого сценария сначала запустите url с помощью команды visit.

describe("create todo spec", () => {
  before(() => {
    // this will launch the page in cypress browser
    cy.visit("http://localhost:3000");
  });
})
Войти в полноэкранный режим Выход из полноэкранного режима

Напишем тесты в todo.cy.js по сценариям, перечисленным ранее.

describe("create todo spec", () => {
  before(() => {
    // this will launch the page in cypress browser
    cy.visit("http://localhost:3000");
  });

  // 1. Create a todo scenario
  it("successfully create a todo", () => {
    // find an input by tagname
    cy.get("input").type("Sprint meeting");

    // submit form by title of submit button
    cy.get("[title='Add Todo']").click();

    // create another todo
    cy.get("input").type("Coding");

    // submit form by classname of submit button
    cy.get(".submit-button").click();

    // check if new todos exist
    cy.contains("Sprint meeting").should("exist");
    cy.contains("Coding").should("exist");
  });

  // 2. Mark todo as complete / incomplete
  it("Should mark todo", () => {
    // mark a first todo as complete
    cy.get("button.mark-complete").eq(0).click();

    // select using css selector
    cy.get("li:nth-child(2) button.mark").click();
  });

  // 3. Delete a todo
  it("Should delete a todo", () => {
    cy.get("li:nth-child(2) button.delete").click();
  }) 

  // 4. Filter all, complete and incomplete todos
  it("should filter between all, complete and incomplete todos", () => {
    // create multiple todos
    cy.get("input").type("Sprint meeting");
    cy.get("[title='Add Todo']").click();
    cy.get("input").type("Code");
    cy.get("[title='Add Todo']").click();
    cy.get("input").type("Exercising");
    cy.get("[title='Add Todo']").click();
    cy.get("input").type("Read");
    cy.get("[title='Add Todo']").click();
    cy.get("input").type("Eat");
    cy.get("[title='Add Todo']").click();
    cy.get("input").type("Code");
    cy.get("[title='Add Todo']").click();

    // mark some as complete
    cy.get("li:nth-child(2) button.mark").click();
    cy.get("li:nth-child(4) button.mark").click();
    cy.get("li:nth-child(6) button.mark").click();

    // filter complete only
    cy.get("button.filter-completed").click();

    // check if only complete exists
    cy.get(".complete").should("exist");
    cy.get(".incomplete").should("not.exist");

    // filter incomplete only
    cy.get("button.filter-active").click();
    cy.get(".complete").should("not.exist");
    cy.get(".incomplete").should("exist");

    // filter all
    cy.get("button.filter-all").click();
    cy.get(".complete").should("exist");
    cy.get(".incomplete").should("exist");
  }); 

  // 5. Delete all complete todos
  it("should delete all completed todos", () => {
    // find and click delete all completed todos
    cy.get("button.delete-all").click();
    cy.get(".complete").should("not.exist");
  });

})
Войти в полноэкранный режим Выход из полноэкранного режима

Выполнение команды cypress run должно показать видео всех действий, записанных в тесте.

Эти тесты можно запустить с помощью электронного браузера, это означает, что вы можете запустить эти тесты на терминале ie

npx cypress run
Войти в полноэкранный режим Выйти из полноэкранного режима

Cypress может выполнять несколько типов тестов, используя основные команды и различные плагины, такие как загрузчик файлов. В компании clickpesa мы используем cypress для проведения e2e тестов.

Ниже перечислены ограничения Cypress

  • Нельзя использовать Cypress для одновременной работы двух браузеров.
  • Он не обеспечивает поддержку нескольких вкладок.
  • Cypress поддерживает только JavaScript для создания тестовых примеров.
  • В настоящее время Cypress не поддерживает такие браузеры, как Safari и IE.
  • Ограниченная поддержка iFrames.

Наиболее популярной альтернативой Cypress является Selenium

Если вам понравилась эта статья, в наших блогах есть еще много подобных статей, следите за нами на dev.to/clickpesa, medium.com/clickpesa-engineering-blog и clickpesa.hashnode.dev

Счастливого хакинга!!!

Оцените статью
devanswers.ru
Добавить комментарий