Статьи

Упрощенное тестирование пользовательского интерфейса с помощью CasperJS

Эта статья написана Мэтью Сеттером, профессиональным техническим писателем и страстным разработчиком веб-приложений. Он также является основателем  Malt Blue , сообщества профессионалов по разработке веб-приложений на PHP и специалистов по разработке облачных решений  PHP  — изучающих разработку облачных вычислений через призму PHP. Вы можете связаться с ним в  TwitterFacebookLinkedIn  или  Google+ в  любое время.

Резюме
Независимо от того, разрабатываете ли вы небольшие или большие приложения, можно с уверенностью сказать, что пользовательское тестирование является важным аспектом процесса разработки.

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

За эти годы разработчики создали множество приложений и технологий, чтобы удовлетворить эту потребность. Селен, который работает очень хорошо, является ярким примером. Но мы всегда ищем новые, более простые (а иногда и «круче») способы сделать то, что мы делаем. Так недавно я начал изучать новую перспективную технологию:  PhantomJS .

Что такое PhantomJS?
PhantomJS — это безголовый WebKit с JavaScript API. Он имеет быструю и встроенную поддержку различных веб-стандартов: обработка DOM, селекторы CSS, JSON, Canvas и SVG.

PhantomJS был создан Арией Хидаят . Сама по себе это довольно впечатляющая технология, но, читая сайт и все сопутствующие API, я наткнулся на CasperJS .

Что такое CasperJS?
Casper — это утилита для написания сценариев и тестирования навигации для PhantomJS, написанная на JavaScript. Это позволяет нам тестировать сайты, такие как PHPUnit или JUnit, что позволяет нам тестировать наш код.

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

В этой статье я расскажу об основах использования CasperJS. Я буду выдавать себя за обычного пользователя, опрашивающего страницу сайта New Relic, в частности, раздел « Мониторинг реальных пользователей ».

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

Особенности
CasperJS имеет ряд функций. В этой статье я сосредоточусь на API тестера . Он имеет ряд функций и утверждений, которые вы ожидаете от хорошего API тестирования, включая:

* assertTextExists
* assertTitle
* assertHttpStatus
* assertDoesntExist
* assertUrlMatch

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

Требования
Если вы еще этого не сделали, установите PhantomJS или Casper, прежде чем мы продолжим работу. Теперь давайте погрузимся прямо в.

Начало работы
Когда я впервые загрузил страницу «Real User Monitoring» на сайте New Relic, она выглядела как на картинке ниже (она изменилась со времени написания этой статьи). Я выделил начальную точку формы регистрации.

New-Relic-частичной Form_

Если вы щелкнете по любому элементу этой формы, остальная часть откроется так, как это показано на рисунке ниже.

ботаник-в-Tshirt-формы

Тесты будут сосредоточены на форме, но мы также рассмотрим тег заголовка, изображение в левой части формы и текст над ней.

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

var url = 'http://newrelic.com/product/real-user-monitoring';
var siteName = 'NewRelic';

Casper поставляется с четырьмя встроенными уровнями регистрации:

* отладка
* информация
* предупреждение
* ошибка

По умолчанию CasperJS отфильтровывает любые записи в журнале под уровнем «ошибка». Так что, если вы начинаете входить в систему и сначала ничего не видите, это может быть причиной. Чтобы убедиться, что выводится журнал, я установил для него значение «отладка». Я также отключил опцию «verbose». Если он включен, мы увидим информацию практически обо всем, что может быть довольно отвлекающим.

var casper = require('casper').create({
    verbose: false,
    logLevel: 'debug'
});

Вы можете найти другие параметры конфигурации на странице API Casper . Функция комментария, как показано ниже, позволяет нам записывать вывод — в данном случае на консоль. Я использовал простое сообщение, чтобы сказать, что тестирование началось.

casper.test.comment('Starting Testing');

Функция «casper.start» начинает запуск тестов.

casper.start(url, function() {
    this.test.assert(
        this.getCurrentUrl() === url, 'url is the one expected'
    );
 
    this.test.assertHttpStatus(200, siteName + ' is up');

Вот что я сделал: я загрузил URL, который был указан в ‘url’, и заявил, что мы можем загрузить нужный адрес. После успешной загрузки страницы пришло время начать вызывать некоторые утверждения, чтобы удостовериться, что контент, который загружается на странице, соответствует нашим ожиданиям.

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

this.test.assertTitle(
    'Real User Monitoring, End User Experience Monitoring : New Relic',
    siteName + ' has the correct title'
);

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

В приведенном ниже коде я сначала проверяю, существует ли форма с идентификатором nr-signup-form. Немного бессмысленно проверять элементы формы, если форма не существует, верно?

this.test.assertExists(
    'form[id="nr-signup-form"]',
    siteName + ' has a form with name "nr-signup-form"'
);

Теперь, прежде чем идти дальше, я должен упомянуть, что когда я утверждаю, существуют ли элементы на загруженной странице, я могу использовать два разных подхода:

* XPath
* Селекторы CSS

Если вам нужна дополнительная информация о XPath, посмотрите некоторые ссылки в разделе «Дальнейшее чтение» или прочитайте этот пост, который я написал для него . Селекторы CSS должны быть довольно понятными. Но на всякий случай я также включил ссылки в раздел «Дальнейшее чтение».

Теперь давайте использовать утверждения для поиска полей ввода.

this.test.assertExists(
    {type: 'xpath', path: '//input[@id="FullName"]' },
    'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//input[@id="Company"]' },
    'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//input[@id="Email"]' },
                'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//input[@id="Password"]' },
    'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//input[@id="PromoCode"]' },
    'the element exists'
);

В пяти утверждениях выше я искал пять полей ввода с идентификаторами «FullName», «Company», «Email», «Password» и «PromoCode». С XPath так же легко найти выбранные элементы.

В тестах, приведенных ниже, я утверждал, что есть четыре списка выбора с соответствующими идентификаторами: «страна», «группа», «company_size» и «HostEstimate».

this.test.assertExists(
    {type: 'xpath', path: '//select[@id="country"]' },
    'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//select[@id="group"]' },
    'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//select[@id="company_size"]' },
    'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//select[@id="HostEstimate"]' },
    'the element exists'
);

Теперь эти тесты были довольно просты. Итак, давайте сделаем их немного сложнее.

this.test.assertExists(
    {
        type: 'xpath',
        path: '//button[@id="sign-up-button"
                and @class="small-button"
                and @type="submit"]'
    }, 'the element exists'
);

В приведенном выше тесте я искал кнопку «Создать бесплатную учетную запись», как показано на рисунке ниже.

создание свободных от аккаунта выделены

Я использовал некоторые условные операторы в этом запросе. Мы ищем кнопку с:

* Идентификатор «кнопки регистрации»
* Класс «маленькой кнопки»
* Тип «отправки»

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

var x = require('casper').selectXPath;
var checkboxXpath = "//input[@type='checkbox' and
@id='checkbox1']/parent::*/a/label[
    contains(., 'Java') or
    contains(., 'Python') or
    contains(., 'Ruby') or
    contains(., 'PHP') or
    contains(., '.NET') or
    contains(., 'Node.js')
]";
this.test.assertExists(x(checkboxXpath), 'the element exists');

В этом тесте я немного упростил тест XPath, инициализировав x с помощью помощника selectXPath. Я посмотрел все флажки на этой странице с выбором «Приложения». Таким образом, запрос XPath искал флажки с сопровождающим тегом метки, который соответствует названиям языков приложения.

Просто чтобы показать, что я не использую исключительно XPath (хотя он невероятно мощный и простой), два теста ниже также используют селекторы CSS, чтобы проверить, что поле ввода и элемент списка также существуют.

this.test.assertVisible('input#Company');
 
this.test.assertExists('li.nav-enterprise', 'the element exists');

Легко искать элементы формы и элементы списка, но что делать, если ваша страница загружена ресурсами, с множеством изображений или объектов Flash? Вот пример:

this.test.assertResourceExists(
    '/images/tshirt-dude-form.png', 'T-shirt Image exists'
);

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

    this.test.assertTextExists(
        'Sign up for free!', 'page body contains "Sign up for free!"'
    );
 
});

Затем мы закрываем функцию «старт».

casper.test.comment('Ending Testing');

Тесты почти завершены, поэтому я выведу еще одну запись в журнале, чтобы пользователь знал об этом.

casper.run(function() {
    this.test.done(16);
    this.echo('So the whole suite ended.');
    require('utils').dump(casper.test.getFailures());
    require('utils').dump(casper.test.getPasses());
    this.exit();
});

Я мог бы написать все тесты, которые я хочу, но без вызова ‘run’ ничего не произойдет. Таким образом, «запустить» запускает вышеупомянутую работу. Что я сделал, так это проверил, что скрипт выполнил 16 тестов, а затем вывел окончательную строку текста в конце с помощью this.echo ().

Когда мы запускаем тесты, иногда полезно получить сводку результатов в конце. С помощью функций getFailures () и getPasses () мы можем увидеть сводку пройденных и неудачных тестов. После всего этого я вызвал this.exit (). Это приводит к выходу из CasperJS и PhantomJS, при желании можно указать код выхода.

Сделав еще один шаг вперед, на этот раз я называю «casperjs» немного по-другому: я прохожу «тест», как показано ниже, вместе с переключателем «xunit». Это экспортирует результаты теста в XML-файл XUnit .

$ casperjs test mytest.js --xunit=log.xml

В следующей основной версии CasperJS вы не сможете запускать и отображать тесты, если «test» не будет передан команде. Важно знать это заранее и избегать неожиданного взлома ваших скриптов.

Автоматизация тестирования
Что делать, если вы хотите автоматизировать процесс тестирования? Я имею в виду, это здорово, что мы можем отрабатывать некоторые тесты вручную, но это может быть утомительным и довольно трудоемким. К счастью, есть расширение, которое помогает автоматизировать ваши тесты. Я не знал об этом, когда начал писать эту серию. Я хочу поблагодарить @casperjs_org за то, что сообщили мне о расширении resurectio для Google Chrome.

Рефухио-твит-ссылка

Клонируйте или загрузите копию и установите ее, затем снова откройте ту же страницу «Новая реликвия», которую мы использовали ранее, и нажмите кнопку «resurectio» в списке кнопок расширения.

Рефухио-ScreenCap

На странице, где вы хотите создать автоматизированный тест, нажмите «Перейти» в поле URL страницы. Все, что вы делаете с этого момента, будет включено в состав созданного тестового сценария.

Я перебрал все элементы формы, от «Электронная почта» до «Размер компании». Затем я снова нажал кнопку расширения, чтобы остановить запись, и нажал «Экспорт CasperJS». Это открывает другую страницу в браузере, которая отображает сгенерированный скрипт.

Скопируйте это в скрипт и запустите его, как и предыдущий скрипт, который вы создали, и вы получите результат, как показано на скриншоте ниже.

Баш-PHP-script_

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

Дальнейшее чтение

XPath
Селекторы CSS
CasperJS
PhantomJS
JUnit
PHPUNit

Conclusion
I hope you’ve enjoyed this gentle introduction to CasperJS and appreciate the power and flexibility that it gives us to design thorough user-accepted test suites through PhantomJS.

We saw how — with a minimal amount of effort — we could test the content of the New Relic ‘Real User Monitoring’ page, albeit with some manual effort to determine the required XPath expressions and CSS selectors.

But for a free solution, it sure delivers a lot of capacity. I’m sure you’ll agree. So what did you think of it? Can you see yourself or your organization incorporating it as part of your deployment and/or continuous integration solution?

In the next part, I’ll branch out from testing and looking at other parts of the CasperJS, including:

* HTTP authentication
* Mouse events
* Submitting forms
* Impersonating user agents
* Making AJAX requests