Today we can find mobile applications for almost everything—gaming, social networking, banking, music, etc. Such a variety of applications developed by a single developer, small startup, or solid company means different development approaches. These approaches are represented by native and hybrid applications. Native applications are developed for a mobile operating system following platform standards, user interface, and user experience guidelines and access mobile device capabilities like the camera, GPS, etc. Hybrid applications typically use websites that are in a native wrapper or container. On Android, this container is called the WebView .
Web browser applications
Registration or login forms with Google, Facebook, or Twitter accounts
Legal and privacy disclaimers
Application FAQs
Support contact forms
We already know that native Android applications can be tested by Espresso. This chapter presents Espresso-Web and shows how it can be used to test Android WebView UI components integrated into mobile applications. Both Espresso and Espresso-Web can be used in combination to fully interact with an application on its different levels.
Espresso-Web Basics
Similar to Espresso’s onData() method, a WebView interaction is comprised of several atoms. WebView interactions use a combination of the Java programming language and a JavaScript bridge to do their work. Because there is no chance of introducing race conditions by exposing data from the JavaScript environment—everything Espresso sees on the Java-based side is an isolated copy—returning data from Web.WebInteraction objects is fully supported, allowing you to verify all the data that’s returned from a request.
The WebDriver framework uses atoms to find and manipulate web elements programmatically. Atoms are used by WebDriver to accommodate browser manipulation. An atom is conceptually similar to a ViewAction. It’s a self-contained unit that performs an action in your UI. You expose atoms using a list of defined methods, such as findElement() and getElement(), to drive the browser from the user’s point of view. However, if you use the WebDriver framework directly, atoms need to be properly orchestrated, requiring logic that is quite verbose.
Within Espresso, the Web and Web.WebInteraction classes wrap this boilerplate and give an Espresso-like feel to interacting with WebView objects. So, in the context of a WebView, atoms are used as a substitution to traditional Espresso ViewMatchers and ViewActions.
The API then looks quite simple, as follows.
To add Espresso-Web to a project, insert the following line of code into the application build.gradle file.
Or add the same dependency to the AndroidX Test Library.
Espresso-Web Building Blocks
WebInteractions—An analogue to Espresso’s ViewInteraction or DataInteraction. Used to perform actions and call validation methods, locate web elements, and set WebView properties.
DriverAtoms—A collection of JavaScript atoms from the WebDriver project.
WebAssertions—Asserts that the given atom’s result is accepted by the provided matcher.
reset()—Deletes the Element and Window references from the web interaction.
forceJavascriptEnabled()—Forces JavaScript usage on a WebView. Enabling JavaScript may reload the WebView under test.
withNoTimeout()—Disables all timeouts on this WebInteraction.
withTimelout()—Sets a defined timeout for current WebInteraction.
inWindow()—Causes this WebInteraction to perform JavaScript evaluation in a specific DOM window.
withElement()—Causes this WebInteraction to supply the given ElementReference to the atom prior to evaluation. After calling this method, it resets any previously selected ElementReference.
withContextualElement()—Evaluates this WebInteraction on the subview of the selected element. Similar to the Espresso withChild() method.
perform()—Executes the provided atom within the current context. This method blocks until the atom returns. Produces a new instance of WebInteraction that can be used in further interactions.
check()—Evaluates the given WebAssertion. After this method completes, the result of the atom’s evaluation is available via get.
get()—Returns the result of a previous call to perform or check.
For better understanding, web interactions can be split into different groups where each group represents some functional load, as shown in Figure 6-1.
webClick()—Simulates the JavaScript events to click on a particular element.
clearElement()—Clears content from an editable element.
webKeys()—Simulates JavaScript key events sent to a certain element.
findElement()—Finds an element using the provided locatorType strategy.
selectActiveElement()—Finds the currently active element in the document.
selectFrameByIndex()—Selects a subframe of the currently selected window by its index.
selectFrameByIdOrName()—Selects a subframe of the given window by its name or ID.
getText()—Returns the visible text beneath a given DOM element.
webScrollIntoView()—Returns true if the desired element is in view after scrolling.
webMatches()—A WebAssertion that asserts that the given atom’s result is accepted by the provided matcher.
webContent()—A WebAssertion that asserts that the document is matched by the provided matcher.
You might wonder why the onWebView() method shown in Figure 6-4 takes the Espresso ViewMatcher (discussed in Chapter 1) as a parameter. The WebView UI element is still an Android native component and can have its own ID, content description, and other element properties. If we have multiple WebView components inside the application screen, we have to specify which WebView we want to operate on.
As you can see, the WebView component can be identified using Espresso ViewMatcher based on the ID property web_view.
For your convenience, an Espresso-Web cheat sheet is included in Appendix A and as an addition to the sample application source code.
Exercise 17
- 1.
Launch a sample application and navigate to Settings. Open the WebView sample section and do the layout dump with the LayoutInspector tool. Observe which WebView properties can be used in UI tests.
- 2.
Similar to Step 1, do the layout dump using a monitor application. Observe which WebView properties can be analyzed using the monitor tool and compare it to the LayoutInspector results.
Writing Tests with Espresso-Web
We are now ready to dive into Espresso web tests. For better understanding, open the web_form.html and web_form_response.html files from the main application assets folder in any browser, open the browser developer tools, and then start to inspect the web pages. It is assumed that you have a basic understanding of HTML page structure and can inspect web page UI elements using browser developer tools.
CLASS_NAME("className")
CSS_SELECTOR("css")
ID("id")
LINK_TEXT("linkText")
NAME("name")
PARTIAL_LINK_TEXT("partialLinkText")
TAG_NAME("tagName")
XPATH("xpath")
The web page is built in a way that allows you to showcase most of the Espresso-Web functionality. Open the chapter6.WebViewTest.kt class to see the implemented test cases. Here is the updatesLabelAndOpensNewPage() test case.
Here, everything is simple. After navigating to the Settings section and clicking on the WebView sample item, the WebView is shown using Android WebViewClient. Espresso-Web handles web page loading, so there is no need to implement additional waiters. All the elements in this test case are located by their IDs, which is the ideal case.
The next test case shows how to find web elements by their CSS properties. This is the common case when element IDs are dynamically created and we cannot rely on them.
Another way a web element can be located is by the XPATH selector, as follows.
Note
Web browser developer tools can help locate elements by XPATH or CSS selectors. It is enough to use the CMD+F or CTRL+F shortcut and try expression on the search field. Elements are highlighted in the page layout.
The next sample test case shows how to operate on elements inside the dialog popup.
The next test case is about testing the interaction with the HTML <select> component. This turns out to be a problematic topic. To begin, the following test case was implemented.
The thing is that this test case fails on the last check only because the HTML <select> component list is not shown, even though webClick() was sent to the found element. Changing the locator type doesn’t help in this case and it is not needed because the element was found. This leads to the fact that something is wrong with the webClick() action only for the HTML <select> element. And after a bit of research, it turned out to be a known problem and there is even a workaround to make it work with the additional button:
Browsers do not allow expanding <select> in pure JavaScript, that control can be expanded only by directly clicking on it using the mouse. The “select.click()” won't work. But there is a solution. We imitate expanded <select> control by creating another select with multiple options being displayed at once, this can be done by setting the “size” parameter. That multiselect will be positioned absolutely over the old single-option select control, and the old one will be hidden using style’s visibility. That way the layout is kept the same, and the new control is displayed seamlessly. The new control looks only little differently, but that shouldn't be a problem, see it for yourself in screenshots below.
But there is a workaround from the testing side, without introducing UI components on the web page. Being on the screen with the web view shown, we can expand <select> by sending a ViewActions.pressKey(KeyEvent.KEYCODE_SPACE) event when it is focused. Just as if you do it via the browser. To move focus to the <select> element, we send as many tab actions as needed to navigate to the desired UI element—ViewActions.pressKey(KeyEvent.KEYCODE_TAB). Unfortunately, tests should sleep for a short amount of time, so the sent action can be applied in the web view. This is how it is done with our sample application.
This test case is fully functional but doesn’t look good. By adding an additional expand function to ViewInteraction, we can clean up our test code.
The verifiesSelectDropDown() test case becomes much more readable.
The last test case in the WebViewTest.kt class contains the rest of the Locator type’s usage samples.
This test case shows how the webScrollIntoView() action can be used as a parameter to the WebAssertion.webMatches() method . This approach provides a more readable error description when the element we operate on is not found.
Exercise 18
- 1.
Open the web_form.html page in the web browser and analyze the page structure. Search the elements by XPATH and CSS selectors.
- 2.
Update the selectsRadioButtonWithCss() test so that the radio button with “Option 2” label is selected.
- 3.
Write a test that finds all the elements by their XPATHs only.
- 4.
Write a test that finds all the elements by their CSS locators only.
Summary
Espresso-Web is a nice addition to the Espresso APIs. It allows you to test hybrid applications with WebView components. Yes, it is not perfect and can’t be used for purely web application testing, but it does its job quite well in an Espresso-like manner.