Puppeteer c ES6 в ноде и браузере, или почему Zora это лучший тест-фреймворк в своей нише

от автора

Все хорошо, пока вам нужно прогнать 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

Итого, как-то так можно, но сложно избавится от оповещений от гитхаба о том, что в какой-то зависимости появилась уязвимость 😀


ссылка на оригинал статьи https://habr.com/ru/post/466551/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *