In our previous post, Selenium Java: An Intelligent Example Part 2, we dug into the code. We demonstrated how everything is tied together. We outlined the BasePage, WebDriverResource, and BrowserFactory classes. In this post we will be digging into the concrete page objects themselves as well as writing a sample test that uses multiple page objects.
Concrete Page Class
Each concrete page class extends BasePage. Doing so provides them with everything they need to be a page object.
Each web page has web elements that we need to be able to locate and then manipulate. We need to enter data into text fields and click buttons. In our example we are using Selenium’s PageFactory. This allows us to do some pretty cool things. The first one is to annotate our web elements.
... @FindBy(how = How.NAME, using = "q") public WebElement searchBox; @FindBy(how = How.NAME, using = "btnG") public WebElement btnSubmit; ...
The @FindBy annotation allows us to select how we are going to find the WebElement we are interested in. The How enum contains 10 values including id, name, CSS name or even XPath.
When we make these WebElements public our tests can access them directly. However, we can also provide convenience methods.
... public GoogleResults searchForCheese(){ searchBox.sendKeys("Cheese"); btnSubmit.click(); GoogleResults resultsPage = getInstance(GoogleResults.class); return resultsPage; } ...
If we are pulling our data from an external source (e.g. JSON file or excel file) this may be the better option. It will keep your tests “clean” and will obey “Separation of Concerns”.
We also have the choice to override the BasePage’s ready method. This method is called on in the BasePage getInstance method before returning the concrete page to the test. This means that the ready method will always be called when we create a new page. If we choose not to override it then the BasePage ready method will be called.
... public ExpectedCondition<?> ready() { super.ready(); return ExpectedConditions.visibilityOfAllElements(Arrays.asList(new WebElement[]{searchBox})); } ...
In the last line of the method above we are calling a static method in the ExpectedConditions class. In this case we will be looking to see if a list of WebElements are visible. The BasePage getInstance method is going to wait until this ready method returns true.
If the web application you are testing does not use ajax to load/reload sections of your page without doing a round trip to the server then this is all you need to wait intelligently.
Test Class
Our example test is called cheeseTest. If you remember back to the first post we are going to be looking for the following page flow.
We are using junit to execute our tests so we will need to add the appropriate imports then annotate our test with the @Test annotation.
Notice with our first line of code in the test we are calling the BasePage getInstance method. This returns us our first page object. From this point we are simply calling methods on these page objects. We may be verifying things or performing an action that returns us another page.
... @Test public void cheeseTest() throws Exception { GoogleSearch searchPage = BasePage.getInstance(GoogleSearch.class); GoogleResults resultsPage = searchPage.searchForCheese(); resultsPage.verifyResults(); CheeseWiki cheesePage = resultsPage.goToWikiPage(); GoudaWiki goudaPage = cheesePage.goToGoudaPage(); goudaPage.verifyGoudaPage(); } ...
The test method above is easy to read and follow. It has no Selenium imports in the test class whatsoever. We are not messing with Selenium selectors or a WebDriver. In fact the test class has no knowledge of the WebDriver at all.
This separation is our goal. It allows us to consolidate all of the Selenium specific information to the page model objects. It allows us to reflect application changes in the page model and not necessarily in the tests themselves. This pattern makes our test more robust and easier to read.
Let’s circle back to our original problem outlined in the first post. How do we manage Technical Debt? Remember, we live in the real world where development resources are scarce and automated system tests are often thrown away rather then re-ran between major releases.
Achieving this level of separation between the web application and our tests allows us to maximize the use of specialization among our resources. Test Architects do “developy things” like building a robust page model. Tester Developers build easy to read and maintain test utilizing the page objects.
Things to remember in closing
Automated System Testing is not a silver bullet. It is not always the best answer, but it can provide you with significant benefits when used as part of your testing plan. Spend the time up front identifying what should be automated. One project may have 30% automation and another may have 70%. The “right” answer is dependent on the specific risks of each application.
When you write automated tests they are an investment and they represent Technical Debt. Managing this debt is key to the success of your testing efforts.
You can find the example project out on GitHub at: Selenium Example Project.