What is Serenity Screenplay?
Screenplay is a test automation design pattern used to write readable, maintainable tests; it is part of Serenity BDD. Screenplay uses a model based on the Actors. Every Screenplay test has at least one actor (you can also have multiple actors). You can model scenarios involving multiple users, each with specific Abilities and Interactions. Besides Actors, other important components of Screenplay are Tasks, Interactions, Questions, and Abilities. Tasks and Actions are reusable across different test scenarios, helping minimize code duplication.
To use Screenplay, you will have to add these dependencies into pom.xml file:
<dependency> <groupId>net.serenity-bdd</groupId> <artifactId>serenity-screenplay</artifactId> <version>4.2.34</version> </dependency> <dependency> <groupId>net.serenity-bdd</groupId> <artifactId>serenity-screenplay-webdriver</artifactId> <version>4.2.34</version> </dependency>
Actors
An actor represents a user who interacts with the application. Actors have abilities, such as the ability to interact with a web browser. They also perform tasks, interactions, and answer questions to check whether the feature behaves as we expect.
This is how we define an actor and give the actor the ability to interact with a web browser using Selenium WebDriver:
final Actor actor = new Actor("User").can(BrowseTheWeb.with(getDriver()));
When writing code with Selenium, it can get a little messy; with a lot of code, it can become hard to read or maintain. For example, it can take a lot of time to figure out the best way to interact with a specific button on a page. With Screenplay, we use actors to perform interaction:
Selenium example:
getDriver().get("https://testingpage.com/login");
final WebElement username = getDriver().findElement(By.id("username"));
username.sendKeys("John-Doe");
final WebElement password = getDriver().findElement(By.id("password"));
password.sendKeys("pass123");
final WebElement loginButton = getDriver().findElement(By.id("login"));
loginButton.click();
In this example, we can see:
- Selenium focuses on how you find an element and click on it or type into the field.
- Code repeats.
Screenplay example:
actor.attemptsTo(
Open.url("https://testingpage.com/login"),
SendKeys.of("John-Doe").into(By.id("username")),
SendKeys.of("pass123").into(By.id("password")),
Click.on(Button.withText("Login"))
);
OR
You can create a separate CommonFields class where you will store common fields:
public static final Target USERNAME = Target.the("Username field").located(By.id("username"));
public static final Target PASSWORD = Target.the("Password field").located(By.id("password"));
public static final Target LOGIN_BUTTON = Target.the("Login button").located(By.id("login"));
And then use it in a test:
actor.attemptsTo(
Open.url("https://examplepage.com/login"),
SendKeys.of("John-Doe").into(CommonFields.USERNAME),
SendKeys.of("pass123").into(CommonFields.PASSWORD),
Click.on(CommonFields.LOGIN_BUTTON)
);
In this example, an actor performs a sequence of actions, which describes what the user does, not how it is done. It is readable, reusable, and maintainable. If an element changes, you only need to update it in one place.
Tasks
A task is a high-level action that an actor performs. It consists of smaller interactions like clicking, typing, etc.
We can additionally create a task for login:
public class LoginTask implements Task {
private final String username;
private final String password;
public LoginTask(final String username, final String password) {
this.username = username;
this.password = password;
}
public static LoginTask usingData(final String username, final String password) {
return new LoginTask(username, password);
}
@Override
public <T extends Actor> void performAs(T actor) {
actor.attemptsTo(
SendKeys.of(username).into(CommonFields.USERNAME),
SendKeys.of(password).into(CommonFields.PASSWORD),
Click.on(CommonFields.LOGIN_BUTTON)
);
}
}
And then use it in test:
actor.attemptsTo(
Open.url("https://examplepage.com/login"),
LoginTask.usingData("John-Doe", "pass123")
);
In previous examples, we saw how to click on some element or type into the field. Next, we will see more basic interactions:
Move mouse
Move the mouse on a specific element (for example, you can use it to trigger a tooltip):
actor.attemptsTo(MoveMouse.to(CommonFields.LOGIN_BUTTON));
Double click
actor.attemptsTo(DoubleClick.on(target));
Right click
actor.attemptsTo(RightClick.on(target));
Enter value
This will enter a value into a field, first it will wait until the field is enabled, and then clear the field of any current values, before entering the specified value:
actor.attemptsTo(Enter.theValue("John-Doe").into(CommonFields.USERNAME));
Upload file
Files can be placed in a hard-coded location or stored on the classpath, which is a better solution:
actor.attemptsTo(Upload.theFile(Path.of("test/data/api/files/" + fileName +".extension")).to(uploadField));
Questions
When an actor wants to get a specific piece of information from the system, it can be done using a Question. Questions are queries about the application’s state. Actors ask questions using the method askFor().
Some of the common Question classes are:
Text.of()
final String text = actor.asksFor(Text.of(TARGET_ELEMENT));
This will extract the text of a target element and return the value that you can use for assertion.
When working with table, we can extract data from cells:
final List<String> tableHeaders = actor.asksFor(Text.ofEach(HEADERS)); final List<List<String>> tableRows = actor.asksFor(Text.ofEach(ROWS));
isVisible()
boolean isVisible = actor.asksFor(Visibility.of(TARGET_ELEMENT));
This checks whether a UI element is displayed.
isDisabled()
boolean isDisabled = actor.asksFor(Disabled.of(TARGET_ELEMENT));
This will check whether an element is disabled.
Presence.of()
boolean isPresent = actor.asksFor(Presence.of(TARGET_ELEMENT));
This checks whether the element is present in the DOM.
Conclusion
With Serenity Screenplay, you can write clean and maintainable automation tests using Actors, Abilities, Tasks, and Questions. Instead of just writing a bunch of code with Screenplay, your automation code will be easy to read and understand for both QA and developers. It is also important to know that Screenplay doesn’t replace Selenium; they work together. Selenium is the engine that communicates with the browser, and Screenplay is the pattern that makes your tests feel like a story.