agoda-com / Kakao

This repo is no longer supported. Please visit a https://github.com/KakaoCup/Kakao
Apache License 2.0
1.11k stars 102 forks source link

ListPopupWindow and Toast intergation #42

Closed Chesteer89 closed 7 years ago

Chesteer89 commented 7 years ago

Hi guys!

First of all - thanks for your work! Can you take a look on KToast and KListPopupWindow and give me some hints (maybe it's just an overkill and it could be done with KView and KListView?

https://gist.github.com/Chesteer89/767ad1b1584e2920a9758bd1dfe95b36 https://gist.github.com/Chesteer89/d6bfe7383c287935ce938e9dfa98c0a1

Unlimity commented 7 years ago

Hey there! Thanks a lot for your interest in our project. Regarding your question: it can be done via KView and KListView. We have inRoot() function located at BaseAssertions. And we have RootBuilder class which helps you to perform this kind of checks.

Take a look at the example:

fun test() {
    screen { list { 
        inRoot { isPlatformPopup() } 
        childAt(0) { hasText("TEST") }
    } }
}

fun testToast() {
    screen { toast { 
        inRoot { withMatcher(ToastMatcher()) }
        hasText("TOAST")
    } }
}
Chesteer89 commented 7 years ago

Are you sure we're able to get list in popup? What kind of matcher should i pass in Screen? (id is not set, withClassName doesnt work, withAnyText matches multiple views)

Unlimity commented 7 years ago

You can try using isInstanceOf matcher for your list. And are you sure that list doesn't have an id?

Unlimity commented 7 years ago

Also you can match the list by matching it's items with use of withDescendant

Chesteer89 commented 7 years ago

Well, yes. We're using ListPopupWindow which doesnt have an id on list (contains DropDownListView). So the idea is to find a popup first (the real diff is inAdapterView(matcher) changed to inRoot(RootMatchers.isPlatformPopup())).

Toast is similiar story. That's why I've changed view to: override val view = this.builder.getViewInteraction().inRoot(ToastMatcher())

Unlimity commented 7 years ago

For toast, as I said previously, you can declare a view with text matcher, for example, and then immediately apply root matcher:

val toast = KView {
    withText("TOAST")
} perform {
    inRoot { withMatcher(ToastMatcher()) }
}

This will return you the KView with view interaction inside, which already has root matcher.

Regarding ListView situation, I think we should ask someone else. @cdsap @VerachadW what do you guys think on this?

VerachadW commented 7 years ago

@Chesteer89 You may try to send the matcher for list with the parent matcher. The popup needs to anchor with the view or the screen window. So, I think we can create a matcher for the parent view and pass it to withParent() for list. This is my idea.

Chesteer89 commented 7 years ago

Thanks guys! Optional parameter or withParent() sounds good.

I'll migrate to new version after update :)

Did you think about Snackbar tests? Is it possible right now or should i write my custom view + actions?

Unlimity commented 7 years ago

No, right now we don't have prebuilt view classes for that, but this is good idea, to make KSnackBar and KToast. Is this issue resolved?

ashdavies commented 6 years ago

I'm currently trying to achieve the same thing here with an AutoCompleteTextView suggestion matcher (PopupWindow/DropDownListView), did you manage to find a resolve using withParent()?

Unlimity commented 6 years ago

Hi there! The thing is, right now this behavior is not achievable due to specifics of these classes. PopupWindow and Toast actually create new Window instance when they show up. And espresso is explicitly bound to your activity's window with ViewRootImpl inside. It cannot access other windows through instrumentation.

Unlimity commented 6 years ago

We added KSnackBar support though.

ashdavies commented 6 years ago

My understanding is that you could use:

onData(equalTo("ITEM"))
  .inRoot(RootMatchers.isPlatformPopup())
  .perform(click())

for an autocomplete item?

I'm just not quite sure how to integrate that with a KListView, or if at all it's possible to do so at the moment.

Unlimity commented 6 years ago

As I recall, we have root matching. So you can match your KListView by any means comfortable to you (by id, by class name, by searching through parent class name, etc.) and then apply root matcher during your actions/assertions:

screen {
  list {
    inRoot { isPlatformPopup() }
    // Do your stuff here
  }
}
ashdavies commented 6 years ago

Yep, but the difficulty is creating list in our Screen<T> class, the suggestion was to use withParent and isRoot, but I've not had much success, what might be a way of selecting it?

Unlimity commented 6 years ago
  val list = KListView(...) {
    isInstanceOf(ListView::class.java)
  }

  ...

  screen {
    list {
      inRoot { isPlatformPopup() }
      // Do your stuff here
    }
  }
ashdavies commented 6 years ago

That's what I've tried, but I'm getting a NoMatchingViewException 😞

Unlimity commented 6 years ago

Can you provide the dumps of layouts when you open your popup and the results of adb shell dumpsys window windows and adb shell dumpsys activity?

Unlimity commented 6 years ago

Also, what exact class do you use to spawn your popups?

ashdavies commented 6 years ago

I'm using a custom view extending from AppCompatAutocompleteTextView

Dumps https://gist.github.com/ashdavies/4cfca8f5ff8697a8f53f566141480f73 https://gist.github.com/ashdavies/829645d64238115db33272af7c21d23f

Unlimity commented 6 years ago

I looked through the source code, and your view uses ListPopupWindow class inside, which uses DropDownListView class (not appcompat). So I would try to match by exact class name, not by parent class name.

ashdavies commented 6 years ago

Tried by class name also, the issue seems to be that the view hierarchy dumped with NoMatchingViewException is that of the activity window, which doesn't display the DropDownListView or the popup decor view, I've tried tweaking with withParent, inRoot to no avail 😞

Unlimity commented 6 years ago

Can you provide your screen class and test code where you try to access your list?

ashdavies commented 6 years ago

Sorry for the delay, I didn't see the notification

Screen

val list = KListView(
    builder = {
      isInstanceOf<ListView>()
      withParent {
        isRoot()
      }
    },
    itemTypeBuilder = {
      itemType(::Item)
    }
)

class Item(interaction: DataInteraction) : KAdapterItem<Item>(interaction) {

  val text = KTextView(interaction) {
    withId(android.R.id.text1)
  }
}

Test

screen {
  list {
    inRoot {
      isPlatformPopup()
    }
    firstChild<FinanceCalculatorScreen.Item> { 
      perform { 
        click()
      }
    }
  }
}
Unlimity commented 6 years ago

Hey there! Try removing this:

withParent {
  isRoot()
}

I'm not sure that your list view is a direct descendant of platform popup view.

ashdavies commented 6 years ago

Still get a NoMatchingViewException, would it help if I submitted a PR to the samples with the problem I'm trying to solve?

Unlimity commented 6 years ago

I just noticed. isInstanceOf<ListView>() is very weird. Kakao doesn't have that function. Kakao's function signature is isInstanceOf(clazz: Class<*>). It's not templated.

ashdavies commented 6 years ago

@Unlimity yep sorry this was an extension function in our code base, just an inline reified alias to Kakao's method

inline fun <reified T> ViewBuilder.isInstanceOf(): Unit = isInstanceOf(T::class.java)

Unlimity commented 6 years ago

Hm, I don't actually know how to help you now :( Could you make a public repo with simple activity and test that reproduces your problem?

ashdavies commented 6 years ago

I'll create a PR for the samples directory with what I'm trying to achieve

ashdavies commented 6 years ago

@Unlimity Hey! I've created a sample so you can see what I'm trying to achieve

Unlimity commented 6 years ago

Sure, I'll take a look as soon as I have free time