Table of Contents
Extensions (Static Methods)
Due to the way we structure our framework, we can create static methods that we can use as extensions on methods involving different object types, such as tapping or long-pressing on a MobileElement, swiping from one element to another, explicitly waiting for an AppiumDriver, or waiting for a MobileElement to be displayed or clickable etc.
MobileElement Extensions
We will begin each method name with ‘me’ (for MobileElement).
meWaitForSeconds() Static Method
- Open up the ‘MobileElementExtensions’ class in the ‘utils.extensions’ package and add the following static method for making the Driver wait for a specified number of seconds…
1234public static WebDriverWait meWaitForSeconds() {WebDriverWait wait = new WebDriverWait(appDriver(), sec);return wait;} - Make sure the import for ‘WebDriverWait’ and the static import for appDriver() method have been added above the class…
123import org.openqa.selenium.support.ui.WebDriverWait;import static utils.appium.Driver.appDriver; - Add a static integer variable set to a value of 10 for ‘sec’, and make it private to the class it is in, just above the weWaitForSeconds() method…
1private static int sec = 10;
meElementIsDisplayed() Static Method
- Add the following static method in the ‘MobileElementExtensions’ class to check that a given MobileElement is displayed, after waiting the specified amount of time from the weWaitForSeconds() method …
1234public static boolean meElementIsDisplayed(MobileElement element) {meWaitForSeconds().until(ExpectedConditions.visibilityOf(element));return element.isDisplayed();} - Also make sure the import for ‘ExpectedConditions’ is added above the class, as well as an import for ‘WebElement’…
12import org.openqa.selenium.support.ui.ExpectedConditions;import io.appium.java_client.MobileElement;
meElementToBeClickable() Static Method
- Add the following static method in the ‘MobileElementExtensions’ class to check that a given MobileElement is clickable, after waiting the specified amount of time from the meWaitForSeconds() method…
1234public static boolean meElementToBeClickable(MobileElement element) {meWaitForSeconds().until(ExpectedConditions.elementToBeClickable(element));return element.isEnabled();}
meTap() Static Method
- Add the following static method in the ‘MobileElementExtensions’ class to wait for a given MobileElement to be clickable/tappable, highlight the clickable/tappable MobileElement, and then tap the MobileElement (this is better than simply clicking as on mobile the more realistic action would actually be tapping)…
1234public static void meTap(MobileElement mobileElement) {meElementToBeClickable(mobileElement);touchAction.tap(tapOptions().withElement(element(mobileElement))).perform();}
For the above to work, we need to add the following static imports…
12import static io.appium.java_client.touch.TapOptions.tapOptions;import static io.appium.java_client.touch.offset.ElementOption.*;
We also need to make sure we import the TouchAction class…
1import io.appium.java_client.TouchAction;
We then need to add a a private static TouchAction object in the top of our class (this way we can reuse the TouchAction class in other similar methods)…
1private static TouchAction touchAction = new TouchAction(appDriver());
meLongPress() Static Method
- Add the following method for long pressing on a Mobile element for two seconds, and then releasing your finger from it…
12345public static void meLongPress(MobileElement mobileElement) {meElementToBeClickable(mobileElement);touchAction.longPress(longPressOptions().withElement(element(mobileElement)).withDuration(ofSeconds(2))).release().perform();}
For the above to work, we need to add two more static imports for all Duration methods (* is wildcard for all) and LongPressOptions longPressOptions method…
12import static io.appium.java_client.touch.LongPressOptions.longPressOptions;import static java.time.Duration.*;
meSwipeFromElementToElement() Static Method
- Add the following static method in the ‘MobileElementExtensions’ class to check that the elements to swipe to and from are clickable and highlighted, and then press down on one element and swipe it to another element and then release the finger / touch…
123456public static void meSwipeFromElementToElement(MobileElement fromMobileElement, MobileElement toMobileElement) {meElementToBeClickable(fromMobileElement);meElementToBeClickable(toMobileElement);touchAction.longPress(longPressOptions().withElement(element(fromMobileElement)).withDuration(ofSeconds(2))).moveTo(element(toMobileElement)).release().perform();}
meSwipeDirection() Static Methods
- Add the following DIRECTION enum to the MobileExtensions class…
123public enum DIRECTION {DOWN, UP, LEFT, RIGHT;} - Add the following method which uses a switch case with the enum we made to swipe in any given direction…
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657public static void meSwipe(DIRECTION direction, long duration) {Dimension size = appDriver().manage().window().getSize();int startX = 0;int endX = 0;int startY = 0;int endY = 0;switch (direction) {case RIGHT:startY = (int) (size.height / 2);startX = (int) (size.width * 0.90);endX = (int) (size.width * 0.05);new TouchAction(appDriver()).press(PointOption.point(startX, startY)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(duration))).moveTo(PointOption.point(endX, startY)).release().perform();break;case LEFT:startY = (int) (size.height / 2);startX = (int) (size.width * 0.05);endX = (int) (size.width * 0.90);new TouchAction(appDriver()).press(PointOption.point(startX, startY)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(duration))).moveTo(PointOption.point(endX, startY)).release().perform();break;case UP:endY = (int) (size.height * 0.70);startY = (int) (size.height * 0.30);startX = (size.width / 2);new TouchAction(appDriver()).press(PointOption.point(startX, startY)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(duration))).moveTo(PointOption.point(endX, startY)).release().perform();break;case DOWN:startY = (int) (size.height * 0.70);endY = (int) (size.height * 0.30);startX = (size.width / 2);new TouchAction(appDriver()).press(PointOption.point(startX, startY)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(duration))).moveTo(PointOption.point(startX, endY)).release().perform();break;}}
meSendKeys() Static Method
- Add the following static method in the ‘MobileElementExtensions’ class to wait for a given textfield MobileElement to be displayed, clear the textfield if necessary (if ‘clearFirst’ is true), and then input a given String into the textfield…
12345public static void meSendKeys(MobileElement element, String text, boolean clearFirst) {meElementIsDisplayed(element);if (clearFirst) meTap(element);element.sendKeys(text);}
meElementIsInvisible() Static Method
- Add the following static method in the ‘MobileElementExtensions’ class to wait for a given MobileElement to not be visible anymore…
1234public static boolean meElementIsInvisible(MobileElement element) {meWaitForSeconds().until(ExpectedConditions.invisibilityOf(element));return !element.isDisplayed();}
meGetAttribute() Static Method
- Add the following static method in the ‘MobileElementExtensions’ class to find a specified attribute of a given element…
123public static String meGetAttribute(MobileElement element, String attribute) {return element.getAttribute(attribute);}
Espresso Specific Mobile Commands
Espresso is an Android test automation library maintained by Google. It has a number of advantages, for example built-in view synchronisation that ensures element finding happens during idle periods in your app.
We can use Espresso framework with our Appium driver when running tests. Espresso contains certain mobile commands / gestures which are specific to the framework. The ‘EspressoExtensions’ class will contain static methods for each of these Espresso specific mobile commands.
First things first, let’s make this ‘EspressoExtensions’ class extend ‘MobileElementExtensions’ class and also add a private static AndroidDriver variable and set it to what’s returned from our ‘appDriver()’ method (making sure we add the correct static import), like below…
1 2 3 4 5 6 7 8 9 10 |
package utils.extensions; import io.appium.java_client.android.AndroidDriver; import static utils.appium.Driver.appDriver; public class EspressoExtensions extends MobileElementExtensions { private static AndroidDriver driver = appDriver(); } |
We will being each method name with ‘es’ (as it is specific to Espresso mobile gestures).
esSwipe() Static Methods
esSwipe(String direction) Static Method
- Add the following static method in the ‘EspressoExtensions’ class in the ‘utils.extensions’ package to swipe the whole screen in a given direction…
12345public static void esSwipe(String direction) {Map<String, Object> params = new HashMap<>();params.put("direction", direction);driver.executeScript("mobile: swipe", params);} - Also make sure the following imports are added…
123import org.openqa.selenium.JavascriptExecutor;import java.util.HashMap;import java.util.Map;
esSwipe(String direction, MobileElement element) Static Method
This is an overloaded version of the above method that also takes a given element that can be swiped on specifically.
- Add the following overloaded static method in the ‘EspressoExtensions’ class to swipe a given element in a given direction…
1234567public static void esSwipe(String direction, MobileElement element) {meElementIsDisplayed(element);Map<String, Object> params = new HashMap<>();params.put("direction", direction);params.put("element", element.getId());driver.executeScript("mobile: swipe", params);}
Also make sure the following import is added for MobileElement…
1import io.appium.java_client.MobileElement;
esIsToastVisible(String text, boolean isRegEx) Static Method
- Add the following static method in the ‘EspressoExtensions’ class to check whether a toast message containing given text (and whether it is Regular Expression or not) is visible…
123456public static void esIsToastVisible(String text, boolean isRegEx) {Map<String, Object> params = new HashMap<>();params.put("text", text);params.put("isRegexp", isRegEx);driver.executeScript("mobile: isToastVisible", params);}
esOpenDrawer() Static Methods
esOpenDrawer(MobileElement element) Static Method
- Add the following overloaded static method in the ‘EspressoExtensions’ class to open the drawer for a given element…
12345public static void esOpenDrawer(MobileElement element) {Map<String, Object> params = new HashMap<>();params.put("element", element);driver.executeScript("mobile: openDrawer", params);}
esOpenDrawer(MobileElement element, int gravity) Static Method
- Add the following static method in the ‘EspressoExtensions’ class to open the drawer for a given element with a given amount of gravity…
123456public static void esOpenDrawer(MobileElement element, int gravity) {Map<String, Object> params = new HashMap<>();params.put("element", element);params.put("gravity", gravity);driver.executeScript("mobile: openDrawer", params);}
esCloseDrawer() Static Methods
esCloseDrawer(MobileElement element) Static Method
- Add the following static method in the ‘EspressoExtensions’ class to close the drawer for a given element…
12345public static void esCloseDrawer(MobileElement element) {Map<String, Object> params = new HashMap<>();params.put("element", element);driver.executeScript("mobile: closeDrawer", params);}
esCloseDrawer(MobileElement element, int gravity) Static Method
- Add the following overloaded static method in the ‘EspressoExtensions’ class to close the drawer for a given element with a given amount of gravity…
123456public static void esCloseDrawer(MobileElement element, int gravity) {Map<String, Object> params = new HashMap<>();params.put("element", element);params.put("gravity", gravity);driver.executeScript("mobile: closeDrawer", params);}
esSetDate(MobileElement element, int year, int monthOfYear, int dayOfMonth) Static Method
- Add the following static method in the ‘EspressoExtensions’ class to set the date on a given DatePicker element…
12345678public static void esSetDate(MobileElement element, int year, int monthOfYear, int dayOfMonth) {Map<String, Object> params = new HashMap<>();params.put("element", element);params.put("year", year);params.put("monthOfYear", monthOfYear);params.put("dayOfMonth", dayOfMonth);driver.executeScript("mobile: setDate", params);}
esSetTime(MobileElement element, int hours, int minutes) Static Method
- Add the following static method in the ‘EspressoExtensions’ class to set the time on a given TimePicker element…
1234567public static void esSetTime(MobileElement element, int hours, int minutes) {Map<String, Object> params = new HashMap<>();params.put("element", element);params.put("hours", hours);params.put("minutes", minutes);driver.executeScript("mobile: setTime", params);}
esNavigateTo(MobileElement element, int menuItemId) Static Method
- Add the following static method in the ‘EspressoExtensions’ class to navigate to a given menu item in a given navigation element (the navigation element must be a child of a DrawerLayout, of type NavigationView, visible on screen and displayed on screen)…
esScrollToPage() Static Methods
esScrollToPage(MobileElement element, String scrollTo, boolean smoothScroll) Static Method
- Add the following static method in the ‘EspressoExtensions’ class to scroll to a given page (first, last, left or right) smoothly or not, using a given element…
1234567public static void esScrollToPage(MobileElement element, String scrollTo, boolean smoothScroll) {Map<String, Object> params = new HashMap<>();params.put("element", element);params.put("scrollTo", scrollTo);params.put("smoothScroll", smoothScroll);driver.executeScript("mobile: scrollToPage", params);}
esScrollToPage(MobileElement element, int ScrollToPage) Static Method
- Add the following static method in the ‘EspressoExtensions’ class to scroll to a given page number, using a given element…
123456public static void esScrollToPage(MobileElement element, int scrollToPage) {Map<String, Object> params = new HashMap<>();params.put("element", element);params.put("scrollToPage", scrollToPage);driver.executeScript("mobile: scrollToPage", params);}
Note: The whole list of Espresso mobile commands can be found at http://appium.io/docs/en/commands/mobile-command/#android-espresso-only
WebDriver Extensions
We will begin each method name with ‘wd’ (for WebDriver).
wdHighlight() Static Method
- Open up the ‘WebDriverExtensions’ class in the ‘utils.extensions’ package and add the following static method for highlighting a given locator using JavaScript…
123456public static Object wdHighlight(By locator) {WebDriver driver = appDriver();WebElement myLocator = driver.findElement(locator);JavascriptExecutor js = (JavascriptExecutor) appDriver();return js.executeScript(wdHighlightedColour, myLocator);} - You will notice that an error is thrown due to not knowing what ‘wdHighlightedColour’ is, so let’s add that in the ‘Settings’ class of the ‘utils.selenium’ package (of course you can also customise this to change the colour and size of the border etc.)…
1public static String wdHighlightedColour = "arguments[0].style.border='5px solid blue"; - Next, go back to the ‘WebDriverExtensions’ class and add a static import for ‘wdHighlightedColour’ in the ‘Settings’ class…
1import static utils.appium.Settings.wdHighlightedColour; - Of course, also check that the imports for ‘WebDriver’, ‘WebElement’, ‘JavaScriptExecutor’ and ‘By’ have been added above the class, as well as a static import again for the browser() method in the ‘Drivers’ class of the ‘utils.selenium’ package…
12345import org.openqa.selenium.By;import org.openqa.selenium.JavascriptExecutor;import org.openqa.selenium.WebDriver;import org.openqa.selenium.WebElement;import static utils.appium.Driver.appDriver;
wdElementIsDisplayed() Static Method
- Open up the ‘WebDriverExtensions’ class in the ‘utils.extensions’ package and add the following static method for checking that a highlighted locator is displayed…
12345public static Object wdElementIsDisplayed(By locator) {wdHighlight(locator);WebDriverWait wait = new WebDriverWait(appDriver(), sec);return wait.until(ExpectedConditions.visibilityOf((WebElement) locator));} - Make sure the import for ‘WebDriverWait’ and ‘ExpectedConditions’ have been added above the class…
12import org.openqa.selenium.support.ui.ExpectedConditions;import org.openqa.selenium.support.ui.WebDriverWait; - Add a static integer variable set to a value of 10 for ‘sec’, and make it private to the class it is in, just above the weWaitForSeconds() method…
1private static int sec = 10;
wdElementIsInvisible() Static Method
- Add the following static method in the ‘WebDriverExtensions’ class to wait for a given locator to not be visible…
1234public static void wdElementToDisappear(By locator) {WebDriverWait wait = new WebDriverWait(appDriver(), 5);wait.until(ExpectedConditions.invisibilityOfElementLocated(locator));}
wdFindElement() Static Method
- Add the following static method in the ‘WebDriverExtensions’ class to find a displayed element by its locator…
1234public static WebElement wdFindElement(By locator) {wdElementIsDisplayed(locator);return appDriver().findElement(locator);}
wdClick() Static Method
- Add the following static method in the ‘WebDriverExtensions’ class to click on an element which has been found by its locator…
123public static void wdClick(By locator) {wdFindElement(locator).click();}
wdSendKeys() Static Method
- Add the following static method in the ‘WebDriverExtensions’ class to wait for a given textfield element (which has been found by its locator) to be displayed, clear the textfield if necessary (if ‘clearFirst’ is true), and then input a given String into the textfield…
1234public static void wdSendKeys(By locator, String text, boolean clearFirst) {if (clearFirst) wdClick(locator);wdFindElement(locator).sendKeys(text);}
Refactoring our Test Scenario to use the Extensions
We can now use our extensions (static methods) to replace previous lines of code in our test methods. Over time, this helps us follow DRY principles (Don’t Repeat Yourself) as well as make our code easier to read 🙂
‘skipWelcomeScreen()’ method
- Open up the ‘NavigationScenarios.feature’ file, right-click on the ‘And the welcome screen is skipped without logging in’ step, and select ‘Go To’ –> ‘Declaration’
- Right-click on the ‘.skipWelcomeScreen()’ method call and select ‘Go To’ –> ‘Declaration’ again
- Edit the ‘skipWelcomeScreen()’ method so it uses the meTap() static method from the ‘MobileElementExtensions’ class (you will have to add a static import to access all the static methods in the MobileElementExtensions class)…
1import static utils.extensions.MobileElementExtensions.*;
12345public PopularTabPage skipWelcomeScreen() {meTap(btnSkipForNow);log.info(":: We click the 'Skip For Now' button on the Welcome screen");return instanceOf(PopularTabPage.class);}
‘navToHomeTab()’ method
- Open up the ‘NavigationScenarios.feature’ file again, right-click on the ‘When I view the Home tab’ step, and select ‘Go To’ –> ‘Declaration’
- Right-click on the ‘.navToHomeTab()’ method call and select ‘Go To’ –> ‘Declaration’ again
- Edit the ‘navToHomeTab()’ method so it uses the meTap() static method from the ‘MobileElementExtensions’ class again (you will have to add a static import to access all the static methods in the MobileElementExtensions class)…
1import static utils.extensions.MobileElementExtensions.*;
12345public HomeTabPage navToHomeTab() {meTap(homeTab);log.info(":: We navigate to the 'Home' tab");return instanceOf(HomeTabPage.class);}
Running the Test Scenario
Running the Test
- Open up ‘NavigationScenarios.feature’ file and make sure you have the ‘@Nexus5xOreo’ tag at the top of the feature file or above each test scenario you want to run
- Right-click on the test scenario and run it
It should pass 😀
