Git Product home page Git Product logo

kakao's Introduction

Kakao

Telegram Telegram master-documentation

Nice and simple DSL for Espresso in Kotlin

Introduction

In many projects we have large amount of automated tests to ensure our application's quality and give our best experience to the user. The most famous library for writing automated tests is 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 a 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)
}
Custom clicks

See Kakao-ext-clicks

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


Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Thanks for supporting Open Source

kakao's People

Contributors

aafanasev avatar ahampe avatar anton46 avatar artbrnv avatar ashdavies avatar btwarog avatar cdsap avatar disssection avatar dsvoronin avatar even-even avatar ghostbuster91 avatar hongwei-bai avatar jkaan avatar judrummer avatar k-kagurazaka avatar keineantwort avatar malinskiy avatar michaelbukachi avatar ming13 avatar nikitae57 avatar peerapon01 avatar psh avatar sashkir7 avatar sebastienrouif avatar undroid-dew avatar unlimity avatar v-rodionov avatar vacxe avatar verachadw avatar yapkolotilov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

kakao's Issues

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

Crash when app is using Firebase Performance library.

Steps to reproduce:

  1. Use firebase performance library
  2. Use Kakao 3.2.3

Observed Results:

  • App crashes when running test with a Firebase perf error, there seems to be some dependency clash maybe?
java.lang.NoSuchMethodError: No static method registerDefaultInstance(Ljava/lang/Class;Lcom/google/protobuf/GeneratedMessageLite;)V in class Lcom/google/protobuf/GeneratedMessageLite; or its super classes (declaration of 'com.google.protobuf.GeneratedMessageLite' appears in /data/app/~~4sKIYTcsfYQtc9JM7hBNvA==/me.mydomain.myapp.test-Im1U7Kqsm8hvB13qvO5VGA==/base.apk)
at com.google.firebase.perf.v1.ApplicationInfo.<clinit>(ApplicationInfo.java:1085)
at com.google.firebase.perf.v1.ApplicationInfo.newBuilder(ApplicationInfo.java:533)
at com.google.firebase.perf.transport.TransportManager.finishInitialization(TransportManager.java:227)
at com.google.firebase.perf.transport.TransportManager.syncInit(TransportManager.java:221)
at com.google.firebase.perf.transport.TransportManager.$r8$lambda$LuAwHBxy50Yf-ziHqcD54KjEPtk(Unknown Source:0)
at com.google.firebase.perf.transport.TransportManager$$ExternalSyntheticLambda1.run(Unknown Source:2)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:920)

This Fix works for me:
Exclude protobuf-lite dependency from Kakao

    androidTestImplementation ("io.github.kakaocup:kakao:3.2.3")
    {
        exclude module: "protobuf-lite"
    }

Implement non-caching text matcher

Steps to reproduce:

  1. Define a KTextView using the withText(resId: Int) matcher and preform a check on it
  2. Change locale mid-test (using kaspresso SystemLanguage)
  3. Perform a check on the same KTextView again

Observed Results:

  • View not found

Expected Results:

  • View found

Relevant Code:

androidx.test.espresso.matcher.ViewMatchers.WithCharSequenceMatcher#matchesSafely caches text

@Override
  protected boolean matchesSafely(TextView textView, Description mismatchDescription) {
    if (null == expectedText) {
      try {
        expectedText = textView.getResources().getString(resourceId);
      } catch (Resources.NotFoundException ignored) {
        /* view could be from a context unaware of the resource id. */
      }
      resourceName = safeGetResourceEntryName(textView.getResources(), resourceId);
    }
    ...
  }

Prepare 3.0.0

TODO:

  • Update package
  • Remove Code of conduct
  • Update version to 3.0.0
  • Remove Jcenter related code
  • Request maven group ID
  • Review Readme file
  • Add migration script or guide
  • Configure CI checks
  • Update libs
  • ...

KRecyclerView built using IndexMatcher could not match children.

Steps to reproduce:

  1. I have a TabLayout with 2 tabs. Both contain RecyclerView with the same id.
  2. I match the RecyclerView in the second tab using withIndex(1) { withId(R.id.recyclerView) }
  3. Now if I try to do any child matching with the RecyclerView inside second tab, it fails. (childAt, childWith, etc).

Observed Results:

androidx.test.espresso.NoMatchingViewException: No views in hierarchy found matching: view holder at 0 position of recycler view: (1th view with: (view.getId() is <2131363464/com.package.name.debug:id/recyclerView>))

Expected Results:

I am expecting it to match the children of the RecyclerView inside the second tab.

Note that matching child items of the RecyclerView inside the first tab (withIndex(0)) works properly.

Relevant Code:

This is how I match my RecyclerViews.

// First tab RecyclerView
KRecyclerView(
  builder = {
      withIndex(0) {
          withId(R.id.recyclerView)
      }
  },
  itemTypeBuilder = {
      itemType(::Item)
  }
)

// Second tab RecyclerView
KRecyclerView(
  builder = {
      withIndex(1) {
          withId(R.id.recyclerView)
      }
  },
  itemTypeBuilder = {
      itemType(::Item)
  }
)

// Item
private class Item(parent: Matcher<View>) : KRecyclerItem<Item>(parent) {
      val moreOptionsIcon = KImageView(parent) { withId(R.id.moreIcon) }
}

Is there something wrong with how it is set up? Please help.

KTextInputLayout's hasHint() and hasError() doesn't work with Spannable

Since TextInputLayout.hint and TextInputLayout.error are CharSequence, the KTextInputLayout.hasHint() and KTextInputLayout.hasError() checks may fail if we assign a Spannable instance to this attributes.

Relevant Code:

   fun hasHint(hint: String) {
       view.check(ViewAssertion { view, notFoundException ->
           if (view is TextInputLayout) {
              if (hint != view.hint) {

android.support.test.espresso.PerformException: Error performing 'fast swipe'

The test is passing 90 percent of the time but failing randomly with below exception, as follows,

android.support.test.espresso.PerformException: Error performing 'fast swipe'
on view 'with id: my.app.package:id/my_refresh_layout'.
...
Caused by: java.lang.RuntimeException: Action will not be performed because the target view
does not match one or more of the following constraints:
at least 90 percent of the view's area is displayed to the user.
Target view: "SwipeRefreshLayout{id=2131689751, res-name=my_refresh_layout, visibility=VISIBLE,
width=480, height=672, has-focus=false, has-focusable=true, has-window-focus=true,
is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false,
is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0,
child-count=2}"

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

TestName: swipeToRefresh

Regards,
Vikram Singh

Implement more extensions to access android resources

Steps to reproduce:

  1. Open ContextUtils.kt
  2. Observe only 3 ext methods
  3. Call Vacxe

Expected Results:

  • What did you expect to happen?

I would like to have more extension to use because right now the codebase is not consistent.
We use the ext method getResourceString to access the string, but if we would like to access the string with args or a quantity string, we have to either have our own ext or invoke InstrumentationRegistry directly.

Relevant Code:

fun getQuantityString(@PluralsRes resId: Int, quantity: Int) = 
 InstrumentationRegistry.getInstrumentation().targetContext.resources.getQuantityString(
              resId,
              quantity,
          )
fun getResourceString(@StringRes resId: Int, vararg args: Any) = 
  InstrumentationRegistry.getInstrumentation().targetContext.resources.getString(
              resId,
              args,
          ) 

PR with reproduction on Sample App

  • See: #

childAt api is not working in recyclerview test

Hi Team,

whenever i am checking for particular position in a recyclerview, it's throwing me below error, as follows,

Exception:
androidx.test.espresso.NoMatchingViewException: No views in hierarchy found matching: (is descendant of a: (view holder at 7 position of recycler view: (with id is reprator.wipro.factlist.test:id/factListRecyclerView)) and with id is <2131230883>)

View Hierarchy:
+>DecorView{id=-1, visibility=VISIBLE, width=720, height=1600, has-focus=false, has-focusable=true, 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={(0,0)(fillxfill) blurRatio=1.0 blurMode=0 ty=BASE_APPLICATION wanim=0x10302fe
fl=LAYOUT_IN_SCREEN LAYOUT_INSET_DECOR SPLIT_TOUCH HARDWARE_ACCELERATED DRAWS_SYSTEM_BAR_BACKGROUNDS
pfl=FORCE_DRAW_STATUS_BAR_BACKGROUND}, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=3}

Although, i am using kaspresso but internally it's using kakao for uI testing, and even for simple kakao testing, it will fail. So need your assistance.

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

Test Name: childAtPosition7

Looking forward for a solution.

Regards,
Vikram Singh

TabLayoutActions is using incorrect constraints.

Steps to reproduce:

  1. Invoke KTabLayout.getSelectedItem during test.

Observed Results:

  • Test fails with the following stack trace:
Caused by: java.lang.RuntimeException: Action will not be performed because the target view does not match one or more of the following constraints:
is assignable from class: class com.google.android.material.bottomnavigation.BottomNavigationView
Target view: "TabLayout{id=2131362763, res-name=tab_layout, visibility=VISIBLE, width=1080, height=154, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=false, is-selected=false, layout-params=com.google.android.material.appbar.CollapsingToolbarLayout$LayoutParams@79c30d1, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=776.0, child-count=1}"

Expected Results:

  • Test should return the selected tab index.

Relevant Code:

class MyScreen : Screen<MyScreen>() {
    val tabs = KTabLayout { withId(R.id.tab_layout) }
}

// During test.

onScreen<MyScreen> {
    val selected = tabs.getSelectedItem()
}

/**
* Returns the currently selected item id
*
* @return selected menu item id
*/
fun getSelectedItem(): Int {
var id = 0
view.perform(object : ViewAction {
override fun getDescription() = "Gets selected item id"
override fun getConstraints() = ViewMatchers.isAssignableFrom(BottomNavigationView::class.java)
override fun perform(uiController: UiController, view: View) {
if (view is BottomNavigationView) {
id = view.selectedItemId
}
}
})

  • It should've used TabLayout as the constraint, and cast the view to TabLayout during iside perform.

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

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.

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

io.github.kakaocup.kakao.common.actions.BaseActions#scrollTo doesn't support recycler view

Steps to reproduce:

1.Edit io.github.kakaocup.sample.NestedRecyclerTest#testSingleItemTypeRecyclerView like that:
image

Observed Results:

java.lang.RuntimeException: Action will not be performed because the target view does not match one or more of the following constraints:
(view has effective visibility <VISIBLE> and is descendant of a view matching (is assignable from class <class android.widget.ScrollView> or is assignable from class <class android.widget.HorizontalScrollView> or is assignable from class <class androidx.core.widget.NestedScrollView> or is assignable from class <class android.widget.ListView>))

Expected Results:

Test doesn't fail

Tests with KViewPager/KViewPager2 SwipeableActions methods are flaky

Steps to reproduce:

(It's hard to reproduce. We are having like 1 flaky test in 10k runs)

  1. Use any of SwipeableActions interface methods on ViewPager/ViewPager2
  2. Assert that child of ViewPager is completely displayed.

Observed Results:

Current page of ViewPager is not fully settled after end of SwipeableActions

Expected Results:

Assertions can be made immediately after swipe action

Relevant Code:

viewPager.swipeLeft()
someChild.isCompletelyDisplayed()

PR with reproduction on Sample App

--

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

Compilation error with Kakao new version 3.5.0

There is a difference between the JDK being used to build the library vs the "target" it is set to. The best practice is to set the JDK to the most up to date to get compiler benefits. But the target is better to be set at a minimum level to increase compatibility. This is similar to libraries setting Android minSdk to 21 or 23. Most Kotlin libraries are set to 11.

I see in this PR from before that 2 things are updated together:

2nd one is not needed and it creates the below incompatibility.

Steps to reproduce:

  1. Just update the version
  2. Compile/assemble test build

Observed Results:

The following error is observed

e: ScreenExtensions.kt:33:12 Cannot inline bytecode built with JVM target 17 into bytecode that is being built with JVM target 11. Please specify proper '-jvm-target' option

Expected Results:

Compile just fine when the host app targets JVM 11

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

WebView `withElement(Locator.LINK_TEXT, <>)` incorrect behavior

Steps to reproduce:

  1. Unmute
 withElement(Locator.LINK_TEXT, "My Home") {
                    click()
                }

in WebTest

Same as:

onWebView().withElement(findElement(Locator.LINK_TEXT, "My Home")).perform(webClick())

WebView content:

val webView = findViewById<WebView>(R.id.webView).apply {
            settings.javaScriptEnabled = true
        }
        webView.loadData(
            """
            <html>
            <body>

            <p id="text">Hello</p>
            <a href="#">My Home</a>

            </body>
            </html>
        """, "text/html", "utf-8"
        )

Observed Results:


java.lang.RuntimeException: java.lang.RuntimeException: Atom evaluation returned null!
	at androidx.test.espresso.web.sugar.Web$WebInteraction$ExceptionPropagator.<init>(Web.java:4)
	at androidx.test.espresso.web.sugar.Web$WebInteraction.doEval(Web.java:31)
	at androidx.test.espresso.web.sugar.Web$WebInteraction.withElement(Web.java:46)
	at io.github.kakaocup.kakao.delegate.WebInteractionDelegate.withElement(WebInteractionDelegate.kt:58)
	at io.github.kakaocup.kakao.web.WebActions$DefaultImpls.perform(WebActions.kt:55)
	at io.github.kakaocup.kakao.web.WebActions$DefaultImpls.click(WebActions.kt:23)
	at io.github.kakaocup.kakao.web.WebElementBuilder$KWebInteraction.click(WebElementBuilder.kt:34)
	at io.github.kakaocup.sample.WebTest$testWebViewHasTextHelloAndClickLink$1$1$2.invoke(WebTest.kt:27)

Expected Results:

  • Test should pass

Information

  • Only on API 29 +

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

Runtime JAR files in the classpath should have the same version. These files were found in the classpath

The `kotlin-dsl` plugin applied to project ':buildSrc' enables experimental Kotlin compiler features. For more information see https://docs.gradle.org/6.7.1/userguide/kotlin_dsl.html#sec:kotlin-dsl_plugin
w: Runtime JAR files in the classpath should have the same version. These files were found in the classpath:
    /Users/vacxe/.gradle/wrapper/dists/gradle-6.7.1-all/2moa8rlfac5eqlcfgk98k0deb/gradle-6.7.1/lib/kotlin-stdlib-1.3.72.jar (version 1.3)
    /Users/vacxe/.gradle/wrapper/dists/gradle-6.7.1-all/2moa8rlfac5eqlcfgk98k0deb/gradle-6.7.1/lib/kotlin-stdlib-common-1.3.72.jar (version 1.3)
    /Users/vacxe/.gradle/wrapper/dists/gradle-6.7.1-all/2moa8rlfac5eqlcfgk98k0deb/gradle-6.7.1/lib/kotlin-stdlib-jdk7-1.3.72.jar (version 1.3)
    /Users/vacxe/.gradle/wrapper/dists/gradle-6.7.1-all/2moa8rlfac5eqlcfgk98k0deb/gradle-6.7.1/lib/kotlin-stdlib-jdk8-1.3.72.jar (version 1.3)
    /Users/vacxe/.gradle/wrapper/dists/gradle-6.7.1-all/2moa8rlfac5eqlcfgk98k0deb/gradle-6.7.1/lib/kotlin-reflect-1.3.72.jar (version 1.3)
    /Users/vacxe/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.4.32/3302f9ec8a5c1ed220781dbd37770072549bd333/kotlin-stdlib-jdk8-1.4.32.jar (version 1.4)
    /Users/vacxe/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-reflect/1.4.31/63db9d66c3d20f7b8f66196e7ba86969daae8b8a/kotlin-reflect-1.4.31.jar (version 1.4)
    /Users/vacxe/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.4.32/3546900a3ebff0c43f31190baf87a9220e37b7ea/kotlin-stdlib-jdk7-1.4.32.jar (version 1.4)
    /Users/vacxe/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.32/461367948840adbb0839c51d91ed74ef4a9ccb52/kotlin-stdlib-1.4.32.jar (version 1.4)
    /Users/vacxe/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/ef50bfa2c0491a11dcc35d9822edbfd6170e1ea2/kotlin-stdlib-common-1.4.32.jar (version 1.4)

Version 3.0.0

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

Potential performance optimisation around hierarchy-traversing matchers

Tl;dr: It's possible that Kakao could be more performant by reordering matchers to apply faster matchers first.


Since Kakao wraps Espresso, Espresso's view finding mechanism is used for actions and assertions. This mechanism will always iterate through entire view hierarchy looking for all views matching the requested predicate (source).

Most matchers should be pretty fast, however some of them will also iterate some part of view hierarchy as part of their check. For example HasDescendantMatcher or IsDescendantOfAMatcher will both go up or down the view hierarchy looking for views that match the requested descendant/ancestor. This means complexity of those matchers is not constant, contrary to simple matchers like WithIdMatcher. Moreover, when these matchers are nested, the overall performance decreases dramatically — for nested isDescendantOfA matchers, Espresso will go through every view in hierarchy, first matcher will go up in hierarchy and execute the nested matcher, which will also go up the hierarchy — I believe the complexity is non-linear in that case.

This is all unavoidable to an extent, but if those slow matchers are used within composite matchers like allOf() or anyOf(), the developer can easily improve Espresso performance by simply putting the expensive, view-traversing matchers later on the list. That way Espresso will short-circuit as soon as it can, potentially not invoking the expensive matcher at all.

If I read the code correctly, Kakao will sometimes put the slow matcher first on the list that is effectively transformed into an allOf() with same ordering, for example

val vb = ViewBuilder().apply {
isDescendantOfA { withMatcher(parentMatcher as Matcher<View>) }
builder(this)
}
or
edit = KEditText {
isDescendantOfA(function)
isAssignableFrom(EditText::class.java)
}
. Simply switching those lines would improve the performance essentially for free, with no obvious downsides. The faster matchers will run first and short-circuit the check if they fail, and the slower matcher will run a couple of times instead of for every single view.

This might look like a small change, but especially with nested matchers the performance impact can be significant —  in one project with ~270 UI tests, simply putting isDescendantOfA and hasDescendant matchers last in allOf calls cut total test run time in half (the project doesn't use Kakao).

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.

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.

TextInputLayoutAssertions methods has false-positive behaviour

Steps to reproduce:

  1. Create an XML file with TextInputEditText and add tags for TextInputEditText ( tagTextInputEditText) and for EditText inside the TextInputEditText (tagEditText). add to Hint any text (e.x. "hint")
  2. create a UI test running the activity with this XML.
  3. add this code KTextInputLayout { withTag(tagEditText) }.hasHint("any text _"+ UUID.randomUUID())

Observed Results:

The Test will pass

Expected Result

The Test should fail!
Expected an AssertionError that EditText is not TextInputLayout

Relevant Code:

The problem is that methods check instances and do not throw an exception if it is different than expected

fun hasHint(hint: String) {
      view.check(ViewAssertion { view, notFoundException ->
          if (view is TextInputLayout) {
              if (hint != view.hint.toString()) {
                  throw AssertionError(
                      "Expected hint is $hint," +
                              " but actual is ${view.hint}"
                  )
              }
          } else {
              notFoundException?.let { throw AssertionError(it) } // here code should throw AssertionError("expected TextInputLayout, but got $view") if notFoundException is null
          }
      })
// code here

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.