CasperJS, PhantomJS, SlimerJS
SlimerJS ― это Firefox для автоматических тестов или же каких-либо подручных задач. PhantomJS ― это webkit браузер без окна, для тех же целей. CasperJS ― это test-кит, обёртка над PhantomJS и SlimerJS. До недавнего времени CasperJS был присмерти, основной разработчик на него забил и никто проектом не занимался. Но судя по коммитам дело сдвинулось с мёртвой точки, и проект скорее жив, чем мёртв. Появилась поддержка свежих версий PhantomJS, которую я так давно ждал.
PhantomJS
С ним у меня возникло множество проблем. Дело в том, что webkit в нём очень древний. Он не способен переварить less.js, нуждается в CSS-префиксах к flexbox, не знает ничего о Function.Prototype.bind. В общем less приходится билдить в CSS, а для JS пихать es5-shim и babel-polifill. Про ES2015 молчу :-) |
|
Стабильностью не страдает, и регулярно падает с PhantomJS has crashed, без объяснения причин. Плохо дружит с jQuery.IndexedDB, с некоторой периодичностью выкидывая ошибки. Не имеет собственного окна, что сильно затрудняет отладку тестов или персональных косяков его самого. Но умеет скриншоты, что несколько выручает. Касательно работы через CasperJS… Почему-то запускает только 1-ый тест в файле. Много отличий в работе с require по сравнению со SlimerJS. Если ваша тестовая база подбита под SlimerJS, то потребуется много работы напильником, прежде чем тесты хотя бы запустятся. CSS. Свежие версии фантома умеют зачатки flexbox-а. С префиксом -webkit и сильно устаревшими свойствами. По сути половину не умеет. Насколько я понял, исходя из беглого поиска, там QWebKit 2.2, который сильно устарел. Его возможности примерно равны Safari 5.1. Открыл я в ней проект… поковырялся, понял что безнадёга полнейшая и пришлось от фантома отказаться. Буду ждать обновлений. В отличии от SlimerJS, фантом умеет падать с правильным exit code, а также умеет работать по file:// протоколу с AJAX запросами, что очень удобно для frontend тестирования. Отладка. Если кратко ― ад. К примеру console.log для объектов выводит [Object object]. Любит при eval уходить в вечные циклы. Stacktrace-ы выдаёт далеко не всегда. Если запускаемый файл содержит ошибки синтаксиса ― где именно не укажет. Ну и т.д.. Обычно причину приходится искать методом проб и ошибок. UPD: отдельные граблки это то каким образом casperJS гоняет данные между тестом и страницей. В случае slimerJS, главное чтобы эти данные могли сериализовываться, к примеру, JSON-ом. В случае phantomJS начинаются какие-то чудеса с null и похожими значениями. Разбираться в чём дело я не стал, а критичные к таким багам данные стал вручную перед отправкой перегонять в JSON-строку, а в тесте уже распаковывать. |
SlimerJS
В отличии от фантома не умеет работать без окна. В Linux эту проблему можно решить, к примеру, так: xvfb-run %команда%, без каких-либо побочных эффектов. Очень многие вещи, такие как require, понимает не так, как их понимает PhantomJS. По сути то тут, то там, приходит ставить условие phantom.casperEngine === 'slimerjs', что сильно поело нервов. В отличии от фантома умеет кое-что и из ES2015, и никакие shim-es5 ему не нужны. Без труда переварил less.js. Отлично справился с flexbox-ом. Пока не умеет опции --web-security=no, что не позволяет нормально тестировать по file:// протоколу, т.к. не проходят AJAX запросы. Всегда завершает скрипт с кодом 0, что создаёт проблемы в определении, провалился ли тест, или был успешен, к примеру, из grunt-задачи. Отладка примерно столь же ужасная, что и в фантоме. Тоже умеет скриншоты. |
|
CasperJS
Располагает интересным API. К примеру может загружать файл в input[type=file], умеет [contenteditable] и курсоры. Умеет дожидаться появления или исчезновения HTMLElement-а по его selector-у и многое другое. Но требует слишком много возни с подключением скриптов, сами тесты приходится писать без ES2015 возможностей. Располагает ужасной отладкой. API не связанное с самими web-страницами очень проблематично работает, к тому же по разному в зависимости от движка. К примеру, подключить к тесту какой-нибудь lodash может оказаться непростой задачей. Test-kit очень сильно отличается от привычных мне подходов (таких как в mocha). Я так и не понял, как удобно организовать большие и сложные тесты. Вывод в консоль в итоге напоминает какой-то хаос. |
|
Резюмируя
Инструментарий есть, но он ужасен. Требует очень большого и длительного внимания. Работает весьма нестабильно. По сути это сплошная головная боль. А сами проекты почти не развиваются. Увы.
Плагин для grunt, оказался чуть менее, чем никаким, ввиду чего пришлось писать собственную реализацию, которая пока тоже далека от желаемой.
String.split и RegExp
При написании тестов столкнулся с интересной проблемой. Метод String.split работал не так, как я от него ожидал. Я долго возился с различными вариантами жадного и ленивого поиска, пока наконец не понял, что дело не в них. Перейду сразу к сути:
'aaaa'.split(/a+/); // ["", ""]
'aaaa'.split(/(a+)/); // ["", "aaaa", ""] WAT?
'aaaa'.split(/(?:a+)/); // ["", ""]
Полез в дебри документации:
Если разделитель является регулярным выражением, содержащим подгруппы, то каждый раз при сопоставлении с разделителем, результаты (включая те, что не определены) захвата подгруппы будут помещаться внутрь выходного массива. Однако, не все браузеры поддерживают эту возможность.
Всё просто. Если знаешь.
CLI-скрипты и stdout
При написании подручных консольных криптов неизбежно встаёт вопрос, а как запускать внешнюю программу так, чтобы не потерять её вывод. Ситуация осложняется тем, что вывод может быть сложнее, нежели просто груда текста. К примеру ffmpeg рисует что-то вроде progressbar-а в поледней строке выдачи. Помимо прочего не хочется терять цвета.
Стандартная библиотека предоставляет ряд методов для запуска сторонних программ. Я перебрал несколько из них, но всё как-то мимо. К примеру при ручном перенаправлении spawn-а за счёт pipe в process.stdout выдачи я не добился. При ручной обработке on('data', нет цветной выдачи и затирания уже отрисованного текста. Ну и т.д.
Как обычно великий stackoverflow помог:
require('child_process').spawn('ffmpeg',
[
'-i', fname,
'-f', ext,
'-vcodec', vcodec,
'-acodec', acodec,
'-ss', fromS,
'-to', endS,
result
], { stdio: 'inherit' });
Весь рецепт в третьем параметре ― { stdio: 'inherit' }!
UPD0. Помимо цветного вывода и затирания текста прекрасно работает пользовательский ввод (пример: ffmpeg спрашивает перезаписать ли сущ-ий файл). Получается, что stdin тоже пробрасывается.
IE8 и babel
В одном из проектов требуется на уровне ТЗ поддержка Internet Explorer 8. Спасибо, что не 7 :) Дошли руки до перевода кодовой базы на es6. Воткнул как обычно preset es2015, потестил, вроде всё работает, но дёрнул меня чёрт проверить и в старом ослике. Подохло там всё. Причём глюк настолько странный, что я невольно задвигал бровями. Перейду сразу к сути:
function init(){ console.log(1); }
var a = { init: function(){ console.log(2); } };
init();
Результат, конечно же, 1. Однако babel превратит сий код в:
"use strict";
function init() {
console.log(1);
}
var a = { init: function init() {
console.log(2);
} };
init();
Ключевое отличие ― именование метода. Теперь он тоже init. Зачем? Не ко мне вопрос. Может быть для упрощения отладки. Но с этого момента старый ишак возвращает 2. Оказывается это старый баг тогдашнего jscript-а.
Лечение: добавляем: plugins: ["transform-jscript"] к настройкам babel-я. Ну и соответственно тащим этот плагин. С этого момента babel будет генерировать вот таких вот монстров:
"use strict";
function init() {
console.log(1);
}
var a = { init: function() {
function init(){ console.log(2); }
init();
} };
init();
Очень бредово, если честно. Ведь можно было сохранить изначальный код в первозданном виде.
Загрузка binary-файла
Возникла задача ― загрузить с сервера XLSX-файл. Запрос ― POST. Файл, понятное дело, бинарный. На проекте уже подключена jQuery, и, первым делом, я решил воспользоваться её стандартным $.ajax. Сколько не бился ― так и не удалось получить blob-результат от запроса. Использовать же решение через iframe-ы и form-ы тоже не хотелось. Во-первых уж больно костыльно (привет 90-ые). во-вторых нужно будет ещё чудить с передачей параметров POST-запроса (content-type: json). Гуглил-гуглил, но ни одного вменяемого решения (которое не стыдно было бы утащить в проект) я так и не нашёл. Но, т.к. интересует поддержка только последних версий современных браузеров, удалось найти и применить более изящное и функциональное решение. Итак…
- Забудем про jQuery и воспользуемся нативным XMLHttpRequest-ом
- Указав ему responseType = 'blob'.
- Получившийся результат (blob) загоняем в URL.createObjectURL.
- В результате получим псевдо-URL ссылку, начинающуюся с "blob:".
Ну а дальше уже по-желанию. Я генерирую новый <a/>-тег, задаю ему нужные аттрибуты download и href (та самая blob-ссылка), вызываю у ссылки .click(). Апосля ссылку удаляю. Проверял в Firefox-е и Chrome-е.
Небольшая заготовка:
function loadBlob(params, data)
{
return new Promise((resolve, reject) =>
{
const xhr = _.assign(new XMLHttpRequest(),
{
onload(){ resolve(xhr.response); },
onerror(e){ reject(e.target.status); }
});
xhr.open(params.method || 'POST', params.url);
xhr.responseType = 'blob';
const contentType = params.contentType || 'application/json';
xhr.setRequestHeader('content-type', contentType);
if(contentType === 'application/json')
data = JSON.stringify(data);
xhr.send(data);
});
}