70
Testing Java Web Applications with Selenium: A Cookbook JavaOne 2016 – CON3080 Jorge Hidalgo & Vicente González

JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Embed Size (px)

Citation preview

Page 1: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Testing Java Web Applications with Selenium: A Cookbook

JavaOne 2016 – CON3080Jorge Hidalgo & Vicente González

Page 2: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Presenter IntroductionsShameless Marketing Division

2

Page 3: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Presenter Introductions

3

Jorge Hidalgo @_deors deors

Senior Technology ArchitectGlobal Java Community LeadAccenture Spain

Father of two children, husband, irish flute & whistle player, video gamer, master chief apprentice, sci-fi ‘junkie’, Star Wars fan, eternal Lego journeyman, gadget lover and in love with Raspberry Pi computers

Vicente González @viarellano viarellano

Technology ArchitectGlobal Java Community ChampionAccenture Spain

52 month experience husband, proud father of 2 years old princess Regina, senior beach user, cooking lover, associate padel player, master Feria de Abril enthusiast, Sevilla FC fan and Pearl Jam ninja evangelizer

Page 4: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

SeleniumWhat is Selenium?

4

Page 5: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Selenium• Selenium is a web browser automation tool• Automate any kind of interaction with a web application• Script to create 1,000 users from data in a spreadsheet• Script to download all invoices in a monthly basis• Automate functional test scripts!

• No backdoor tricks, as close to the real stuff as possible• Excellent for functional test script automation

5

Page 6: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Selenium• Selenium is open source• No vendor lock-in• Smaller inversion (in both capital and operational expenses)

• Selenium is mature and with a large and vibrant community• Runs in many browsers and systems• Scripts can be written in many programming languages• Selenium usage is widely extended• Easier to find people with the skill

6

Page 7: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Selenium• Selenium is formed of various components/projects:• Selenium WebDriver

• Core API and language bindings – to interact with browsers• Browser-specific implementations – because they are not created equal• Executes scripts (tests) in local and remote machines

• Selenium Grid• Simple, efficient, client-server setup to distribute test execution and workload• Across many machines, multiple operating systems and browsers• Even mobile OS/browsers

• Selenium IDE• Firefox plug-in to record and playback tests

7

Page 8: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Demo #1• Selenium in one minute... maybe in two

8

Page 9: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Selenium 101• Writing a test script is simple using the WebDriver API• Very much resembles what the tester is doing

• Scripts are usually wrapped in a test framework like JUnit• Assertion capabilities• Reporting capabilities

WebDriver driver = new ChromeDriver();WebElement findOwnerLink = driver.findElement(By.linkText("Find owner"));findOwnerLink.click();driver.findElement(By.id("lastName")).sendKeys("Schroeder");driver.findElement(By.id("findowners")).click();

9

Page 10: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Selenium 101• The WebDriver API has methods to:• Find elements in a page:

• By its ID (the preferred way)• By the text in a link, CSS selector, class name, element name

• Interact with the elements:• Click a button• Enter text in (or clear) a text field• Select a value in a radio-button or drop-down list

• Access to the page source:• For example, to verify if some JavaScript or some arbitrary text is present

• Take screenshots!

10

Page 11: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Selenium 101• It is even possible to work with DOM using XPath expressions

11

Page 12: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #1Working with the DOM in Selenium

12

Page 13: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #1 – Working with the DOM• The first rule of working with the DOM in Selenium is...

13

Page 14: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #1 – Working with the DOM• The first rule of working with the DOM in Selenium is...

• ...WE DON’T WORK WITH THE DOM IN SELENIUM!

14

Page 15: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #1 – Working with the DOM• We are *very* serious• Working directly with the DOM makes tests brittle• They focus on the implementation detail• They move far from the perspective of the tester

• Interact with visual, recognizable, elements in the UI• Test cases are hard to maintain and easy to break

• Use this option only as a last resort mechanism• And when doing it, document the tests so it is easier to understand

what it pretends

15

Page 16: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #1 – Working with the DOM• As a corollary, two derived tips – these two come from conversations

with developers arguing about ‘why we don’t automate our tests’• Tip #1.1• “Our web pages don’t have IDs”• Always add meaningful IDs in generated / crafted HTML elements• If possible, make them unique (or at least grouped by function)

• Tip #1.2• “The framework we are using generate random or non-consistent IDs”• Pick another framework! • Testability must be a primary driver while taking technology decisions

16

Page 17: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Running Selenium• Step 1)• Start the Selenium grid

• Step 2)• Start the application to be tested

• Step 3)• Launch the tests

17

Page 18: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Running Selenium• Step 1)• Start the Selenium grid

• Add the Selenium folder to PATH – external drivers need to be found at runtime

• Add the Selenium folder to PATH – external drivers need to be found at runtime

• Then each of the test nodes

set PATH=%PATH%;%SELENIUM_HOME% or export PATH=$PATH:$SELENIUM_HOME

java -jar selenium-server-standalone-2.53.1.jar -role node -port 5555 \ -hub http://localhost:4444/grid/register \ -browser browserName=chrome \ -browser browserName=firefox

java -jar selenium-server-standalone-2.53.1.jar -role hub -port 4444

18

Page 19: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Running Selenium• Step 2)• Start the application to be tested

• In your local IDE• A local or remote server (command-line, script, service...)• In a continuous integration job

• Step 3)• Launch the tests

• In your local IDE• A local or remote process (command-line, script...)• In a continuous integration job

19

Page 20: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Demo #1 revisited• Let’s repeat the demo following the process step by step

20

Page 21: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #2Automate Test Data Creation

21

Page 22: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #2 – Test Data Creation• Start by crafting scripts to create test data• Very simple scripts• Use input files (CSV, spreadsheet) to provide data for multiple records• Even if nothing else is automated, there is business value in those

scripts• These scripts are very easy to transform in a functional test script• Add some assertions on the output (ok/error)• Use DbUnit to assert the state in the database is the expected one

22

Page 23: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #2 – Test Data Creation• Using Pet Clinic as a example, let’s create a few owners. Start by

reading the file and feed the test data to the scriptprivate Optional<Owner> parseOwner(String testDataLine) {

String[] testDataTokens = testDataLine.split("\t"); if (testDataTokens.length != 5) { return Optional.empty(); } else { Owner owner = new Owner(); owner.setFirstName(testDataTokens[0]); owner.setLastName(testDataTokens[1]); owner.setAddress(testDataTokens[2]); owner.setCity(testDataTokens[3]); owner.setTelephone(testDataTokens[4]); return Optional.of(owner); }}

23

Page 24: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #2 – Test Data Creation• For each line in the file, a new owner will be created. To keep things

clean, let’s separate test data logic from the actual test stepsprivate void createNewUserBulk(final WebDriver driver, final String baseUrl) {

Optional<List<String>> testData = readTestData(); if (testData.isPresent()) { for (String testDataLine : testData.get()) { Optional<Owner> owner = parseOwner(testDataLine); if (owner.isPresent()) { createNewUser(driver, baseUrl, owner.get()); } } }}

24

Page 25: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #2 – Test Data Creation• The actual test steps are very, very simple – just access the form,

populate the data, and click the buttonprivate void createNewUser( final WebDriver driver, final String baseUrl, Owner owner) {

driver.get(baseUrl + "/owners/new");

driver.findElement(By.id("firstName")).sendKeys(owner.getFirstName()); driver.findElement(By.id("lastName")).sendKeys(owner.getLastName()); driver.findElement(By.id("address")).sendKeys(owner.getAddress()); driver.findElement(By.id("city")).sendKeys(owner.getCity()); driver.findElement(By.id("telephone")).sendKeys(owner.getTelephone());

driver.findElement(By.id("addowner")).click();}

25

Page 26: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #2 – Test Data Creation• Adding assertions is simple and will transform the useful test data

creation script into a functional test case (even more useful)private void createNewUser( final WebDriver driver, final String baseUrl, Owner owner) {

... driver.findElement(By.id("addowner")).click();

assertTrue(driver.getPageSource().contains(owner.getFirstName())); assertTrue(driver.getPageSource().contains(owner.getLastName())); assertTrue(driver.getPageSource().contains(owner.getAddress())); assertTrue(driver.getPageSource().contains(owner.getCity())); assertTrue(driver.getPageSource().contains(owner.getTelephone()));}

26

Page 27: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Demo #2• How to create multiple owners for the Pet Clinic application

27

Page 28: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #3Decouple driver/browser logic from test logic

28

Page 29: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #3 – Decouple Driver/Browser Logic• This will make tests reusable across browsers• The election of which browsers will be used can be done externally

and passed as parameter to the test• Using environment variables• Using Java VM system properties

• Will work in 99% of test cases• WebDriver API already decouples core from browser implementations• In some corner cases, driver specific API idiom will be needed

29

Page 30: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #3 – Decouple Driver/Browser Logic• An exemplar implementation in Jorge’s post here:

• Four steps:• Step 1) Define some variables to hold desired setup• Step 2) Read values from environment variables and/or system properties• Step 3) Create test methods per browser and execute/skip test logic based on

the desired setup• Step 4) Create test logic methods that receive a Driver and URL as parameters

https://deors.wordpress.com/2013/02/18/selectable-selenium/

30

Page 31: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #3 – Decouple Driver/Browser Logic• Step 1) Define some variables to hold desired setup

private static boolean RUN_HTMLUNIT;private static boolean RUN_FIREFOX;private static boolean RUN_CHROME;private static boolean RUN_OPERA;private static boolean RUN_IE;

private static String SELENIUM_HUB_URL;private static String TARGET_SERVER_URL;

31

Page 32: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #3 – Decouple Driver/Browser Logic• Step 2) Read values from environment variables and/or system

properties (in a method annotated with @BeforeClass)RUN_HTMLUNIT = getConfigurationProperty( "RUN_HTMLUNIT", "test.run.htmlunit", true);

logger.info("running the tests in HtmlUnit: " + RUN_HTMLUNIT);

TARGET_SERVER_URL = getConfigurationProperty( "TARGET_SERVER_URL", "test.target.server.url", "http://localhost:58080/petclinic");

logger.info("using target server at: " + TARGET_SERVER_URL);

32

Page 33: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #3 – Decouple Driver/Browser Logic• More on Step 2) All properties should have sensible defaults. For

example:• HtmlUnit browser, which is an in-memory browser, enabled by default• All other browsers, which may or may not be present, disabled by default• Selenium grid, running on localhost by default• Application to be tested, running on localhost by default

• The default settings proposed above allows for easy test execution, without further concern, in a developer workstation

33

Page 34: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #3 – Decouple Driver/Browser Logic• Step 3) Create test methods (annotated with @Test) per browser and

execute/skip test logic based on the desired setup@Testpublic void testChrome() throws MalformedURLException, IOException {

Assume.assumeTrue(RUN_CHROME); try { Capabilities browser = DesiredCapabilities.chrome(); WebDriver driver = new RemoteWebDriver(new URL(SELENIUM_HUB_URL), browser); testNewPetFirstVisit(driver, TARGET_SERVER_URL); } finally { if (driver != null) { driver.quit(); } }}

34

Page 35: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #3 – Decouple Driver/Browser Logic• Step 4) Create test logic methods (not annotated) that receive a

Driver and the base URL where the app is deployed as parameterspublic void testNewPetFirstVisit(final WebDriver driver, final String baseUrl) {

driver.get(baseUrl);

WebElement findOwnerLink = driver.findElement(By.linkText("Find owner")); findOwnerLink.click();

driver.findElement(By.id("lastName")).clear(); driver.findElement(By.id("lastName")).sendKeys("Schroeder");

driver.findElement(By.id("findowners")).click();

...} 35

Page 36: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Running Selenium (revisited)When the tests are executed, parameters will be read, if any. If not, sensible defaults will be applied, and tests executed.Some examples:• Run the tests in HtmlUnit, Chrome and Firefox, with Selenium grid and

application both deployed at localhost

• Run the tests in Chrome only, with Selenium grid and application deployed at some remote (shared) servers

-Dtest.run.chrome=true -Dtest.run.firefox=true

-Dtest.run.htmlunit=false -Dtest.run.chrome=true \ -Dtest.selenium.hub.url=http://selenium.test.acme.com:4444/wd/hub \ -Dtest.target.server.url=http://appserver.test.acme.com:58080/petclinic

36

Page 37: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Running Selenium (revisited)If using Apache Maven, Failsafe plug-in should be used for integration tests instead of Surefire (which should be used only for unit tests)• Easy separation of unit test and integration test results• Great if used along a code coverage tool like JaCoCo and a quality dashboard

like SonarQube• Remember to pass test parameters embedded in argLine parameter so they

are passed appropriately to the JUnit test class:mvn failsafe-integration-test -DargLine=" \ -Dtest.run.htmlunit=false -Dtest.run.chrome=true \ -Dtest.selenium.hub.url=http://selenium.test.acme.com:4444/wd/hub \ -Dtest.target.server.url=http://appserver.test.acme.com:58080/petclinic"

37

Page 38: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Running Selenium (revisited)This is an example of a continuous delivery pipeline in Jenkins which is using separate build jobs for unit and integration testsInternally it is using both Maven Surefire and Failsafe to differentiate both suite executions

Test results can be used as a quality gate along the pipeline

38

Page 39: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved. 39

YOU SHALL NOT PASS!

Image © New Line Cinema: “The Lord of the Rings – The Fellowship of the Ring”

Page 40: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Running Selenium (revisited)This is an example of how using both Maven Surefire and Failsafe allows for better reportingTools, like SonarQube, which are aware of the difference between unit and integration tests, can show coverage metrics per kind of test, and combine all for overall value

40

Page 41: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Demo #3• Working with multiple browsers from the IDE• Role of Selenium in continuous delivery pipelines

41

Page 42: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #4Use Wait conditions for quicker and more robust tests

42

Page 43: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #4 – Fluent Wait Conditions• When at some point of the test, it is needed to wait for something to

happen• Navigating to a new page• Waiting for the server-side transaction to finish• Waiting for an AJAX call

• Don’t be tempted by the dark side – never use Thread.sleep() or similar idiom• If child process finishes sooner, time is wasted – scripts run slower

• WebDriver API provides with a more elegant approach – WebDriverWait

43

Page 44: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved. 44Image credit: http://imgur.com/gallery/x9IkCWC

Page 45: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #4 – Fluent Wait Conditions• WebDriverWait instances are created for a specific WebDriver

instance, good if multiple tests are executed in parallel• The condition is passed as a Function or Predicate from Google Guava

(pre-Java 8 functional interfaces) so they are lambda-ready!• The Function will evaluate some condition, for example find some

element and return it, or check that page title is as expected• If the element is not found, the WebDriver API usual

NotFoundException is ignored until the timeout is over

45

Page 46: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #4 – Fluent Wait Conditions• This is an example using pre-lambda syntax

driver.get(baseUrl);

// wait for the application to get fully loadedWebElement findOwnerLink = (new WebDriverWait(driver, 5)).until( new ExpectedCondition<WebElement>() {

public WebElement apply(WebDriver d) { return d.findElement(By.linkText("Find owner")); }});

findOwnerLink.click();

...rest of script

46

Page 47: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #4 – Fluent Wait Conditions• This is the very same example but using a lambda expression

• The cast is needed so the compiler knows which functional interface is being used (Function or Predicate) as until() method is overloaded

driver.get(baseUrl);

// wait for the application to get fully loadedWebElement findOwnerLink = (new WebDriverWait(driver, 5)).until( (Function<WebDriver, WebElement>) d -> d.findElement(By.linkText("Find owner")));findOwnerLink.click();

...rest of script

47

Page 48: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #4 – Fluent Wait Conditions• This is another example using a Predicate as a lambda expression

• The cast is, once again, needed

...rest of script

findOwnerLink.click();

(new WebDriverWait(driver, 5)).until((Predicate<WebDriver>) d -> d.getCurrentUrl().startsWith(baseUrl + "/owners/search"));

...rest of script

48

Page 49: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Demo #4• Focus on WebDriver fluent wait conditions

49

Page 50: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #5Page Object idiom for highly reusable test scripts

50

Page 51: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #5 – Page Object Idiom• Testing is about sending data, and verifying responses• A test script should only contain details about data and operations,

and code to check the responses• Implement an unique way for accessing each element in your page• Decouple test code from the way you access elements from the UI• Avoid reworking effort when your application changes• Reduce duplicated code

51

Page 52: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #5 – Page Object IdiomPage Object Pattern

• An abstraction of the web pages under test• Encapsulates the code to access the web elements of the UI• Provides an API that facilitates the creation of test cases even for

people who may not understand about the implementation• Single point for rework• Will make your test code more readable

52

Page 53: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #5 – Page Object Idiom• Implement a constructor with the driver and the baseURL as the

parameter

public OwnerPage(WebDriver driver, String baseUrl) {

this.driver = driver; this.baseUrl = baseUrl; }

53

Page 54: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #5 – Page Object Idiom• Define the elements in the page

private By titleElement = By.xpath("//h2");

private By mainContent = By.id("main");

private By editOwnerCommand = By.linkText("Edit Owner");

private By addNewPetCommand = By.linkText("Add New Pet");

private By editPetCommand = By.linkText("Edit Pet");

private By addVisitCommand = By.linkText("Add Visit");

private By linkToHome = By.linkText("Home");

private By nameTag = By.xpath("//table[1]/tbody/tr[1]/td"); //sorry about using the DOM this time...

private By addressTag = By.xpath("//table[1]/tbody/tr[2]/td"); //please don’t do this...

54

Page 55: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #5 – Page Object Idiom• Provide the public API for accessing the elements in your page

public String getName() {

WebElement element = driver.findElement(nameTag); String name = element.getText(); return name; }

public String getAddress() {

WebElement element = driver.findElement(addressTag); String address = element.getText(); return address; }

55

Page 56: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

56Copyright © 2016 Accenture All rights reserved.

Tip #5 – Page Object Idiom• …and for actions the user will do in the page under test

public PetPage navigateToEditPet(String petId) {

driver.findElement(editPetCommand).click();

(new WebDriverWait(driver, 5)).until( (Predicate<WebDriver>) d -> getUrl().startsWith(baseUrl + "/owners/"+petId+"/pets") && getUrl().contains("edit"));

return new PetPage(driver, baseUrl); }

Page 57: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #5 – Page Object Idiom• The test code should be something like

driver.get(baseUrl);

HomePage homePage = new HomePage(driver, baseUrl);

FindOwnersPage findOwnersPage = homePage.navigateToFindOwners();

OwnerPage ownerPage = findOwnersPage.findOwner("Schroeder"); assertTrue(ownerPage.getName().contains("David Schroeder"));

PetPage petPage = ownerPage.navigateToAddNewPet();

petPage.createPet("Mimi", "2011-10-02"); assertTrue(ownerPage.getName().contains("David Schroeder")); assertTrue(ownerPage.getMainText().contains("Mimi")); assertTrue(ownerPage.getMainText().contains("2011-10-02"));

57

Page 58: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #5 – Page Object Idiom• Before and after

driver.get(baseUrl);

HomePage homePage = new HomePage(driver, baseUrl);

FindOwnersPage findOwnersPage = homePage.navigateToFindOwners();

OwnerPage ownerPage = findOwnersPage.findOwner("Schroeder"); assertTrue(ownerPage.getName().contains("David Schroeder"));

driver.get(baseUrl);

WebElement findOwnerLink = (new WebDriverWait(driver, 5)) .until((Function<WebDriver, WebElement>) d -> d.findElement(By.linkText("Find owner")));

findOwnerLink.click();

(new WebDriverWait(driver, 5)).until((Predicate<WebDriver>) d -> d.getCurrentUrl().startsWith(baseUrl + "/owners/search"));

driver.findElement(By.id("lastName")).clear();driver.findElement(By.id("lastName")).sendKeys("Schroeder");driver.findElement(By.id("findowners")).click();

(new WebDriverWait(driver, 5)).until((Predicate<WebDriver>) d -> d.getCurrentUrl().equals(baseUrl + "/owners/9"));

assertTrue(driver.findElement(By.id("main")).getText().contains("David Schroeder"));

58

Page 59: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Demo #5• Writing tests using Page Objects

59

Page 60: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #5+Using the PageFactory with Page Object Design Pattern

60

Page 61: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #5+ – The PageFactory• Use the PageFactory to initialize the elements of the page under test

import org.openqa.selenium.support.FindBy;import org.openqa.selenium.support.PageFactory;import org.openqa.selenium.support.ui.WebDriverWait;

...

public OwnerPageFactory(WebDriver driver, String baseUrl) {

this.driver = driver; this.baseUrl = baseUrl; PageFactory.initElements(driver, this); }

61

Page 62: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #5+ – The PageFactory• Use the FindBy annotation while defining the WebElements

@FindBy(id="main")private WebElement mainContent;

@FindBy(linkText="Edit Owner")private By editOwnerCommand;

@FindBy(linkText="Add New Pet")private WebElement addNewPetCommand;

@FindBy(linkText="Edit Pet")private WebElement editPetCommand;

@FindBy(linkText="Add Visit")private WebElement addVisitCommand;

62

Page 63: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #5+ – The PageFactory• Your test could be something like…

public PetPage navigateToEditPet(String petId) {

editPetCommand.click();

(new WebDriverWait(driver, 5)).until((Predicate<WebDriver>) d -> getUrl().startsWith(baseUrl + "/owners/"+petId+"/pets") && getUrl().contains("edit"));

return new PetPage(driver, baseUrl); }

63

Page 64: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Demo #5+• The Page Object design pattern and the PageFactory

64

Page 65: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #6Working with Absolute Coordinates

65

Page 66: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #6 – Working with Absolute Coordinates• As with Tip #1, this is a technique we do not recommend• It makes tests brittle• Element locations are likely to change in different browsers• Even more difficult (and less useful) with responsive pages• Only as a last resort mechanism

• In Selenium it is not directly possible to locate an element at some point in page• However, it is possible to traverse a list of elements, ask for each

element location and size, and identify whether an element is under one specific point in page

66

Page 67: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #6 – Working with Absolute Coordinates• To ask for location and size, use this WebDriver methods:

• For example:

WebElement.getLocation();WebElement.getSize();

List<WebElement> elements = driver.findElements(By....);for (WebElement e: elements) { Point loc = e.getLocation(); Dimension size = e.getSize(); if (x >= loc.getX() && x <= loc.getX() + size.getWidth() && y >= loc.getY() && y <= loc.getY() + size.getHeight()) {

return e; }}

67

Page 68: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Tip #6 – Working with Absolute Coordinates• Having that multiple elements can be at the same physical location• Exercise caution• Do not traverse all page elements during a search• Select carefully which elements to use: by class, tag name or XPath (DOM)

• For example, in Pet Clinic home, H2 and IMG elements overlap

68

Page 69: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Demo #6• Working with absolute coordinates

69

Page 70: JavaOne 2016 - CON3080 - Testing Java Web Applications with Selenium: A Cookbook

Copyright © 2016 Accenture All rights reserved.

Q & AWe wish you enjoyed the talk

Visit https://github.com/deors/deors.demos.petclinic to get the code

70

Jorge Hidalgo @_deors

Vicente González @viarellano