События Testplane
Обзор
Как устроено описание событий
Ниже описаны все события testplane, на которые можно подписаться в своем плагине.
Описание каждого события начинается с тегов, которые представлены следующими вариантами:
- sync или async обозначают, соответственно, синхронный и асинхронный режимы вызова обработчика события;
- master обозначает, что данное событие доступно из мастер-процесса testplane;
- worker обозначает, что данное событие доступно из воркеров (подпроцессов) testplane;
- can be intercepted обозначает, что данное событие можно перехватить и соответственно, изменить.
Д алее идут:
- описание обстоятельств, при которых триггерится событие;
- сниппет с кодом, показывающим как на него подписаться;
- параметры обработчика события;
- и опционально, примеры использования данного события в плагине или плагинах.
Схема генерации событий

Описание последовательности событий
Testplane можно запускать как через CLI (командную строку), так и через её API: из скрипта с помощью команды run.
После запуска testplane загружает все плагины и переходит к парсингу CLI, если она была запущена через CLI, или сразу к стадии инициализации, если её запустили через API.
Парсинг CLI
Если testplane была запущена через CLI, то она триггерит событие CLI. Любой плагин может подписаться на это событие, чтобы добавить свои опции и команды к testplane до того, как testplane распарсит CLI.
Если testplane была запущена с помощью API, то стадия парсинга CLI будет пропущена и сразу же начнется стадия инициализации.
Инициализация
Во время инициализации testplane триггерит событие INIT. Это событие срабатывает всего 1 раз за весь запуск testplane. Подписавшись на это событие, плагины могут выполнить всю необходимую им подготовительную работу: открыть и прочитать какие-то файлы, поднять dev-сервер, и нициализировать структуры данных, и т. д. и т. п.
Затем testplane запускает подпроцессы (так называемые воркеры), в рамках которых будут выполняться все тесты. В мастер-процессе testplane тесты не выполняются, а только осуществляется общая оркестрация всего процесса запуска тестов, включая генерацию событий при завершении выполнения отдельных тестов.
Количество воркеров, которые запускает testplane, регулируется параметром workers в разделе system конфига testplane. При запуске очередного воркера testplane триггерит специальное событие NEW_WORKER_PROCESS.
Все тесты testplane запускает в воркерах, чтобы не столкнуться с ограничениями по памяти и CPU для мастер-процесса. Как только в воркере количество выполненных тестов достигнет значения testsPerWorker, вор кер завершит свою работу, и будет запущен новый воркер. Соответственно, будет снова послано событие NEW_WORKER_PROCESS.
Чтение тестов
После этого testplane читает все тесты с файловой системы в мастер-процессе. Посылая для каждого файла, перед тем как его прочитать, событие BEFORE_FILE_READ и после прочтения — событие AFTER_FILE_READ.
После того, как все тесты будут прочитаны, триггерится событие AFTER_TESTS_READ.
Запуск тестов
Затем testplane посылает события RUNNER_START и BEGIN. И стартует новую сессию (браузерную сессию), в которой будут выполняться тесты. При старте сессии testplane триггерит событие SESSION_START.
Если количество тестов, выполненных в рамках одной сессии, достигнет значения параметра testsPerSession, то testplane завершит сессию, стриггерив событие SESSION_END, и запустит новую, послав событие SESSION_START.
Если тест при выполнении упадет с критической ошибкой, то testplane:
- досрочно удалит сессию и браузер, который с ней связан;
- создаст новую сессию;
- запросит новый браузер, и привяжет его к новой сессии.
Это нужно для того, чтобы сбой в сессии при выполнении одного из тестов не начал влиять на запуски последующих тестов.
После создания новой сессии testplane переходит к выполнению тестов. Все тесты выполняются в воркерах, но сам запуск и сбор результатов прогона тестов осуществляется в рамках мастер-процесса. Мастер-процесс триггерит событие SUITE_BEGIN для describe-блоков в файле с тестами и TEST_BEGIN для it-блоков. Если тест отключен с помощью хелперов типа skip.in и тому подобных, то триггерится событие TEST_PENDING.
Далее воркеры получают от мастер-процесса информацию о конкретных тестах, которые они должны запустить. Так как тесты хранятся в файлах, то воркеры читают конкретно те файлы, в которых находятся требуемые тесты. И перед чтением каждого такого файла, в каждом воркере триггерится событие BEFORE_FILE_READ, а после прочтения — событие AFTER_FILE_READ.
После того как соответствующие файлы с тестами будут прочитаны воркером, триггерится событие AFTER_TESTS_READ.
Перечисленные 3 события — BEFORE_FILE_READ, AFTER_FILE_READ и AFTER_TESTS_READ будут триггериться в воркерах в процессе прогона тестов каждый раз как воркеры будут получать от мастер-процесса очередные тесты, которые нужно запустить. Кроме случаев, когда соответствующий файл с тестами уже был прочитан воркером ранее. Так как после первого чтения какого-либо фа йла, воркер сохраняет его в кэше, чтобы в следующий раз избежать повторного чтения файла с тестами.
Потому что один файл может содержать множество тестов. А запуск тестов идет по отдельным тестам, а не по файлам. Поэтому в какой-то момент времени может прогоняться тест из файла, из которого уже прогонялся другой тест. В таких случаях кэширование защищает от ненужных повторных чтений одних и тех же файлов.
Прежде чем тест будет запущен, триггерится событие NEW_BROWSER. Однако это событие будет триггериться не для всех тестов, так как один и тот же браузер может использоваться много раз для запуска тестов (см. параметр sessionsPerBrowser). Также в случае падения теста с критической ошибкой происходит пересоздание браузера, чтобы не допустить падения других тестов в этом браузере из-за системного сбоя. В этом случае снова будет послано событие NEW_BROWSER.
Завершение тестов
После того как тест завершается, может быть послано событие SESSION_END. Но это только в том случае, если суммарное количество тестов, которые были запущены в использованной сессии, превысило значение testsPerSession.
Далее всё будет зависеть от того, с каким результатом завершился прогон теста. Если тест прошел успешно, то testplane стриггерит событие TEST_PASS. Если тест упал — TEST_FAIL. Если тест упал, но его следует запустить повторно (см. настройки retry и shouldRetry в конфиге testplane), то вместо события TEST_FAIL будет отправлено событие RETRY.
Если тест не нужно запускать повторно, и результат — окончательный, то testplane триггерит события TEST_END и SUITE_END, если речь идет о завершении выполнения describe-блока.
После того как все тесты будут выполнены, а сессии завершены, testplane триггерит события END и RUNNER_END.
Обновление эталонных скриншотов
Во время запуска тестов может произойти обновление эталонных скриншотов. Это может произойти по следующим причинам:
- разработчик запустил testplane в специальном GUI-режиме и дал команду «принять скриншоты»;
- разработчик указал опцию
--update-refпри запуске testplane; - у тестов не было эталонных скриншотов.
Во всех этих случаях триггерится событие UPDATE_REFERENCE.
Ошибки и аварийное завершение работы
Если во время прогона тестов, в одном из перехватчиков событий произойдет критическая ошибка, то testplane стриггерит для этого теста событие ERROR. При этом остальные тесты будут выполняться в штатном порядке.
Если во время прогона тестов, testplane получит сигнал SIGTERM (например, в результате нажатия клавиш Ctrl + C), то testplane стриггерит событие EXIT и досрочно прервет выполнение тестов.
Про перехват событий
Testplane позволяет разработчику перехватывать ряд событий и изменять их на другие, игнорировать или задерживать их обработку.
События, которые могут быть перехвачены, снабжены в описании тегом can be intercepted. Всего таких событий 7:
Меняем одно событие на другое
Например, код ниже показывает, как можно перехватывать событие TEST_FAIL и изменять его на событие TEST_PENDING — то есть автоматически отключать падающие тесты, не давая им уронить общий прогон тестов:
module.exports = testplane => {
testplane.intercept(testplane.events.TEST_FAIL, ({ event, data }) => {
const test = Object.create(data);
test.pending = true;
test.skipReason = "intercepted failure";
return { event: testplane.events.TEST_PENDING, data: test };
});
testplane.on(testplane.events.TEST_FAIL, test => {
// этот обработчик никогда не будет вызван
});
testplane.on(testplane.evenst.TEST_PENDING, test => {
// этот обработчик будет всегда вызываться вместо обработчика для TEST_FAIL
});
};
Оставляем событие as is
Если по какой-либо причине перехваченное событие нужно оставить как есть, то его обработчик должен вернуть точно такой же объект или любое falsey значение: undefined, null или false.
module.exports = testplane => {
testplane.intercept(testplane.events.TEST_FAIL, ({ event, data }) => {
return { event, data };
// или return;
// или return null;
// или return false;
});
testplane.on(testplane.events.TEST_FAIL, test => {
// этот обработчик будет вызван как обычно,
// потому что перехват события TEST_FAIL никак не меняет его
});
};
Игнорируем событие
Чтобы проигнорировать какое-либо событие и не дать ему распространяться дальше, нужно вернуть из обработчика (в котором перехватывается событие) пустой объект:
module.exports = testplane => {
testplane.intercept(testplane.events.TEST_FAIL, ({ event, data }) => {
return {};
});
testplane.on(testplane.events.TEST_FAIL, test => {
// этот обработчик никогда не будет вызван
});
};
Задерживаем обработку события
Приведенный выше подход с игнорированием события можно использовать для задержки появления тех или иных событий, например:
module.exports = testplane => {
const intercepted = [];
testplane.intercept(testplane.events.TEST_FAIL, ({ event, data }) => {
// собираем все события TEST_FAIL
intercepted.push({ event, data });
// и не даем им распространяться дальше
return {};
});
testplane.on(testplane.events.END, () => {
// после окончания прогона тестов, триггерим все накопленные события TEST_FAIL
intercepted.forEach(({ event, data }) => testplane.emit(event, data));
});
};
Передача информации между обработчиками событий
События, кот орые триггерятся в мастер-процессе и в воркерах testplane не могут обмениваться информацией через глобальные переменные.
Например, такой подход работать не будет:
module.exports = testplane => {
let flag = false;
testplane.on(testplane.events.RUNNER_START, () => {
flag = true;
});
testplane.on(testplane.events.NEW_BROWSER, () => {
// будет выведено значение false, потому что событие NEW_BROWSER
// триггерится в воркере testplane, а RUNNER_START – в мастер-процессе
console.info(flag);
});
testplane.on(testplane.events.RUNNER_END, () => {
// будет выведено значение true
console.info(flag);
});
};
Но можно решить проблему следующим образом:
module.exports = (testplane, opts) => {
testplane.on(testplane.events.RUNNER_START, () => {
opts.flag = true;
});
testplane.on(testplane.events.NEW_BROWSER, () => {
// будет выведено значение true, потому что свойства в конфиге,
// которые имеют примитивный тип (а переменная "opts" является частью конфига),
// автоматически передаются в воркеры во время события RUNNER_START
console.info(opts.flag);
});
};
Или следующим образом: смотрите пример из описания событ ия NEW_WORKER_PROCESS.
Параллельное выполнение кода плагина
У раннера тестов есть метод registerWorkers, который регистрирует код плагина для параллельного выполнения в воркерах testplane. Метод принимает следующие параметры:
| >Параметр | Тип | Описание |
| >workerFilepath | String | Абсолютный путь к воркеру. |
| >exportedMethods | String[] | Список экспортируемых методов. |
При этом возвращает объект, который содержит асинхронные функции с именами из экспортированных методов.
Файл с путем workerFilepath должен экспортировать объект, который содержит асинхронные функции с именами из exportedMethods.