Screenplay Pattern approach using Selenium and Java

Run Screenplay tests in BrowserStack Real Device Cloud with this step-by-step guide.

Get Started free
Guide Banner Image
Home Guide Screenplay Pattern approach using Selenium and Java

Screenplay Pattern approach using Selenium and Java

The Screenplay Pattern is an advanced approach to writing automated tests focusing on clear, business-driven workflows. Unlike traditional testing models, it emphasizes roles, tasks, and outcomes, making tests more understandable and maintainable.

By implementing the Screenplay Pattern with Selenium and Java, you can improve the structure of your tests, making them more flexible and aligned with real-world user interactions.

This article will explore how to effectively implement the Screenplay Pattern using Selenium and integrate it with BrowserStack to run tests across real devices, ensuring comprehensive coverage and reliable results.

What is Screenplay Pattern?

The Screenplay Pattern is a user-centric approach to writing workflow-level automated acceptance tests. This helps automation testers to write test cases in terms of Business language.

Building blocks in Screenplay Pattern

To understand the building blocks better, let’s take an example

  1. Demouser login into the Application by entering their username and password and clicking on the Sign in button
  2. Demouser will be taken into the Homepage
  3. Demouser should see his/her name in the profile section

In the above example

  1. Demouser is the Actor
  2. Filling the username and password, then clicking on the button is the Task

Checking if the profile section is displaying the name of demouser is the Question

History of the Screenplay Pattern

The Screenplay Pattern, introduced to JavaScript in 2016 by Serenity/JS, has roots dating back to 2007, evolving through key contributions:

  • 2007-2008: Antony Marcano and Kevin Lawrence develop the Roles, Goals, Tasks, Actions model during the AAFTT workshop, inspiring early implementations like JNarrate (Java).
  • 2011-2013: Screenplay emerges in Ruby (Cuke Salad) and Java (Screenplay4j, ScreenplayJVM) with blog posts and talks formalizing its concepts.
  • 2015-2016: Serenity BDD integrates Screenplay for Java, popularizing it. Key publications and blog posts clarify its principles, and Serenity/JS introduces the pattern to JavaScript.
  • 2017-2021: Variants appear in Python (ScreenPy) and .NET (Boa Constrictor), and books like BDD in Action and a blog series further refine their application in testing frameworks.

What is Page Object Model?

The Page Object Model (POM) is a design pattern in test automation where a dedicated class represents each page of an application. This class acts as an interface, encapsulating the page’s elements and interactions. Test scripts then interact with the UI via the methods defined in these classes, promoting code reuse and readability.

The POM was introduced as testing tools like Selenium and WebDriver gained popularity, often among teams with varying levels of programming expertise.

Key Features:

  • Each application page has a corresponding class.
  • Classes contain methods representing actions that can be performed on-page components.
  • As the complexity of a page grows, the class methods also expand.

Challenges:

While POM effectively models page components, it does not inherently align with user roles, actions, and outcomes, essential for capturing business logic and user journeys. Tools like JUnit and TestNG may struggle to express these workflows in terms of business representation clearly.

Elements of the Screenplay Pattern

The Screenplay Pattern models test scenarios using a metaphor inspired by stage performances, framing tests as scripts that describe how actors interact with the system under test to achieve their objectives.

This approach helps define:

  • Who is interacting with the system?
  • Why is the interaction necessary?
  • What actions are required to achieve their goals?
  • How are those actions executed?

Core Components of the Screenplay Pattern:

  1. Actors: Represent users or external systems interacting with the application under test.
  2. Abilities: Serve as lightweight wrappers around integration libraries that interact with the system.
  3. Interactions: Represent individual, low-level actions that actors can perform via an interface.
  4. Tasks: Combine sequences of interactions into meaningful business workflow steps.
  5. Questions: Allow retrieval of information from the application and testing environment.

Screenplay Pattern Implementation in Selenium

To start implementing Screenplay Pattern, you can use Serenity BDD framework which has inbuilt integration to write our tests in the Screenplay pattern.

Note: Get all the required Maven dependencies as a pre-requisite.

To write Tests, Tasks, and Questions considering Browserstack’s demo application. The structure of the below classes can be seen below.

Structure of Classes in Screenplay Pattern Example

Here are the steps to implement the Screenplay Pattern approach in Selenium Java:

Step 1: Create PageObject in a refactored and effective manner

Login Page:

import net.serenitybdd.screenplay.targets.Target;
import net.thucydides.core.pages.PageObject;

public class BStackLoginPage extends PageObject {

public static final Target USERNAME = Target.the("Username")
.locatedBy("#username input");

public static final Target PASSWORD = Target.the("Password")
.locatedBy("#password input");

public static final Target LOGIN_BTN = Target.the("Login Button")
.locatedBy("#login-btn");
}

Dashboard Page:

import net.serenitybdd.screenplay.targets.Target;
import net.thucydides.core.pages.PageObject;

public class BstackDashboardPage extends PageObject {

public static final Target SIGNOUT = Target.the("sign out")
.locatedBy(".username");
}

Step 2: Create Tasks for the above created PageObjects

Task to access the webpage

import com.ui.screenplay.pageobject.BStackLoginPage;
import net.serenitybdd.screenplay.Actor;
import net.serenitybdd.screenplay.Task;
import net.serenitybdd.screenplay.actions.Open;
import net.thucydides.core.annotations.Step;

import static net.serenitybdd.screenplay.Tasks.instrumented;

public class AccessWebPage implements Task {
public static AccessWebPage loginPage() {
return instrumented(AccessWebPage.class);
}

BStackLoginPage loginPage;

@Step("{0} access Login page")
public <T extends Actor> void performAs(T t) {
t.attemptsTo(Open.browserOn().the(loginPage));
}
}

Task to Login to the application

import com.ui.screenplay.pageobject.BStackLoginPage;
import net.serenitybdd.core.steps.Instrumented;
import net.serenitybdd.screenplay.Actor;
import net.serenitybdd.screenplay.Task;
import net.serenitybdd.screenplay.actions.Click;
import net.serenitybdd.screenplay.actions.Enter;
import net.thucydides.core.annotations.Step;
import org.openqa.selenium.Keys;

public class LoginToBstack implements Task {

@Step("{0} enter username and password '#username' '#password")
public <T extends Actor> void performAs(T actor)
{
actor.attemptsTo(Enter.theValue(username).into(BStackLoginPage.USERNAME).thenHit(Keys.TAB));
actor.attemptsTo(Enter.theValue(password).into(BStackLoginPage.PASSWORD).thenHit(Keys.TAB));
actor.attemptsTo(Click.on(BStackLoginPage.LOGIN_BTN));
}

private String username;
private String password;

public LoginToBstack(String username, String password) {
this.username = username;
this.password = password;
}

public static Task withCredentials(String username, String password) {
return Instrumented
.instanceOf(LoginToBstack.class)
.withProperties(username, password);
}
}

Step 3: Create a Question to fetch user information from the Profile section of Homepage

import com.ui.screenplay.pageobject.BstackDashboardPage;
import net.serenitybdd.screenplay.Actor;
import net.serenitybdd.screenplay.Question;
import net.serenitybdd.screenplay.questions.Text;

public class Dashboard implements Question<String> {

public static Question<String> displayed() {
return new Dashboard();
}

public String answeredBy(Actor actor) {
return Text.of(BstackDashboardPage.SIGNOUT).answeredBy(actor);
}
}

Step 4: Create a test

import com.ui.screenplay.questions.Dashboard;
import com.ui.screenplay.tasks.LoginToBstack;
import net.serenitybdd.junit.runners.SerenityRunner;
import net.serenitybdd.screenplay.Actor;
import static net.serenitybdd.screenplay.GivenWhenThen.*;
import net.serenitybdd.screenplay.abilities.BrowseTheWeb;
import net.serenitybdd.screenplay.actions.Open;
import net.thucydides.core.annotations.Managed;
import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;

@RunWith(SerenityRunner.class)
public class ScreenPlayTest {

private Actor demoUser = Actor.named("Demo User");

@Managed
private WebDriver hisBrowser;

@Before
public void demoUserCanBrowseTheWeb(){
demoUser.can(BrowseTheWeb.with(hisBrowser));
}

@Test
public void browseTheWebAsDemoUser(){
demoUser.attemptsTo(Open.url("https://bstackdemo.com/signin"));
givenThat(demoUser).attemptsTo(LoginToBstack.withCredentials("demouser", "testingisfun99"));
then(demoUser).should(seeThat(Dashboard.displayed(), CoreMatchers.equalTo("demouser1")));
}

}

Step 5: Once you run the above test, the report will be generated in the path target > site > serenity > index.html

Serenity HTML Report

Screenplay with Serenity BDD not just provides better maintainability and readability of code but also produces an extensive HTML Report as seen above.

Running Screenplay tests in BrowserStack Real Device Cloud

To get the optimum benefits of the test automation suite, the key metrics are:

  • Stable Tests
  • Test Execution Time
  • Test Coverage: Running the tests across multiple browsers and devices

It’s not realistic for any organization to run the tests across all the browsers and devices to ensure maximum test coverage. Building an in-house infrastructure means more cost to set up and maintain the infrastructure. With BrowserStack’s real device cloud, it’s now very easy to set up your tests with minimum changes to existing code, and just by making a few configurations, you can run your tests against 3000+ device and browser combinations under real user conditions.

Test on Real Devices & Browsers

In order to run the above Screenplay test against the BrowserStack Cloud environment follow the steps below:

Step 1: Create a New Custom driver provider

import java.net.URL;
import java.util.Iterator;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;

import net.thucydides.core.util.EnvironmentVariables;
import net.thucydides.core.util.SystemEnvironmentVariables;
import net.thucydides.core.webdriver.DriverSource;

public class BrowserStackSerenityDriver implements DriverSource {

public WebDriver newDriver() {
EnvironmentVariables environmentVariables = SystemEnvironmentVariables.createEnvironmentVariables();

String username = System.getenv("BROWSERSTACK_USERNAME");
if (username == null) {
username = (String) environmentVariables.getProperty("browserstack.user");
}

String accessKey = System.getenv("BROWSERSTACK_ACCESS_KEY");
if (accessKey == null) {
accessKey = (String) environmentVariables.getProperty("browserstack.key");
}

String environment = System.getProperty("environment");
DesiredCapabilities capabilities = new DesiredCapabilities();

Iterator it = environmentVariables.getKeys().iterator();
while (it.hasNext()) {
String key = (String) it.next();

if (key.equals("browserstack.user") || key.equals("browserstack.key")
|| key.equals("browserstack.server")) {
continue;
} else if (key.startsWith("bstack_")) {
capabilities.setCapability(key.replace("bstack_", ""), environmentVariables.getProperty(key));
if (key.equals("bstack_browserstack.local")
&& environmentVariables.getProperty(key).equalsIgnoreCase("true")) {
System.setProperty("browserstack.local", "true");
}
} else if (environment != null && key.startsWith("environment." + environment)) {
capabilities.setCapability(key.replace("environment." + environment + ".", ""),
environmentVariables.getProperty(key));
if (key.equals("environment." + environment + ".browserstack.local")
&& environmentVariables.getProperty(key).equalsIgnoreCase("true")) {
System.setProperty("browserstack.local", "true");
}
}
}

try {
return new RemoteWebDriver(new URL("https://" + username + ":" + accessKey + "@"
+ environmentVariables.getProperty("browserstack.server") + "/wd/hub"), capabilities);
} catch (Exception e) {
System.out.println(e);
return null;
}
}

public boolean takesScreenshots() {
return true;
}
}

Step 2: Add BrowserStack Environment setup code in the Before hook

public class BrowserStackSerenityTest {
static Local bsLocal;

@BeforeClass
public static void setUp() throws Exception {
EnvironmentVariables environmentVariables = SystemEnvironmentVariables.createEnvironmentVariables();

String accessKey = System.getenv("BROWSERSTACK_ACCESS_KEY");
if (accessKey == null) {
accessKey = (String) environmentVariables.getProperty("browserstack.key");
}

String environment = System.getProperty("environment");
String key = "bstack_browserstack.local";
boolean is_local = environmentVariables.getProperty(key) != null
&& environmentVariables.getProperty(key).equals("true");

if (environment != null && !is_local) {
key = "environment." + environment + ".browserstack.local";
is_local = environmentVariables.getProperty(key) != null
&& environmentVariables.getProperty(key).equals("true");
}

if (is_local) {
bsLocal = new Local();
Map<String, String> bsLocalArgs = new HashMap<String, String>();
bsLocalArgs.put("key", accessKey);
bsLocal.start(bsLocalArgs);
}
}

@AfterClass
public static void tearDown() throws Exception {
if (bsLocal != null) {
bsLocal.stop();
}
}
}

Step 3: Add Browserstack related configuration keys to Serenity.properties file

webdriver.driver = provided
webdriver.provided.type = mydriver
webdriver.provided.mydriver = com.ui.screenplay.BrowserStackSerenityDriver
serenity.driver.capabilities = mydriver

webdriver.timeouts.implicitlywait = 5000
serenity.use.unique.browser = false
serenity.dry.run=false
serenity.take.screenshots=AFTER_EACH_STEP

browserstack.user=gurudattananthap_jG62JF
browserstack.key=9yV8DdY2CwdWNizFWxqC
browserstack.server=hub.browserstack.com

bstack_build=browserstack-screenplay-build-1
bstack_debug=true
bstack_browserstack.console=verbose

environment.single.name=serenity_single_test
environment.single.browser=chrome

Once you run the test, the execution happens in BrowserStack Cloud. The Execution results, text, video, and console logs can be found in the BrowserStack Automate Dashboard

BrowserStack Execution Result

These tests can be easily shared with the team using Slack, JIRA, GitHub, or Trello integrations on BrowserStack for effective bug reporting.

BrowserStack Percy Banner

Percy Integration with Screenplay for Visual Testing

Integrate Percy to implement Selenium Screenplay Pattern Approach in Visual Testing to make it more effective by following the below steps:

Step 1: Add Percy Java Selenium dependency to the project POM.xml

<dependency>
<groupId>io.percy</groupId>
<artifactId>percy-java-selenium</artifactId>
<version>1.0.0</version>
</dependency>

Step 2: Add Package.json file to the root of the Project and run the below command

npm install --save-dev @percy/cli

Step 3: Add Percy Snapshot method to your test like below

import com.ui.screenplay.hooks.BrowserStackSerenityTest;
import com.ui.screenplay.questions.Dashboard;
import com.ui.screenplay.tasks.LoginToBstack;
import net.serenitybdd.junit.runners.SerenityRunner;
import net.serenitybdd.screenplay.Actor;
import static net.serenitybdd.screenplay.GivenWhenThen.*;
import net.serenitybdd.screenplay.abilities.BrowseTheWeb;
import net.serenitybdd.screenplay.actions.Open;
import net.thucydides.core.annotations.Managed;
import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;
import io.percy.selenium.Percy;

@RunWith(SerenityRunner.class)
public class ScreenPlayTest extends BrowserStackSerenityTest {

private Actor demoUser = Actor.named("Demo User");
private static Percy percy;

@Managed
WebDriver hisBrowser;

@Before
public void demoUserCanBrowseTheWeb(){
demoUser.can(BrowseTheWeb.with(hisBrowser));
percy = new Percy(hisBrowser);
}

@Test
public void browseTheWebAsDemoUser(){
demoUser.attemptsTo(Open.url("https://bstackdemo.com/signin"));
givenThat(demoUser).attemptsTo(LoginToBstack.withCredentials("demouser", "testingisfun99"));
then(demoUser).should(seeThat(Dashboard.displayed(), CoreMatchers.equalTo("demouser")));
percy.snapshot("Bstack Homepage");
}

}

Step 4: Export the PERCY_TOKEN = <Your token>. Refer to the document here to get your Percy token.

Step 5: Execute your maven/java command using Percy CLI

./node_modules/.bin/percy exec -- mvn verify

Now the test will run in BrowserStack Cloud along with Percy integration to Capture snapshots and compare the Visual Changes. A new build will be created in Percy under your project where every time you can run a Visual Regression Test.

Screenplay Pattern Selenium Build on Percy

Talk to an Expert

Conclusion

Screenplay pattern provides an effective way to organize, maintain, and refactor the PageObject classes. As the Screenplay pattern is Integrated with BDD, you don’t need to Maintain driver objects and can leverage inbuilt methods easily and effectively.

Screenplay Serenity BDD produces detailed HTML reports that can also be quickly run in the BrowserStack cloud to test under real user conditions.

Tags
Automated UI Testing Automation Testing Selenium Visual Testing