• Stars
    star
    110
  • Rank 316,770 (Top 7 %)
  • Language
    Kotlin
  • License
    Apache License 2.0
  • Created over 7 years ago
  • Updated almost 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A kotlin library to simplify how to do espresso tests on Android.

codebeat badge bitrise badge Download

kappuccino

A framework to simplify the way you do instrumentation tests in your app, using Espresso and Kotlin.

Here is how you do instrumentation tests today, using simply Espresso:

@Test fun loginFieldsAreVisible() {
  onView(withId(R.id.username)).check(matches(isDisplayed())
  onView(withId(R.id.password)).check(matches(isDisplayed())
  onView(withId(R.id.login_button)).check(matches(isDisplayed())
}

This is just to check a simple login screen, and we are not even considering that we may need to scroll to one of these views, due to small screens support.

With scroll, our test will be something like this:

@Test fun loginFieldsAreVisible() {
  onView(withId(R.id.username)).perform(scrollTo()).check(matches(isDisplayed())
  onView(withId(R.id.password)).perform(scrollTo()).check(matches(isDisplayed())
  onView(withId(R.id.login_button)).perform(scrollTo()).check(matches(isDisplayed())
}

We have to repeat a lot of code, this makes the tests hard to read and understand at a first look. Also you may forget some scrollTo(), or mismatch the check function. At the end, all we want to do is check if the views with these ids are displayed.

So, this is how you do the same test with kappuccino library:

@Test fun loginFieldsAreVisible() {
  displayed {
    id(R.id.username)
    id(R.id.password)
    id(R.id.login_button)
  }
}

Cleaner, easier to write and understand. To scroll, all you have to do is pass a parameter to the function:

@Test fun loginFieldsAreVisible() {
  displayed(scroll = true) {
    id(R.id.username)
    id(R.id.password)
    id(R.id.login_button)
  }
}

Installation

1 - Setup kotlin in your project, see the instructions here

2 - Create a kotlin directory into 'src/androidTest/', check the sample code for reference.

3 - Set you sourceDataSet into your build.gradle file

sourceSets {
    androidTest.java.srcDirs = ['src/androidTest/kotlin']
  }

4 - Add library and dependencies into your build.gradle file and sync

androidTestImplementation 'br.com.concretesolutions:kappuccino:$latest.version'

5 - This library depends on the following libraries:

So, ensure those libraries are also in your dependencies.

androidTestImplementation "com.android.support.test.espresso:espresso-intents:$versions.espresso"
androidTestImplementation "com.android.support.test.espresso:espresso-core:$versions.espresso"
androidTestImplementation "com.android.support.test.espresso:espresso-contrib:$versions.espresso"
androidTestImplementation "com.android.support.test.uiautomator:uiautomator-v18:$versions.uiAutomator"

And you're ready to go!

If you have any module conflicts, try to exclude the conflicting module, for example:

androidTestImplementation('br.com.concretesolutions:kappuccino:$latest.version', {
        exclude group: 'com.android.support'
    })

Assertion methods

These are the methods to make view assertions

checked {}
notChecked {}

clickable {}
notClickable {}

selected {}
notSelected {}

displayed {}
notDisplayed {}

notExist {}

Action methods

These are methods to interact with views

click {}
doubleClick {}
longClick {}

typeText {}
clearText {}

Scroll

The scroll method is now a parameter for all the above methods, the default value is false, for example:

@Test fun scrollToButton_andClick() {
  click(scroll = true) {
    id(R.id.login_button)
  }
}

In this case, it will scroll to the view and click. If you don't provide a parameter, the scroll will not happen.

Combine matchers (Matchers.allOf)

To combine multiple matchers, use the allOf method:

@Test fun scrollToButton_andClick() {
  click(scroll = true) {
    allOf {
        id(R.id.login_button)
        text(R.string.login_button_text)
    }
  }
}

Hierarchy

There are two methods of hierarchy matchers: Parent and Descendant.

Parent

You can use Parent method with two different approaches: block matching or combining.

1 - Block matching:
For block matching, pass the parentId as method parameter.

Then, kappuccino will match all the views inside the block:

@Test fun matchParent_blockMatching_example() {
  displayed {
    parent(R.id.parent) {
        id(R.id.username)
        id(R.id.password)
        id(R.id.login_button)
    }
  }
}

Here, kappuccino will check if all the views (username, password and login_button) are descendant of the declared parent, and are displayed.

For better understanding, the code above is equivalent to the one below, using pure Espresso:

@Test fun matchParent_example() {
    onView(
        allOf(isDescendantOf(withId(R.id.parent)), withId(R.id.username)))
        .check(matches(isDisplayed()))
    onView(
        allOf(isDescendantOf(withId(R.id.parent)), withId(R.id.password)))
        .check(matches(isDisplayed()))
    onView(
        allOf(isDescendantOf(withId(R.id.parent)), withId(R.id.login_button)))
        .check(matches(isDisplayed()))
}

2 - Combination of matchers:
You can use the parent method as a combination of matchers:

@Test fun matchParent_combining_example() {
    displayed {
        allOf {
            parent {
                id(R.id.parent)
            }
            id(R.id.username)
        }
    }
}

Here, you will check if the view with id = R.id.username, and with parent with id = R.id.parent, is displayed

Descendant

It works just like the parent method, for both cases (block matching and combining matchers)

@Test fun descendant_block_example() {
    displayed {
        allOf {
            descendant {
                id(R.id.username)
            }
            id(R.id.parent)
        }
    }
}

Here, we'll check if the parent, with child R.id.username is displayed. Same use for block matching.

RecyclerView

To interact with the recycler view:

@Test fun recyclerView_example() {
    recyclerView(R.id.recycler_view) {
        sizeIs(10)
        atPosition(3) {
            displayed {
                id(R.id.item_description)
                text(R.string.description_text)
                text("Item header text")
            }
        }
    }
}

To type text in a RecyclerView item's EditText:

@Test fun recyclerView_textInput_example() {
    recyclerView(R.id.recycler_view) {
        atPosition(0) {
            typeText(R.id.editText, "Position 0")
        }

        atPosition(1) {
            typeText(R.id.editText, "Position 1")
        }
    }
}

To swipe a RecyclerView's item left or right:

@Test fun recyclerView_swipeLeft_example() {
    recyclerView(R.id.recycler_view() {
        atPosition(0) {
            swipeLeft()
        }

        atPosition(1) {
            swipeRight()
        }
    }
}

Menu and action bar

To interact with the options menu:

@Test
fun whenClickingOnItem1_shouldShowCorrectText() {
    menu {
        onItem(R.string.item_1) {
            click()
        }
    }

    displayed {
        text(R.string.item_1_selected)
    }
}

To interact with the action bar:

@Test
fun whenClickingOnActionBarItem_shouldClearText() {
    menu(openOptionsMenu = false) {
        onActionBarItem(R.id.item_clear) {
            click()
        }
    }

    notDisplayed {
        id(R.id.txt_menu)
    }

Matchers

You can use the following matchers:

fun id(@IdRes viewId: Int)
fun text(@StringRes textId: Int)
fun text(text: String)
fun contentDescription(@StringRes contentDescriptionId: Int)
fun contentDescription(contentDescription: String)
fun image(@DrawableRes imageId: Int)
fun textColor(@ColorRes colorId: Int)
fun parent(@IdRes parentId: Int)
fun descendant(@IdRes descendantId: Int)
fun custom(viewMatcher: Matcher<View>) // Here you can pass a custom matcher

TextInputLayout Matchers

You can match TextInputLayout now:

To check if TextInputLayout has an error text

@Test
fun textInputLayout_hasTextError_example() {
    textInputLayout(R.id.textInputLayout) {
        hasTextError()
    }
}

To check error text with text

@Test
fun textInputLayout_checkTextErrorWithText_example() {
    textInputLayout(R.id.textInputLayout) {
         withTextError("example text error")
    }
}

To check error text with an string resource

@Test
fun textInputLayout_checkTextErrorWithResource_example() {
    textInputLayout(R.id.textInputLayout) {
         withTextError(R.string.textError)
    }
}

Intent Matchers

You can match intents easily now:

@Test
fun intentMatcherTest() {
    val WHATS_PACKAGE_NAME = "com.whatsapp"
    val PLAY_STORE_URL = "https://play.google.com/store/apps/details?id="
    Intents.init()
    matchIntent {
        action(Intent.ACTION_VIEW)
        url(PLAY_STORE_URL + WHATS_PACKAGE_NAME)
        result {
           ok()
        }
    }

    click {
        id(R.id.btn_start_activity)
    }

    matchIntent {
        action(Intent.ACTION_VIEW)
        url(PLAY_STORE_URL + WHATS_PACKAGE_NAME)
    }

    Intents.release()
}

If you use some of the result methods (resultOk, resultCanceled, resultData) it's going to be like use the Espresso intending method. If you DON'T use any of the result methods, it's the same as use the Espresso intended method. The above code it will be something like this, without kappuccino

@Test
fun intentMatcherTest() {
    val WHATS_PACKAGE_NAME = "com.whatsapp"
    val PLAY_STORE_URL = "https://play.google.com/store/apps/details?id="
    Intents.init()

    val matcher = allOf(hasAction(Intent.ACTION_VIEW), hasData(Uri.parse(PLAY_STORE_URL + WHATS_PACKAGE_NAME)))
    val result = ActivityResult(Activity.RESULT_OK, null)
    intending(matcher).respondWith(result);

    click {
        id(R.id.btn_start_activity)
    }

    intended(matcher)

    Intents.release()
}

You can also use a custom intent matcher with the custom method

Runtime permissions

Easily handle runtime permissions

@Test
fun grantContactsPermission() {
    click {
        id(R.id.btn_request_permission)
    }

    runtimePermission(Manifest.permission.READ_CONTACTS) {
        allow()
    }

    displayed {
        text("PERMISSION GRANTED")
    }
}

Background matcher

Check view's background. The background must be VectorDrawable, BitmapDrawable or ColorDrawable

@Test
fun backgroundColorTest() {
    displayed {
        allOf {
            id(R.id.view_background)
            background(R.drawable.ic_android)
        }
    }

    displayed {
        background(R.color.colorAccent)
    }
}

For more examples, please check the sample code.

Wiki: coming soon.

Tip: this framework was based on Robots Pattern. It's a good idea to use this framework in combination with this pattern.

LICENSE

This project is available under Apache Public License version 2.0. See LICENSE.

More Repositories

1

pareto.js

An extremely small, intuitive and fast functional utility library for JavaScript
TypeScript
261
star
2

canarinho

Utilitários Android para padrões Brasileiros
Java
178
star
3

qa-studyguide

177
star
4

front-end-guide

150
star
5

android-studyguide

75
star
6

requestmatcher

A simple and powerful way for making programatic assertions in your fake API
Java
45
star
7

recrutamento-fe

Página do teste para o recrutamento de novos Front-Ends da Concrete Solutions
34
star
8

sunomono

A simple gem to generate all files needed in a project that will support Cucumber, Calabash Android and Calabash IOS and Appium Android and IOS.
Ruby
32
star
9

ng-security

A security module for AngularJS.
JavaScript
31
star
10

ios-recruiting-brazil

Desafio iOS da Concrete
30
star
11

desafio-java

26
star
12

yosef-android

An Android implementation of the Yosef protocol for dynamic views from the Backend
Kotlin
25
star
13

desafio-android

23
star
14

qa-automation-samples

Repository with examples of automation tools used by Concrete QAs
Java
23
star
15

magneton

A simple gem to generate all files needed in a project that will support Cucumber, SitePrism, Capybara and Selenium.
Ruby
17
star
16

qa-recruiting-brazil

Repositório com o desafio do Processo Seletivo para QA na Concrete
12
star
17

yosef-ios

yosef-ios
Swift
11
star
18

concrete_project_template

Concrete Project Template
Kotlin
10
star
19

tecla-sap

JavaScript
8
star
20

java-studyguide

7
star
21

java-recruiting-hsa

Java Recruiting Code Challenge for HSA
6
star
22

treinamentos-android

Conjunto de treinamentos Android
Java
4
star
23

cesta-basica

JavaScript
4
star
24

cs-dotnet-training

Estudo sobre .NET Core
C#
4
star
25

mock-api

Java
4
star
26

concrete_android_community

Reune referências técnicas de Android com análises, comparações entre bibliotecas, padrões de projetos e recomendações técnicas.
3
star
27

api-coupons-hsa

Coupons API for HSA Code Challenges
JavaScript
2
star
28

modern-js-guide

The Modern JavaScript Guide
2
star
29

ComposeKmmMoviesApp

Kotlin
2
star
30

frontend-recruiting-hsa

2
star
31

ios-recruiting-hsa

2
star
32

qa-recruiting-hsa

1
star
33

api-categories-hsa

Categories API for HSA Code Challenges
JavaScript
1
star
34

cs-links-api

Slack integrations for slash-command
JavaScript
1
star
35

desafio-node-espanol

Desafio de Node.js en castellano.
1
star
36

external-ios-recruiting-brazil

1
star
37

gotic

Go Terminal Interface Commander
Go
1
star
38

android-recruiting-hsa

Android Recruiting Code Challenge for HSA
1
star