Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Как прогнать несколько часов автотестов за 5 минут и при этом, чтобы это было стабильно и не вызывало головной боли при каждой сборке? Без лишней воды и вступлений предоставляю вашему внимаю сборник костылей и подпорок элегантных архитектурных решений, без которых невозможно добиться высокой скорости и стабильности автотестов.
Входные данные: фреймворк codeception, сервер с selenium, код на PHP, который нужно протестировать.
Кратко перечислю сборник элегантных архитектурных решений, а затем остановимся на каждом из них подробнее.
Теперь подробнее про каждый пункт.
В codeception есть возможность поделить тесты на группы с помощью нотации @ group в phpdoc метода или же всего класса. После распределения таким образом тесты на группы происходит их запуск в отдельном процессе, с помощью https://robo.li/.
Как выполнить этот запуск смотрим тут https://robo.li/tasks/Testing/#codecept.
Небольшая подсказка: group($group) set group option. Can be called multiple times.
Сколько делать одновременных потоков — каждый сам должен понять, все зависит от ресурсов сервера, на котором крутятся тесты. На нашем проекте может одновременно крутиться десятки потоков.
Нужно не забыть перезапускать упавшие тесты также в несколько потоков. Если этого не делать, то будет ситуация когда прогон тестов из 10 потоков прошедший за 5 минут будет ждать перезапуска, например, 3 тестов, которые в один поток могут выполняться больше минуты.
В какой-то момент выяснилось, что заполнение полей форм, а в тестах приходится много заполнять полей всяких разных форм, занимает очень много времени и чтобы сэкономить драгоценное время применена заплатка в виде заполнения поля на javascript.
Мы переписали метод fillField библиотеки codeception.
Собственно вот часть кода, отражающая, суть:
Сами причины почему selenium долго заполняет поля текстом не ясны, к сожалению
Это настолько очевидное и банальное казалось бы, но в самом фреймворке нет такой возможности, на то он и фреймворк как говорится, пусть люди сами делают что хотят.
Кто то скажет, что перезапуск тестов это зло и что вы все неправильно делаете, но реальность такова, что тесты все же падают, и довольно часто. И мы просто смирились с этим и адаптировались к этому.
Перезапуск перезапуску рознь, во-первых каждое падение и перезапуск должно фиксироваться с максимально возможным количеством сопутствующих данных, чтобы разработчик мог оценить и понять причину падений.
Во-вторых нужно иметь под рукой список тестов отсортированных по количеству падений, чтобы понимать над каким тестом стоит провести исправительную работу.
Перезапуск должен быть только тех тестов которые упали — то что не упало не перезапускаем, чтобы не тратить время. Тесты не связаны между собой и запуск одного отдельного теста не должен вызывать проблем.
Когда у вас запущено много потоков, а сервер с selenium один и он уставший, может проскальзывать ошибка под названием «Session timed out or not found», и тут нам помогает перезапуск сессии с selenium.
Вот код который делает перезапуск сессии:
robots.txt это текстовой файл находящийся на домене который будет тестироваться, запрос на него нужен, чтобы точно знать, что сессия в браузере рабочая и можно начать выполнение теста, этот блок кода должен выполняться перед каждым тестом.
Подробнее про текстовой файл будет в следующем пункте.
Чтобы очистить контекст текущей сессии браузера в selenium нужно сделать запрос на любую другую страницу, чтобы поменялась структура документа. Чтобы не нагружать серверы ненужным рендерингом, запрос делается на любой текстовой файл, и так сложилось, что этим файлом у нас стал robots.txt.
Собственно зачем делать эту так называемую очистку контекста? Она нужна для того, чтобы тесты не смогли использовать разметку страницы из предыдущего теста. Часто бывало такое, что браузер находил элементы, которых нет на текущей странице или же, наоборот, не находил то, что нужно было найти и падал с ошибкой. Это происходит из-за того, что сценарий тестов на PHP не дожидается загрузки новой страницы и начинает делать всякие seeElement, waitForText в уже имеющейся(старой) структуре документа и в результате мы не понимаем, что произошло и почему упал тест. И тут то нас спасает предварительный запрос на robots.txt, после него мы имеем чистое место для начала нового тестового сценария.
Проблемы с запуском и прохождением тестов могут быть не только на стороне сервера selenium (вот это неожиданность), но и на исполняющей стороне, там где запущен php процесс, обычно это CI сервер.
Так как на CI сервере происходит очень много всего, то по разным причинам php процессы, внезапно, могут обрываться и тем самым у нас получится что целая группа тестов останется не пройденной и придется ждать ее отдельного перезапуска. Тут к нам спешат на помощьЧип и Дейл проверка состояния процесса и его перезапуск если он вдруг завершится.
Чтобы сделать перезапуск процесса пришлось переопределить метод \Robo\Task\Base\ParallelExec::run
Вот отрывок кода:
В этом методе уже есть проверка состояния процесса и нам нужно дописать десяток строк, чтобы этот процесс перезапускался по какому то условию, условие лежит в $this->doRerunCallback, это функция обратного вызова, в которой мы определяем нужно ли делать перезапуск. В ней у нас лежит следующее:
Т.е. по сути там проверка есть ли отчет по данной группе тестов или нет.
Кто уже сталкивался с браузерными тестами или вообще с автотестированием, наверное, знают из каких костылей и подпорок могут состоять эти системы. Я представил большую часть подпорок со стороны кода.
Как видно, большой упор делается на автоматический перезапуск, и если сборка упала, то с высокой долей вероятности это ошибка в коде и нужно смотреть на упавший тест.
Входные данные: фреймворк codeception, сервер с selenium, код на PHP, который нужно протестировать.
Кратко перечислю сборник элегантных архитектурных решений, а затем остановимся на каждом из них подробнее.
- Первое и самое очевидное — это запараллеливание выполнения тестов, точнее запуск в несколько потоков групп тестов.
- Заполнение полей форм с помощью javascript.
- Далее на третьем месте по списку, но не по значимости стоит такое явление как перезапуск только тех тестов, которые упали, а не всего набора тестов.
- Перезапуск сессии с selenium.
- Очистка контекста в браузерных тестах
- Перезапуск потока.
Теперь подробнее про каждый пункт.
Параллельный запуск
В codeception есть возможность поделить тесты на группы с помощью нотации @ group в phpdoc метода или же всего класса. После распределения таким образом тесты на группы происходит их запуск в отдельном процессе, с помощью https://robo.li/.
Как выполнить этот запуск смотрим тут https://robo.li/tasks/Testing/#codecept.
Небольшая подсказка: group($group) set group option. Can be called multiple times.
Сколько делать одновременных потоков — каждый сам должен понять, все зависит от ресурсов сервера, на котором крутятся тесты. На нашем проекте может одновременно крутиться десятки потоков.
Нужно не забыть перезапускать упавшие тесты также в несколько потоков. Если этого не делать, то будет ситуация когда прогон тестов из 10 потоков прошедший за 5 минут будет ждать перезапуска, например, 3 тестов, которые в один поток могут выполняться больше минуты.
Заполнение форм через Javascript
В какой-то момент выяснилось, что заполнение полей форм, а в тестах приходится много заполнять полей всяких разных форм, занимает очень много времени и чтобы сэкономить драгоценное время применена заплатка в виде заполнения поля на javascript.
Мы переписали метод fillField библиотеки codeception.
Собственно вот часть кода, отражающая, суть:
$webDriver->executeJS(
‘element = document.querySelector(arguments[0]);
element.value = arguments[1]’,
[$selector, $value])
Сами причины почему selenium долго заполняет поля текстом не ясны, к сожалению
Перезапуск упавших тестов
Это настолько очевидное и банальное казалось бы, но в самом фреймворке нет такой возможности, на то он и фреймворк как говорится, пусть люди сами делают что хотят.
Кто то скажет, что перезапуск тестов это зло и что вы все неправильно делаете, но реальность такова, что тесты все же падают, и довольно часто. И мы просто смирились с этим и адаптировались к этому.
Перезапуск перезапуску рознь, во-первых каждое падение и перезапуск должно фиксироваться с максимально возможным количеством сопутствующих данных, чтобы разработчик мог оценить и понять причину падений.
Во-вторых нужно иметь под рукой список тестов отсортированных по количеству падений, чтобы понимать над каким тестом стоит провести исправительную работу.
Перезапуск должен быть только тех тестов которые упали — то что не упало не перезапускаем, чтобы не тратить время. Тесты не связаны между собой и запуск одного отдельного теста не должен вызывать проблем.
Перезапуск сессии с selenium
Когда у вас запущено много потоков, а сервер с selenium один и он уставший, может проскальзывать ошибка под названием «Session timed out or not found», и тут нам помогает перезапуск сессии с selenium.
Вот код который делает перезапуск сессии:
/** @var WebDriver $webDriver */
$webDriver = $I->getWebDriver();
try {
$I->amOnPage('/robots.txt');
} catch (\Facebook\WebDriver\Exception\WebDriverException $e) {
try {
$I->comment('Что-то с сессией в селениуме - пытаюсь перезапустить сессию');
$webDriver->debugWebDriverLogs();
$webDriver->_restart();
} catch (\Throwable $e) {
$I->comment('Омайгад перезапустить не удалось, просто создаю новую сессию');
$webDriver->_initializeSession();
}
$I->amOnPage('/robots.txt');
}
robots.txt это текстовой файл находящийся на домене который будет тестироваться, запрос на него нужен, чтобы точно знать, что сессия в браузере рабочая и можно начать выполнение теста, этот блок кода должен выполняться перед каждым тестом.
Подробнее про текстовой файл будет в следующем пункте.
Очистка контекста в браузерных тестах
Чтобы очистить контекст текущей сессии браузера в selenium нужно сделать запрос на любую другую страницу, чтобы поменялась структура документа. Чтобы не нагружать серверы ненужным рендерингом, запрос делается на любой текстовой файл, и так сложилось, что этим файлом у нас стал robots.txt.
Собственно зачем делать эту так называемую очистку контекста? Она нужна для того, чтобы тесты не смогли использовать разметку страницы из предыдущего теста. Часто бывало такое, что браузер находил элементы, которых нет на текущей странице или же, наоборот, не находил то, что нужно было найти и падал с ошибкой. Это происходит из-за того, что сценарий тестов на PHP не дожидается загрузки новой страницы и начинает делать всякие seeElement, waitForText в уже имеющейся(старой) структуре документа и в результате мы не понимаем, что произошло и почему упал тест. И тут то нас спасает предварительный запрос на robots.txt, после него мы имеем чистое место для начала нового тестового сценария.
Перезапуск потока
Проблемы с запуском и прохождением тестов могут быть не только на стороне сервера selenium (вот это неожиданность), но и на исполняющей стороне, там где запущен php процесс, обычно это CI сервер.
Так как на CI сервере происходит очень много всего, то по разным причинам php процессы, внезапно, могут обрываться и тем самым у нас получится что целая группа тестов останется не пройденной и придется ждать ее отдельного перезапуска. Тут к нам спешат на помощь
Чтобы сделать перезапуск процесса пришлось переопределить метод \Robo\Task\Base\ParallelExec::run
Вот отрывок кода:
if (!$process->isRunning()) {
$doRerun = $this->doRerunCallback;
$nbRerun = $nbRerunForProcess[$process->getCommandLine()] ?? 0;
if ($doRerun($process) && $nbRerun <= 3) {
$this->printTaskInfo(
'Try to rerun ' . $nbRerun . ' time ' .
" for {command}: \n\n{output} \nend output for rerun",
[
'command' => $process->getCommandLine(),
'output' => $process->getOutput(),
'_style' => ['command' => 'fg=white;bg=magenta'],
]
);
$nbRerun++;
$nbRerunForProcess[$process->getCommandLine()] = $nbRerun;
$process->start();
} else {
В этом методе уже есть проверка состояния процесса и нам нужно дописать десяток строк, чтобы этот процесс перезапускался по какому то условию, условие лежит в $this->doRerunCallback, это функция обратного вызова, в которой мы определяем нужно ли делать перезапуск. В ней у нас лежит следующее:
$parallel->setDoRerunCallback(function (Process $process) use ($self) {
$group = $self->getGroupFromCmd($process->getCommandLine());
if ($group) {
return !$self->checkGroupReportExist($group);
}
return false;
});
Т.е. по сути там проверка есть ли отчет по данной группе тестов или нет.
Итог
Кто уже сталкивался с браузерными тестами или вообще с автотестированием, наверное, знают из каких костылей и подпорок могут состоять эти системы. Я представил большую часть подпорок со стороны кода.
Как видно, большой упор делается на автоматический перезапуск, и если сборка упала, то с высокой долей вероятности это ошибка в коде и нужно смотреть на упавший тест.