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

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

.
Тестирование PDF-файла при помощи Cypress
07.09.2023 00:00

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

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

Начнем с описания нашего приложения. Его можно клонировать с моей страницы GitHub и посмотреть на решения, описанные в этой статье. По сути это простой html-файл, содержащий ссылки на два PDF-файла. Клик по кнопке скачает их на ваш компьютер.


Верификация загрузки

Для начала напишем простой тест на скачивание нашего файла. Код теста будет простым:

cy.visit('/')
cy.contains('simple.pdf')
.click()

Этот тест завершится сразу после клика по кнопке. Но как мы узнаем, произошло ли что-то на самом деле? Ну, для начала, это можно проверить вручную, заглянув в папку /cypress/downloads, где хранятся все загрузки, сделанные нашими тестами. Целевую папку загрузок можно настроить, изменив атрибут downloadsFolder в файле cypress.config.ts.

Но как нам проверить, был ли файл действительно скачан? Самый простой способ – воспользоваться командой cy.readFile(). Эта команда выдаст ошибку, если файл не найден, и отлично подходит для нашего случая.

Однако тут стоит отметить, что при запуске тестов через npx cypress open скачанные файлы будут перезаписаны. Это тоже важно, потому что мы можем попасть в ситуацию ложноположительного результата – мы используем команду cy.readFile(), а файл с таким именем уже находится в папке загрузок и был там еще до запуска теста.

В случае со скриптом запуска npx cypress этого не произойдет – он автоматически очистит содержимое папки загрузок перед запуском. Чтобы изменить это поведение, можно настроить опцию trashAssetsBeforeRuns в файле cypress.config.ts file.

К тому же, создавая тесты, стоит добавить папку cypress/downloads в файл .gitignore, чтобы она нечаянно не разнесла вам репозиторий.

Проверка содержимого файла

Команда cy.readFile() помогает убедиться, что файл был скачан, но не сильно помогает с нашим PDF-файлом. По иронии судьбы, она не может сделать именно то, что заявляет в своем собственном имени – прочитать файл. Вот результат работы этой команды в консоли:


К сожалению, в Cypress нет встроенного способа для чтения содержимого нашего файла, поэтому придется придумать что-то самостоятельно. Довольно-таки просто будет воспользоваться cy.task(), но тут есть ряд небольших нюансов, с которыми нужно разобраться.

Для начала создадим наш сценарий. Быстрый поиск парсера pdf на npmjs.com приведет нас к симпатичному маленькому пакету pdf-parse. То, как им пользоваться, подробно описано на его страничке, поэтому начнем.

const fs = require("fs");
const path = require('path')
const pdf = require('pdf-parse');
export const readPdf = (pathToPdf: string) => {
    const resolvedPath = path.resolve(pathToPdf)
let dataBuffer = fs.readFileSync(resolvedPath);
pdf(dataBuffer).then(function ({ text }) {
        return text
    });
}

Теперь у нас есть функция readPdf, которая принимает аргумент pathToPdf. Это путь к нашей папке загрузок. Теперь можно выполнить вызов при помощи команды cy.task(). Однако прежде чем заняться этим, нужно добавить путь в функцию setupNodeEvents function в файле cypress.config.ts:

import { defineConfig } from 'cypress'
import { readPdf } from 'cypress/scripts/readPdf'
export default defineConfig({
e2e: {
setupNodeEvents(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) {
on('task', {
readPdf
})
},
baseUrl: 'http://localhost:3000'
},
});

В настройках мы импортируем наш сценарий, который я сохранил в созданной для своих нужд папке cypress/scripts. В setupNodeEvents я передаю сценарий readPdf. Это означает, что при любом вызове cy.task('readPdf') будет вызван cypress/scripts/readPdf, возвращая содержимое PDF-файла.

Теперь все почти отлично. Остался всего один нюанс. По какой-то причине мы получим эту ошибку:


Понимание причины заняло у меня некоторое время – я понял, что моя функция еще обрабатывает PDF-файл, а cy.task() не ждет, пока она закончит. Чтобы убедиться, что функция действительно завершила свою работу, нужно обернуть ее в обещание. Обещания в Cypress могут по первости смущать (меня они точно смутили), но в этом случае код довольно прост:

const fs = require("fs");
const path = require('path')
const pdf = require('pdf-parse');
export const readPdf = (pathToPdf: string) => {
    return new Promise((resolve) => {
const pdfPath = path.resolve(pathToPdf)
let dataBuffer = fs.readFileSync(pdfPath);
pdf(dataBuffer).then(function ({ text }) {
            resolve(text)
        });
})
}

Таким образом мы убедимся, что несмотря на то, что обработка файла занимает время, Cypress подождет ее завершения. По умолчанию он будет ждать до 60 секунд. Это можно изменить в файле cypress.config.ts – опция taskTimeout.


Команда cy.task() передаст текст PDF следующей команде, где мы можем сразу его проверить:

cy.task('readPdf', 'cypress/downloads/simple.pdf')
.should('contain', 'Hello darkness my old friend')

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

export const readPdf = (pathToPdf: string) => {
    return new Promise((resolve) => {
const pdfPath = path.resolve(pathToPdf)
let dataBuffer = fs.readFileSync(pdfPath);
pdf(dataBuffer).then(function ({ text }) {
            const arr = text.split("\n");
resolve(arr)
        });
})
}

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