Современные web-приложения зачастую содержат множество "движущихся частей" и сторонних зависимостей. В процессе рефакторинга и добавления/изменения функциональности в таком приложении может произойти поломка существующих use-case сценариев и нестабильная работа в определенных браузерах.
Для своевременного обнаружения таких ситуаций и выполнения непрерывной интеграции необходимо функциональное тестирование web-приложения. В статье пойдет речь о двух бесплатные open-source решениях:
Рассматриваемые решения обеспечивают похожий, на первый взгляд, ряд возможностей:
- Автоматизированное функциональное тестирование с последовательным и параллельным выполнением тестов и их группировкой, интеграция с Gulp и Mocha;
- Поддержка большинства десктопных и мобильных браузеров; выполнение взаимодействия с реальным DOM API (web-приложение открывается в нескрытом окне браузера в обычной вкладке — тест максимально приближен к жизни);
- Широкая поддержка селекторов элементов страницы: document.querySelector, CSS-селекторы и даже XPath; комбинация этих вариантов позволяет сохранить работоспособность функционального теста при внесении изменений в разметку;
- Автоматическое ожидание готовности DOM-модели браузера, синхронная подача команд визуального взаимодействия (Щелчки по кнопкам, ввод в текстовые поля); удобные инструменты для ожидания клиентского JS-кода и сетевой AJAX-активности;
- Поддержка iframe-ов — за счет выбора текущего контекста DOMWindow — окна для теста и выполнении команд в его пределах с возможностью переключения в любой момент;
- Возможности для расширения — оба решения предоставляют возможности по добавлению поддержки кастомных браузеров и расширению собственной функциональности, в том числе добавления новых команд.
Пример функционального теста
Примером для тестирования будет web-приложение вида TodoMVC, с сервером на node.js и клиентской SPA-страницей на React+Redux-е. Для приближения условий тестирования к реальным, во все redux-овские action-ы добавлены случайные задержки, эмулирующие сетевое взаимодействие с backend-ом. (За основу взято http://todomvc.com/examples/react/ )
В дальнейшем будет предполагаться, что тестовое web-приложение запущено по адресу http://localhost:4000/. Функциональный тест будет простым и включать в себя добавление todo-элемента, правку его содержимого, отметку как выполненное/невыполненное задание и удаление.
Языком для написания тестов в обоих фреймворках является JS (ES2016 и ES5 соответственно для TestCafe и Nightwatch), однако это прекрасно подходит для web-приложений, написанных на любом языке. Если Вы давно не разрабатывали на JS, то необходимо учитывать, что современные редакции ушли очень далеко от старого ES3, и включают удобные средства для написания кода, объектно-ориентированного и функционального программирования и многое другое.
Установка TestCafe производится всего одной командой npm install -g testcafe
. После выполнения загрузки и установки необходимых зависимостей, выполнение теста производится командой testcafe <browser-name> <tests-directory>/
в соответствующей директории.
Исходный код функционального теста, проверяющего изложенный выше use-case-сценарий, может быть таким:
import { expect } from 'chai'; import { Selector } from 'testcafe'; const MAX_TIME_AJAX_WAIT = 2500; // 2.5 seconds by default const querySelector = Selector((val) => document.querySelector(val), { timeout: MAX_TIME_AJAX_WAIT }); const querySelectorCondition = Selector((val, checkFunc) => { const foundElems = document.querySelectorAll(val); if(!foundElems) return null; for(let i=0; i < foundElems.length; i++) { if(checkFunc(foundElems[i])) return foundElems[i]; } return null; }, { timeout: MAX_TIME_AJAX_WAIT }); fixture `Example page` .page `http://localhost:4000/`; test('Emulate user actions and perform a verification', async t => { await t.setNativeDialogHandler(() => true); const inputNewTodo = await querySelector('header.header input.new-todo'); await t.typeText(inputNewTodo, 'New TODO element\r\n'); const addedTodoElement = await querySelectorCondition( 'section.main label', (elm) => (elm.innerText === 'New TODO element') ); await t.doubleClick(addedTodoElement); const addedTodoEditInput = await querySelectorCondition( 'section.main input[type=text]', (elm) => (elm.value === 'New TODO element') ); await t.typeText(addedTodoEditInput, ' changed\r\n'); const addedTodoCheckboxAC = await querySelectorCondition( 'section.main input[type=checkbox]:not([checked])', (elm) => (elm.nextSibling.innerText === 'New TODO element changed') ); await t.click(addedTodoCheckboxAC); const addedTodoCheckboxBC = await querySelectorCondition( 'section.main input[type=checkbox]', (elm) => (elm.nextSibling.innerText === 'New TODO element changed') ); await t.click(addedTodoCheckboxBC); const addedTodoDelBtn = await querySelectorCondition( 'button.destroy', (elm) => (elm.previousSibling.innerText === 'New TODO element changed') ); await t.click(addedTodoDelBtn); });
Адрес тестируемой web-страницы определяется в fixture-части, за которыми следуют функциональные тесты, по завершении каждого из которых web-страница автоматически восстанавливается в исходное состояние. Для поиска DOM-элементов на странице используются testcafe-специфические Selector-ы, использующиеся в качестве обертки для функции, которая будет выполнять запрос к DOM-модели, возможно используя аргументы.
В этом примере первый селектор представляет обертку над document.querySelector, а второй — над document.querySelectorAll с callback-функцией, помогающей выбрать нужный элемент из списка. Обертка Selector принимает опции, здесь в частности устанавливается максимальное время, в течении которого testcafe будет ожидает появление элемента с заданными характеристиками в DOM-модели.
Сам функциональный тест представляет собой набор асинхронных вызовов Selector-ов, между которыми производятся действия посредством test controller-а, инстанцированного переменной t. Назначение большинства его методов очевидно из названий (click, typeText и т.д.), а t.setNativeDialogHandler
используется для предотвращения генерации alert-подобных окон, которые могут “подвесить” тест — что очень удобно.
Установка Nightwatch тоже начинается с простой команды npm install -g nightwatch
, однако для запуска функциональных тестов еще потребуется Selenuim-server (Можно загрузить здесь http://selenium-release.storage.googleapis.com/3.0/selenium-server-standalone-3.0.1.jar, на компьютере должен быть установлен Java SE runtime) и webdriver-ы для каждого из браузеров, в которых будет запускаться тест.
Для запуска тестов сначала надо создать конфигурационный файл nightwatch.json, описывающий расположение тестов, пути и настройки для selenium-server и webdriver-ов, а далее использовать простую команду nightwatch
в текущей директории.
Если использовать Microsoft Web Driver (Edge), то nightwatch.json может выглядеть примерно так:
{ "src_folders" : ["nightwatch-tests"], "output_folder" : "reports", "custom_commands_path" : "", "custom_assertions_path" : "", "page_objects_path" : "", "globals_path" : "", "selenium" : { "start_process" : true, "server_path" : "nightwatch-bin/selenium-server-standalone-3.0.1.jar", "log_path" : "", "port" : 4444, "cli_args" : { "webdriver.edge.driver" : "nightwatch-bin/MicrosoftWebDriver.exe" } }, "test_settings" : { "default" : { "selenium_port" : 4444, "selenium_host" : "localhost", "desiredCapabilities": { "browserName": "MicrosoftEdge", "acceptSslCerts": true } } } }
Исходный код функционального теста, проверяющий аналогичный рассмотренному выше use-case-сценарий, может выглядеть так:
module.exports = { 'Demo test' : function (client) { client.url('http://localhost:4000/') .waitForElementVisible('body', 1000) // May use CSS selectors for element search .waitForElementVisible('header.header input.new-todo', 1000) .setValue('header.header input.new-todo', ['New TODO element', client.Keys.ENTER]) // Or use Xpath - it's more powerful tool .useXpath() .waitForElementVisible('//section[@class=\'main\']//label[text()=\'New TODO element\']', 2000) .execute(function() { // For dispatch double click - Nightwatch doesn't support it by default var evt = new MouseEvent('dblclick', {'view': window, 'bubbles': true,'cancelable': true}); var foundElems = document.querySelectorAll('section.main label'); if(!foundElems) return; var elm = null; for(var i=0; i < foundElems.length; i++) { if(foundElems[i].innerText === 'New TODO element') { elm = foundElems[i]; break; } } elm.dispatchEvent(evt); }) .waitForElementVisible('//section[@class=\'main\']//input[@type=\'text\']', 2000) .clearValue('//section[@class=\'main\']//input[@type=\'text\']') .setValue('//section[@class=\'main\']//input[@type=\'text\']', ['New TODO element changed', client.Keys.ENTER] ) .waitForElementVisible( '//section[@class=\'main\']//label[text()=\'New TODO element changed\']' + '/preceding-sibling::input[@type=\'checkbox\' and not(@checked)]', 2000 ) .click( '//section[@class=\'main\']//label[text()=\'New TODO element changed\']' + '/preceding-sibling::input[@type=\'checkbox\' and not(@checked)]' ) .waitForElementVisible( '//section[@class=\'main\']//label[text()=\'New TODO element changed\']' + '/preceding-sibling::input[@type=\'checkbox\']', 2000 ) .click( '//section[@class=\'main\']//label[text()=\'New TODO element changed\']' + '/preceding-sibling::input[@type=\'checkbox\']' ) .waitForElementVisible( '//section[@class=\'main\']//label[text()=\'New TODO element changed\']' + '/following-sibling::button[@class=\'destroy\']', 2000 ) .click( '//section[@class=\'main\']//label[text()=\'New TODO element changed\']' + '/following-sibling::button[@class=\'destroy\']' ) .waitForElementNotPresent( '//section[@class=\'main\']//label[text()=\'New TODO element changed\']', 2000 ) .pause(2000) .end(); } }
Среди особенностей кода легко заметить, что Nightwatch не поддерживает эмуляцию двойного щелчка по элементу — вместо этого приходится реализовывать обходной путь с inject-функцией на клиенте, выполняющую dispatchEvent на целевом элементе управления.
Удобной возможностью Nightwatch является поддержка XPath, который предоставляет значительно более широкие возможности по селекции элементов DOM-модели, по сравнению с CSS-селекторами — взять хотя бы извлечение элемента по его текстовому наполнению, что довольно часто встречается в функциональном тесте.
Сравнение функциональности TestCafe и Nightwatch
Установка:
[T]estCafe: Достаточно одной команды npm install -g testcafe
, и можно сразу приступать к написанию и запуску тестов в текущей директории web-проекта
[N]ightwatch: Хотя установка npm-модуля делается просто — npm install -g nightwatch
, потребуется мануальная загрузка selenium-standalone-server, webdriver-ов для всех интересующих браузеров, ручное создание конфигурационного файла (А может даже загрузка Java, если она не установлена)
Выборка DOM-элементов:
T: Используется механизм Selector-ов — оберток над клиентскими JS-функциями, выбирающими один или множество DOM-узлов; это обеспечивает практически неограниченные возможности для селекции, начиная от простой обертки над document.querySelector
или document.getElementById
, и заканчивая произвольной логикой прохода по DOM-элементам. При этом TestCafe самостоятельно обеспечивает проверку наличия/отсутствия элементов по заданному Selector-у в течении указанного времени.
N: На выбор предоставляется CSS selectors или XPath — в принципе, второго варианта достаточно для решения большинства задач по выборке элементов на странице, хотя это конечно не сравнится с возможностью задания сложной произвольной логики поиска.
Язык написания тестов:
T: Из коробки предоставляется возможность написания тестов непосредственно на языке ES2016, что позволяет писать простой и читабельный код тестов на том же языке, что и само web-приложение Это также удобно в случаях, когда требуется импортировать определенный модуль из тестируемого проекта.
N: Устаревший ES5-синтаксис и exports-конструкции в исходном коде функционального теста (Возможность прикрутить ES6 все-таки есть, но на костылях)
Поддержка асинхронных операций:
T: Все API-функции основаны на Promise-ах, что позволяет описывать произвольную асинхронную логику работы теста, и при этом интегрировать собственные функции со стороны node.js. Благодаря поддержке ES2016, этот код можно записывать при помощи async и await конструкций в последовательном стиле.
N: В тесте можно реализовать последовательность команд по анализу и управлению содержимым web-страницы, однако они складываются во внутреннюю очередь событий, и интегрировать собственные асинхронные функции с ними проблематично.
Вставка клиентского кода на лету:
T: Легко осуществляется при помощи соответствующего API, поддерживается создание и последующее выполнение клиентских функций, однократное исполнение injected-кода, замена существующих функций в исходной web-странице, а также исполнение клиентских функций в callback-ах node.js-функций с привязкой к тестовому контроллеру.
N: Есть функциональность для выполнение JS-кода на клиентской стороне, или даже вставки целого script-блока, но средств интеграции с тест-контроллером не предоставляется. Подходит для простого синхронного интегрируемого JS-кода, но в более общем случае интеграция проблематична.
Описания утверждений и ожиданий:
T: По умолчанию — обычный язык утверждений chai/expect, можно использовать и любую другую совместимую библиотеку утверждений.
N: Расширенный язык assert
и expect
, включающий средства для описания состояния страницы, в том числе наличия элемента и фокуса на нем, принадлежность к CSS-классу и так далее. Выглядит удобно, однако в первую очередь обусловлено отсутствием полноценной поддержки асинхронных операций в тесте, при необходимости наличия утверждений и ожиданий.
Управление тестируемой web-страницей:
T: Предполагает возможность mock-а для блокирующих исполнение сценариев функций alert
, confirm
и так далее, с возможностью задания возвращаемого значения. Поддерживаются сложные манипуляции с DOM-моделью, эмуляция пользовательского взаимодействия с элементами управления, и даже возможность приостановки JS-сценария за счет обертывания клиентских JS-функций
N: Поддерживаются команды для непосредственного управления отображаемым окном браузера и подлежащей DOM-моделью, интеграция с клиентским JS-сценарием затруднена
Работа с курсором мыши:
T: Предоставляется виртуальный курсор, посредством которого осуществляются hover, click и drag-события для целевым визуальных элементов страницы. В процессе выполнения теста можно наблюдать за перемещением курсора и выполняемыми действиями.
N: Средства для работы с курсором вообще есть — это функции из webdriver api, однако работать с действиями, сложнее одиночного левого клика, довольно проблематично — взять хотя бы двойной щелчок.
Реализация взаимодействия с браузером в TestCafe и NightWatch
Фреймворк NightWatch основывается на известной, в некоторой мере уже традиционной, библиотеке Selenium webdriver, преимущества которой включают устоявшееся API, высокую степень документированности и обширное Q&A в интернете, а также универсальный и достаточно низкоуровневый доступ к браузеру… Взаимодействие с браузерами организуется посредством webdriver-ов, по одному на каждый обозреватель.
Основной недостаток — webdriver-ы существуют далеко не для всех браузеров, а также требуют отдельного обновления при смене версии самого браузера, а еще для работы необходимо наличие внешней зависимости от Java. Более подробно о технологии webdriver можно прочесть в http://www.w3.org/TR/webdriver/
Фреймворк TestCafe основан на подходе, в котором взаимодействие с приложением браузера сведено к минимуму — используются только функции открытия окна/вкладки, изменения размера окна, перехода по URL-адресу и некоторые другие.
Взаимодействие с web-страницей производится посредством web-proxy сервера testcafe-hammerhead, осуществляющего загрузку удаленной web-страницы и модификацию исходного JS-кода таким образом, чтобы выполнить шаги функционального теста, посредством интеграции с DOM-моделью, поиска и управления визуальными элементами, выполнение произвольного JS-кода и так далее. (https://github.com/DevExpress/testcafe-hammerhead/)
Метод TestCafe более универсален, поскольку может потенциально работать с любым браузером, поддерживающим HTML5 и ES 5+, в то время как NightWatch требует соответствующий webdriver. Кроме того, это позволяет прогонять тесты не только на локальном браузере, но на любом браузере в сети, включая любые мобильные — без установки какого-либо ПО на телефоне.
Пример тестирования вышерассмотренного web-приложения в браузере на Android показан в следующем видео: https://youtu.be/2na5jkqvUx0
Однако testcafe-hammerhead имеет и потенциальные недостатки: накладные расходы на анализ и модификацию исходного JS-кода тестируемой страницы, производимые в свою очередь JS-коде ядра Testcafe, а также теоретически некорректная работа тестируемой web-страницы или интеграции теста, если исходный код был проксирован неверно. (К примеру, замещение alert-окна в testcafe можно обойти таким примером http://pastebin.com/p6gLWA75 — и неумешленно или специально “подвесить” его выполнение)
Выводы
Конечно, selenium-webdriver, на котором основан Nightwatch, является популярным и широко известным решением, имеющем стандартный API-интерфейс, что несомненно является его достоинством. Кроме того, в смежных областях задач, например автоматизации целевого web-ресурса в заданном браузере — фактически написании бота для удаленного web-сайта — selenium-webdriver подходит лучше.
Однако для функционального тестирования разрабатываемого или поддерживаемого web-приложения TestCafe несомненно впереди по широкому ряду причин:
1) Запуск тестов в любом браузере, в том числе мобильных телефонах и планшетах, причем это может производиться пакетным образом для всех интересуемых браузеров и не требует установки никакого дополнительного ПО.
2) Удобство написания теста на ES2016, включающее async/await-конструкции для последовательной записи кода, импорт программных элементов из самого проекта, передачу функций в клиент и обратно и так далее — широкие возможности по интеграции и управлению клиентским web-приложением.
3) Широкая поддержка selector-ов для визуальных элементов, легкое взаимодействие с DOM-моделью, виртуальный курсор мыши, эмуляция разнообразных и сложных взаимодействий пользователя со страницей.
Таким образом, среди существующих open-source решений для функционального тестирования web-приложений, TestCafe выглядит очень привлекательным вариантом, к тому же при сочетании легковесности и функциональности.
Автор статьи не имеет отношения к разработке TestCafe и Nightwatch и рассматривал эти инструменты как обыкновенный пользователь.
Исходные коды приложения и функциональных тестов: https://github.com/IhostVlad/react-todomvc-with-functional-tests
ссылка на оригинал статьи https://habrahabr.ru/post/315526/
Добавить комментарий