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

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

.
Основы Cypress: загрузка файла
31.05.2023 00:00

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

Эта статья – часть серии "Основы Cypress". В этой серии я попытался пошагово объяснить базовые вещи. Если вы хотите узнать больше, открывайте любую статью серии.

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

Загрузка файла с Cypress

Начиная с версии 9.3.0, в Cypress есть команда .selectFile(), которая способна работать с любыми необходимыми нам загрузками файлов. Ее легко использовать:

cy.get('#upload')
.selectFile('cypress/fixtures/logo.png')

Итак, какой же элемент нам нужно выбрать? Тут придется немного нырнуть в код. Каждый раз, когда вы что-то загружаете, на странице присутствует элемент <input type=file>. Даже если вы его не видите, уверяю вас, он там есть. Это элемент HTML5, дающий приложению API, чтобы взаимодействовать с браузером и открыть окно выбора файла. На странице этот элемент обычно отображается так:


Однако если наша задача – загрузить файл через Cypress, то мы не кликаем по этой кнопке, а выбираем элемент <input> и применяем к нему функцию .selectFile(). Таким образом вместо того, чтобы взаимодействовать с диалоговым окном выбора файла, мы просто задаем путь к файлу, который планируем загрузить. Но что, если кнопка "Выбрать файл" отсутствует – вместо нее мы видим кнопку загрузки или область, на которую файл можно перетащить?

Загрузка в область перетаскивания

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


В таких случаях элемент <input> зачастую спрятан. Интересно тут то, что элемент <input> может располагаться в странных участках DOM – зачастую вдали от области перетаскивания. Это связано с тем, что вставка файла контролируется через JavaScript. Представьте, что ваш файл забирают из области перетаскивания и передают в элемент <input>, где с ним и разбираются.

В моем проекте Trelloapp эта область выглядит вот так:


Как можно видеть, стиль у элемента <input> - display:none, и он, следовательно, от пользователя скрыт. Чтобы загрузить файл в такую область, можно выбрать одну из трех стратегий:

// input невидим, поэтому нужно пропустить интерфейсные проверки Cypress
cy.get('input[type=file]')
.selectFile('cypress/fixtures/logo.png', { force: true })
// делаем input видимым, применяя к нему функцию jQuery
cy.get('input[type=file]')
.invoke('show')
.selectFile('cypress/fixtures/logo.png')
// используем способности Cypress взаимодействовать с областью перетаскивания
cy.get('[data-cy=upload-image]')
.selectFile('cypress/fixtures/logo.png', { action: 'drag-drop' })

Заметьте, что в третьем примере мы выбираем саму область целиком, а не ищем элемент <input>. Это важно, потому что в применении этих двух стратегий есть разница. Если мы выберем не тот элемент, то можем получить сообщение вроде "cy.selectFile() можно применять только к <input type="file"> или к <label for="fileInput">, указывающей на него или содержащей его".

Загрузка через API

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

cy.fixture('logo.png', 'binary').then( image => {
const blob = Cypress.Blob.binaryStringToBlob(image, 'image/png');
const formData = new FormData();
formData.append('image', blob, 'logo.png');
        cy.request({
method: 'POST',
url: '/api/upload',
body: formData,
headers: {
'content-type': 'multipart/form-data'
},
})
})

Разберу это подробно. Наш элемент <input type=file> - это обычно часть html <form>. Как правило, элемент <form> содержит множество элементов <input>. Прежде чем они отправляются в API, с ними работает интерфейс FormData. По сути любые данные добавляются к объекту FormData, а затем отправляются на сервер посредством API.

В Cypress FormData нужно создавать вручную, добавив к нему наше изображение. Как можно заметить, прежде чем мы применяем .append() к изображению, чтобы прикрепить его к объекту FormData, мы работаем с изображением. Это происходит двумя путями:

  1. Мы загружаем изображение как фикстуру, используя бинарную кодировку.
  2. Мы конвертируем это бинарно закодированное изображение в Blob.

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

Как только мы справились с файлом и заполнили объект FormData, мы готовы вызывать API. Телом нашего запроса будет сам объект FormData. Единственная дополнительная деталь – добавление заголовка 'content-type': 'multipart/form-data' – чтобы сервер знал, какой тип запроса мы отправляем.

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

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