Skip to content

Nested steps

Vadzim Hushchanskou edited this page Apr 15, 2022 · 11 revisions

Preconditions

Nested steps are implemented in a way to be suitable for any Report Portal java agent. But they use AspectJ to catch @Step annotation during the test runtime. AspectJ requires to be configured. Here are the most common ways to do this.

Plain Java

If you run your tests in plain java command-line you need to download AspectJ Weaver jar file somewhere to your machine and specify -javaagent:/path/to/aspectjweaver-1.9.8.jar JVM arg. Along with that you should keep Report Portal Agent dependencies somewhere in your classpath. E.G.:

java -javaagent:/path/to/aspectjweaver-1.9.8.jar -jar testng-tests-1.0.0-SNAPSHOT.jar /path/to/SuteXmlFile.xml

Gradle

In gradle it's pretty simple to add AspectJ support to your test. You just need to specify the dependency and add some initialization logic into your test task.

dependencies {
testImplementation 'com.epam.reportportal:agent-java-junit5:5.1.1'
testImplementation 'org.aspectj:aspectjweaver:1.9.2'
}
test {
doFirst {
def weaver = configurations.testRuntimeClasspath.find { it.name.contains("aspectjweaver") }
jvmArgs +="-javaagent:$weaver"
}
}

Maven

In maven required configuration a little lengthy, since you need to specify dependencies and a test plugin:

<?xml version="1.0" encoding="UTF-8"?>
<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.epam.reportportal.example</groupId>
<artifactId>example-steps</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<aspectj.version>1.9.8</aspectj.version>
</properties>
<dependencies>
<dependency>
<groupId>com.epam.reportportal</groupId>
<artifactId>agent-java-junit5</artifactId>
<version>5.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<!-- Required for nested steps -->
<argLine>
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
</argLine>
</configuration>
<dependencies>
<!-- Required for nested steps -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

What is Nested Steps

Nested steps is a common way to group your test logs into small described pieces. Here is how one of our internal test looks like: nested_steps

Let's imagine we have a test for some products ordering flow:

publicclassTest {
@TestvoidorderProductsTest() {
finalIntegerproductCount = 5;
finalDoubleprice = 3.0;
finalDoubletotalPrice = price * productCount;
LOGGER.info("Main page displayed");
OrderingSimulator.logIn();
LOGGER.info("User logged in");
List<String> products = OrderingSimulator.getProducts();
LOGGER.info("Products page opened");
Stringproduct = OrderingSimulator.chooseProduct();
LOGGER.info("Product click event");
LOGGER.info(productCount + " products selected");
OrderingSimulator.addProduct(product, productCount);
LOGGER.info(productCount + " products added to the cart");
Assert.assertEquals(5, productCount);
OrderingSimulator.doPayment(totalPrice);
LOGGER.info("Successful payment");
OrderingSimulator.logOut();
LOGGER.info("User logged out");
}
}

After running this method with our listener we have next results on the Report Portal page: Screen Shot 2022-03-24 at 5 10 59 PM

Pretty much stuff with different logic is included in the one single test method. So we can move different operations to separate methods:

publicclassTest {
@TestvoidorderProductsTest() {
finalIntegerproductCount = 5;
finalDoubleprice = 3.0;
finalDoubletotalPrice = price * productCount;
navigateToMainPage();
login();
navigateToProductsPage();
addProductToCart(productCount);
pay(totalPrice);
logout();
}
publicvoidnavigateToMainPage() {
LOGGER.info("Main page displayed");
}
publicvoidlogin() {
OrderingSimulator.logIn();
LOGGER.info("User logged in");
}
publicvoidnavigateToProductsPage() {
List<String> products = OrderingSimulator.getProducts();
LOGGER.info("Products page opened");
}
publicvoidaddProductToCart(Integercount) {
Stringproduct = clickOnProduct();
selectProductsCount(count);
clickCartButton(product, count);
}
privateStringclickOnProduct() {
LOGGER.info("Product click event");
returnOrderingSimulator.chooseProduct();
}
privatevoidselectProductsCount(Integercount) {
LOGGER.info(count + " products selected");
}
privatevoidclickCartButton(Stringproduct, IntegerproductCount) {
OrderingSimulator.addProduct(product, productCount);
LOGGER.info(productCount + " products added to the cart");
Assert.assertEquals(5, productCount);
}
publicvoidpay(DoubletotalPrice) {
OrderingSimulator.doPayment(totalPrice);
LOGGER.info("Successful payment");
}
publicvoidlogout() {
OrderingSimulator.logOut();
LOGGER.info("User logged out");
}
}

Much better, but result on the Report Portal looks the same. So we grouped our logic by methods, but we cannot see our grouping on the view. That's a problem. And we can solve it using @Step annotation.

In Report Portal Step is a TestItem without statistics that required for splitting large test methods on multiple parts to provide clear and concise view for them. Steps have flexible structure and can be put under other Steps. @Step annotation consists of 4 fields:

public @interface Step {
Stringvalue() default"";
Stringdescription() default"";
booleanisIgnored() defaultfalse;
StepTemplateConfigtemplateConfig() default@StepTemplateConfig;
}

Field value of type String is required for TestItem name creation using static part, templates provided by user and method arguments:

publicclassTest {
//static part - "My name is "//user template - "{number}"//parameter with name 'number' and for example value = 3//result: "My number is 3"@Step("My number is {number}")
publicvoidrandomMethod(Stringnumber) {
// your step logic here
}
}

Field description of type String contains TestItem description value.
Field isIgnored of type boolean allows to enable/disable Step handling for the current method.
Field templateConfig contains @StepTemplateConfig annotation and required for resulted value template configuration.

Templates have two special references: method and this.

  • method - is a reference on the current method name
  • this - is a reference on the object in which context the step was invoked.

It is also possible to refer inner fields with dots (.). E.G. {this.field.innerValue}.

You can check out Step template and step template config guide to understand template building patterns.

So now we can update our test with @Step annotation and get a view that matches with our grouping:

publicclassTest {
@TestvoidorderProductsTest() {
finalIntegerproductCount = 5;
finalDoubleprice = 3.0;
finalDoubletotalPrice = price * productCount;
navigateToMainPage();
login();
navigateToProductsPage();
addProductToCart(productCount);
pay(totalPrice);
logout();
}
@SteppublicvoidnavigateToMainPage() {
LOGGER.info("Main page displayed");
}
@Steppublicvoidlogin() {
OrderingSimulator.logIn();
LOGGER.info("User logged in");
}
@SteppublicvoidnavigateToProductsPage() {
List<String> products = OrderingSimulator.getProducts();
LOGGER.info("Products page opened");
}
@Step("Add {count} products to the cart")
publicvoidaddProductToCart(Integercount) {
Stringproduct = clickOnProduct();
selectProductsCount(count);
clickCartButton(product, count);
}
@StepprivateStringclickOnProduct() {
LOGGER.info("Product click event");
returnOrderingSimulator.chooseProduct();
}
@Step("{method} with {count} products")
privatevoidselectProductsCount(Integercount) {
LOGGER.info(count + " products selected");
}
@Step("{productCount} products added")
privatevoidclickCartButton(Stringproduct, IntegerproductCount) {
OrderingSimulator.addProduct(product, productCount);
LOGGER.info(productCount + " products added to the cart");
Assert.assertEquals(5, productCount.intValue());
}
@Step("Payment step with price = {totalPrice}")
publicvoidpay(DoubletotalPrice) {
OrderingSimulator.doPayment(totalPrice);
LOGGER.info("Successful payment");
}
@Steppublicvoidlogout() {
OrderingSimulator.logOut();
LOGGER.info("User logged out");
}
}

Results on the Report Portal page: Screen Shot 2022-03-24 at 5 18 20 PM

Now we have a view where we can show/hide required steps by clicking on them.

Clone this wiki locally