Nice and simple DSL for Espresso in Kotlin

Overview

Kakao

Kotlin version badge Telegram Telegram

Nice and simple DSL for Espresso in Kotlin

Introduction

At Agoda, we have more than 1000 automated tests to ensure our application's quality and give our best experience to the user. All of them are written with Espresso from Google. Even though Espresso is working really well with our test, the code readability is quite low. Let's look at some of the examples of how we write the test.

onView(allOf(withId(R.id.price_item), hasDescendant(withText("Standard Rate"))))
        .check(matches(withEffectiveVisibility(Visibility.VISIBLE)));

This is an example just to check the visibility and you can see that it's not looking that good. As Agoda developers, we want to improve not just our codebase quality, but also our implementation of tests as well. This is why we are introducing Kakao. The library that will make you enjoy writing automated tests like you are drinking a hot chocolate.

coco

Benefits

  • Readability
  • Reusability
  • Extensible DSL

How to use it

Create Screen

Create your entity Screen where you will add the views involved in the interactions of the tests:

class FormScreen : Screen<FormScreen>()

Screen can represent the whole user interface or a portion of UI. If you are using Page Object pattern you can put the interactions of Kakao inside the Page Objects.

Create KView

Screen contains KView, these are the Android Framework views where you want to do the interactions:

class FormScreen : Screen<FormScreen>() {
    val phone = KEditText { withId(R.id.phone) }
    val email = KEditText { withId(R.id.email) }
    val submit = KButton { withId(R.id.submit) }
}

Kakao provides different types depending on the type of view:

  • KView
  • KEditText
  • KTextView
  • KButton
  • KImageView
  • KWebView
  • KCheckbox
  • KViewPager
  • KSeekBar
  • KSwitch
  • and more

Every KView contains matchers to retrieve the view involved in the ViewInteraction. Some examples of matchers provided by Kakao:

  • withId
  • withText
  • withContentDescription
  • withDrawable
  • withBackgroundColor
  • and more

Like in Espresso you can combine different matchers:

val email = KEditText { 
    withId(R.id.email)
    withText(R.string.email)
}

And you can use your custom matchers:

val email = KEditText { 
    withId(R.id.email)
    matches { MyCustomMatcher.matches(position) }
}

Write the interaction.

The syntax of the test with Kakao is very easy, once you have the Screen and the KView defined, you only have to apply the actions or assertions like in Espresso:

onScreen<FormScreen> {
    phone {
       hasText("971201771")
    }
    button {
       click()
    }
}

Kakao provides multiple actions/assertions based on Espresso. Furthermore, you can combine them, just like the matchers. You can use your custom assertions or your custom actions too:

onScreen<FormScreen> {
    phone {
       assert { MyCustomAssertion.isThaiNumber() }
    }
    button {
       act { MyCustomAction.clickOnTheCorner() }
    }
}

Advanced

ListView/RecyclersView

Kakao offers an easy way to interact with your RecyclerView and ListView

Create the KListView/KRecyclerView

Inside your Screen create the KView matching with your view:

For KListView:

val list = KListView { builder = { withId(R.id.list) } }

For KRecyclerView:

val recycler = KRecyclerView { builder = { withId(R.id.recycler_view) } }

You can combine different matchers to retrieve your view.

Create KAdapterItem/KRecyclerItem

Every adapter contains different Items, Kakao provides an easy way to define the different items of your adapter with KAdapterItem and KRecyclerItem. If your adapter contains multiple Items but your interactions in your tests only work with one is not required to create all of them.

KAdapterItem

class Item(i: DataInteraction) : KAdapterItem<Item>(i) {
    val title = KTextView(i) { withId(R.id.title) }
    val subtitle = KTextView(i) { withId(R.id.subtitle) }
    val button = KButton(i) { withId(R.id.button) }
}

KRecyclerItem

class Item(parent: Matcher<View>) : KRecyclerItem<Item>(parent) {
    val title: KTextView = KTextView(parent) { withId(R.id.title) }
    val subtitle: KTextView = KTextView(parent) { withId(R.id.subtitle) }
}

The KView defined in the Item corresponds views used on the Item. You can assign the KItems to the KListView/ KRecyclerView like:

val recycler: KRecyclerView = KRecyclerView({
    withId(R.id.recycler_view)
}, itemTypeBuilder = {
    itemType(::Item)
})

And finally your final interaction will be:

onScreen<RecyclerScreen> {
    recycler {
        firstChild<TestRecyclerScreen.Item> {
            isVisible()
            title { hasText("Title 1") }
        }
    }
}

Kakao provides different accessors in the adapter:

  • childAt
  • firstChild
  • lastChild
  • childWith
Custom KView

If you have custom Views in your tests and you want to create your own KView, we have KBaseView. Just extend this class and implement as much additional Action/Assertion interfaces as you want. You also need to override constructors that you need.

class KMyView : KBaseView<KView>, MyActions, MyAssertions {
    constructor(function: ViewBuilder.() -> Unit) : super(function)
    constructor(parent: Matcher<View>, function: ViewBuilder.() -> Unit) : super(parent, function)
    constructor(parent: DataInteraction, function: ViewBuilder.() -> Unit) : super(parent, function)
}
Intercepting

If you need to add custom logic during the Kakao -> Espresso call chain (for example, logging) or if you need to completely change the ViewAssertion or ViewAction that are being sent to Espresso during runtime in some cases, you can use the intercepting mechanism.

Interceptors are lambdas that you pass to a configuration DSL that will be invoked before ViewInteraction, DataInteraction or Web.WebInteraction classes' perform and check calls happening from inside Kakao.

You have the ability to provide interceptors at 3 different levels: Kakao runtime, your 'Screen' classes and any individual KView instance.

On each invocation of Espresso function that can be intercepted, Kakao will aggregate all available interceptors for this particular call and invoke them in descending order: KView interceptor -> Active Screens interceptors -> Kakao interceptor.

Each of the interceptors in the chain can break the chain call by setting isOverride to true during configuration. In that case Kakao will not only stop invoking remaining interceptors in the chain, but will not perform the Espresso call. It means that in such case, the responsibility to actually invoke Espresso lies on the shoulders of the developer.

Here's the examples of intercepting configurations:

class SomeTest {
    @Before
    fun setup() {
        Kakao { // Kakao runtime
            intercept {
                onViewInteraction { // Intercepting calls on ViewInteraction classes across whole runtime
                    onPerform { interaction, action -> // Intercept perform() call
                        Log.d("KAKAO", "$interaction is performing $action")
                    }
                }
            }
        }
    }
    
    @Test
    fun test() {
        onScreen<MyScreen> {
            intercept {
                onViewInteraction { // Intercepting calls on ViewInteraction classes while in the context of MyScreen
                    onCheck { interaction, assertion -> // Intercept check() call
                        Log.d("KAKAO", "$interaction is checking $assertion")
                    }
                }
            }
            
            myView {
                intercept { // Intercepting ViewInteraction calls on this individual view
                    onPerform(true) { interaction, action -> // Intercept perform() call and overriding the chain 
                        // When performing actions on this view, Kakao level interceptor will not be called
                        // and we have to manually call Espresso now.
                        Log.d("KAKAO_VIEW", "$interaction is performing $action")
                        interaction.perform(action)
                    }
                }
            }
        }
    }
}

For more detailed info please refer to the documentation.

Setup

Maven

<dependency>
  <groupId>io.github.kakaocup</groupId>
  <artifactId>kakao</artifactId>
  <version><latest version></version>
  <type>pom</type>
</dependency>

or Gradle:

dependencies {
    androidTestImplementation 'io.github.kakaocup:kakao:<latest version>'
}

Contribution Policy

Kakao is an open source project, and depends on its users to improve it. We are more than happy to find you interested in taking the project forward.

Kindly refer to the Contribution Guidelines for detailed information.

Code of Conduct

Please refer to Code of Conduct document.

License

Kakao is open source and available under the Apache License, Version 2.0.

Thanks to

Comments
  • hasText not checking escaped

    hasText not checking escaped "%" character

    Steps to reproduce:

    1. Set the text on a TextView to have an escaped %. Ex. <string name="recording_scaling">Scaling (%%)</string>
    2. call hasText on the KTextView with the resource id

    Observed Results:

    Expected: with string from resource id: <2131821564>[recording_scaling] value: Scaling (%%)
         Got: "AppCompatTextView{id=2131362820, res-name=titleTextView, visibility=VISIBLE, width=220, height=48, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, layout-params=androidx.constraintlayout.widget.ConstraintLayout$LayoutParams@e829a5e, tag=null, root-is-layout-requested=false, has-input-connection=false, x=16.0, y=0.0, text=Scaling (%), input-type=0, ime-target=false, has-links=false}"
    

    Expected Results:

    • The checked should have succeeded

    Relevant Code:

    <string name="recording_scaling">Scaling (%%)</string>
    title.hasText(R.string.recording_scaling) // Fails
    
    opened by dsuresh-ap 3
  • KTextInputLayout > hasError for null values

    KTextInputLayout > hasError for null values

    Steps to reproduce:

    class UrlValidationScreen : Screen<UrlValidationScreen>() {
        val urlInputLayout = KTextInputLayout { withId(R.id.urlInputLayout) }
    }
    
     // Assert
    urlInputLayout {
        hasError(null) // Will not work
    }
    

    Observed Results:

    Null cannot be passed to hasText image

    Expected Results:

    The hasText(String) function allows null values.

    Relevant Code:

    See above

    enhancement 
    opened by dsuresh-ap 3
  • Use dedicated ViewPager/ViewPager2 actions

    Use dedicated ViewPager/ViewPager2 actions

    General SwipeableActions were used before that were not fully synchronized with ViewPager's state. Tests were flaky in rare conditions because of that.

    Not to break public API I have added new methods. But IMO it would be good to get rid of SwipeableActions methods from KViewPager because:

    • ViewPager doesn't support swipeDown/Up
    • Are not fully synchronized by Espresso. Since swipe actions ends earlier then ViewPager settles. That makes tests flaky

    closes #65

    opened by audkar 2
  • Add support text view style assertions bold and italic

    Add support text view style assertions bold and italic

    Hi @Vacxe , feel typeface match might be quite strict sometimes(has to match style and weight too), maybe add this one as well for a few simple scenarios?

    opened by hongwei-bai 2
  • Question on the use of the Awaitility library with Kakao

    Question on the use of the Awaitility library with Kakao

    Hi,

    This a question, rather than a bug. Is there some way I could extend Kakao (+Kakao Compose) to implement the Awaitility library ? At the moment, I need to add code like this below which pollutes my test code somewhat. I'd like to hide the await.atMost statements from the test code.

    The app I'm testing needs a hybrid of Espresso, Compose and UIAutomator and I use different Page Object implementations depending on the view I'm automating.

           await.atMost(TEN_SECONDS).untilAsserted {
                filesCheckedTitle {
                    assertIsDisplayed()
                    assertTextEquals(title.value)
                }
            }
    
    

    Thanks,

    Pentti

    opened by braindonor 2
  • Add ability to get Matcher of KBaseView

    Add ability to get Matcher of KBaseView

    Relevant Code:

      private val header = KTextView { withText(R.string.text) }
      private val recyclerView = KRecyclerView({ withId(R.id.recyclerView) }, itemTypeBuilder = {})
    
    
      fun scrollToTextView() {
         // I want to do something like this
         recyclerView.scrollTo(header.getViewMatchers()) 
         // Potentially to have an extension func to do this 
         recyclerView.scrollTo(header)
         // As it currently stands, it seems like I have to redefine my matchers.
         recyclerView.scrollTo { withText(R.string.text) } 
      }
    

    In short I would like access to the matchers all KBaseView was initialized with.

    opened by tevjef 2
  • checking the initial state of app with progressbar

    checking the initial state of app with progressbar

    Hi Team,

    Actually i am trying to test the initial state of app, where app is in loading state but whenever i run the test in throws me following error, as follows,

    Exception: junit.framework.AssertionFailedError: 'is displayed on the screen to the user' doesn't match the selected view. Expected: is displayed on the screen to the user Got: "ProgressBar{id=2131230934, res-name=lee_progress, visibility=GONE, width=96, height=96, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, layout-params=android.widget.FrameLayout$LayoutParams@f29348d, tag=null, root-is-layout-requested=false, has-input-connection=false, x=312.0, y=676.0}"

    at dalvik.system.VMStack.getThreadStackTrace(Native Method)
    at java.lang.Thread.getStackTrace(Thread.java:1720)
    at com.kaspersky.kaspresso.failure.FailureLoggingProviderImpl.describedWith(FailureLoggingProviderImpl.kt:91)
    at com.kaspersky.kaspresso.failure.FailureLoggingProviderImpl.logDescriptionAndThrow(FailureLoggingProviderImpl.kt:69)
    at com.kaspersky.kaspresso.failure.LoggingFailureHandler.logDescriptionAndThrow(Unknown Source:2)
    at com.kaspersky.kaspresso.failure.LoggingFailureHandler.handle(LoggingFailureHandler.kt:21)
    at androidx.test.espresso.ViewInteraction.waitForAndHandleInteractionResults(ViewInteraction.java:103)
    at androidx.test.espresso.ViewInteraction.check(ViewInteraction.java:31)
    at com.kaspersky.kaspresso.interceptors.tolibrary.kakao.KakaoViewInterceptor$interceptCheck$1.invoke(KakaoViewInterceptor.kt:29)
    at com.kaspersky.kaspresso.interceptors.tolibrary.kakao.KakaoViewInterceptor$interceptCheck$1.invoke(KakaoViewInterceptor.kt:16)
    at com.kaspersky.kaspresso.autoscroll.AutoScrollProviderImpl.withAutoScroll(AutoScrollProviderImpl.kt:30)
    at com.kaspersky.kaspresso.interceptors.behavior.impl.autoscroll.AutoScrollViewBehaviorInterceptor.withAutoScroll(Unknown Source:12)
    at com.kaspersky.kaspresso.interceptors.behavior.impl.autoscroll.AutoScrollViewBehaviorInterceptor.intercept(AutoScrollViewBehaviorInterceptor.kt:26)
    at com.kaspersky.kaspresso.interceptors.behavior.impl.autoscroll.AutoScrollViewBehaviorInterceptor.intercept(AutoScrollViewBehaviorInterceptor.kt:14)
    at com.kaspersky.kaspresso.interceptors.tolibrary.kakao.KakaoViewInterceptor$interceptCheck$$inlined$fold$lambda$1.invoke(KakaoViewInterceptor.kt:34)
    at com.kaspersky.kaspresso.interceptors.tolibrary.kakao.KakaoViewInterceptor$interceptCheck$$inlined$fold$lambda$1.invoke(KakaoViewInterceptor.kt:16)
    at com.kaspersky.kaspresso.systemsafety.SystemDialogSafetyProviderImpl.passSystemDialogs(SystemDialogSafetyProviderImpl.kt:51)
    at com.kaspersky.kaspresso.interceptors.behavior.impl.systemsafety.SystemDialogSafetyViewBehaviorInterceptor.passSystemDialogs(Unknown Source:7)
    at com.kaspersky.kaspresso.interceptors.behavior.impl.systemsafety.SystemDialogSafetyViewBehaviorInterceptor.intercept(SystemDialogSafetyViewBehaviorInterceptor.kt:28)
    at com.kaspersky.kaspresso.interceptors.behavior.impl.systemsafety.SystemDialogSafetyViewBehaviorInterceptor.intercept(SystemDialogSafetyViewBehaviorInterceptor.kt:15)
    at com.kaspersky.kaspresso.interceptors.tolibrary.kakao.KakaoViewInterceptor$interceptCheck$$inlined$fold$lambda$1.invoke(KakaoViewInterceptor.kt:34)
    at com.kaspersky.kaspresso.interceptors.tolibrary.kakao.KakaoViewInterceptor$interceptCheck$$inlined$fold$lambda$1.invoke(KakaoViewInterceptor.kt:16)
    at com.kaspersky.kaspresso.flakysafety.algorithm.FlakySafetyAlgorithm.invokeFlakySafely(FlakySafetyAlgorithm.kt:32)
    at com.kaspersky.kaspresso.flakysafety.algorithm.FlakySafetyAlgorithm.invokeFlakySafely$default(FlakySafetyAlgorithm.kt:24)
    at com.kaspersky.kaspresso.flakysafety.FlakySafetyProviderSimpleImpl.flakySafely(FlakySafetyProviderSimpleImpl.kt:27)
    at com.kaspersky.kaspresso.interceptors.behavior.impl.flakysafety.FlakySafeViewBehaviorInterceptor.flakySafely(Unknown Source:7)
    at com.kaspersky.kaspresso.interceptors.behavior.impl.flakysafety.FlakySafeViewBehaviorInterceptor.intercept(FlakySafeViewBehaviorInterceptor.kt:26)
    at com.kaspersky.kaspresso.interceptors.behavior.impl.flakysafety.FlakySafeViewBehaviorInterceptor.intercept(FlakySafeViewBehaviorInterceptor.kt:14)
    at com.kaspersky.kaspresso.interceptors.tolibrary.kakao.KakaoViewInterceptor$interceptCheck$$inlined$fold$lambda$1.invoke(KakaoViewInterceptor.kt:34)
    at com.kaspersky.kaspresso.interceptors.tolibrary.kakao.KakaoViewInterceptor$interceptCheck$$inlined$fold$lambda$1.invoke(KakaoViewInterceptor.kt:16)
    at com.kaspersky.kaspresso.interceptors.tolibrary.kakao.KakaoViewInterceptor.interceptCheck(KakaoViewInterceptor.kt:36)
    at com.kaspersky.kaspresso.interceptors.tolibrary.LibraryInterceptorsInjector$injectKaspressoInKakao$1$1$1.invoke(LibraryInterceptorsInjector.kt:54)
    at com.kaspersky.kaspresso.interceptors.tolibrary.LibraryInterceptorsInjector$injectKaspressoInKakao$1$1$1.invoke(LibraryInterceptorsInjector.kt:22)
    at io.github.kakaocup.kakao.delegate.Delegate$DefaultImpls.interceptOnCheck(Delegate.kt:61)
    at io.github.kakaocup.kakao.delegate.Delegate$DefaultImpls.access$interceptOnCheck(Delegate.kt:13)
    at io.github.kakaocup.kakao.delegate.Delegate$interceptCheck$1.invoke(Delegate.kt:28)
    at io.github.kakaocup.kakao.delegate.Delegate$DefaultImpls.interceptCheck(Delegate.kt:33)
    at io.github.kakaocup.kakao.delegate.ViewInteractionDelegate.interceptCheck(ViewInteractionDelegate.kt:21)
    at io.github.kakaocup.kakao.delegate.ViewInteractionDelegate.interceptCheck(ViewInteractionDelegate.kt:21)
    at io.github.kakaocup.kakao.delegate.ViewInteractionDelegate.check(ViewInteractionDelegate.kt:26)
    at io.github.kakaocup.kakao.common.assertions.BaseAssertions$DefaultImpls.isDisplayed(BaseAssertions.kt:36)
    at io.github.kakaocup.kakao.common.views.KBaseView.isDisplayed(KBaseView.kt:34)
    at reprator.wipro.factlist.test.FactListKaspressoTest$initialScreenTest$3$1$1$1.invoke(FactListKaspressoTest.kt:62)
    at reprator.wipro.factlist.test.FactListKaspressoTest$initialScreenTest$3$1$1$1.invoke(FactListKaspressoTest.kt:20)
    at io.github.kakaocup.kakao.common.views.KBaseView.invoke(KBaseView.kt:83)
    at reprator.wipro.factlist.test.FactListKaspressoTest$initialScreenTest$3$1$1.invoke(FactListKaspressoTest.kt:61)
    at reprator.wipro.factlist.test.FactListKaspressoTest$initialScreenTest$3$1$1.invoke(FactListKaspressoTest.kt:20)
    at io.github.kakaocup.kakao.screen.Screen.invoke(Screen.kt:119)
    at reprator.wipro.factlist.test.FactListKaspressoTest$initialScreenTest$3$1.invoke(FactListKaspressoTest.kt:59)
    at reprator.wipro.factlist.test.FactListKaspressoTest$initialScreenTest$3$1.invoke(FactListKaspressoTest.kt:20)
    at com.kaspersky.kaspresso.testcases.core.testcontext.TestContext.step(TestContext.kt:39)
    at reprator.wipro.factlist.test.FactListKaspressoTest$initialScreenTest$3.invoke(FactListKaspressoTest.kt:56)
    at reprator.wipro.factlist.test.FactListKaspressoTest$initialScreenTest$3.invoke(FactListKaspressoTest.kt:20)
    at com.kaspersky.kaspresso.testcases.core.TestRunner.runMainTestSection(TestRunner.kt:144)
    at com.kaspersky.kaspresso.testcases.core.TestRunner.run(TestRunner.kt:58)
    at com.kaspersky.kaspresso.testcases.core.sections.MainTestSection.run(MainTestSection.kt:29)
    at reprator.wipro.factlist.test.FactListKaspressoTest.initialScreenTest(FactListKaspressoTest.kt:55)
    at java.lang.reflect.Method.invoke(Native Method)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at androidx.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)
    at androidx.test.internal.runner.junit4.statement.RunAfters.evaluate(RunAfters.java:61)
    at dagger.hilt.android.internal.testing.MarkThatRulesRanRule$1.evaluate(MarkThatRulesRanRule.java:106)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:27)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
    at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
    at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:395)
    at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2198)
    

    Caused by: junit.framework.AssertionFailedError: 'is displayed on the screen to the user' doesn't match the selected view. Expected: is displayed on the screen to the user Got: "ProgressBar{id=2131230934, res-name=lee_progress, visibility=GONE, width=96, height=96, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, layout-params=android.widget.FrameLayout$LayoutParams@f29348d, tag=null, root-is-layout-requested=false, has-input-connection=false, x=312.0, y=676.0}"

    at androidx.test.espresso.matcher.ViewMatchers.assertThat(ViewMatchers.java:17)
    at androidx.test.espresso.assertion.ViewAssertions$MatchesViewAssertion.check(ViewAssertions.java:15)
    at com.kaspersky.kaspresso.proxy.ViewAssertionProxy.check(ViewAssertionProxy.kt:26)
    at androidx.test.espresso.ViewInteraction$SingleExecutionViewAssertion.check(ViewInteraction.java:10)
    at androidx.test.espresso.ViewInteraction$2.call(ViewInteraction.java:11)
    at androidx.test.espresso.ViewInteraction$2.call(ViewInteraction.java:2)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at android.os.Handler.handleCallback(Handler.java:914)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:225)
    at android.app.ActivityThread.main(ActivityThread.java:7563)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:994)
    

    Repository: https://github.com/TheReprator/Wipro/blob/initial_state_test/appModules/factList/src/androidTest/kotlin/reprator/wipro/factlist/test/FactListKaspressoTest.kt

    Test Name: initialScreenTest

    Kindly assist.

    Regards, Vikram Singh

    opened by TheReprator 2
  • Remove dokka

    Remove dokka

    Looks like dokka creating more issues than solving...

    * What went wrong:
    Could not determine the dependencies of task ':kakao:dokka'.
    > class org.gradle.api.internal.file.CompositeFileCollection$1 cannot be cast to class java.util.List (org.gradle.api.internal.file.CompositeFileCollection$1 is in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader @48533e64; java.util.List is in module java.base of loader 'bootstrap')
    
    
    opened by Vacxe 2
  • Prepare 3.0.0

    Prepare 3.0.0

    TODO:

    • [x] Update package
    • [x] Remove Code of conduct
    • [x] Update version to 3.0.0
    • [x] Remove Jcenter related code
    • [x] Request maven group ID
    • [x] Review Readme file
    • [ ] Add migration script or guide
    • [x] Configure CI checks
    • [x] Update libs
    • [ ] ...
    opened by Vacxe 2
  • Memory leak at IndexMatcher class instance

    Memory leak at IndexMatcher class instance

    Hi. We use Ui tests for memory leaks detektion. But we've found leak at IndexMatcher that was added in https://github.com/agoda-com/Kakao/pull/200/files The IndexMatcher contains link on a View and create memory leak when we leave Activity/Fragment in test step.

    To reproduce:

    1. Create any screen with using of ViewBuilder.withIndex
    class TestScreen : Screen<TestScreen>() {
        val testView = KView {
            withIndex(0) {
                ...
            }
        }
    }
    
    1. Open some TestActivity/TestFragmen.
    2. Use testView in test step.
    3. Close that TestActivity/TestFragmen.

    Result:

    E/TestRunner: ----- begin exception -----
    E/TestRunner: leakcanary.NoLeakAssertionFailedError: Application memory leaks were detected:
    E/TestRunner: ====================================
    E/TestRunner: HEAP ANALYSIS RESULT
    E/TestRunner: ====================================
    E/TestRunner: 1 APPLICATION LEAKS
    E/TestRunner: References underlined with "~~~" are likely causes.
    E/TestRunner: Learn more at https://squ.re/leaks.
    E/TestRunner: 12136 bytes retained by leaking objects
    E/TestRunner: Displaying only 1 leak trace out of 3 with the same signature
    E/TestRunner: Signature: c7099d19e12c64f391b6157a00d3e14c2eed18a7
    E/TestRunner: ┬──�
    E/TestRunner: ��
    E/TestRunner: │ GC Root: Input or output parameters in native code
    E/TestRunner: │
    E/TestRunner: ├─ com.project.android.test.mock.valuation.FlatValuationUiTest instance
    E/TestRunner: │    Leaking: UNKNOWN
    E/TestRunner: │    Retaining 1,5 MB in 24968 objects
    E/TestRunner: │    ↓ FlatValuationUiTest.valuationScreen
    E/TestRunner: │                          ~~~~~~~~~~~~~~~
    E/TestRunner: ├─ com.project.android.screens.FlatValuationScreen instance
    E/TestRunner: │    Leaking: UNKNOWN
    E/TestRunner: │    Retaining 1,5 MB in 24965 objects
    E/TestRunner: │    ↓ FlatValuationScreen.salePriceView
    E/TestRunner: │                          ~~~~~~~~~~~~~
    E/TestRunner: ├─ io.github.kakaocup.kakao.text.KTextView instance
    E/TestRunner: │    Leaking: UNKNOWN
    E/TestRunner: │    Retaining 1,4 MB in 24432 objects
    E/TestRunner: │    ↓ KBaseView.view
    E/TestRunner: │                ~~~~
    E/TestRunner: ├─ io.github.kakaocup.kakao.delegate.ViewInteractionDelegate instance
    E/TestRunner: │    Leaking: UNKNOWN
    E/TestRunner: │    Retaining 1,4 MB in 24431 objects
    E/TestRunner: │    ↓ ViewInteractionDelegate.interaction
    E/TestRunner: │                              ~~~~~~~~~~~
    E/TestRunner: ├─ androidx.test.espresso.ViewInteraction instance
    E/TestRunner: │    Leaking: UNKNOWN
    E/TestRunner: │    Retaining 1,4 MB in 24430 objects
    E/TestRunner: │    ↓ ViewInteraction.viewMatcher
    E/TestRunner: │                      ~~~~~~~~~~~
    E/TestRunner: ├─ org.hamcrest.core.AllOf instance
    E/TestRunner: │    Leaking: UNKNOWN
    E/TestRunner: │    Retaining 1,4 MB in 24418 objects
    E/TestRunner: │    ↓ AllOf.matchers
    E/TestRunner: │            ~~~~~~~~
    E/TestRunner: ├─ java.util.ArrayList instance
    E/TestRunner: │    Leaking: UNKNOWN
    E/TestRunner: │    Retaining 1,4 MB in 24417 objects
    E/TestRunner: │    ↓ ArrayList[0]
    E/TestRunner: │               ~~~
    E/TestRunner: ├─ io.github.kakaocup.kakao.common.matchers.IndexMatcher instance
    E/TestRunner: │    Leaking: UNKNOWN
    E/TestRunner: │    Retaining 1,4 MB in 24415 objects
    E/TestRunner: │    ↓ IndexMatcher.seen
    E/TestRunner: │                   ~~~~
    E/TestRunner: ├─ java.util.LinkedHashSet instance
    E/TestRunner: │    Leaking: UNKNOWN
    E/TestRunner: │    Retaining 1,4 MB in 24407 objects
    E/TestRunner: │    ↓ LinkedHashSet[element()]
    E/TestRunner: │                   ~~~~~~~~~~~
    E/TestRunner: ╰→ android.widget.LinearLayout instance
    E/TestRunner: ​     Leaking: YES (ObjectWatcher was watching this because com.project.valuation.ui.screen.flat.FlatValuationFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks) and View.mContext references a destroyed activity)
    E/TestRunner: ​     Retaining 2,1 kB in 52 objects
    E/TestRunner: ​     key = ea5d88bb-8313-47b4-b590-24555ab2b30a
    E/TestRunner: ​     watchDurationMillis = 6078
    E/TestRunner: ​     retainedDurationMillis = 1078
    E/TestRunner: ​     View not part of a window view hierarchy
    E/TestRunner: ​     View.mAttachInfo is null (view detached)
    E/TestRunner: ​     View.mWindowAttachCount = 1
    E/TestRunner: ​     mContext instance of com.project.valuation.ui.screen.flat.FlatValuationActivity with mDestroyed = true
    
    opened by cosic 1
  • Change espresso core to an api dependency.

    Change espresso core to an api dependency.

    Else this leads to unresovled references in the IDE as Espresso is part of the public api, for example in:

    fun click(location: GeneralLocation = GeneralLocation.VISIBLE_CENTER)
    

    in BaseActions

    opened by PaulWoitaschek 1
  • KRecyclerView: RecyclerActions::scrollToEnd() throws NullPointerException

    KRecyclerView: RecyclerActions::scrollToEnd() throws NullPointerException

    Steps to reproduce:

    1. Use long-loading RecyclerView items in a standard RecyclerView
    2. call scrollToEnd() in a UI test

    Observed Results:

    The code

                        val lastView = view.findViewHolderForLayoutPosition(position)!!.itemView
                        view.scrollBy(0, lastView.height)
    

    throws a NullPointerException because lastView is null.

    Expected Results:

    I expected the RecyclerView just to scroll down to have the last item fully visible.

    Relevant Code:

      override fun scrollToEnd() {
          view.perform(object : ViewAction {
              override fun getDescription() = "Scroll RecyclerView to the bottom"
    
              override fun getConstraints() = ViewMatchers.isAssignableFrom(RecyclerView::class.java)
    
              override fun perform(controller: UiController, view: View) {
                  if (view is RecyclerView) {
                      val position = view.adapter!!.itemCount - 1
                      view.scrollToPosition(position)
                      controller.loopMainThreadUntilIdle()
                      val lastView = view.findViewHolderForLayoutPosition(position)!!.itemView
                      view.scrollBy(0, lastView.height)
                      controller.loopMainThreadUntilIdle()
                  }
              }
          })
      }
    

    Workaround:

    I've created an extension function to still be able to do what I'd like to do:

    fun KRecyclerView.scrollToEndRepeatedly(repetitions: Int) {
    
        view.perform(
            object : ViewAction {
                override fun getDescription() =
                    "Scroll RecyclerView to the bottom"
    
                override fun getConstraints() =
                    ViewMatchers.isAssignableFrom(
                        RecyclerView::class.java
                    )
    
                override fun perform(controller: UiController, view: View) {
                    if (view is RecyclerView) {
                        var lastViewFound = false
                        var tryCount = 0
                        do {
                            tryCount++
                            val position = view.adapter!!.itemCount - 1
                            view.scrollToPosition(position)
                            controller.loopMainThreadUntilIdle()
                            val lastView =
                                view.findViewHolderForLayoutPosition(
                                    position
                                )
                            lastView?.let {
                                view.scrollBy(0, lastView.itemView.height)
                                lastViewFound = false
                            }
                            controller.loopMainThreadUntilIdle()
                        } while ((!lastViewFound) && (tryCount < repetitions))
                    }
                }
    
            }
        )
    }
    

    While this does what it's supposed to do, I think, there must be a better solution using interceptors which fit's more naturally into Kakaos concepts, i.e. making the repetions parameter superfluous.

    opened by cee-dee 0
  • KImageView -> hasDrawable doesn't work with SVG images

    KImageView -> hasDrawable doesn't work with SVG images

    Steps to reproduce:

    1. Import a SVG image in Android Studio
    2. Try to use hasDrawable with the SVG set.

    Observed Results:

    The images are considered different although they are the same

    Expected Results:

    The images should be the same

    Relevant Code:

    withId<KImageView>(R.id.criticalErrorImage) {
        hasDrawable(R.drawable.error_graphic)
    }
    

    I've tried also to create a CustomMatcher trying to compare the constantState but it fails anyway unfortunately.

    opened by filnik 6
  • hasDrawable not working with imageview as excpected

    hasDrawable not working with imageview as excpected

    Hi Team,

    First thanks for this great UI Testing framework. Currently i am trying to check drawable with imageview but i am getting below error, as follows,

    androidx.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: 'with drawable id -1 or provided instance' doesn't match the selected view. Expected: with drawable id -1 or provided instance Got: "AppCompatImageView{id=2131230883, res-name=factImage, visibility=VISIBLE, width=180, height=220, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, layout-params=androidx.constraintlayout.widget.ConstraintLayout$LayoutParams@df6814a, tag=null, root-is-layout-requested=false, has-input-connection=false, x=508.0, y=65.0}"

    Repository: https://github.com/TheReprator/Wipro/blob/image_error/appModules/factList/src/androidTest/kotlin/reprator/wipro/factlist/test/FactListKaspressoTest.kt

    Test Name: imageViewDrawableComparison

    Looking forward for a solution.

    Regards, Vikram Singh

    bug 
    opened by TheReprator 2
  • Autocomplete Test API 29

    Autocomplete Test API 29

    Master:

    io.github.kakaocup.sample.AutoCompleteTest > testContentItemsListView[emulator(AVD) - 10] FAILED 
    	androidx.test.espresso.NoMatchingRootException: Matcher '(with decor view of type PopupWindow$PopupViewContainer)' did not match any of the following roots: [Root{application-window-token=android.view.ViewRootImpl$W@df914cb, window-token=android.view.ViewRootImpl$W@df914cb, has-window-focus=true, layout-params-type=1, layout-params-string={(0,0)(fillxfill) ty=BASE_APPLICATION wanim=0x10302fe
    	fl=LAYOUT_IN_SCREEN LAYOUT_INSET_DECOR SPLIT_TOUCH HARDWARE_ACCELERATED DRAWS_SYSTEM_BAR_BACKGROUNDS
    
    

    Reproduced only on 29 API

    opened by Vacxe 0
Releases(3.2.1)
  • 3.2.1(Dec 12, 2022)

    What's Changed

    • Use dedicated ViewPager/ViewPager2 actions by @audkar in https://github.com/KakaoCup/Kakao/pull/66
    • Fix dokka version by @Vacxe in https://github.com/KakaoCup/Kakao/pull/67
    • Add rare base view matchers by @Vacxe in https://github.com/KakaoCup/Kakao/pull/68

    New Contributors

    • @audkar made their first contribution in https://github.com/KakaoCup/Kakao/pull/66

    Full Changelog: https://github.com/KakaoCup/Kakao/compare/3.2.0...3.2.1

    Source code(tar.gz)
    Source code(zip)
  • 3.2.0(Nov 17, 2022)

    What's Changed

    • Add support text view style assertions bold and italic by @hongwei-bai in https://github.com/KakaoCup/Kakao/pull/59
    • Shift emulators apis +3 by @Vacxe in https://github.com/KakaoCup/Kakao/pull/61
    • Add TextViewActions#clickSpanWithText() by @iyakovlev in https://github.com/KakaoCup/Kakao/pull/62
    • Add KImageView tests by @Vacxe in https://github.com/KakaoCup/Kakao/pull/63
    • Prepare release 3.2.0 by @Vacxe in https://github.com/KakaoCup/Kakao/pull/64

    Full Changelog: https://github.com/KakaoCup/Kakao/compare/3.1.0...3.2.0

    Source code(tar.gz)
    Source code(zip)
  • 3.1.0(Jul 14, 2022)

    What's Changed

    • #53 Fix memory leak at IndexMatcher.kt by @cosic in https://github.com/KakaoCup/Kakao/pull/54
    • Add support for compound drawables by @Vacxe in https://github.com/KakaoCup/Kakao/pull/55
    • Ignore isPlatformPopup line by @Vacxe in https://github.com/KakaoCup/Kakao/pull/30
    • Add support for gravity, text size, typeface for text view by @hongwei-bai in https://github.com/KakaoCup/Kakao/pull/57
    • Typeface pre 28 support by @Vacxe in https://github.com/KakaoCup/Kakao/pull/58

    New Contributors

    • @cosic made their first contribution in https://github.com/KakaoCup/Kakao/pull/54
    • @hongwei-bai made their first contribution in https://github.com/KakaoCup/Kakao/pull/57

    Full Changelog: https://github.com/KakaoCup/Kakao/compare/3.0.6...3.1.0

    Source code(tar.gz)
    Source code(zip)
  • 3.0.6(Dec 3, 2021)

    What's Changed

    • Add hint matcher by @Vacxe in https://github.com/KakaoCup/Kakao/pull/46
    • Fix a link to CONTRIBUTING in README by @aafanasev in https://github.com/KakaoCup/Kakao/pull/47
    • Compare TextInputLayout's hint and error as String by @iyakovlev in https://github.com/KakaoCup/Kakao/pull/50

    New Contributors

    • @aafanasev made their first contribution in https://github.com/KakaoCup/Kakao/pull/47
    • @iyakovlev made their first contribution in https://github.com/KakaoCup/Kakao/pull/50

    Full Changelog: https://github.com/KakaoCup/Kakao/compare/3.0.5...3.0.6

    Source code(tar.gz)
    Source code(zip)
  • 3.0.5(Oct 3, 2021)

  • 3.0.4(Sep 7, 2021)

  • 3.0.1(May 29, 2021)

A calculator for quick simple calculations with a nice user interface and no ads

Simple Calculator A calculator with the basic functions and a customizable widget. You can copy the result or formula to clipboard by long pressing it

Simple Mobile Tools 482 Dec 31, 2022
Warning on new versions available even when using Kotlin-DSL plugin.

Version Checker Gradle Lint Warning on new versions available even when using Kotlin-DSL plugin. This detector checks with a central repository to see

null 93 Nov 26, 2022
A lightweight, feature-rich wrapper for the Telegram Bot API, providing a handy Kotlin DSL to quickly build your bot.

Kotlin Telegram Bot Kotlin based wrapper over Telegram API. Current version of the Telegram Api: 6.0 Principles Annotations Magic. The basic interacti

Jey 61 Dec 27, 2022
Simple-todo-app - Simple Memo App using SQLite

Judul Aplikasi Aplikasi Memo Sederhana menggunakan SQLite. Fitur Aplikasi Memo y

Ananda Muhamad Lukman 0 Jan 3, 2022
MarsRealEstate is a simple demo app using ViewModel & LiveData with Retrofit, Glide and Moshi in Kotlin.

Android-NASA-Real-Estate-Mars Is Android app that uses Kotlin and MVVM architecture is a simple app that uses Coroutines, LiveData with Retrofit, Glid

Dmytro K 0 Nov 17, 2021
A Simple Movie App Built With Kotlin and Themoviedb API

Movie api https://www.themoviedb.org/documentation/api main fragment detail frag

Alex92w 1 Apr 26, 2022
Spantastic - an Android library that provides a simple and Kotlin fluent API for creating Android Spannable

Spantastic is an Android library that provides a simple and Kotlin fluent API for creating Android Spannable. This library wrappers SpannableStringBuilder and add methods to easily decorate the text with multiple spans.

Wellington Cabral da Silva 12 Nov 27, 2022
A simple login and logout Android application written in Kotlin

A simple login and logout Android application written in Kotlin. It authorizes user using an api and shows profile screen.

Emre Uysal 4 Aug 2, 2022
A simple, modern and coroutine based Kotlin Email API for clientside projects

Kotlinmailer is a Kotlin Mail API, using coroutines and providing DSLs. It may be used in a Ktor Backend for verification mails.

David Bieregger 4 Oct 5, 2022
Simple Notes app, MVVM with Google Architectural components Room database, LiveData and ViewModel. Written in Kotlin using androidx libraries

Simple Notes app, MVVM with Google Architectural components Room database, LiveData and ViewModel. Written in Kotlin using androidx libraries. Implemented Firebase Auth and Database, and used Room database

Javokhir Jambulov 3 Aug 1, 2022
A simple NewsApp built using Jetpack Compose, MVVM Architecture, Dagger Hilt and Kotlin Flow

NewsApp is simple App which uses NewsAPI to get top headlines for country you live in or you can search for a specific news. Focus of this app is to d

ElvisOperator 4 Aug 20, 2022
🎥 A Simple and Minimal Movies Android Application to demonstrate the Modern Android Development and Jetpack Compose.

ComposeMovie Android ?? A Simple and Minimal Movies Android Application to demonstrate the Modern Android Development and Jetpack Compose. Built with

null 13 Oct 1, 2022
A simple and easy to use stopwatch and timer library for android

TimeIt Now with Timer support! A simple and easy to use stopwatch and timer library for android Introduction A stopwatch can be a very important widge

Yashovardhan Dhanania 35 Dec 10, 2022
A simple, secure and instant messaging app. It's cloudbased and free.

Hasten A simple, secure and instant messaging application. It's cloudbased and free. Notice I'm probably at school right now and can't do much ?? . I'

Sliver Hywel 2 Sep 2, 2022
A simple android Twitter client written in Kotlin

Blum Blum is an unofficial, simple, fast Twitter client written in Kotlin. This project is a complete rewrite of the Java version. Screenshot Build To

Andrea Pivetta 77 Nov 29, 2022
A Simple Calculator developed in Kotlin

Calculator developed in Kotlin This was my first contact with mobile programming using Kotlin. The calc is basic, but it was the first project I did.

Ricardo Amaro 1 Oct 11, 2021
Simple blockchain example written in Kotlin

Blockchain in Kotlin This is a plain example how Cryptographic blockchains work by constructing a blockchain containing three blocks. Once constructed

Andrius Degutis 0 Feb 28, 2022
AsukaBot - A simple Kotlin Discord Bot

The Discord bot of the serber, a private Discord server. Asuka is a Discord But running on the JDA API but made in kotlin.

Yann ItzPeaxel 2 Apr 24, 2022