Как перестать писать @Step аннотации для Allure
Привет! В этой заметке хочу поделиться лайфхаком, который позволит перестать ставить аннотации @Step
в коде тестов.
Давайте сначала обрисуем суть проблемы. При построении проектов автоматизции нам зачастую приходится прикручивать какие-то логеры или репортеры. Для меня репортером по умолчанию является Allure. С его помощью можно генерировать достаточно информативные и понятные отчеты. Но есть у него один небольшой недостаток - если мы хотим логировать шаги теста, то над методами нужно ставить аннотацию @Step.
Пример:
class LoginPage{
private SelenideElement email = $("#email");
private SelenideElement password = $("#password")
private SelenideElement submitBtn = $(".btnLogin")
@Step
public void loginAs(String name, String password){
email.setValue(name)
password.setValue(password)
submitBtn.click()
}
}
Теперь метод loginAs
будет отображаться в отчете, так как мы указали над ним аннотацию @Step
. Все бы ничего, да вот когда в классе страницы
не один метод, а 5 или 10, уже становится не так радостно расставлять эти аннотации. К тому же, бывает, пишешь тест, описываешь поведение
страниц, запускаешь тесты, а потом "Аx, я же забыл поставить аннотации для аллюра". А когда в команде 2-3 человека, приходится следить за этими аннотациями
в PR, что никак не радует. В один прекрасный день я подумал: можно ведь как-то сделать без аннотаций?..
Оказалось, что можно. Allure в своей работе использует Aspectj, который мы и можем попробовать хакнуть и использовать для своих целей.
Пробуем написать свой класс аспектов:
/**
* Created by sergey on 05.06.17.
*/
@SuppressWarnings("unused")
@Aspect
public class CustomAspect {
private static Allure ALLURE = Allure.LIFECYCLE;
@Pointcut("execution(* com.automation.remarks.video.service.pages.*.*(..))")
public void anyMethod() {
//pointcut body, should be empty
}
@Pointcut("execution(* com.codeborne.selenide.SelenideElement.should*(..))")
public void selenide() {
//pointcut body, should be empty
}
@Before("anyMethod() || selenide()")
public void stepStart(JoinPoint joinPoint) {
String stepTitle = createTitle(joinPoint);
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
StepStartedEvent startedEvent = new StepStartedEvent(
getName(methodSignature.getName(), joinPoint.getArgs())
);
if (!stepTitle.isEmpty()) {
startedEvent.setTitle(stepTitle);
}
ALLURE.fire(startedEvent);
}
@AfterThrowing(pointcut = "anyMethod() || selenide()", throwing = "e")
public void stepFailed(JoinPoint joinPoint, Throwable e) {
ALLURE.fire(new StepFailureEvent().withThrowable(e));
ALLURE.fire(new StepFinishedEvent());
}
@AfterReturning(pointcut = "anyMethod() || selenide()", returning = "result")
public void stepStop(JoinPoint joinPoint, Object result) {
ALLURE.fire(new StepFinishedEvent());
}
public String createTitle(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Step step = methodSignature.getMethod().getAnnotation(Step.class);
return step == null ? "" : getTitle(step.value(), methodSignature.getName(), joinPoint.getThis(), joinPoint.getArgs());
}
/**
* For tests only
*/
static void setAllure(Allure allure) {
CustomAspect.ALLURE = allure;
}
}
Наверное, для неподготовленного читателя выглядит очень "вырвиглазно". Что я вообще написал? AspectJ оперирует понятиями Pointcut, которые я и объявил в самом начале класса.
@Pointcut("execution(* public com.automation.remarks.video.service.pages.*.*(..))")
public void anyMethod() {
//pointcut body, should be empty
}
@Pointcut("execution(* public com.codeborne.selenide.SelenideElement.should*(..))")
public void selenide() {
//pointcut body, should be empty
}
Говоря проще, я написал селекторы, с помощью которых указал AspectJ учитывать только публичные методы из пакета public com.automation.remarks.video.service.pages
и
методы Selenide, которые начинаются со слова should
.
Далее я объявил условия @Before, @After, @AfterThrowing и @AfterReturning. В @Before мы извлекаем имя метода и его параметры, а в @After либо завершаем шаг успешно, либо маркаем, как неуспешный, и прикрепляем к нему текст ошибки. Все достаточно просто.
Далее, чтобы это все заработало, нам нужно в папке src/main/resources/META-INF создать файлик под названием aop-ajc.xml:
<aspectj>
<aspects>
<aspect name="com.automation.remarks.video.service.pages.utils.CustomAspect"/>
</aspects>
</aspectj>
По факту мы в этом файле просто подключаем наш новый аспект. Теперь можно просто запускать тесты и смотреть на результат точно так же, как мы делали это раньше. Таким образом мы избавились от аннотаций в Page объектах и значительно упростили себе жизнь. В целом эта идея работает и имеет право на жизнь. Вы можете ее либо переиспользовать, либо развить и поделиться решением со всем миром автоматизации.
P/S Конечно, в таком подходе есть ряд недостатков. Первый: мы жестко завязались на жизненный цикл аллюра. Код, приведенный выше, работает только
с первой версией, для второй версии нужно будет переписать вызовы ALLURE.fire(new StepFailureEvent().withThrowable(e));
.
Второе: дебажить аспекты практически невозможно (по крайней мере, я не нашел толковых примеров). Есть только упоминания, что вот в Eclipse как-то можно.
Из-за этого код приходится писать почти вслепую. И третье: с Котлином эта тема не работает, так как сам AspectJ нормально не поддерживает Котлин.