Часто бывает так, что веб-приложение состоит из большого количества динамически перестраивающихся форм с разным текстом и элементами управления. Тестирование такого приложения превращается в кошмар.

Нужно прокликать 100500 страниц и проверить весь функционал… И перед следующим релизом еще раз проверить то же самое… И еще… И завтра опять. В какой то момент проверка начинает занимать больше времени, чем разработка нового функционала. «А как же е2е-тесты?» — спросите вы. Но, во-первых, их еще нужно написать. А во-вторых, перед тем как начать их писать, нужно написать тест-кейсы. Очень много тест-кейсов.

Если при чтении этих строк ваш лоб покрылся испариной, не переживайте. В этой статье я поделюсь с вами идеей, как мы в Tinkoff автоматизировали тестирование одного из веб-приложений, не написав при этом ни одного тест-кейса и е2е-теста.




Автоматическое написание тест-кейсов


Так уж вышло, что тестирование нашего веб-приложения в основном связано с проверками интерфейса. Нужно проверить, что на экране присутствует кнопка, нужный заголовок и текст, а при вводе невалидного значения в input появляется сообщение об ошибке.

Соответственно, при написании тест-кейса нужно записывать все действия:

  • «Нажали кнопку»
  • «Ввели значение ХХХ»
  • «Выбрали значение YYY в выпадающем списке»

и проверки:

  • «Появился текст: ХХХ»
  • «Появилось сообщение об ошибке: YYY»
  • «Появился заголовок: ZZZ»

После анализа всего функционала нашего веб-приложения мы выделили около 30 уникальных действий и проверок. Стало понятно, что этот процесс можно автоматизировать. Для этого нужно всего лишь отследить все действия тестировщика на странице и реакцию сайта на эти действия.

Начнем с перехвата событий. Чтобы отследить взаимодействие с такими контролами, как кнопка, переключатель и чек-бокс, нужно подписаться на событие click. В каждом фреймворке для этого существуют свои методы. Например, fromEvent в Angular и document.addEventListener в JavaScript и React. Для элементов управления с возможностью ввода, таких как календарь или инпут, изменится только тип события, на которое нужно подписаться: вместо click будет focusout.

fromEvent(this.elementRef.nativeElement, 'click')
.subscribe(tagName => {
 	if (tagName === 'BUTTON') {
   		this.testCaseService.addAction(`Нажать на кнопку "${action.name}"`);
 	} else if (tagName === 'INPUT-CALENDAR') {
   		this.testCaseService
     			.addAction(`Выбрать дату "${action.name}" "${action.value}"`);
 	}
});

Ну и, наконец, самое главное — проверки. То, как сайт должен вести себя в ответ на действия тестировщика.

Что обычно проверяет тестировщик? Например, он ввел невалидное значение в input, сайт отреагировал на это сообщением об ошибке. Или, допустим, мы нажали на кнопку и в ответ открылся новый экран, изменился заголовок, появился новый текст, перестроились элементы управления. Все эти изменения связаны с изменением в DOM-дереве. Есть много вариантов отследить их. Можно, например, использовать MutationObserver в React и JavaScript или ngAfterViewInit в Angular (проставляя директиву на интересующие элементы формы на сайте).

ngAfterViewInit() {
    const tagName = this.nativeElement.nodeName;
	const text = this.nativeElement.textContent;
    if (['SPAN', 'P'].includes(tagName)) {
 	 	 this.testCaseService.addContent(`**Появился текст** "${text}"\n`);
     } else if (tagName === 'H1') {
 	  	this.testCaseService.addContent(`**Появился заголовок** "${text}"\n`);
     } …	
}

Код будет очень сильно зависеть от верстки. Посмотрим на разметку. Эти кнопки взяты из «Гугл-переводчика».


<div class="tlid-input-button input-button header-button tlid-input-button-text text-icon" role="tab" tabindex="-1">
  	<div class="text">Текст</div>
</div>
<div class="tlid-input-button input-button header-button tlid-input-button-docs documents-icon" role="tab" tabindex="-1">
 	 <div class="text">Документы</div>
</div>

Несмотря на то что кнопки не представлены в виде тэгов button, присмотревшись к разметке, по css-классу “input-button” можно выделить все кнопки на странице, а по вложенному css-классу “text” можно достать названия кнопок.

Полдела сделано, осталось только записать все, что мы отследили, в тест-кейс.

Мы включаем перехват всех действий на сайте по определенному сочетанию клавиш и только на тестовом контуре. Остановку перехвата всех событий на сайте осуществляем тоже по определенному сочетанию клавиш. Это позволяет начать и остановить автоматическую запись тест-кейса с любого места.

Автоматическое написание е2е-тестов


Если посмотреть на автоматически сгенерированный тест-кейс, то это по сути пользовательские сценарии, приведенные к одному виду. А значит, их можно сконвертировать в е2е-тесты. Можно даже сразу писать е2е-тесты после перехвата всех действий и проверок, минуя тест-кейсы.

Сейчас существует большое количество различных фреймворков с gherkin-нотацией, основанных на поведенческих сценариях: SpecFlow, xBehave.net., Cucumber.js, CodeceptJS и т. д.

Чтобы получить features из тест-кейса, нужно добавить перед действиями ключевую фразу When и перед всеми проверками Then и And.

Получим автоматически сгенерированный е2е-тест:

Feature: Автоматически сгенерированный е2е-тест
  Background:
 	When Авторизуемся "логин" "пароль"

  Scenario:
 	When Нажать на кнопку "Ответил кто-то другой"
 	Then Переход на экран "Кем приходится клиенту"
 	And Появился заголовок "Ответил кто-то другой"
 	When Выбрать в поле "Кем приходится клиенту" "Родственник"
 	When Выбрать в поле "Уточнение" "Супруг или супруга"
 	When Нажать на кнопку "Продолжить"

Чтобы прогон тестов заработал, сгенерированных features мало — нужно написать обработчик для всех действий и проверок.

Есть хорошая новость: писать обработчик для каждой фичи не нужно. Как я уже говорила, несмотря на большое количество различных форм на сайте, у нас получилось всего 30 уникальных действий и проверок, а значит, ровно столько же будет и методов в общем обработчике для всех е2е-тестов. Код будет немного отличаться — в зависимости от выбранного фреймворка с gherkin-нотацией и верстки на сайте. Но написание самого обработчика не займет много времени.

When('Нажать на кнопку {string}', async function (button: string) {
	const xpath = "//button";
	const btn = await getItemByText(xpath, button);
	await waitAndClick(btn);
});
When('Выбрать дату {string} {string}', async function (label: string, text: string) {
	const xpath = "//*[contains(text(),'" + label + "')]/ancestor::outline";
	await inputSendKeys(currentBrowser().element(by.xpath(xpath)), text);
});

Теперь, проверяя очередную задачу, за тестировщика автоматически пишется тест-кейс и прогоняется автоматически сгенерированный е2е-тест.

Если кратко, вам нужно:

  1. Подписаться на события взаимодействия с элементами управления и реакцию сайта на эти действия (через отслеживание перестроения DOM-дерева).
  2. Конвертировать данные из п. 1 в е2е-тесты.
  3. Написать общий обработчик для прогона е2е-тестов.

Этот подход поможет вам уйти от рутины. Вы сможете автоматизировать написание тест-кейсов и е2е-тестов для простых проверок, связанных с интерфейсом. Мы же пошли еще дальше и проверяем автоматически также запись в БД и отправку в сторонние сервисы.

Об этом, а также о версионировании, стеке технологий и даже о проблемах на первом этапе внедрения и их решении я рассказывала на конференции Heisenbug-2019 в Москве.

В этой статье я постаралась передать основную идею, не вдаваясь в подробности.

Заключение


Сейчас на написание тест-кейса и е2е-теста у нас уходит в среднем 2 минуты — это в 60 раз быстрее первоначальных подсчетов, когда мы хотели писать тест-кейсы и е2е-тесты вручную.

Мы не меняли процессы в команде. Больше не нужно было выделять емкость тестирования на написание тест-кейсов и брать в команду автоматизатора.

Мы полностью ушли от понятия регресса. Если раньше, при двухнедельном спринте, регресс у нас занимал больше 3 дней, то сейчас регресс занимает время на прогон всех е2е-тестов, а это всего 2 часа.

При ручном написании е2е-тестов очень сложно идти параллельно с тестированием. Теперь же е2е-тесты пишутся автоматически во время тестирования задачи, и тестировщику не нужно проверять один и тот же функционал дважды.

В результате наша команда, не меняя состав, стала работать намного эффективнее.