Автоматизация тестирования — единственный способ обеспечить качество в среде CI (непрерывной интеграции). Тестирование серверной логики и веб-интерфейсов, хотя и занимает много времени и трудно обеспечить точность, хорошо документировано, и существуют передовые методы.
Генерация PDF — функция, часто используемая в продуктах, ориентированных на потребителя (например, подтверждение бронирования, анализ данных, персонализированные документы планирования). Тем не менее, несмотря на то, что часто это критически важная функция, тестирование сгенерированных PDF-файлов обычно упускается из виду.
Мы поэкспериментировали с новым подходом к тестированию PDF, объединив Puppeteer (способ манипулирования безголовым Chrome), среду тестирования jest JavaScript и расширение jest визуальной регрессии от American Express.
Основной предпосылкой визуального регрессионного тестирования является сравнение заведомо исправных снимков интерфейса с снимками, созданными после изменения базового кода. С ошибкой сборки, если обнаруживается разница в пикселях выше некоторого порога.
Для этого нам нужно:
- Способ преобразования страницы PDF в снимки изображений, которые могут использоваться инструментом сравнения изображений. Формат PDF не подходит для сравнения и не работает с существующими инструментами сравнения изображений. Puppeteer сможет генерировать PNG для каждой страницы PDF.
- Фреймворк для запуска тестов (шутка).
- Инструмент для сравнения сгенерированных снимков с новыми снимками. (шутка-изображение-моментальный снимок).
Создание моментальных снимков из PDF
Puppeteer — безголовый экземпляр хрома. Его можно использовать для создания визуальных снимков веб-сайтов для сравнения изображений с помощью простой функции «скриншот».
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://example.com'); await page.screenshot({path: 'example.png'}); await browser.close(); })();
В Chrome есть встроенный просмотрщик PDF, и Puppeteer может манипулировать Chrome, чтобы открыть URL-адрес или файл. Таким образом, Puppeteer может открыть файл PDF, а затем использовать его для создания снимка экрана страницы. В приведенном выше примере мы можем изменить строку 6, чтобы использовать файловый протокол для открытия локального файла PDF.
await page.goto(“file:///Users/me/project/test.pdf”)
Одной из проблем с этим подходом является полоса прокрутки в правой части окна просмотра. Из-за того, как хром загружает свою полосу прокрутки, было бы неплохо удалить ее из моментального снимка, поскольку это может добавить ненадежность и проблемы с синхронизацией в тестах.
К счастью, Puppeteer может использовать параметр клипа в своей функции скриншота, чтобы обрезать скриншот. Например:
page.screenshot({clip: { x: 0, y: 0, width: 790, height: 600, }});
Это удалит полосу прокрутки со снимка экрана. Вы можете редактировать значения, основанные на точке обзора, установленной для окна, чтобы удалить только полосу прокрутки. Chrome также отображает имя файла в средстве просмотра PDF, поэтому, если вновь созданные файлы имеют имена, отличные от исходных файлов, то обрезку можно расширить, чтобы исключить верхнюю часть средства просмотра PDF, где отображаются имена файлов.
В шутку
Jest — это восхитительная платформа для тестирования JavaScript с упором на простоту.
Добавить шутку в существующий JS-проект так же просто, как:
yarn add --dev jest npm install --save-dev jest # Yes, it's cool again!
Полную информацию о начале работы с jest можно найти здесь: https://jestjs.io/docs/en/getting-started.
Тогда наш тестовый шаблон будет выглядеть следующим образом
describe('pdf suite', () => { it('works', async () => { expect(1).toBe(1) }); });
У American Express есть отличное расширение для jest, обеспечивающее сравнение изображений для визуального регрессионного тестирования. Его можно добавить следующим образом:
yarn add -D jest-image-snapshot npm i --save-dev jest-image-snapshot
Затем встроенный объект ожидания Jest можно расширить следующим образом:
const { toMatchImageSnapshot } = require('jest-image-snapshot'); expect.extend({ toMatchImageSnapshot });
Полную документацию по настройке можно найти здесь: https://github.com/americanexpress/jest-image-snapshot
Настройка браузера для шутки
Jest, как и большинство инструментов тестирования, имеет функции настройки и демонтажа, которые можно добавить в набор. Они могут настраивать и отключать общие ресурсы, используемые несколькими тестами, а браузер — это ресурс, который следует использовать совместно, чтобы сократить время выполнения теста и дублирование кода в ваших тестах.
const puppeteer = require('puppeteer'); describe('pdf suite', () => { let browser; beforeAll(async () => { browser = await puppeteer.launch({ headless: false }) }); it('works', async () => { expect(1).toBe(1) }); afterAll(async () => { await browser.close(); }); });
Тестирование первой страницы
Код скриншота Puppeteer теперь можно комбинировать с настройкой сравнения шутливых изображений.
const puppeteer = require('puppeteer'); describe('pdf suite', () => { let browser; beforeAll(async () => { browser = await puppeteer.launch({ headless: false }) }); it('works', async () => { const page = await browser.newPage() await page.goto("file:///Users/me/project/test.pdf") await page.waitFor(500); // depends on the size of your file, requires some manual configuring. const image = await page.screenshot({clip: { x: 0, y: 0, width: 790, height: 600, }}); expect(image).toMatchImageSnapshot({ failureThreshold: '0.10', failureThresholdType: 'percent', includeAA: true, }); }); afterAll(async () => { await browser.close(); }); });
Теперь мы можем отредактировать проверяемый PDF-файл, повторно запустить его и получить процент сбоев и визуальное различие, которое можно проверить вручную.
Если мы возьмем базовый 1-страничный PDF.
Затем сгенерируйте наш первый снимок.
Если мы отредактируем оригинал, переместив заголовок, изменив текст подзаголовка и добавив кружок.
Затем после повторного запуска тестов мы получим визуальную разницу, показывающую все измененные компоненты красным цветом.
Переход на первую страницу
Очевидно, что многие PDF-файлы имеют длину более одной страницы…
К счастью, мы можем имитировать нажатия клавиш с помощью Puppeteer. Вместо того, чтобы писать сложную логику прокрутки, мы можем имитировать нажатие стрелки вправо, ярлык Chrome для перехода к следующей странице в средстве просмотра PDF.
await page.keyboard.press(‘ArrowRight’);
Предполагая, что мы заранее знаем количество страниц документа, мы можем построить базовый цикл for для страниц документа, делая снимки экрана и нажимая стрелку вправо на каждой итерации.
it('works', async () => { const numberOfPages = 20; const page = await browser.newPage() await page.goto("file:///Users/me/project/test.pdf") await page.waitFor(500); for(let i=0; i < numberOfPages; i++) { const image = await page.screenshot({clip: { x: 0, y: 0, width: 780, height: 600, }}); expect(image).toMatchImageSnapshot({ failureThreshold: '0.07', failureThresholdType: 'percent', includeAA: true, }); await page.keyboard.press('ArrowRight'); } });
Срывать…
В заключение, мы использовали описанный выше подход для выявления проблем в больших сгенерированных отчетах в формате PDF со сложными графиками, стилями и мультимедиа. Тесты сэкономили часы ручной проверки и выявили проблемы, о которых мы даже не подозревали.