Skip to content

Nested steps

Vadzim Hushchanskou edited this page Mar 24, 2022 · 11 revisions

Nested steps in java agents

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"?>
<project xmlns="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>

    <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>1.9.8</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

What's that

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:

public class Test {
	@Test
	void orderProductsTest() {

		final Integer productCount = 5;
		final Double price = 3.0;
		final Double totalPrice = price * productCount;

		LOGGER.info("Main page displayed");

		OrderingSimulator.logIn();
		LOGGER.info("User logged in");

		List<String> products = OrderingSimulator.getProducts();
		LOGGER.info("Products page opened");

		String product = 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:

public class Test {
	@Test
	void orderProductsTest() {

		final Integer productCount = 5;
		final Double price = 3.0;
		final Double totalPrice = price * productCount;

		navigateToMainPage();
		login();
		navigateToProductsPage();
		addProductToCart(productCount);
		pay(totalPrice);
		logout();
	}

	public void navigateToMainPage() {
		LOGGER.info("Main page displayed");
	}

	public void login() {
		OrderingSimulator.logIn();
		LOGGER.info("User logged in");
	}

	public void navigateToProductsPage() {
		List<String> products = OrderingSimulator.getProducts();
		LOGGER.info("Products page opened");
	}

	public void addProductToCart(Integer count) {
		String product = clickOnProduct();
		selectProductsCount(count);
		clickCartButton(product, count);
	}

	private String clickOnProduct() {
		LOGGER.info("Product click event");
		return OrderingSimulator.chooseProduct();
	}

	private void selectProductsCount(Integer count) {
		LOGGER.info(count + " products selected");
	}

	private void clickCartButton(String product, Integer productCount) {
		OrderingSimulator.addProduct(product, productCount);
		LOGGER.info(productCount + " products added to the cart");
		Assert.assertEquals(5, productCount);
	}

	public void pay(Double totalPrice) {
		OrderingSimulator.doPayment(totalPrice);
		LOGGER.info("Successful payment");
	}

	public void logout() {
		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 {
	String value() default "";
	String description() default "";
	boolean isIgnored() default false;
	StepTemplateConfig templateConfig() default @StepTemplateConfig;
}

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

public class Test {
	//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}")
	public void randomMethod(String number) {
         // 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.

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:

public class Test {
	@Test
	void orderProductsTest() {

		final Integer productCount = 5;
		final Double price = 3.0;
		final Double totalPrice = price * productCount;

		navigateToMainPage();
		login();
		navigateToProductsPage();
		addProductToCart(productCount);
		pay(totalPrice);
		logout();
	}

	@Step
	public void navigateToMainPage() {
		LOGGER.info("Main page displayed");
	}

	@Step
	public void login() {
		OrderingSimulator.logIn();
		LOGGER.info("User logged in");
	}

	@Step
	public void navigateToProductsPage() {
		List<String> products = OrderingSimulator.getProducts();
		LOGGER.info("Products page opened");
	}

	@Step("Add {count} products to the cart")
	public void addProductToCart(Integer count) {
		String product = clickOnProduct();
		selectProductsCount(count);
		clickCartButton(product, count);
	}

	@Step
	private String clickOnProduct() {
		LOGGER.info("Product click event");
		return OrderingSimulator.chooseProduct();
	}

	@Step("{method} with {count} products")
	private void selectProductsCount(Integer count) {
		LOGGER.info(count + " products selected");
	}

	@Step("{productCount} products added")
	private void clickCartButton(String product, Integer productCount) {
		OrderingSimulator.addProduct(product, productCount);
		LOGGER.info(productCount + " products added to the cart");
		Assert.assertEquals(5, productCount.intValue());
	}

	@Step("Payment step with price = {totalPrice}")
	public void pay(Double totalPrice) {
		OrderingSimulator.doPayment(totalPrice);
		LOGGER.info("Successful payment");
	}

	@Step
	public void logout() {
		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.