Меня зовут Лилия, я QA Lead в одном из проектов финансовой группы БКС (сервис по подбору выгодных для клиента предложений из ряда кредитных продуктов), и сегодня я расскажу, как мы автоматизировали смок-тестирование, с какими проблемами столкнулись и какой стек технологий используем.
Сначала мы решили автоматизировать регресс-тестирование, но время шло, функциональность менялась и мы поняли, что довольно много времени тратится на поддержку уже написанных автотестов. Поэтому решили автоматизировать сначала смок-тест, а затем уже расширять его до автоматического проведения регрессионного тестирования. Перед отделом тестирования была поставлена задача в максимально сжатые сроки произвести автоматизацию смок-тестирования, т.к. проект продолжал расти и обрастать дополнительными функциями.
Смок-тестирование, как его еще называют «дымовое тестирование» – быстрая проверка наиболее критичной функциональности.
На нашем проекте:
Автотесты мы пишем на вот таком стеке: Java + Selenium + Cucumber + отчеты в Allure2.
1. Фича файл с расширением .feature с описанием сценариев тестов на языке Gerkin.
Пример:
2. Шаги steps. В нем находятся классы, в которых описаны действия с элементами на странице и проверки этих элементов.
Пример:
3. Работа с локаторами на страницах (паттерн PageObject)
Пример:
4. Отчет в Allure2
Пока мы писали автотесты, у финансовой группы БКС появился Selenoid, и мы смогли настроить запуск тестов в pipline GitLab
У нас есть несколько стендов, на которых и происходит разработка, отладка, приемка, а еще есть очень много feature-стендов, где мы тестируем новые функции, разрабатываемые распределенными командами.
Так же у нас есть несколько стендов веток, которые соответствуют разным средам разработки, при изменении файлов на стенде происходит автоматический запуск соответствующего стенда с автотестами.
Сейчас в нашем проекте при публикации релиза на приемочный стенд за 15 минут в автоматическом режиме выполняется полный набор смок-тестов. В зависимости от полученных результатов команда тестирования принимает решение о приемке релиз-кандидата в тестирование для регрессионного тестирования.
Сначала мы решили автоматизировать регресс-тестирование, но время шло, функциональность менялась и мы поняли, что довольно много времени тратится на поддержку уже написанных автотестов. Поэтому решили автоматизировать сначала смок-тест, а затем уже расширять его до автоматического проведения регрессионного тестирования. Перед отделом тестирования была поставлена задача в максимально сжатые сроки произвести автоматизацию смок-тестирования, т.к. проект продолжал расти и обрастать дополнительными функциями.
Что такое смок-тестирование
Смок-тестирование, как его еще называют «дымовое тестирование» – быстрая проверка наиболее критичной функциональности.
На нашем проекте:
- Регистрация/авторизация.
- Вход.
- Заполнение анкеты.
- Витрина предложений.
- Отправка заявки/переход по ссылке на сайт партнера.
- Обратная связь.
- Блокировка.
Стек технологий для написания автотестов
Автотесты мы пишем на вот таком стеке: Java + Selenium + Cucumber + отчеты в Allure2.
BDD Автотесты для смок-тестирования
1. Фича файл с расширением .feature с описанием сценариев тестов на языке Gerkin.
Пример:
Функция: Проверка профиля, личного кабинета и заявок
@all
Сценарий: Проверка блоков на странице профиля
Дано Перейти на страницу Главная в приложении
Когда Главная: Нажимаю на ссылка Войти
И Вход: Ввожу в поле Телефон значение **********
И Вход: Нажимаю на кнопка Получить код
И Вход: Ввожу в поле Код подтверждения значение ****
И Главная: Навожу курсор на элемент ссылка Меню пользователя
И Главная: Нажимаю на ссылка Профиль
Тогда Происходит переход на страницу Профиль
Тогда Профиль: Отображается элемент заголовок Регистрационные данные
Тогда Профиль: Отображается элемент заголовок Паспортные данные
Тогда Профиль: Отображается элемент заголовок Адрес регистрации
Тогда Профиль: Отображается элемент заголовок Адрес проживания
Тогда Профиль: Отображается элемент заголовок Сведения о трудоустройстве
2. Шаги steps. В нем находятся классы, в которых описаны действия с элементами на странице и проверки этих элементов.
Пример:
@When("^Нажимаю клавишу (.*)")
public void pressKey(String key) {
webElementUtils.pressKey(key);
}
@When("^(.*): Нажимаю на (.*)")
public void press(String pageTitle, String elementName) {
waitUtils.waitElementToBeClickable(getWebElementOnWebPageWithWaiter(elementName, pageTitle)).click();
}
@When("^(.*): Ставлю отметку на (.*)")
public void checkCheckbox(String pageTitle, String elementName) {
WebElement element = getWebElementOnWebPageWithWaiter(elementName, pageTitle);
if (!webElementUtils.isCheckboxSelected(element)) {
element.click();
}
}
@When("^(.*): Снимаю отметку с (.*)")
public void uncheckCheckbox(String pageTitle, String elementName) {
WebElement element = getWebElementOnWebPageWithWaiter(elementName, pageTitle);
if (webElementUtils.isCheckboxSelected(element)) {
element.click();
}
}
@And("^(.*): Стираем значение в (.*)$")
public void erase(String pageTitle, String elementName) {
WebElement element = getWebElementOnWebPageWithWaiter(elementName, pageTitle);
webElementUtils.clearElement(element);
}
@And("^(.*): Ввожу в (.*) значение (.*)$")
public void enterValue(String pageTitle, String elementName, String text) {
WebElement element = getWebElementOnWebPageWithWaiter(elementName, pageTitle);
webElementUtils.fillElementWithText(element, expressionUtils.parseString(text));
}
@And("^(.*): в (.*) выбрать (.*)$")
public void selectValue(String pageTitle, String dropdownListName, String value) {
WebElement element = getWebElementOnWebPageWithWaiter(dropdownListName, pageTitle);
webElementUtils.selectValueFromCombobox(element, value);
}
@Then("^(.*): в (.*) отсутствует текст$")
public void elementDoesNotContainAnyText(String pageTitle, String elementName) {
WebElement element = getWebElementOnWebPageWithWaiter(elementName, pageTitle);
assertEquals("", webElementUtils.getTextFromWebElement(element).trim());
}
@Then("^(.*): ползунок (.*) находится в положении (.*)$")
public void checkSliderPosition(String pageTitle, String elementName, String expectedPosition) {
WebElement element = getWebElementOnWebPageWithWaiter(elementName, pageTitle);
String sliderTrackPosition = StringUtils.substringBetween(element.findElement(By.cssSelector(".rc-slider-track")).getAttribute("style"), "width: ", ";");
String sliderHandlePosition = StringUtils.substringBetween(element.findElement(By.cssSelector(".rc-slider-handle")).getAttribute("style"), "left: ", ";");
assertEquals(expectedPosition, sliderTrackPosition);
assertEquals(expectedPosition, sliderHandlePosition);
}
@Then("^(.*): Отображается компонент (.*)$")
public void checkComponentIsDisplayed(String pageTitle, String component) {
WebElement element = getWebElementOnWebPageWithWaiter(component, component);
assertTrue(webElementUtils.isElementVisible(element));
}
@When("^(.*): Загружаю файл (.+) в (.*)$")
public void loadFileInField(String pageTitle, String fileName, String elementName) {
WebElement element = getWebElementOnWebPage(elementName, pageTitle);
File file = new File(Objects.requireNonNull(getClass().getClassLoader().getResource(fileName)).getFile());
element.sendKeys(file.getAbsolutePath());
}
@Then("^(.+): У элемента (.+) атрибут (.+) имеет значение (.+)$")
public void checkAttributeInElement(String pageTitle, String elementName, String attributeName, String expectedValue) {
WebElement element = getWebElementOnWebPage(elementName, pageTitle);
String attribute = webElementUtils.getAttribute(element, attributeName);
String message = String.format("Атрибут '%s' у элемента '%s' на странице '%s' не соответствует ожидаемому значению.\n" +
"Ожидаемое значение: '%s'.\nФактическое значение: '%s'.\n", attributeName, elementName, pageTitle,
expectedValue, attribute);
assertEquals(message, expectedValue, attribute);
}
@Then("^(.+): У элемента (.+) значение (.+)/$")
public void checkValueTag(String pageTitle, String tagName, String expectedValue) {
WebElement title = webDriver.findElement(By.tagName(tagName));
assertEquals(expectedValue, title.getAttribute("innerHTML").trim());
}
}
3. Работа с локаторами на страницах (паттерн PageObject)
Пример:
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import ru.bcs.creditmarkt.acceptance.pageobject.annotation.PageObject;
import ru.yandex.qatools.htmlelements.annotations.Name;
@PageObject(title = "Вход", path = "/entry/login")
public class LoginPage extends WebPage {
@Name("ссылка Регистрация")
@FindBy(xpath = "//a[text()='Регистрация']")
private WebElement registrationLink;
@Name("ссылка Вход")
@FindBy(xpath = "//a[text()='Вход']")
private WebElement loginLink;
@Name("поле Телефон")
@FindBy(css = "#phone")
private WebElement phoneInput;
@Name("кнопка Получить код")
@FindBy(css = "button[type=submit]")
private WebElement receiveCodeButton;
@Name("поле Код подтверждения")
@FindBy(css = "input#sms")
private WebElement smsInput;
@Name("чекбокс Согласен с условиями обработки персональных данных")
@FindBy(css = "button#personalAgreement")
private WebElement personalAgreementCheckbox;
@Name("иконка Чат-бот")
@FindBy(css = "div.wa-userpic")
private WebElement chatBotIcon;
}
4. Отчет в Allure2
Настройка CI
Пока мы писали автотесты, у финансовой группы БКС появился Selenoid, и мы смогли настроить запуск тестов в pipline GitLab
Организация написания автотестов для разных стендов
У нас есть несколько стендов, на которых и происходит разработка, отладка, приемка, а еще есть очень много feature-стендов, где мы тестируем новые функции, разрабатываемые распределенными командами.
Так же у нас есть несколько стендов веток, которые соответствуют разным средам разработки, при изменении файлов на стенде происходит автоматический запуск соответствующего стенда с автотестами.
Итого
Сейчас в нашем проекте при публикации релиза на приемочный стенд за 15 минут в автоматическом режиме выполняется полный набор смок-тестов. В зависимости от полученных результатов команда тестирования принимает решение о приемке релиз-кандидата в тестирование для регрессионного тестирования.