Android Autotesting With Kaspresso

Written by azamatnurkhojayev | Published 2024/02/09
Tech Story Tags: android | android-tutorial | kaspresso | android-development | android-app-development | mobile-app-development | android-guide | android-autotesting

TLDRKaspresso is a powerful framework for automating testing of mobile applications on the Android platform.via the TL;DR App

Kaspresso is a powerful framework for automating the testing of mobile applications on the Android platform.

One of the key features of Kaspresso is its user-friendly and intuitive architecture. Thanks to the modular approach, developers can easily customize and modify test scripts and take different approaches to creating test cases.

Kaspresso libraries allow you to effectively interact with UI elements, perform actions, and test expected results.

Thus, Kaspresso is an incredibly useful tool for developers and testers, which facilitates and speeds up the process of automating the testing of mobile applications on the Android platform.

Kaspresso is based on Espresso and UI Automator and provides a wide range of additional functions, such as:

  • Built-in protection against flaky tests
  • Jetpack Compose support
  • Screenshot testing with native approach (with dark mode support)
  • Declarative approach for writing tests
  • Ability to interact with other applications and system elements and interfaces
  • Human readability with Kotlin DSL wrappers over UiAutomator and Espresso
  • Detailed logs and reports (logs, view hierarchy, screenshots, video, etc.)
  • ADB support
  • Allure support
  • Robolectric support
  • Easy migration from Espresso
  • Flexible configuration options
  • Automatic artifacts pulling after test execution
  • Significantly faster execution of UI Automator commands. With Kaspresso, some UI Automator commands run 10 times faster!
  • Page object pattern from the box

Let's get started with the project. First, you need to add dependencies to the build.gradle file:

androidTestImplementation 'com.kaspersky.android-components:kaspresso:1.5.3'

For example, I’ll take a ready-made test Notes application. Where it will be possible to add, change, and delete notes.

We will write the test in the androidTest folder

We create the MainScreen class and inherit it from KScreen. So, we follow the Page Object approach.

The Page Object approach implies that the modeled class will completely describe one screen of the application under test - all screen elements and methods for interacting with these elements. This way, we won't have to re-declar the same elements every time in our autotests.

Next, we redefine layoutId and viewClass in our case, it is R.layout.activity_main and MainActivity::class.java:

object MainScreen: KScreen<MainScreen>() {  
  
    private val buttonAddNote = KButton{ withId(R.id.add_button) }  
  
    fun clickAddNote(){  
        buttonAddNote.click()  
    }  
  
    override val layoutId: Int = R.layout.activity_main  
    override val viewClass: Class<*> = MainActivity::class.java  
  
}

Kaspresso uses the convenient Kotlin DSL over Espresso to interact with elements provided by the Kakao library. Many ready-made wrappers have already been created for standard UI widgets. Let's use one of them to find a button - we need the KButton class. We initialize it with a block, inside which we call a function to search for the widget using the id of the desired button - withId(R.id.add_button).

Then, we create the clickAddNote method and add a call to the click() method on the element.

Let's also add a test for the list of notes to MainScreen.

object MainScreen: KScreen<MainScreen>() {  
  
    private val buttonAddNote = KButton{ withId(R.id.add_button) }
      
    val noteRv = KRecyclerView(  
        builder = { withId(R.id.notes_rv) },  
        itemTypeBuilder = { itemType(::NoteItem) }  
    )  
  
    fun clickAddNote(){  
        buttonAddNote.click()  
    }  
  
    override val layoutId: Int = R.layout.activity_main  
    override val viewClass: Class<*> = MainActivity::class.java  
  
    class NoteItem(matcher: Matcher<View>) : KRecyclerItem<NoteItem>(matcher) {  
  
        val deleteIcon = KImageView(matcher) { withId(R.id.delete_icon) }  
        val tvNoteTitle = KTextView(matcher) { withId(R.id.title_tv) }  
        val tvNoteText = KTextView(matcher) { withId(R.id.notes_tv) }  
  
    }  
}

Added KRecyclerView for interaction with RecyclerView. In KRecyclerView's builder, we determine by which id to find the RecyclerView. itemTypeBuilder determines what elements our RecyclerView consists of. NoteItem is our class which is used to describe a list item i.e. elements of View Holder.

Now, let's write a test for the adding a note screen. Here, we are already using the input field, and we need the KEditText class, to enter text we call the typeText method. At the end, we call the pressBack method. The first time is needed to hide the keyboard, and the second time to close the screen, and when closing, notes are saved.

object NoteDetailScreen : KScreen<NoteDetailScreen>() {  
  
    private val titleEd = KEditText{ withId(R.id.title_et) }  
    private val noteEd = KEditText{ withId(R.id.note_et) }  
  
    fun inputNoteData(title: String, note: String) {  
        titleEd.typeText(title)  
        noteEd.typeText(note)  
        pressBack()  
		pressBack()
    }  
  
    override val layoutId: Int  
        get() = R.layout.fragment_notes_detail  
    override val viewClass: Class<*>  
        get() = NotesListFragment::class.java  
}

We create NoteTest by inheriting from TestCase and need to add a rule that provides control over the activity MainActivity in the autotest. This is necessary to be able to interact with the interface of this activity in the application, in our case this is our main screen.

class NoteTest : TestCase() {  
  
    @get:Rule  
    val activity = activityScenarioRule<MainActivity>()  
  
    @Test  
    fun noteTest() = run {  
  
        step("Navigate to add note screen") {  
            MainScreen.clickAddNote()  
        }  
  
        step("Add new note and back") {  
            NoteDetailScreen {  
                inputNoteData("Title first", "Note text")  
            }  
        }  
        step("Delete first note") {  
            MainScreen {  
                noteRv {  
                    childAt<MainScreen.NoteItem>(0) {  
                        tvNoteTitle.hasAnyText()  
                        tvNoteText.hasAnyText()  
                        deleteIcon.click()  
                    }  
                }            
            }        
        }    
    }
}

Next, we create the noteTest method with the @Test annotation. The first step is going to the page and adding a note. The next step is adding a new note using the NoteDetailScreen.inputNoteData method. The last step is to contact the RecyclerView and get the first element of the list, check whether the element has at least some text using the hasAnyText method, and at the end call deleteIcon to click.

Finally, we run the test and wait for the result!

Links:

Notes project with Kaspresso Kaspresso


Written by azamatnurkhojayev | I'm an Android developer. Teaches courses at the programming school, and writes articles about development.
Published by HackerNoon on 2024/02/09