A journey beyond the page object pattern

Post on 09-Jan-2017

8.702 views 2 download

Transcript of A journey beyond the page object pattern

SCREENPLAYA Journey Beyond The PageObject Pattern

Antony Marcano Jan Molak Kostas Mamalis

CONTACT• Kostas Mamalis

@agiletestinguk

kostas@masterthought.net

• Antony Marcano@AntonyMarcano

antony@riverglide.com

• Jan Molak@JanMolak

jan.molak@smartcodeltd.co.uk

CONTEXT

IN THE BEGINNING• Long established financial institution

• Used Scrum with 2 week Sprints

• Outsourced development to large consultancy

• No automated acceptance tests (few unit tests)

ALONG THE JOURNEY• Realised that automated tests were

essential

• Wrote lots of Cucumber tests

• Backed by Selenium/WebDriver

• Used the PageObject Pattern

SELENIUM AND

THE PAGE OBJECT PATTERN

Illustrated with the Pet Clinic

TESTING THE PET CLINIC

WEBDRIVER EXAMPLE

DesiredCapabilities capabilities = new DesiredCapabilities();WebDriver driver = new PhantomJSDriver(desiredCapabilities());

driver.get(baseUrl+"owners/find.html");

driver.findElement(By.cssSelector("#search-owner-form button")).click();

assertThat( driver.findElements(By.cssSelector("owners tbody tr")).size(), is(10));

FINDING ALL OWNERS - WEBDRIVER EXAMPLE

DesiredCapabilities capabilities = new DesiredCapabilities();WebDriver driver = new PhantomJSDriver(desiredCapabilities());

driver.get(baseUrl+"owners/find.html");

driver.findElement(By.cssSelector("#search-owner-form button")).click();

assertThat( driver.findElements(By.cssSelector("owners tbody tr")).size(), is(10));

FINDING ALL OWNERS - PAGEOBJECT EXAMPLE

DesiredCapabilities capabilities = new DesiredCapabilities();WebDriver driver = new PhantomJSDriver(desiredCapabilities());

driver.get(baseUrl+"owners/find.html");

FindOwnersPage findOwners = PageFactory.initElements(driver, FindOwnersPage.class);

OwnersPage owners = findOwners.findWith(EMPTY_SEARCH_TERMS);assertThat(owners.numberOfOwners(), is(10));

PROBLEMS AROSE

• Large PageObject classes

• Brittle test-code (less than raw Selenium)

• Duplication across PageObjects for each of the ‘portals’

THEY TRIED THE FOLLOWING

• Separate behaviour into Navigation Classes

• Reduce duplication with inheritance

Causing ...

• Large Navigation classes

• Deep inheritance hierarchy

EFFECTS ON THE TEAM• Took longer and longer to add new tests• Got harder to diagnose problems• Low trust in the ‘test framework’ and Cucumber• Reduced faith

in automated testing• Impacted morale

WHAT WAS THE ANSWER?

Antony Marcano at first AAFTT in 2007

THE INSIGHT

Roles ←Who➥ Goals ←Why

➥ Tasks ←What➥ Actions ←How

Inspired by Kevin Lawrence’s talk at the first AAFTT in 2007More of his thinking here: http://www.developertesting.com/archives/month200710/20071013-In%20Praise%20of%20Abstraction.html

2008 - JNARRATE@Test public void should_be_able_to_edit_a_page() { Given.thatThe(wiki).wasAbleTo(beAtThe(PointWhereItHasBeen.JUST_INSTALLED)); And.thatThe(user).wasAbleTo(navigateToTheHomePage()); And.thatThe(user).wasAbleTo(navigateToTheHomePage());

When.the(user).attemptsTo( changeTheContent().to("Welcome to Acceptance Test Driven Development") );

Then.the(textOnTheScreen().ofThe(user)). shouldBe("Welcome to Acceptance Test Driven Development");}Playing with fluent APIs and started to explore the model of Tasks & Actions(although back then the labels I used more like Kevin’s labels).

task

task

task

2009 - SCREENPLAY - A TASK

public void perform() {

you.need(To.doTheFollowing( // actionsClick.onThe(OptionsMenu.EDIT_BUTTON),ClearTheContent.ofThe(Editor.CONTENT_PANEL),

Type.theText(newContent).intoThe(Editor.CONTENT_PANEL),

Click.onThe(Editor.SAVE_BUTTON)));

}

Actor

2012 - THE JOURNEY PATTERN

Tasks

Abilities Actions Screen

Elements

contains

enable

performs

composed ofhas

interact with

JOURNEY PATTERN APPLIED JUNIT

Roles ← Who

➥ Goals ← Why

➥ Tasks ← What

➥ Actions ← How

Actor theReceptionist = new Actor().with(WebBrowsing.ability());

@Test public void should_find_all_owners_by_default

theReceptionist.attemptsTo(Go.to(findOwnersScreen.url()),Search.forOwnersWith(EMPTY_SEARCH_TERMS),Count.theNumberOfOwners()

);

Enter.the(searchTerms). into(findOwnersScreen.searchTerms),Click.onThe(findOwnersScreen.searchButton)

JOURNEY PATTERN APPLIED CUCUMBER

Roles ← Who

➥ Goals ← Why

➥ Tasks ← What

➥ Actions ← How

As a Pet Clinic Receptionist

Scenario: Find all owners by default

When I search for owners with BLANK search terms@When(“^I search for owners with BLANK search terms$”)

theReceptionist.attemptsTo(Search.forOwnersWith(EMPTY_SEARCH_TERMS)

);

Enter.the(searchTerms). into(findOwnersScreen.searchTerms),Click.onThe(findOwnersScreen.searchButton)

PUTTING IT ALL TOGETHERActor theReceptionist = new Actor().with(WebBrowsing.ability());

theReceptionist.attemptsTo( Go.to(findOwnersScreen.url()), Search.forOwnersWith(EMPTY_SEARCH_TERMS), Count.theNumberOfOwners());

assertThat(theReceptionist.sawThatThe(numberOfOwners()),was(theExpectedNumberOfOwners)

);

A TASK…

private static String searchTerms;

@Overridepublic void performAs(Actor asAReceptionist) { asAReceptionist.attemptTo( Enter.the(searchTerms).into(findOwnersScreen.searchTerms), Click.onThe(findOwnersScreen.searchButton) );}

public SearchForOwnersWith(String searchTerms) { this.searchTerms = searchTerms;}

A SCREEN

@Url("owners/find.html")public class FindOwnersScreen extends WebScreen {

@LocateBy(css="#search-owner-form input") public ScreenElement searchTerms;

@LocateBy(css="#search-owner-form button") public ScreenElement searchButton;}

AN ACTIONpublic class Enter extends WebDriverInteraction implements Perform { private String text; private ScreenElement field;

public void performAs(Actor actor) { web(actor).findElement(field.locator()).sendKeys(text); }

public Enter(String text) { this.text = text; }

public static Enter the(String text) {return new Enter(text);}

public Perform into(ScreenElement field) { this.field = field; return this; }}

PROBLEMS SOLVED• Smaller “Screen” classes

• Small, focused “Task” classes

• Readable code

• Consistent metaphor

• Minimal inheritance

• Removed need for duplication across behaviours previously in PageObjects or “Navigation” classes

DESIGN PRINCIPLES• DRY - navigational steps in one place

• Separation of Concerns - Page Structure and Actions separate

• Small Classes - easy to comprehend

• Single Responsibility - classes focused on one thing and one thing only

• Minimise conditional logic - navigational if-thens replaced with composable sequences

SCREENPLAY REVIVEDUnder development for everyone to use

Watch This Space!

CONTACT• Kostas Mamalis

@agiletestinguk

kostas@masterthought.net

• Antony Marcano@AntonyMarcano

antony@riverglide.com

• Jan Molak@JanMolak

jan.molak@smartcodeltd.co.uk

THANK YOU!