{"id":21541674,"url":"https://github.com/concretesolutions/kappuccino","last_synced_at":"2025-10-08T20:50:44.265Z","repository":{"id":54338512,"uuid":"86185439","full_name":"concretesolutions/kappuccino","owner":"concretesolutions","description":"A kotlin library to simplify how to do espresso tests on Android.","archived":false,"fork":false,"pushed_at":"2018-12-13T13:38:23.000Z","size":406,"stargazers_count":109,"open_issues_count":9,"forks_count":20,"subscribers_count":4,"default_branch":"develop","last_synced_at":"2025-04-10T04:39:24.937Z","etag":null,"topics":["android","espresso","instrumentation","robots","tests"],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/concretesolutions.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-25T20:09:51.000Z","updated_at":"2024-10-02T20:07:34.000Z","dependencies_parsed_at":"2022-08-13T12:30:49.136Z","dependency_job_id":null,"html_url":"https://github.com/concretesolutions/kappuccino","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/concretesolutions/kappuccino","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/concretesolutions%2Fkappuccino","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/concretesolutions%2Fkappuccino/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/concretesolutions%2Fkappuccino/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/concretesolutions%2Fkappuccino/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/concretesolutions","download_url":"https://codeload.github.com/concretesolutions/kappuccino/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/concretesolutions%2Fkappuccino/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279000638,"owners_count":26082819,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","espresso","instrumentation","robots","tests"],"created_at":"2024-11-24T05:07:14.627Z","updated_at":"2025-10-08T20:50:44.217Z","avatar_url":"https://github.com/concretesolutions.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ca href=\"https://codebeat.co/projects/github-com-heitorcolangelo-kappuccino-develop\"\u003e\u003cimg alt=\"codebeat badge\" src=\"https://codebeat.co/badges/56bd966e-a50b-4f9c-a3d5-f7fdb9f0afca\" /\u003e\u003c/a\u003e\n\u003ca href=\"https://www.bitrise.io/app/e3a8637b620dcfff/status.svg?token=Z0f8z81pqA5QUo7QEtGvzw\"\u003e\u003cimg alt=\"bitrise badge\" src=\"https://www.bitrise.io/app/e3a8637b620dcfff/status.svg?token=Z0f8z81pqA5QUo7QEtGvzw\" /\u003e\u003c/a\u003e\n[ ![Download](https://api.bintray.com/packages/concrete/concrete-maven/kappuccino/images/download.svg) ](https://bintray.com/concrete/concrete-maven/kappuccino/_latestVersion)\n# kappuccino\nA framework to simplify the way you do instrumentation tests in your app, using \u003ca href=\"https://google.github.io/android-testing-support-library/docs/espresso/\"\u003eEspresso\u003c/a\u003e and \u003ca href=\"https://kotlinlang.org/\"\u003eKotlin\u003c/a\u003e.\n\nHere is how you do instrumentation tests today, using simply Espresso:\n\n``` kotlin\n@Test fun loginFieldsAreVisible() {\n  onView(withId(R.id.username)).check(matches(isDisplayed())\n  onView(withId(R.id.password)).check(matches(isDisplayed())\n  onView(withId(R.id.login_button)).check(matches(isDisplayed())\n}\n```\nThis 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,\ndue to small screens support.\n\nWith scroll, our test will be something like this:\n\n``` kotlin\n@Test fun loginFieldsAreVisible() {\n  onView(withId(R.id.username)).perform(scrollTo()).check(matches(isDisplayed())\n  onView(withId(R.id.password)).perform(scrollTo()).check(matches(isDisplayed())\n  onView(withId(R.id.login_button)).perform(scrollTo()).check(matches(isDisplayed())\n}\n```\n\nWe 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.\nAt the end, all we want to do is check if the views with these ids are displayed.\n\nSo, this is how you do the same test with kappuccino library:\n\n``` kotlin\n@Test fun loginFieldsAreVisible() {\n  displayed {\n    id(R.id.username)\n    id(R.id.password)\n    id(R.id.login_button)\n  }\n}\n```\n\nCleaner, easier to write and understand. \nTo scroll, all you have to do is pass a parameter to the function:\n\n``` kotlin\n@Test fun loginFieldsAreVisible() {\n  displayed(scroll = true) {\n    id(R.id.username)\n    id(R.id.password)\n    id(R.id.login_button)\n  }\n}\n```\n\n## Installation\n\n1 - Setup kotlin in your project, see the instructions \u003ca href=\"https://kotlinlang.org/docs/tutorials/kotlin-android.html\"\u003ehere\u003c/a\u003e\n\n2 - Create a kotlin directory into 'src/androidTest/', check the sample code for reference.\n\n3 - Set you sourceDataSet into your build.gradle file\n\n``` groovy\nsourceSets {\n    androidTest.java.srcDirs = ['src/androidTest/kotlin']\n  }\n```\n\n4 - Add library and dependencies into your build.gradle file and sync\n\n``` groovy\nandroidTestImplementation 'br.com.concretesolutions:kappuccino:$latest.version'\n```\n\n5 - This library depends on the following libraries:\n- [Espresso](https://developer.android.com/training/testing/espresso/index.html): mandatory\n- [UiAutomator](https://developer.android.com/training/testing/ui-automator.html): optional, used for runtime permissions\n\nSo, ensure those libraries are also in your dependencies.\n\n``` groovy\nandroidTestImplementation \"com.android.support.test.espresso:espresso-intents:$versions.espresso\"\nandroidTestImplementation \"com.android.support.test.espresso:espresso-core:$versions.espresso\"\nandroidTestImplementation \"com.android.support.test.espresso:espresso-contrib:$versions.espresso\"\nandroidTestImplementation \"com.android.support.test.uiautomator:uiautomator-v18:$versions.uiAutomator\"\n```\n\nAnd you're ready to go!\n\n#### If you have any module conflicts, try to exclude the conflicting module, for example:\n\n``` groovy\nandroidTestImplementation('br.com.concretesolutions:kappuccino:$latest.version', {\n        exclude group: 'com.android.support'\n    })\n```\n\n### Assertion methods\nThese are the methods to make view assertions\n``` kotlin\nchecked {}\nnotChecked {}\n\nclickable {}\nnotClickable {}\n\nselected {}\nnotSelected {}\n\ndisplayed {}\nnotDisplayed {}\n\nnotExist {}\n```\n\n### Action methods\nThese are methods to interact with views\n``` kotlin\nclick {}\ndoubleClick {}\nlongClick {}\n\ntypeText {}\nclearText {}\n```\n\n### Scroll\nThe scroll method is now a parameter for all the above methods, the default value is false, for example:\n\n``` kotlin\n@Test fun scrollToButton_andClick() {\n  click(scroll = true) {\n    id(R.id.login_button)\n  }\n}\n```\nIn this case, it will scroll to the view and click. If you don't provide a parameter, the scroll will not happen.\n\n### Combine matchers (Matchers.allOf)\nTo combine multiple matchers, use the allOf method:\n\n``` kotlin\n@Test fun scrollToButton_andClick() {\n  click(scroll = true) {\n    allOf {\n        id(R.id.login_button)\n        text(R.string.login_button_text)\n    }\n  }\n}\n```\n\n### Hierarchy\nThere are two methods of hierarchy matchers: Parent and Descendant.\n\n#### Parent\nYou can use Parent method with two different approaches: block matching or combining.\n\n\u003cb\u003e1 - Block matching:\u003c/b\u003e\u003cbr /\u003e\nFor block matching, pass the parentId as method parameter.\n\nThen, kappuccino will match all the views inside the block:\n\n``` kotlin\n@Test fun matchParent_blockMatching_example() {\n  displayed {\n    parent(R.id.parent) {\n        id(R.id.username)\n        id(R.id.password)\n        id(R.id.login_button)\n    }\n  }\n}\n```\nHere, kappuccino will check if all the views (username, password and login_button) are descendant of the declared parent, and are displayed.\n\nFor better understanding, the code above is equivalent to the one below, using pure Espresso:\n\n``` kotlin\n@Test fun matchParent_example() {\n    onView(\n        allOf(isDescendantOf(withId(R.id.parent)), withId(R.id.username)))\n        .check(matches(isDisplayed()))\n    onView(\n        allOf(isDescendantOf(withId(R.id.parent)), withId(R.id.password)))\n        .check(matches(isDisplayed()))\n    onView(\n        allOf(isDescendantOf(withId(R.id.parent)), withId(R.id.login_button)))\n        .check(matches(isDisplayed()))\n}\n```\n\n\u003cb\u003e2 - Combination of matchers:\u003c/b\u003e\u003cbr /\u003e\nYou can use the parent method as a combination of matchers:\n\n``` kotlin\n@Test fun matchParent_combining_example() {\n    displayed {\n        allOf {\n            parent {\n                id(R.id.parent)\n            }\n            id(R.id.username)\n        }\n    }\n}\n```\nHere, you will check if the view with id = R.id.username, and with parent with id = R.id.parent, is displayed\n\n#### Descendant\nIt works just like the parent method, for both cases (block matching and combining matchers)\n\n``` kotlin\n@Test fun descendant_block_example() {\n    displayed {\n        allOf {\n            descendant {\n                id(R.id.username)\n            }\n            id(R.id.parent)\n        }\n    }\n}\n```\nHere, we'll check if the parent, with child R.id.username is displayed.\nSame use for block matching.\n\n### RecyclerView\nTo interact with the recycler view:\n\n``` kotlin\n@Test fun recyclerView_example() {\n    recyclerView(R.id.recycler_view) {\n        sizeIs(10)\n        atPosition(3) {\n            displayed {\n                id(R.id.item_description)\n                text(R.string.description_text)\n                text(\"Item header text\")\n            }\n        }\n    }\n}\n```\n\nTo type text in a RecyclerView item's EditText:\n\n``` kotlin\n@Test fun recyclerView_textInput_example() {\n    recyclerView(R.id.recycler_view) {\n        atPosition(0) {\n            typeText(R.id.editText, \"Position 0\")\n        }\n\n        atPosition(1) {\n            typeText(R.id.editText, \"Position 1\")\n        }\n    }\n}\n```\n\nTo swipe a RecyclerView's item left or right:\n\n``` kotlin\n@Test fun recyclerView_swipeLeft_example() {\n    recyclerView(R.id.recycler_view() {\n        atPosition(0) {\n            swipeLeft()\n        }\n\n        atPosition(1) {\n            swipeRight()\n        }\n    }\n}\n```\n\n### Menu and action bar\nTo interact with the options menu:\n\n``` kotlin\n@Test\nfun whenClickingOnItem1_shouldShowCorrectText() {\n    menu {\n        onItem(R.string.item_1) {\n            click()\n        }\n    }\n\n    displayed {\n        text(R.string.item_1_selected)\n    }\n}\n```\n\nTo interact with the action bar:\n\n``` kotlin\n@Test\nfun whenClickingOnActionBarItem_shouldClearText() {\n    menu(openOptionsMenu = false) {\n        onActionBarItem(R.id.item_clear) {\n            click()\n        }\n    }\n\n    notDisplayed {\n        id(R.id.txt_menu)\n    }\n```\n\n### Matchers\nYou can use the following matchers:\n``` kotlin\nfun id(@IdRes viewId: Int)\nfun text(@StringRes textId: Int)\nfun text(text: String)\nfun contentDescription(@StringRes contentDescriptionId: Int)\nfun contentDescription(contentDescription: String)\nfun image(@DrawableRes imageId: Int)\nfun textColor(@ColorRes colorId: Int)\nfun parent(@IdRes parentId: Int)\nfun descendant(@IdRes descendantId: Int)\nfun custom(viewMatcher: Matcher\u003cView\u003e) // Here you can pass a custom matcher\n```\n\n### TextInputLayout Matchers\nYou can match TextInputLayout now:\n\nTo check if TextInputLayout has an error text\n``` kotlin\n@Test\nfun textInputLayout_hasTextError_example() {\n    textInputLayout(R.id.textInputLayout) {\n        hasTextError()\n    }\n}\n```\n\nTo check error text with text\n``` kotlin\n@Test\nfun textInputLayout_checkTextErrorWithText_example() {\n    textInputLayout(R.id.textInputLayout) {\n         withTextError(\"example text error\")\n    }\n}\n```\n\nTo check error text with an string resource\n``` kotlin\n@Test\nfun textInputLayout_checkTextErrorWithResource_example() {\n    textInputLayout(R.id.textInputLayout) {\n         withTextError(R.string.textError)\n    }\n}\n```\n\n### Intent Matchers\nYou can match intents easily now:\n``` kotlin\n@Test\nfun intentMatcherTest() {\n    val WHATS_PACKAGE_NAME = \"com.whatsapp\"\n    val PLAY_STORE_URL = \"https://play.google.com/store/apps/details?id=\"\n    Intents.init()\n    matchIntent {\n        action(Intent.ACTION_VIEW)\n        url(PLAY_STORE_URL + WHATS_PACKAGE_NAME)\n        result {\n           ok()\n        }\n    }\n\n    click {\n        id(R.id.btn_start_activity)\n    }\n\n    matchIntent {\n        action(Intent.ACTION_VIEW)\n        url(PLAY_STORE_URL + WHATS_PACKAGE_NAME)\n    }\n\n    Intents.release()\n}\n```\nIf you use some of the result methods (resultOk, resultCanceled, resultData) it's going to be like use the Espresso \u003ci\u003eintending\u003c/i\u003e method.\nIf you DON'T use any of the result methods, it's the same as use the Espresso \u003ci\u003eintended\u003c/i\u003e method.\nThe above code it will be something like this, without kappuccino\n``` kotlin\n@Test\nfun intentMatcherTest() {\n    val WHATS_PACKAGE_NAME = \"com.whatsapp\"\n    val PLAY_STORE_URL = \"https://play.google.com/store/apps/details?id=\"\n    Intents.init()\n\n    val matcher = allOf(hasAction(Intent.ACTION_VIEW), hasData(Uri.parse(PLAY_STORE_URL + WHATS_PACKAGE_NAME)))\n    val result = ActivityResult(Activity.RESULT_OK, null)\n    intending(matcher).respondWith(result);\n\n    click {\n        id(R.id.btn_start_activity)\n    }\n\n    intended(matcher)\n\n    Intents.release()\n}\n```\nYou can also use a custom intent matcher with the \u003ci\u003ecustom\u003c/i\u003e method\n\n### Runtime permissions\nEasily handle runtime permissions\n``` kotlin\n@Test\nfun grantContactsPermission() {\n    click {\n        id(R.id.btn_request_permission)\n    }\n\n    runtimePermission(Manifest.permission.READ_CONTACTS) {\n        allow()\n    }\n\n    displayed {\n        text(\"PERMISSION GRANTED\")\n    }\n}\n```\n\n### Background matcher\nCheck view's background. The background must be VectorDrawable, BitmapDrawable or ColorDrawable\n``` kotlin\n@Test\nfun backgroundColorTest() {\n    displayed {\n        allOf {\n            id(R.id.view_background)\n            background(R.drawable.ic_android)\n        }\n    }\n\n    displayed {\n        background(R.color.colorAccent)\n    }\n}\n```\n\n#### For more examples, please check the sample code.\n\n#### Wiki: coming soon.\n\n\u003cb\u003eTip:\u003c/b\u003e this framework was based on \u003ca href=\"https://news.realm.io/news/kau-jake-wharton-testing-robots/\"\u003eRobots Pattern\u003c/a\u003e. It's a good idea to use this framework in combination with this pattern.\n## LICENSE\n\nThis project is available under Apache Public License version 2.0. See [LICENSE](LICENSE).\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconcretesolutions%2Fkappuccino","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconcretesolutions%2Fkappuccino","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconcretesolutions%2Fkappuccino/lists"}