Традиционное функциональное тестирование в браузерах без головы проходит медленно. Флагшток делает его легким и молниеносным.

Flagpole - это инструмент автоматизации контроля качества с открытым исходным кодом, который мы создали здесь, в FloSports. Я рад сообщить, что сейчас его используют несколько команд разработчиков, и они также вносят запросы на вытягивание. ДА !!!

Вы можете найти Flagpole на Github и NPM.

В предыдущих постах я рассмотрел, как приступить к работе с Flagpole для тестирования REST API: Часть 1 и Часть 2. Так что это хорошие места для начала. Флагшток также можно использовать для тестирования HTML-страниц, изображений, CSS и т. Д.

Эта статья предназначена для быстрого ознакомления с тестированием HTML-страниц. Мы рассмотрим это с помощью синтаксического анализа только запросов и DOM, что позволяет избежать накладных расходов, связанных с полным браузером без заголовка. Это означает, что он не может выполнять JavaScript, но мы обнаружили, что во многих случаях это нормально. Это (плюс асинхронный параллелизм) означает, что сотни тестов могут выполняться всего за несколько секунд. Идеально подходит для хуков git pre-commit или pre-push.

Я сделаю быстрый обзор настройки здесь.

npm i -g flagpole
mkdir flagpole-example
cd flagpole-example
npm init
npm i flagpole

С помощью приведенных выше команд мы установили Flagpole глобально, создали наш проект (или использовали ваш существующий проект), инициализировали npm в проекте и, наконец, установили модуль Flagpole локально в проекте.

Следующее, что нам нужно сделать, это запустить команду flagpole init, чтобы инициализировать Flagpole в этом проекте. Он задаст вам ряд вопросов, например, какие среды вы хотите поддерживать и в какой папке вы хотите хранить свои тесты.

Теперь, если вы запустите команду flagpole list, вы увидите, что в вашем проекте в настоящее время нет тестов. Итак, давайте создадим его!

Сначала нам нужно создать набор тестов. Мы используем команду flagpole add suite и заполняем вопросы. Мы скажем, что это HTML-страница для типа теста. В моем примере я буду тестировать Medium и просто настраиваю все это на домен .com, поскольку у меня нет доступа к доменам Pre-prod Medium.

Теперь вы увидите, что файл example.js был создан. И внутри этого файла мы определяем Suite и Scenario. Давайте продолжим и создадим второй сценарий в этом пакете на всякий случай.

Поэтому мы вводим сценарий добавления флагштока в командной строке. Затем ответьте на эти вопросы, обязательно выбрав тот же набор, что и раньше. В моем примере я выбрал полуслучайную страницу статьи. Итак, теперь у нас есть сценарий для тестирования домашней страницы Medium и страницы статьи.

Вы увидите, что example.js полностью спроектирован для нас и готов приступить к созданию более ценных тестов.

Давайте продолжим и запустим наши тесты с помощью команды flagpole run.

Вы заметите, что для всего пакета здесь потребовалось 843 мсек. Это потому, что он запустил оба сценария одновременно. Давайте добавим еще несколько тестов, которые мы поместим в обратный вызов assertions.

Первое, что давайте сделаем в утверждениях сценария нашей домашней страницы, - это проверим наличие главной и трех второстепенных историй. Сначала давайте получим ссылку на эти блоки в DOM с их селекторами.

const topStory = response
    .select(‘.extremeHero-postContent’);
const secondaryTopStories = response
    .select(‘.extremeHero-smallCardContainer article’);

Теперь давайте удостоверимся, что у главной новости есть заголовок. Обратите внимание, что я использую метод find для получения h1 под ссылкой topStory, которую мы захватили в последнем фрагменте кода. Затем мы вытащили текст из этого h1, вырезали пробелы вокруг него и убедились, что его длина больше 0.

topStory.find('h1').text().trim().length().greaterThan(0);

Затем давайте удостоверимся, что у автора главной статьи есть подписка.

topStory.find('.postMetaInline--author')
    .property('href').startsWith('https://')
    .and().text().matches(/[A-Z]+/i);

Итак, мы снова нашли подходящий селектор DOM под topStory. Затем мы использовали метод property для получения атрибута href. Вы можете использовать метод attribute вместо свойства, если хотите. Это сделает то же самое. Затем мы использовали метод startWith, чтобы убедиться, что это https.

Обратите внимание на метод и после того, как мы проверим свойство. Это не только упрощает чтение, но и сигнализирует нашим связанным методам вернуться к предыдущему выбранному элементу. Поэтому вместо того, чтобы проводить больше тестов для атрибута href, он возвращается к элементу .postMetaInline - author. Затем вы видите, что мы извлекаем текст из этого элемента и проверяем его на соответствие регулярному выражению.

Далее, давайте убедимся, что есть ровно три второстепенных истории.

secondaryTopStories
    .label('There should be three secondary stories')
    .length().equals(3);

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

Хорошо, теперь давай сойдем с ума. Я хочу загрузить все три статьи и провести с ними несколько различных тестов. И я хочу загрузить каждое изображение, чтобы убедиться, что оно загружается и кажется действительным. Давай сделаем это!

secondaryTopStories.each((article, i) => {
    article
        .label(`Article ${i} has an image div`)
            .find('.extremeHero-image')
        .label(`Article ${i} has a link`)
            .property('href').length().greaterThan(0)
        .and()
        .label(`Article ${i} has a background image`)
            .css('background-image')
            .load(`Image from secondary story ${i} loads`, true);
});

Выше мы использовали метод each, чтобы убедиться, что каждая статья имеет div для изображения, который представляет собой ссылку с методом href, имеет свойство css с background-image… и тогда…

Мы использовали метод load для динамического создания сценариев на лету. Второй верный аргумент - это то, что заставляет его автоматически создавать эти специальные сценарии. Он делает все возможное, чтобы понять, что вы хотите оттуда, без необходимости кодировать это!

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

Обратите внимание, что у нас есть три дополнительных сценария, созданных с названием, которое мы дали им на лету. Не только это, но и поскольку он был загружен из свойства background-image, Флагшток предположил, что это должно было быть изображение. Поэтому в этих динамически создаваемых сценариях он проверяет соответствие типа mime.

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

secondaryTopStories.each((article, i) => {
    article
        .find('.extremeHero-image')
        .css('background-image')
        .load(`Image from secondary story ${i} loads`)
        .assertions((image) => {
            image
                .length().greaterThan(0)
                .select('width').equals(200)
                .select('height').greaterThanOrEquals(199);
        });
});

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

В рамках этих утверждений мы проверяем, больше ли размер файла 0, ширина изображения 200 и высота больше или равна 199. Флагшток по-прежнему автоматически определяет, что мы пытаемся загрузить изображение и делать правильные вещи ... включая тестирование MIME-типа для нас без необходимости добавлять это утверждение отдельно.

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

.load(`Image from secondary story ${i} loads`).image()

Как вы могли догадаться, мы могли бы делать такие же утверждения «на лету» для других типов URL-адресов, которые мы хотим загружать динамически. Это может быть CSS (который также проверяет, действителен ли он), скрипты, json, другие html-страницы или общие ресурсы, такие как видео, аудио или что-то еще (нет специальной проверки, которую мы можем сделать для них, кроме того, что они загружаются и размер файла).

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

Без проблем. Давайте возьмем тот второй сценарий, который мы создали сначала, чтобы загрузить страницу статьи и использовать ее. Сейчас это выглядит так…

suite.Scenario('Article page loads')
    .open('/feedzaitech/writing-testable-code-b3201d4538eb')
    .html()
    .assertions(function (response) {
    });

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

const articleScenario = suite.Scenario('Article page loads')
    .html()
    .assertions(function (response) {
    });

Как есть, этот сценарий никогда не будет выполнен, потому что для него нет URL-адреса для загрузки. Однако он загрузится сам по себе, как только мы его назначим. Обратите внимание, что мы назвали его articleScenario как константу. Вот как мы будем ссылаться на него в сценарии нашей домашней страницы.

Итак, мы добавим это в сценарий нашей домашней страницы сверху:

topStory.find('a').first().load(articleScenario);

Все, что для этого нужно, - это найти первый тег в topStory, а затем загрузить его! Мы передаем ему ссылку на наш сценарий вместо строки имени для динамически созданного сценария, и он готов к гонкам!

Теперь вы можете поместить любые другие тесты в этот сценарий для страницы статьи. Или даже загрузите оттуда еще несколько сценариев!

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

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