Как обновить hermione до версии 4.x
Данный рецепт актуален только для тех проектов, которые используют hermione младше 4-й версии.
Почему стоит обновиться?
Когда-то очень давно hermione перешла на «временный» форк пакета webdriverio@4 (сокр. wdio), который она использовала «под капотом», т. к. проблемы во внешнем wdio тормозили её разработку: постоянные баги в wdio, разногласия относительно вносимых изменений и т. п. И если сначала форк регулярно обновлялся командой hermione, чтобы предоставить пользователю актуальную функциональность, то со временем форк значительно отстал от текущей версии wdio во внешнем мире.
К тому времени во внешнем мире в wdio уже появилось много различных фич, которые интересны разработчикам: Chrome DevTools Protocol (CDP), стабы внешних запросов, расширенные возможности работы с мобильными устройствами и т. п. Поэтому у команды hermione не осталось выбора: нужно было отказываться от форка webdriverio@4 и переходить на самую актуальную версию webdriverio@7.
Сейчас уже доступна 8-я версия webdriverio и hermione@7 уже использует её.
Кроме этого, пользователям становилось всё неудобнее пользоваться устаревшими командами: тайпинги приходилось подключать из отдельного пакета (в новом wdio они поставляются из коробки), за документацией по командам приходилось ходить на старый сайт, в то время как некоторые пользователи иногда заходили на актуальную страницу и не могли понять, почему команды из документации не работают в hermione.
Таким образом, причин для радикального апгрейда — сразу на 3 мажора вверх — накопилось достаточно.
Что изменилось?
Изменений очень много, поэтому ниже будут перечис лены только самые важные / интересные из них.
API команд
async/await вместо чейнинга
В новой версии теперь нельзя писать тесты, используя chaining. Доступен только async/await-синтаксис:
- Было
- Стало
it('some test', function() {
return this.browser
.foo()
.bar()
.baz();
});
it('some test', async function() {
await this.browser.foo();
await this.browser.bar();
await this.browser.baz();
});
А начиная с версии hermione@4.9.0 можно писать тесты ещё короче, так как hermione теперь передает в функцию объект с полем browser
:
it("some test", async function ({ browser }) {
await browser.foo();
await browser.bar();
await browser.baz();
});
Далее в примерах «Ста ло» мы будем везде предполагать, что речь идет о hermione с версией не меньше, чем 4.9.0. Если по какой-то причине вы планируете использовать версию hermione 4+ младше 4.9.0, то к браузеру в тестах нужно обращаться как и раньше — через this, например: await this.browser.getText('.selector').
Результат вместо объекта с ключом value
Теперь при получении результатов команд вместо объекта с ключом value
возвращается реальный результат (старое поведение часто приводило к ошибкам в тестах):
- Было
- Стало
it('some test', async function() {
const { value } = await this.browser.getText('.selector');
console.log(value); // some text
});
it('some test', async function({ browser }) {
const text = await browser.getText('.selector');
console.log(text); // some text
});
Работа с элементами напрямую
С помощью команды browser.$
можно получить инстанс найденного элемента и работать с ним в тесте. Это удобно, когда с элементом нужно взаимодействовать больше одного раза (при этом элемент не будет повторно искаться на странице):
- Было
- Стало
it('some test', async function() {
await this.browser.clearElement('.input');
await this.browser.setValue('.input', 'text');
});
it('some test', async function({ browser }) {
const elem = await browser.$('.input');
await elem.clearElement();
await elem.setValue('text');
});
Смотрите также команды:
Передача аргументов через объект
Для многих команд аргументы теперь передаются с помощью объекта с понятными ключами вместо последовательной передачи аргументов, в которых было очень легко запутаться. Например, в команде waitForExist, в которой в качестве аргументов раньше передавались даже булевые значения:
- Было
- Стало
it('some test', async function() {
await this.browser.waitForExist('.selector', 1000, true);
});
it('some test', async function({ browser }) {
const elem = await browser.$('.selector');
await elem.waitForExist({
timeout: 1000,
interval: 500,
reverse: true,
timeoutMsg: 'still exists'
});
});
Специальная команда для React
Бонус для тех, кто уже перешел на React — теперь в тестах можно использовать команды browser.react$
и browser.react$$
для поиска на странице конкретных react-компонентов с определенными состояниями. Аналогичные команды есть и для элементов — element.react$
и element.react$$
.
Также читайте статью о вариантах работы с react-компонентами на сайте webdriverio.
Пример использования:
- Было
- Стало
it('some test', async function() {
// специальных команд для работы с react-компонентами – нет :(
});
it('some test', async function({ browser }) {
const component = await browser.react$('MyComponent', {
props: { someProp: true },
state: 'some-state'
});
const result = await component.isDisplayed();
});
Актуальная документация
Пока hermione использовала старую версию webdriverio@4, приходилось все время уточнять, что документация на все команды лежит по отдельному адресу: v4.webdriver.io/api.html. Теперь же описания всех команд webdriverio, которые используются hermione, можно найти по стандартному адресу: webdriver.io/docs/api.
Помимо этого, мы перевели описания всех команд на русский язык и адаптировали все примеры использования этих команд под hermione, так как в webdriverio свой раннер и примеры в его документации неприменимы напрямую в hermione.
Тесты выполняются быстрее
При локальных прогонах нескольких тестов вы, скорее всего, этого ускорения не заметите, но на большом количестве тестов это будет весьма ощутимо. Новые команды работают быстрее приме рно на 15% (при условии, что вы отказались от использования старых команд).
Простой запуск тестов на локальном браузере
Раньше для того, чтобы запустить тесты локально на своем браузере, нужно было запустить selenium-standalone
и указать hermione магический gridUrl
, чтобы все заработало. Сейчас же — достаточно в конфиге указать браузеру опцию automationProtocol
со значением devtools
:
// hermione.conf.js
module.exports = {
browsers: {
chrome: {
automationProtocol: 'devtools',
desiredCapabilities: {
// ...
}
}
},
// другие настройки hermione...
};
Также мы планируем добавить отдельную кнопку в hermione GUI для перехода в CDP-режим, чтобы было еще проще.
- На данный момент это полноценно поддержано только в браузере Chrome. * Переснимать скриншоты в таком режиме можно только для дебага, т. к. в конвейере браузеры запускаются под Linux, соответственно: рендеринг страницы будет отличаться и в пулл-реквесте тесты упадут с диффом.
API для стаба походов по сети
В новой версии доступна возможность застабать ответы вашего сервиса или переопределить их. Делается это с помощью команды mock.respond(). Также можно запрещать походы по урлам внешних сервисов.
Более подробно о всех возможностях читайте в рецепте «Как отслеживать и перехватывать сетевые запросы и ответы».
На данный момент эта функциональность работает только в режиме Chrome DevTools Protocol (CDP), который работает только в Chrome и Firefox Nightly.
Конфигурация браузеров в конфиге
Для браузеров, поддерживающих работу W3C-протокола, вместо поля version
нужно указывать browserVersion
. А для дополнительных опций нужно добавлять префикс браузера:
- Было
- Стало
module.exports = {
browsers: {
'chrome-desktop': {
desiredCapabilities: {
browserName: 'chrome',
version: '75',
'chromeOptions': {
// ...
}
}
}
}
};
module.exports = {
browsers: {
'chrome-desktop': {
desiredCapabilities: {
browserName: 'chrome',
browserVersion: '75',
'goog:chromeOptions': { // для браузера Chrome нужен префикс 'goog'
// ...
}
}
}
}
};
Подробнее про вендорные префиксы читайте по ссылке.
Список всех доступных настроек можно посмотреть в спецификации.
Как переехать?
Мы обновились сразу на 3 мажора webdriverio, поэтому просто обновить версию hermione в package.json
не получится. Основные проблемы при переезде — это отсутствующий chaining в тестах и устаревшие команды тестов. Чтобы вам было проще разобраться с обеими проблемами, мы написали для вас следующую инструкцию.
1. Обновите hermione до 4+, установите плагин мигратора и кодмод
А именно:
- обновите версию hermione до hermione@4;
- установите плагин hermione-wdio-migrator для плавной миграции команд;
- установите пакет hermione-codemod для конвертации существующих тестов в новый синтаксис;
Всё это вы можете сделать одной командой:
npm install -D hermione@4 hermione-wdio-migrator hermione-codemod --save-exact
Версии всех плагинов hermione (например, html-reporter) также необходимо обновить до последних версий, т. к. часть из них может работать неправильно с новой версией hermione.
2. Запустите кодмод на async/await
Кодмод перегенерит ваши тесты с chaining-формата в формат async/await:
- zsh
- bash
Если вы используете командный процессор zsh, то файлы тестов можно передавать не только в виде относительных путей, но и в виде глобов, например: somefolder/**/*.js и т. п.
npx jscodeshift -t node_modules/hermione-codemod/transforms/browser-chaining-to-async-await.js path_to_file_mask
Если вы используете командный процессор bash, то задавать пути в виде глобов как в zsh не получится, поэтому команда будет сложнее, если вам нужно обработать группу файлов. Например:
npx jscodeshift -t node_modules/hermione-codemod/transforms/browser-chaining-to-async-await.js $(find ./somefolder -type f -iname '*.js' | xargs echo)
При успешном завершении вы увидите соответствующее сообщение:
Results:
0 errors
0 unmodified
0 skipped
251 ok
Но бывают и кейсы, с которыми текущий кодмод не может справиться. Для таких тестов будет выведена ошибка с информацией о проблемном файле:
WARN: can't correctly transform ConditionalExpression, fix it manually
file: tests/hermione/suites/common/promotion-page/promotion-page.hermione.js
position: {"start":112,"end":116}
Такие тесты придется править руками. Мы старались учесть большинство тестов, поэтому таких случаев должно быть не очень много.
После этого вы уже можете вливать свои изменения (это не обязательно), чтобы выполнять переезд по частям. Дело в том, что в wdio@4 синтаксис async/await тоже будет работать. Т. е. вы всегда могли писать тесты таким образом (а некоторые сервисы уже давно так и делали).
3. Запустите кодмод на удаление value
Так как теперь при получении результатов команд вместо объекта с ключом value
возвращается реальный результат, нужно изменить во всех тестах сохранение результата. Следующий кодмод предназначен именно для этого случая.
Команда очень похожа на предыдущую, меняется только путь к файлу реализации очередного кодмода:
npx jscodeshift -t node_modules/hermione-codemod/transforms/remove-browser-prop.js path_to_file_mask
При наличии ворнингов проблемные тесты нужно будет исправить вручную. Например, если в рамках одного теста есть многократное использование value
через деструктуризацию, то кодмод с таким не справится и в коде теста получится несколько переменных с одним именем. Например:
// тест на wdio@4:
it('test', function(){
return this.browser
...
.getText('.button')
.then((value) => {
assert.equal(value, 'Кнопка', 'Нам нужна кнопка');
})
...
.getValue('.input')
.then((value) => {
assert.equal(value, 'Привет', 'С нами не поздоровались');
});
});
// перегенерится для wdio@7 в такой формат:
it('test', async function() {
const value = await this.browser.getText('.button');
assert.equal(value, 'Кнопка', 'Нам нужна кнопка');
...
// здесь будет ошибка из-за повторного использования названия переменной,
// поэтому кодмод выдаст ворнинг о проблемном месте
const value = await this.browser.getValue('.input');
assert.equal(value, 'Привет', 'С нами не поздоровались');
});
Если при запуске кодмодов в вашем проекте оказалось слишком много тестов, которые не поддаются автоматической миграции, то обратитесь, пожалуйста, в github issues за помощью. Мы проанализируем эти ошибки и поможем с переездом.
4. Добавьте hermione-wdio-migrator в конфиг hermione
Этот плагин «под капотом» просто добавляет реализацию старых команд с помощью нового API, чтобы в момент переезда вам не пришлось актуализировать тесты самостоятельно. В дальнейшем, конечно, нужно будет в своих тестах заменить эти устаревшие команды на новые:
module.exports = {
plugins: {
"hermione-wdio-migrator": {
enabled: true,
},
// остальные плагины hermione...
},
// другие настройки hermione...
};