The Page Object Model, also known as POM, is a design pattern in Selenium and Appium that creates an object repository for storing all web elements. It provides cleaner test code, separation of concerns and reduces overall code duplication.
The Page Object Model principle keeps locators and test login separately from each other.
A single Page Object is an instantiation of an object-orientated class, describing locators on a specific page (or view) and its interactions.
What are the advantages of using Page Object Model?
There are several advantages to using the Page Object Model design pattern.
- Improves code maintenance: All locators for a specific flow are grouped together in a specific POM. If a page or application changes its UI, you no longer need to modify all your tests scattered with locators. Simply change it in the page object model.
- Encourages code reuse: There is no need to write the same logic across different tests. For example, no need to keep doing the same log in flow if your test requires a logged-in state. Keep all logic for a specific page or action in its own POM file.
- Improves code readability: You can find where specific test code is located more easily. If a specific change is required, you can do it in the appropriate file. You no longer need to modify several files for a change in one specific test logic.
POM Example
A good example of a Page Object Model would be the Login Page Object.
This will be responsible for typing in the username and password, and proceeding with the actual login.
Other test cases can use this POM when a logged-in state is required.
package com.testingbot.example;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.AndroidElement;
import io.appium.java_client.android.AndroidKeyCode;
import io.appium.java_client.pagefactory.AndroidFindBy;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import org.openqa.selenium.support.PageFactory;
public class LoginPage {
private AndroidDriver<AndroidElement> driver;
public LoginPage() {
}
public LoginPage(AndroidDriver<AndroidElemen>> driver) {
this.driver = driver;
}
By keyboard = By.className("UIAKeyboard");
By userNameElement = By.id("userName");
By passwordElement = By.id("password");
By loginElement = By.id("login");
public boolean isDisplayed() {
return loginElement.isDisplayed();
}
public void typeUserName(String name) {
userNameElement.sendKeys(name);
}
public void typePassword(String password) {
passwordElement.sendKeys(password);
}
public void clickLogin() {
loginElement.click();
}
public void hideKeyboardIfVisible() {
if (keyboard != null) {
driver.pressKeyCode(AndroidKeyCode.KEYCODE_ESCAPE);
}
}
public void login (String name, String password) {
hideKeyboardIfVisible();
typeUserName(name);
typePassword(password);
clickLogin();
}
}
This is an example of a Login Page Object Model. The login
method will be responsible for several things. It will hide the soft keyboard, type in a username, a password and submit.
This is logic that may be used by several other test cases that require a logged in state.
Of course, you can implement Page Object Model in any programming language; Java, Kotlin, Swift, ...
How can I use the Page Factory with Appium?
You can use Selenium's PageFactory class with Appium and POM.
The PageFactory class can be used with initElements
to initialize all elements located by the @FindBy
(or AndroidFindBy
) annotation.
PageFactory will automatically assign the elements to your Page Object Model.
package com.testingbot.example;
import io.appium.java_client.MobileBy;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.AndroidElement;
import io.appium.java_client.android.AndroidKeyCode;
import io.appium.java_client.pagefactory.AndroidFindBy;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import org.openqa.selenium.support.PageFactory;
import java.util.concurrent.TimeUnit;
public class ExamplePage {
private AndroidDriver driver;
public ProfilePage() {
}
public ProfilePage(AndroidDriver driver) {
this.driver = driver;
PageFactory.initElements(new AppiumFieldDecorator(driver), this);
}
@AndroidFindBy(className = "UIAKeyboard")
private AndroidElement keyboard;
@AndroidFindBy(id = "userName")
private AndroidElement userNameElement;
@AndroidFindBy(id = "password")
private AndroidElement passwordElement;
@AndroidFindBy(id = "login")
private AndroidElement loginElement;
public boolean isDisplayed() {
return loginElement.isDisplayed();
}
public void typeUserName(String name) {
userNameElement.sendKeys(name);
}
public void typePassword(String password) {
passwordElement.sendKeys(password);
}
public void clickLogin() {
loginElement.click();
}
public void hideKeyboardIfVisible() {
if (keyboard != null) {
driver.pressKeyCode(AndroidKeyCode.KEYCODE_ESCAPE);
}
}
public void login (String name, String password) {
hideKeyboardIfVisible();
typeUserName(name);
typePassword(password);
clickLogin();
}
}
There are various annotations which you can use for declaring elements:
-
@FindBy
PageFactory will automatically assign elements annotated with
@FindBy
, skipping the requirement of usingdriver.findElement()
-
@FindBys
Use more than 1 criteria to locate elements, using an
AND
condition; all criteria must be met. It uses nested@FindBy
annotations to locate an element. -
@FindAll
Similar to
@FindBys
but uses anOR
condition; at least one criteria must be matched. -
@CacheLookUp
Can be used to keep specific elements in memory. If your tests use a specific element frequently, you might see a small speedup using this.
What are the different locator strategies that are used for @FindBy?
FindBy uses the same locator strategies as the findElement()
method used in POM.
-
id
-
name
-
className
-
xpath
-
css
-
tagName
-
linkText
-
partialLinkText