selenium - the page object pattern
TRANSCRIPT
The Page Object Pa-ern A basic DRY abstrac-on pa1ern for Web browser automa-on test
development, maintenance and versioning
Alex Kogon
Basic Web Test Development
• What do we do in Selenium Web Test Development? Just like the user, we launch a Web browser, load pages, and read, write, and click on bu1ons and links.
• How do we interact with the Web browser? The Web browser is interface via an Object in your code, which can be told to load pages, and queried for Element Objects that are currently on the displayed Web page. These Element Objects may be read from, wri1en to, or clicked on.
A First Basic Selenium Script
1. Load www.google.com 2. Find the search text field 3. Enter “Alex Kogon” 4. Find the search bu1on 5. Click on it
A First Basic Selenium Script (Java) public class BasicWebTest {
private WebDriver theSeleniumWebDriver;
@Before
public void setUp() throws Exception {
theSeleniumWebDriver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), DesiredCapabilities.firefox());
}
@Test
public void test() {
theSeleniumWebDriver.get("http://www.google.com");
final WebElement myGoogleInputElement = theSeleniumWebDriver.findElement(By.cssSelector("#lst-‐ib"));
myGoogleInputElement.sendKeys("Alex Kogon");
final WebElement mySearchButtonElement = theSeleniumWebDriver.findElement(By.cssSelector("#tsf > div.tsf-‐p > div.jsb > center > input[type=\"submit\"]:nth-‐child(1)"));
mySearchButtonElement.click();
}
}
Adding more searches
1. Load www.google.com 2. Find the search text field 3. Enter other varia-ons on name (“Alexander Kogon”, “Kogon, Alex”) 4. Find the search bu1on 5. Click on it
Another Search
@Test public void anotherTest() { theSeleniumWebDriver.get("http://www.google.com"); final WebElement myGoogleInputElement = theSeleniumWebDriver.findElement(By.cssSelector("#lst-‐ib")); myGoogleInputElement.sendKeys("Kogon, Alex"); final WebElement mySearchButtonElement = theSeleniumWebDriver.findElement(By.cssSelector("#tsf > div.tsf-‐p > div.jsb > center > input[type=\"submit\"]:nth-‐child(1)")); mySearchButtonElement.click(); }
Et cetera… @Test
public void yetAnotherTest() {
theSeleniumWebDriver.get("http://www.google.com");
final WebElement myGoogleInputElement = theSeleniumWebDriver.findElement(By.cssSelector("#lst-‐ib"));
myGoogleInputElement.sendKeys("Alexander Kogon");
final WebElement mySearchButtonElement = theSeleniumWebDriver.findElement(By.cssSelector("#tsf > div.tsf-‐p > div.jsb > center > input[type=\"submit\"]:nth-‐child(1)"));
mySearchButtonElement.click();
}
@Test
public void andSoAnotherTest() {
theSeleniumWebDriver.get("http://www.google.com");
final WebElement myGoogleInputElement = theSeleniumWebDriver.findElement(By.cssSelector("#lst-‐ib"));
myGoogleInputElement.sendKeys("Kogon, Alexander");
final WebElement mySearchButtonElement = theSeleniumWebDriver.findElement(By.cssSelector("#tsf > div.tsf-‐p > div.jsb > center > input[type=\"submit\"]:nth-‐child(1)"));
mySearchButtonElement.click();
}
Eureka!
Isn’t this great? With almost no effort we can write test scripts building new func-onality from what we’ve already done by copying and pas-ng the exis-ng work we’ve done and modifying a couple things.
So what’s the problem?
How about if something changes? By building a test from an exis-ng site and copying the CSS selectors, it is easy to build a large body of func-onality tests. However, what happens if the CSS selectors (or other underlying func-onality) change?
Bri-le Selectors
Please give us a unique selector! When construc-ng selectors, there are a variety of op-ons on how it may be done. The best selectors reference unique iden-fiers associated with the HTML element, like the Google Search Text Field in our example code: theSeleniumWebDriver.findElement(By .cssSelector("#lst-‐ib")); However, quite oben the developers have not inserted unique tags, and we get a selector similar to what we used for the Google Search Bu1on in our example code: final WebElement mySearchButtonElement = theSeleniumWebDriver .findElement(By .cssSelector("#tsf > div.tsf-‐p > div.jsb > center > input[type=\"submit\"]:nth-‐child(1)"));
What Happens When Selectors Change?
Layout based selectors are a headache… Look at the second selector again: cssSelector("#tsf > div.tsf-‐p > div.jsb > center > input[type=\"submit\"]:nth-‐child(1)")); With a bit of knowledge of CSS selectors, you can see that this is querying a subsec-on of the page for all of it’s submit bu1ons, and asking for the first one. What if the layout of the page changed, or there was another bu1on inserted before it? Suddenly either no bu1on will be found or the wrong one clicked. Ideally, the developers can be convinced to insert unique selectors into every element you need to access, but oben this is not the case. How to handle the changes?
Brute Force
Search and Replace So let’s assume the guys at Google put in another bu1on before the search bu1on. Our CSS selector now looks like: cssSelector("#tsf > div.tsf-‐p > div.jsb > center > input[type=\"submit\"]:nth-‐child(2)")); OK, well we’ve got four uses of this selector, but they are all in the same file, so searching and replacing is not the end of the world. What about if we had hundreds? And they were in different files? What if we just had to change it once? While copying and pas-ng code all over is easy and convenient for reuse, it creates a lot of extra busy work when something needs to be changed. By crea-ng a “Single Point Of Truth”, or a single way in which this selector is accessed, we only need to change it once. A bit of extra work up front to save a lot of work in the future.
Don’t Repeat Yourself (DRY)
Encapsulate code in a single loca-on OK, so we’ve copied the same code in four places, and now we need to change it. To do so most easily, we would like to only have to change it once. What do we do? Refactor. One of the most powerful innova-ons in modern IDE’s is the ability to automa-cally have the IDE change code, a.k.a. “refactoring”. Refactoring allows you to change the architecture of your code on the fly, adop-ng more complicated structures as demanded, while keeping things as simple as possible at the -me. There are several possible solu-ons for how to refactor this code to be DRY; we will work with extrac-ng a common method that is accessed from all the tests.
What if the Selector is used across mulOple script files?
Share methods within a u-lity class So now we’ve created a single method containing the CSS Selector that can be easily modified to update the Selector when it changes. However, that method is only in one class. If the selector is used from mul-ple test script class files, the change must be implemented in each one. Wouldn’t it be easier to just move the method to another class where it could be shared by all the different test scripts? The “Move” refactor is also quite helpful in doing this.
The Page Object Pa-ern
Break your u-lity classes out by Page The u-lity class we have just created contains all the logic to access elements on the Web pages we use in our test. Assuming there are many Web pages, they are all mixed together in our class. By breaking out the u-lity class into separate classes, each with the accessors for a single Web page, we create a be1er abstrac-on where we can easily share func-onality across our test suite by bringing in (and extending) exis-ng Page Object code for each page a test uses.
Code
Page Object Pa-ern Architecture
Google Home Page
Google Search Result Page
Google Home Page Object
Google Search Result Page Object
Page Objects Web Pages
Test 1
Test 2
Test 3
Test Scripts
Maven and Page Objects
Store each page as a library in Maven Now that we’ve broken up our accessors into Page Objects so they can be easily shared across test scripts, why not put them into a separate module so that they can be easily shared across test projects as well? By pugng each Page Object into a unique Maven module, tests can simply define all the Page Objects they need to reference in their Maven configura-on (pom.xml) file, and have access to the Page Objects without needing to copy the Page Objects into mul-ple projects (another DRY viola-on) or have them all in a single project.
Maven, Page Objects, and Versioning
Added benefits of using Maven OK, now we’ve encapsulated each of our CSS selectors in a single accessor method, stored each of these in a unique Object represen-ng each Web page used, and created a Maven library for this Page Object. What next? When running Web automa-on tes-ng in a large, dynamic, organiza-on, you will find quite oben that there are mul-ple versions of each Web page that must be tested simultaneously. Reliability tes-ng on the live site requires the version currently in live is tested; integra-on tes-ng on imminent releases require the version ready for release; and development tes-ng on new versions of the Web page (perhaps a pipeline of mul-ple future releases) require the version for each of those pages. Luckily Maven already provides us with a solu-on. Maven libraries are easily versioned for deployment, such that many versions of the library can be available for use by the tests depending on which version of each page is to be tested. A single test script is s-ll able to be used on the various versions, as the differences in the implementa-on in each version is abstracted behind the Page Object.
Code
Page Object Pa-ern Versioning
Google Home Page 0.0.1
Google Home Page 0.0.2
Google Home Page Object V 0.0.1
Google Home Page Object V 0.0.2
Page Objects Web Pages
Test 1
Test 2
Test 3
Test Scripts
ConOnuous Deployment and IntegraOon
The full enchilada Now that we’ve got reusable, versioned Page Objects referenced from our Web script code via Maven, and automated tests which leverage the page objects by version, let’s see how this works in our Automa-on solu-ons. A Con-nuous Deployment and Integra-on system can be easily leveraged to provide tes-ng across all the mul-ple versions in real -me. By allowing the defini-on of the version of each page to be deployed for each tes-ng to be defined, the correct version of each Page can be deployed into the Web applica-on servers for the test run, and the correct version of each Page Object corresponding to that Page version used (by the iden-cal test) by dynamically instruc-ng Maven to use the same version of the Page Object that was just deployed for tes-ng. In this way it is very easy to test mul-ple integra-on scenarios in real -me (produc-on, integra-on, development, etc.), and to easily test any possible integra-on of versions by simply defining the versions to be used for an Automa-on run. Try doing that with hard coding.
ConOnuous IntegraOon with Versioning
Jenkins
ConOnuous IntegraOon Server
Trigger 0.0.1 Build
Checkout 0.0.1 Branch
Git
Version Control Server
Build 0.0.1 Branch
Nexus
Maven Deployment
Server
Tomcat
Web ApplicaOon Server
Deploy 0.0.1 Branch
Run 0.0.1 Test
Maven
Test Runner
Run With 0.0.1 Page Object
Selenium Test Run Firefox
Drive Browser
Deliver 0.0.1 Page Objects
Deliver 0.0.1 Pages