This article will discuss the possibility of using @FindBy annotation to search for elements on a page, as well as creating your own classes for working with elements and containers like forms, tables, etc.
Introduction to @FindBy
First, let's take a look at the test, which is simply looking for some phrase on ya.ru
public class SearchTest { @Test(dataProvider = "pageObjects") public void testSearch(final SearchPage searchPage) { searchPage.init(driver); driver.get("http://ya.ru"); searchPage.search("Bolek i Lolek"); } @DataProvider private Object[][] pageObjects() { return new Object[][]{ {new SimpleSearchPage()}, {new AnnotatedSearchPage()}, {new ExtendedSearchPage()}, {new SearchPageWithSearchForm()} }; } private WebDriver driver; @BeforeClass public void beforeClass() { driver = new FirefoxDriver(); } @AfterClass public void afterClass() { driver.quit(); } }
As you can see, the test is very simple and does not actually test anything, but simply runs 4 times with different page-objects, considering the evolution of which we will study @FindBy.
Initially, the page class for the search page looked like this:
public class SimpleSearchPage implements SearchPage { private WebDriver driver; @Override public void search(final String query) { driver.findElement(By.id("text")).sendKeys(query); driver.findElement(By.cssSelector("input[type=\"submit\"]")).click(); } @Override public void init(final WebDriver driver) { this.driver = driver; } }
Here we see a rather standard approach to using a web driver to search for items, i.e. driver.findElement (By.something ()).
With a slight hand movement, you can transform this class using annotations.
public class AnnotatedSearchPage implements SearchPage { @FindBy(id = "text") private WebElement searchField; @FindBy(css = "input[type=\"submit\"]") @CacheLookup private WebElement searchButton; @Override public void search(final String query) { searchField.sendKeys(query); searchButton.click(); } @Override public void init(final WebDriver driver) { PageFactory.initElements(driver, this); } }
How it works?! In the init () method, we call
PageFactory.initElements(driver, this);
. The driver does not begin to search for elements on the page immediately, but searches for them as soon as we turn to the class field. For example, the string
searchButton.click();
“Turns” into
driver.findElement(By.cssSelector("input[type=\"submit\"]")).click();
It seems that there are not enough advantages from such an approach, but they are:
- You do not need to write driver.findElements (...) and copy-paste this search throughout the class;
- You can use the @CacheLookup annotation: having found the element for the first time, the driver caches it and in the future already uses the cached object, which gives a small increase in the speed of tests;
- you can get away from using the WebElement interface and create your own classes for page elements like Button, TextField, etc.
Creating your own classes for page elements
I have repeatedly bothered about the fact that the WebElement interface is not very convenient:
- it provides unnecessary functionality, for example, .getCssValue (), which is absolutely unnecessary in the selenium tests;
- it cannot be expanded, i.e. you cannot add a couple of your own very convenient methods for every day, well, and, accordingly, unnecessary methods cannot be removed from it (for example, the .isEnabled () method simply does not make sense for any reference);
- in order to work with more complex elements (for example, drop-down lists), you have to explicitly call the constructor of the Select class, which is more like some kind of hack.
Let's see how you can modify the page-class using its interfaces for the elements on the page.
public class ExtendedSearchPage implements SearchPage { @FindBy(id = "text") private TextField searchField; @FindBy(css = "input[type=\"submit\"]") private Button searchButton; @Override public void search(final String query) { searchField.clearAndType(query); searchButton.click(); } @Override public void init(final WebDriver driver) { PageFactory.initElements(new ExtendedFieldDecorator(driver), this); } }
So, here we already use TextField and Button instead of WebElement. The key point here is the use of a self-written FieldDecorator in the init () method. Now it is he who initializes the fields of the class ExtendedSearchPage. Pros from using this approach:
- readability of tests increases: looking at the field type, it immediately becomes clear that this is a button, and not just some abstract element on the page, although in fact it may not be a button, but it depends on the implementation of the Button class;
- the ability to add your own methods, for example, clearAndType () for input fields;
- a more elegant way to create container classes (tables, forms, etc.);
Of course, there is also a minus: for each element found, one more object is created in memory, which simply delegates all calls to the WebElement object.
Creating container classes
As usual, first a bit of code.
public class SearchPageWithSearchForm implements SearchPage { @FindBy(tagName = "form") private SearchForm searchForm; @Override public void search(final String query) { searchForm.search(query); } @Override public void init(final WebDriver driver) { PageFactory.initElements(new ExtendedFieldDecorator(driver), this); } } public class SearchForm extends AbstractContainer { @FindBy(id = "text") private TextField searchField; @FindBy(css = "input[type=\"submit\"]") private Button searchButton; public void search(final String query) { searchField.clearAndType(query); searchButton.click(); } }
Here we already see a full-fledged class for the search form, which can be safely reused on different pages (of course, if the search form is implemented in the same way on all pages of the site). Additionally, it is possible to implement the process of initializing container objects in such a way that the search for elements will take place only inside the container, and not across the whole page. Thus, it turns out that the container does not know anything about the surrounding world, which ultimately allows us to use its for testing other pages.
Sources
Sample sources can be downloaded
from here .