Creating Tasks in Serenity Screenplay
John Ferguson Smart
I Help Manual Testers Become World-Class Test Automation Engineers. Agile Test Automation and BDD Expert | International Speaker And Author | Coach, Trainer and Mentor
Serenity Screenplay helps us write cleaner, more elegant and more maintainable code, and Serenity 2.0.13 has added some great improvements in this area. Read on to find out more!
One of the most powerful aspects of Serenity Screenplay is the way it documents user interactions with the system in a clean and readable way, both within the code, and in the Serenity reports. For example, the following test code is both readable and reportable:
@Test
public void the_fruit_should_be_norrishing() {
Actor freddy = Actor.named("Freddy").describedAs("A fruit lover");
givenThat(freddy).wasAbleTo(Peel.anApple());
when(freddy).attemptsTo(Eat.anApple(),
Eat.aLargePear());
then(freddy).should(seeThat("Freddy is not hungry",isNotHungry());
}
In the Serenity reports, each of these steps will appear:
In Serenity Screenplay, we usually want a task to appear in a report. They correspond to a logical business action or unit of user behaviour, and we want to see them as an integral part of our living documentation. But sometimes you don’t. In this article, we will look at some of the different ways you can create new tasks, and how this affects whether they will appear in your reports.
Creating tasks with simple constructors
Serenity uses instrumentation to report on task activity. Any instrumented task will always appear in the reports. You can instrument a class explicity, using the Tasks.instrumented(). So if we have a class like the following:
public class EatsAnApple implements Performable {
@Override
public <T extends Actor> void performAs(T actor) {...}
}
Then you can create this class using the instrumented() method like this:
Using builders and factory classes
More complex tasks may have parameters to fine-tune their behavior. For example, in the following task, we refine the reporting to include the size of the fruit:
The ofSize() method creates an instrumented instance of the EatsAPear class, with the right size value. We could write a Factory class to build a more fluent DSL like the one we saw at the start of the article:
Performable eatsAnApple = Tasks.instrumented(EatsAnApple.class);
freddy.attemptsTo(eatAnApple);
But with Serenity 2.0.13, this code can be simplified. If we add a default constructor to our performable class, we can avoid having to instrumente the class at all:
class EatsAPear implements Performable {
private final String size;
public EatsAPear(String size) {
this.size = size;
}
@Override
@Step("{0} eats a #size pear")
public void performAs(T actor) {}
static EatsAPear ofSize(String size) {
return instrumented(EatsAPear.class, size);
}
}
Now we can use the parameterised constructors directly in our factory class, like this:
public static class Eat {
public static Performable anApple() {
return new EatsAnApple();
}
public static Performable aLargePear() {
return EatsAPear.ofSize("large");
}
public static Performable aSmallPear() {
return EatsAPear.ofSize("small");
}
}
Silent Tasks
But sometimes, you might want to not have a task appear in a report, but still use a Screenplay approach. For example, you may want a task that appears when it is called in the middle of a test, but not when the same task is called when preparing the test data.
To achieve this, we make our task implement the CanBeSilent interface. This interface has a single method, isSilent(), which determines whether the task should appear in the report:
class EatsAPear implements Performable {
private String size;
// Needed by Serenity
public EatsAPear() {}
public EatsAPear(String size) {
this.size = size;
}
@Override
@Step("{0} eats a #size pear")
public void performAs(T actor) {}
static EatsAPear ofSize(String size) {
return instrumented(EatsAPear.class, size);
}
}
So if our test had the following code:
Actor.named("Annie").attemptsTo(EatsAWatermelon.quietly(), EatsAWatermelon.noisily());
Then only “Annie eats a watermelon loudly” would appear in the reports.
If you have a task class that you never want instrumented (for example, one that sets up some background test data), just add the IsSilent interface – this is a marker interface with no methods, so adding it to your class declaration is enough:
class EatsAWatermelon implements Performable, IsSilent {
...
}
Conclusion
Serenity Screenplay gives you a great deal of flexibility in how you write your Performable classes, and whether you want them to appear in the report or not. And in Serenity 2.0.13, you no longer need to explicitly instrument Screenplay tasks.
We cover this topic in more detail in the latest module of the Serenity Dojo Screenplaycourse, part of the Serenity Dojo Online Curriculum that you can find here. If you haven’t seen the Screenplay course yet, you can check it out here – the first module is free!
Quality Engineering | Test Architect | Lead SDET | Accessibility Specialist. "I've never heard of you" ~ A world-famous Agile coach.
2 个月I was digging in to Serenity/Screenplay over last year, and your name was always popping up on searches - stretching back years. ?? Thanks for the guides! I did start to use screenplay pattern a couple of years back. Especially after seeing a succession of companies with test engineers who seemed to spend most of the time maintaining test code, more than writing new test code! A lot of that was also down to sheer abuse and bizarre application, of the pagepobjects way. But once I got my head round it, screenplay felt so much more logical for coidng tests.