App & Browser Testing Made Easy

Give your users a seamless experience by testing on 3000+ real devices and browsers. Don't compromise with emulators and simulators

Home Guide Screenplay Pattern approach using Selenium and Java

Screenplay Pattern approach using Selenium and Java

By Gurudatt S A, Community Contributor -

Table of Contents

What is Page Object Model?

A page object is an object-oriented class that serves as an interface to a page of your Application Under Test (AUT). The tests then use the methods of this page object class whenever they need to interact with the UI of that page.

Page Object Model was recommended at a time when Selenium and WebDriver were being used increasingly by testing teams, and not necessarily by those with extensive programming skills.

Let’s consider an application with the below use cases

  1. Login page contains the username, password, and login button
  2. After successful login, the application displays the homepage

With the Page Object Model, you need to create separate classes for each page, and each page will contain methods for each component. As the components within the page grow, the methods will also grow.

Also given any application, it will have certain roles, and those roles will perform certain actions which result in an outcome. These tests should also be designed in a similar way where you can clearly see the user journey, actions, the roles performed, and validate the outcomes.

Using the PageObject model and Test Runner like JUnit and TestNG, such tests cannot be designed with a clear definition in terms of Business representation.

The Screenplay pattern comes to the rescue when designing the tests in the Role, Tasks, and Outcome driven approach.

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 tests 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

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 wide 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.

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

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.

Test on Real Device Cloud for Free

Tags
Automated UI Testing Automation Testing Selenium Visual Testing

Featured Articles

Setting up BrowserStack Automate

Different Types of Testing to Deliver a Bug-Free User Experience

Cross Browser Testing vs Responsive Design Testing – what should I do?