Все хорошо, пока вам нужно прогнать JS исходники через бабели-шмабели для создания бандла, но начинается сущий ад, когда вы захотите написать тесты для вашего сайта или бибилиотеки. Проблема в том, что все тест-фреймворки используют специфичные функции из ноды или/и написаны в ES5. Таким образом запуск E2E тестов становятся не тривиальной задачей и предлагает танцы с бубном транспиляций и sourcemap-ов для покрытия кода. Вы же не хотите что бы ошибки указывали не туда?
В этой статье я опишу свой опыт использования Puppeteer для небольшой задачи,
и как я запустил ES6 модули в ноде и браузере, имея всего один исходник для тестов без сборщиков.
Зачем вообще Puppeteer, спросите вы, почему не WebDriver? Просто я заметил, как мучаются создатели популярных опенсорс WebGL библиотек, например, у них есть в наличии 300 страниц с примерами, каждый из которых может сломаться при любом коммите. Они проверяют их после каждого изменения, и если что-то забыли открыть — извините ?\_(?)_/?
, сломалось. Коли никто до сих пор не решил данную задачу, то я решил попробовать это, пока в своей небольшой либе. Первая мысль которая была, это запустить headless-gl, но он морально устарел. Node-gles уже поддерживает WebGL2, но не редкое расширение, которое я использовал. WebDriver? Даже не пробовал. Не уверен что это возможно, python/C#/Java были мне не нужны, а требовался JS/TS с последней нодой и с новейшим браузерным API, так залетающие фичи могут быть по последней спеке.
Почему ES6 модули? Поддержка WebGL и ES6 в браузерах примерно на одном уровне. А с модулями бандл или нет, пусть решает пользователь, просто можно собрать обе версии. Но оказывается для юнит тестирования, очень удобно использовать версию именно с модулями, так как sourcemap-ы добываются очень просто, а тесты после этого без каких либо лишних телодвижений можно запускать как в ноде, так и в браузере. Запуская их в puppeteer, E2E с покрытием кода дается почти бесплатно. Typescript c таргетом в ES6 наверно был нужен, но на маленьком проекте покрытым тестами, пойдет и обычный js.
Итак, хватит введений, я поставил в проект puppeteer
и puppeteer-to-istanbul
и написал такую обертку
// puppeteer.js
import puppeteer from 'puppeteer';
import pup2ist from 'puppeteer-to-istanbul';
(async () => {
const browser = await puppeteer.launch({
headless: process.env.HEADLESS, // headless customization
slowMo: 250 // good fature for new configs
});
const page = (await browser.pages())[0];
// enable coverage
await page.coverage.startJSCoverage();
await page.coverage.startCSSCoverage();
// some additional code with console events here...
// navigate to unit test page
await page.goto('http://127.0.0.1:1234/');
// disable coverage
const jsCoverage = await page.coverage.stopJSCoverage();
const cssCoverage = await page.coverage.stopCSSCoverage();
pup2ist.write([...jsCoverage, ...cssCoverage])
await new Promise(resolve => setTimeout(resolve, 6000));
await browser.close();
})();
Которую можно запускать командой node --experimental-modules --no-warnings ./test/puppeteer.js
с 11+ нодой, или даже без флагов на node 13.2+. Конечно можно использовать require
, то се… Но зачем? Это же вообще бэкенд, тут поддержка у клиентов даже не нужна! Следующий код из package.json
позволяет нам кастомизировать HEADLESS загрузку в консоли и в CI облаке, если требуются различные настройки для них. В travs/circle-ci наверно будет стоять linux и можно устанавливать там переменные среды в таком формате. concurrently
открывает параллельно два процесса в одной консоли.
// package.json
{
//bla-bla...
"type": "module", // this line indicates that we are using es6 modules
"scripts": {
"test": "node --experimental-modules --no-warnings ./test/puppeteer.js",
"server": "http-server -c-1 -p 1234",
"not-bad-cmd--dude": "concurrently -k -s first \"npm:test\" \"npm:server\"",
"ci": "HEADLESS=true concurrently -k -s first \"npm:test\" \"npm:server\"",
}
}
На локальной машине после ввода команды npm run server
будет запускаться http сервер, а на npm run test
puppeteer в отдельном окне окне хрома. Вот в принципе и все что нужно знать про puppeteer. Дальнейшие примеры по скриншотам, эмуляциям девайсов, админкам и т.д., расположены тут. Кстати, вместе с пакетом puppeteer
вам установился отдельный хром в node_modules
, если он вам не нужен, замените его на puppeteer-core
или puppeteer-firefox
. Следует заметить что в примере выше мы бесплатно получили JS/CSS покрытие кода которое пишется в папку .nyc_output, пока не будем заострять на этом внимание, на данном этапе нам от этого не холодно не жарко, но если что — оно там есть, и статистика покрытия тестов почти готова к просмотру.
Теперь перейдем к самим тестам, пытаясь выбрать в чем я буду запускать E2E в моей небольшой либе, я наткнулся на следующие графики, где сравнивали производительности фреймворков для тестирования. Наверно, время выполнения не так важно, но когда какой нибудь Jest запускает их в 10 раз медленнее, возникает вопрос "что это, и зачем это нужно". Основной критерий выбора это был запуск es6 со строчкой <script type="module" src="./test.js"></script>
в html странице. Так как на момент написания моего кода, нода еще не поддерживала в полной мере ES6 (вчера вышла 12.3 в которой сняли флаги). Я решил, что если взять фреймворк с исходниками на TS или же ES6+, то оно точно должно запускаться. Вообще, наверное можно было взять какой нибудь mocha, объявить его выше на странице и обращаться к объявленному классу, но что будет если выпадет ошибка? В общем, можете назвать свой любимый тест раннер тут. Я лишь скажу что Zora поддерживает TAP формат, и это значит для нее подходит целый зоопарк TAP пожирателей. В ней есть большинство ассертов, она поддерживает async, она одна из самых быстрых, написана на чистом ES6 без зависимостей от самой ноды. Мне показалась она настоящим бриллиантом для небольших проектов.
В итоге у меня получились какие-то такие тесты, которые работают как в браузере так и в ноде. В документации по Zora есть исчерпывающая инструкция по ассертам и группировкам команд.
// test.js
import MyLibrary from '../dist/my-library.module.js';
import { test } from 'https://cdn.jsdelivr.net/npm/zora@3.0.3/dist/bundle/module.js';
test('CPU', async (t) => {
// some stuff here
t.ok(tfps != null, 'fps = ' + (tfps != null ? tfps.toFixed(1) : 'null'));
t.ok(tcpu != null, 'cpu = ' + (tcpu != null ? tcpu.toFixed(1) : 'null'));
});
test('Memory', async (t) => {
// some stuff here
t.ok(tmem != null, 'mem = ' + (tmem != null ? tmem.toFixed(1) : 'null'));
});
// etc...
Для того что бы показать консоль без сборок, мне пришлось сделать подобный снифер. Голая консоль не очень презентабельна, можно было бы подключить TAP вывод куда-нибудь для наведения марафета. Но самое забавное — это то, что результаты тестов на вашем клиенте можно посмотреть в онлайне. Так же, совершенно этот же код запускается в CI по любому коммиту.
<!DOCTYPE html>
<html lang="en">
<head>
<!-- some declarations in head -->
</head>
<body>
<!-- some declarations in body -->
<script>
const addSniffer = (spyTarget) => function() {
spyTarget.apply(window.console, arguments);
sniffer([...arguments]);
}
window.console.log = addSniffer(window.console.log);
window.console.error = addSniffer(window.console.error);
let screen = document.getElementById('screen');
function sniffer(string) {
let screen = document.getElementById("screen");
string.forEach(line => {
let div = document.createElement("div");
let text = document.createTextNode(line);
div.appendChild(text)
screen.appendChild(div);
});
}
</script>
<script type="module" src="./test.js"></script>
</body>
</html>
Но это еще не все, имея готовые тесты, можно подключить таких ботов как renovate/greenkeeper/dependabot, которые бы обновляли зависимости в вашей библиотеке, и делали автокоммиты, предварительно проверяя корректность обновлений. А travis/github-ci/circle-ci бы выкладывали бы новую версию npm пакетов.
Например такой конфиг от renovate, делает автокоммиты по воскресеньям, и поднимает версию
{
"automerge": true,
"automergeType": "branch",
"bumpVersion": "patch",
"schedule": ["on sunday"],
"ignorePaths": [".circleci"]
}
А travis, когда вы сами подняли версию или же какой то бот, может автоматом выкладывать пакет в npm. Для этого нужно создать аккаунт на travis-ci.org
, включить f2a как описано в данной статье, ввести два секретных ключа $NPM_EMAIL
и $NPM_TOKEN
, и создать подобный конфиг.
language: node_js
node_js: '12'
script:
- npm run ci
deploy:
provider: npm
email: $NPM_EMAIL
api_key: $NPM_TOKEN
on:
branch: master
Итого, как-то так можно, но сложно избавится от оповещений от гитхаба о том, что в какой-то зависимости появилась уязвимость :D
mrG0bliN
сочная статья спасибо