Skip to content
This repository was archived by the owner on May 21, 2023. It is now read-only.

Repository files navigation

Xteps

This project is archived. New version - https://github.com/evpl/xteps2

High-level contextual steps in your tests for any reporting tool

Maven CentralJavadocLicenseHits-of-CodeLines of code

Xteps is a facade that provides a convenient way to report test steps. Integrations with Allure, Qase, TestIT and ReportPortal are ready, but you can write your own listener for another reporting system or just create an issue.

Table of Contents

How to use

Requires Java 8+ version or Kotlin JVM. Just add suitable dependency.

Java (checked exceptions are not ignored)Java (checked exceptions are ignored) / Kotlin JVM
Allurexteps-allure

Maven CentralJavadoc
unchecked-xteps-allure

Maven CentralJavadoc
Qasexteps-qase

Maven CentralJavadoc
unchecked-xteps-qase

Maven CentralJavadoc
ReportPortalxteps-reportportal

Maven CentralJavadoc
unchecked-xteps-reportportal

Maven CentralJavadoc
TestITxteps-testit

Maven CentralJavadoc
unchecked-xteps-testit

Maven CentralJavadoc
Custom reporting systemxteps

Maven CentralJavadoc
unchecked-xteps

Maven CentralJavadoc

Maven:

<dependency>
<groupId>com.plugatar.xteps</groupId>
<artifactId>{artifact name from table}</artifactId>
<version>5.8</version>
<scope>test</scope>
</dependency>

Gradle:

dependencies {
testImplementation 'com.plugatar.xteps:{artifact name from table}:5.8'
}

Kotlin DSL:

dependencies {
testImplementation("com.plugatar.xteps:{artifact name from table}:5.8")
}

Read more about checked exceptions and integrations.

API

Static methods

First part of Xteps API is a set of step, stepTo and threadHook static methods located in the com.plugatar.xteps.checked.Xteps / com.plugatar.xteps.unchecked.UncheckedXteps class.

classExampleTest {
@TestvoidstaticMethodsExample() {
WebDriverdriver = stepTo("Create WebDriver", () -> {
/* ... */returnnewChromeDriver();
});
threadHook(driver::quit);
step("Login", () -> {
step("Open page", () ->
driver.navigate().to("https://.../login")
);
step("Type login", () ->
driver.findElement(By.id("login_field")).sendKeys("user123")
);
step("Type password", () ->
driver.findElement(By.id("password_field")).sendKeys("1234567890")
);
step("Click on login button", () ->
driver.findElements(By.id("login_button"))
);
});
finalConnectiondbConnection = stepTo("Get database connection", () -> {
/* ... */returngetDatabaseConnection();
});
threadHook(dbConnection::close);
step("Check user full name label", () -> {
finalByusernameLabelLocator = By.id("full_name_label");
newWebDriverWait(driver, Duration.ofSeconds(5)).until(visibilityOfElementLocated(usernameLabelLocator));
finalStringdatabaseFullName = dbConnection.createStatement()
.executeQuery("select full_name from users where username = user123")
.getString("full_name");
finalStringuiFullName = driver.findElement(usernameLabelLocator).getText();
assertEquals(uiFullName, databaseFullName);
});
}
}

Steps chain

Second part is a steps chain starts with stepsChain, stepsChainOf, stepsChainOf(T), stepsChainOf(T1, T2) and stepsChainOf(T1, T2, T3) static method located in the com.plugatar.xteps.checked.Xteps / com.plugatar.xteps.unchecked.UncheckedXteps class.

classExampleTest {
@TestvoidstepsChainMethodExample() {
stepsChain()
.stepToCtx("Create WebDriver", () -> {
/* ... */returnnewChromeDriver();
})
.chainHook(WebDriver::quit)
.nestedSteps("Login", chain -> chain
.step("Open page", driver ->
driver.navigate().to("https://.../login")
)
.step("Type login", driver ->
driver.findElement(By.id("login_field")).sendKeys("user123")
)
.step("Type password", driver ->
driver.findElement(By.id("password_field")).sendKeys("1234567890")
)
.step("Click on login button", driver ->
driver.findElements(By.id("login_button"))
)
)
.stepToCtx("Get database connection", () -> {
/* ... */returngetDatabaseConnection();
})
.chainHook(Connection::close)
.step("Check user full name label", (dbConnection, driver) -> {
finalByusernameLabelLocator = By.id("full_name_label");
newWebDriverWait(driver, Duration.ofSeconds(5)).until(visibilityOfElementLocated(usernameLabelLocator));
finalStringdatabaseFullName = dbConnection.createStatement()
.executeQuery("select full_name from users where username = user123")
.getString("full_name");
finalStringuiFullName = driver.findElement(usernameLabelLocator).getText();
assertEquals(uiFullName, databaseFullName);
})
.callChainHooks();
}
}

Step objects

Third part is a set of steps objects located in the com.plugatar.xteps.checked.stepobject / com.plugatar.xteps.unchecked.stepobject package.

classCreateWebDriverextendsSupplierStep<WebDriver> {
publicCreateWebDriver() {
super("Create WebDriver", () -> {
/* ... */returnnewChromeDriver();
});
}
}
classOpenPageextendsConsumerStep<WebDriver> {
publicOpenPage(Stringurl) {
super("Open page: " + url, driver ->
driver.navigate().to(url)
);
}
}
classTypeLoginextendsConsumerStep<WebDriver> {
publicTypeLogin(Stringlogin) {
super("Type login: " + login, driver ->
driver.findElement(By.id("login_field")).sendKeys(login)
);
}
}
classTypePasswordextendsConsumerStep<WebDriver> {
publicTypePassword(Stringpassword) {
super("Type password: " + password, driver ->
driver.findElement(By.id("password_field")).sendKeys(password)
);
}
}
classClickOnLoginButtonextendsConsumerStep<WebDriver> {
publicClickOnLoginButton() {
super("Click on login button", driver ->
driver.findElements(By.id("login_button"))
);
}
}
classDatabaseConnectionextendsSupplierStep<Connection> {
publicDatabaseConnection() {
super("Get database connection", () -> {
/* ... */returngetDatabaseConnection();
});
}
}
classCheckUserFullNameextendsBiConsumerStep<Connection, WebDriver> {
publicCheckUserFullName(Stringusername) {
super("Check user full name", (dbConnection, driver) -> {
finalByusernameLabelLocator = By.id("full_name_label");
newWebDriverWait(driver, Duration.ofSeconds(5)).until(visibilityOfElementLocated(usernameLabelLocator));
finalStringdatabaseFullName = dbConnection.createStatement()
.executeQuery("select full_name from users where username = " + username)
.getString("full_name");
finalStringuiFullName = driver.findElement(usernameLabelLocator).getText();
assertEquals(uiFullName, databaseFullName);
});
}
}
publicclassExampleTest {
@TestvoidstepObjectsExample() {
stepsChain()
.stepToCtx(newCreateWebDriver())
.chainHook(WebDriver::quit)
.step(newOpenPage("https://.../login"))
.step(newTypeLogin("user123"))
.step(newTypePassword("1234567890"))
.step(newClickOnLoginButton())
.stepToCtx(newDatabaseConnection())
.chainHook(Connection::close)
.step(newCheckUserFullName("user123"))
.callChainHooks();
}
}

Steps can be combined.

classLoginAsextendsConsumerStep<WebDriver> {
publicLoginAs(Stringusername, Stringpassword) {
super("Login as " + username + " / " + password, driver ->
stepsChainOf(driver)
.step(newOpenPage("https://.../login"))
.step(newTypeLogin(username))
.step(newTypePassword(password))
.step(newClickOnLoginButton())
);
}
}

You can use stubs before actually implementing the steps.

@TestvoiddummyStepObjectsExample() {
stepsChain()
.stepToCtx(SupplierStep.<WebDriver>dummy("Create WebDriver"))
.chainHook(WebDriver::quit)
.step(RunnableStep.dummy("Open page https://.../login"))
.step(RunnableStep.dummy("Type login user123"))
.step(RunnableStep.dummy("Type password 1234567890"))
.step(RunnableStep.dummy("Click on login button"))
.stepToCtx(SupplierStep.<Connection>dummy("Get database connection"))
.chainHook(Connection::close)
.step(RunnableStep.dummy("Check user full name"))
.callChainHooks();
}

BDD style example.

classOpenPageextendsConsumerStep<WebDriver> {
publicOpenPage(Stringurl) {
super("I open page " + url, driver ->
driver.navigate().to(url)
);
}
}
/*... */publicclassExampleTest {
@TestvoidbddStyleExample() {
stepsChain()
.withCtx(() -> {
/* ... */returnnewChromeDriver();
})
.chainHook(WebDriver::quit)
.step("When", newOpenPage("https://.../login"))
.step("And", newTypeLogin("user123"))
.step("And", newTypePassword("1234567890"))
.step("And", newClickOnLoginButton())
.step("Then", newUserFullNameIs("Expected Name"))
.callChainHooks();
}
}

Parameters

There are two ways to load parameters. Be aware that higher source override lower one - properties from file can be overridden by system properties.

PrioritySource
1System properties
2Properties file (xteps.properties)

Properties list

NameTypeRequiredDefault valueDescription
xteps.enabledBooleanNotrueEnable/disable steps logging.
xteps.spiBooleanNotrueEnable/disable Service Provider Interface mechanism to detect and instantiate com.plugatar.xteps.base.StepListener implementations. Implementations should have zero-argument public constructor.
xteps.listenersStringNoList of com.plugatar.xteps.base.StepListener implementations names in Class#getTypeName() format. Names should be separated by ,. Implementations should have zero-argument public constructor.
xteps.cleanStackTraceBooleanNotrueRemoves all stack trace lines about Xteps from any exception except XtepsException.
xteps.defaultHooksOrderEnumNoFROM_LASTThe order in which chain and thread hooks of the same priority will be called - FROM_FIRST / FROM_LAST.
xteps.threadHooksThreadIntervalLongNo100Interval between thread hooks daemon thread executions in milliseconds.
xteps.threadHooksThreadPriorityIntegerNo5Thread hooks daemon thread priority in the range 1 to 10.

Examples

xteps.properties file example:

xteps.enabled=true
xteps.spi=true
xteps.listeners=com.my.prj.StepListenerImpl1,com.my.prj.StepListenerImpl2
xteps.cleanStackTrace=true
xteps.defaultHooksOrder=FROM_LAST
xteps.threadHooksThreadInterval=100
xteps.threadHooksThreadPriority=5

Additional features

Steps chain hooks

You can use hooks in a steps chain. Hooks will be called in case of any exception in steps chain or in case of callChainHooks method call. You also can use hooks to release resources.

stepsChain().withCtx(newAutoCloseableImpl())
.chainHook(AutoCloseable::close)
.step("Step", ctx -> {
//...
})
.callChainHooks();

Thread hooks

You can use thread hooks. Hooks will be called after current thread is finished. You also can use hooks to release resources.

stepsChain().withCtx(newAutoCloseableImpl())
.threadHook(AutoCloseable::close)
.step("Step", ctx -> {
//...
});

Clean stack trace

cleanStackTrace option is enabled by default. It allows to clear the stack trace from Xteps calls.

finalclassExampleTest {
@Testvoidtest() {
step("Step", () -> {
//...step("Nested step", () -> {
//...thrownewRuntimeException("Nested step exception");
});
});
}
}

Stacktrace will look like this.

java.lang.RuntimeException: Nested step exception
at my.project.ExampleTest.lambda$null$0(ExampleTest.java:15)
at my.project.ExampleTest.lambda$test$1(ExampleTest.java:13)
at my.project.ExampleTest.test(ExampleTest.java:11)
...

Checked exceptions

Xteps is tolerant to checked exceptions. Each method allows you to throw generic exception, the type of this exception is defined by the step body.

@Testvoidtest() throwsMalformedURLException {
step("Step", () -> {
//...step("Nested step", () -> {
newURL("abc");
});
});
}

You can choose a mechanism to cheat checked exceptions if you want to hide them. Xteps give you the built-in static methods by com.plugatar.xteps.base functional interfaces.

@Testvoidtest() {
step("Step", () -> {
//...step("Nested step", ThrowingRunnable.unchecked(() -> {
newURL("abc");
}));
});
}

Unchecked Xteps just hides any exceptions.

@Testvoidtest() {
step("Step", () -> {
//...step("Nested step", () -> {
newURL("abc");
});
});
}

Integrations

Set up your project with Allure / Qase / ReportPortal and then just add suitable Xteps dependency.

You can use step name or step description replacements by the way provided by Allure / Qase / TestIT / ReportPortal.

stepsChain()
.withCtx("value")
.withCtx(111)
.step("Step with context = {0} and second context = {1}", (integer, string) -> {
//...
});

This step will be reported with name "Step with context = 111 and second context = value".

You can also use utility methods for Allure, Qase and TestIT - AllureStepUtils, QaseStepUtils, TestITStepUtils. It allows you to change the step name and other step attributes at runtime.

JDK 8 unreported exception bug

You may run into a problem if you use Xteps and JDK 8. The issue is caused by generic exceptions.

stepsChain()
.nestedStepsTo("Step", chain -> chain
.step("Nested step", () -> {})
);

This code can fail build with this exception java: unreported exception java.lang.Throwable; must be caught or declared to be thrown.

You can switch to JDK 9+ or switch to Unchecked Xteps or use functional interfaces static methods to hide any exceptions.

stepsChain()
.nestedStepsTo("Step", ThrowingFunction.unchecked(chain -> chain
.step("Nested step", () -> {})
));