Date post: | 09-Jan-2017 |
Category: |
Technology |
Upload: | riverglide |
View: | 8,701 times |
Download: | 2 times |
SCREENPLAYA Journey Beyond The PageObject Pattern
Antony Marcano Jan Molak Kostas Mamalis
CONTACT• Kostas Mamalis
@agiletestinguk
• Antony Marcano@AntonyMarcano
• Jan Molak@JanMolak
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
• Antony Marcano@AntonyMarcano
• Jan Molak@JanMolak
THANK YOU!