Skip to content

Commit 87da943

Browse files
authored
add more matchers (#124)
* add more matchers * fix
1 parent a4409c0 commit 87da943

10 files changed

Lines changed: 158 additions & 3 deletions

File tree

library/src/main/java/com/qautomatron/kopi/library/element/ElementActionConfig.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package com.qautomatron.kopi.library.element
33
import androidx.test.espresso.NoMatchingViewException
44
import androidx.test.espresso.PerformException
55

6+
/**
7+
* Timeout for perform
8+
*/
69
object ElementActionConfig {
710
var timeout = 3_000L
811
var interval: Long = 250

library/src/main/java/com/qautomatron/kopi/library/element/ElementAssertionConfig.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package com.qautomatron.kopi.library.element
33
import androidx.test.espresso.NoMatchingViewException
44
import androidx.test.espresso.PerformException
55

6+
/**
7+
* Timeout for check()
8+
*/
69
object ElementAssertionConfig {
710
var timeout = 3_000L
811
var interval: Long = 250

library/src/main/java/com/qautomatron/kopi/library/element/ElementAssertions.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import androidx.test.espresso.ViewInteraction
88
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
99
import androidx.test.espresso.assertion.ViewAssertions.matches
1010
import androidx.test.espresso.matcher.ViewMatchers.*
11+
import com.qautomatron.kopi.library.matcher.IsNotMovingMatcher
1112
import kotlinx.coroutines.delay
1213
import kotlinx.coroutines.runBlocking
1314
import org.hamcrest.Matchers.anyOf
@@ -105,4 +106,10 @@ interface ElementAssertions<T> {
105106
* @param resourceId expected text by resourceId
106107
*/
107108
fun sameAs(resourceId: Int) = check(matches(withText(resourceId)))
109+
110+
/**
111+
* Check element is not moving
112+
* @param timeoutInMills
113+
*/
114+
fun isNotMoving(timeoutInMills: Int? = null) = check(matches(IsNotMovingMatcher(timeoutInMills)))
108115
}

library/src/main/java/com/qautomatron/kopi/library/element/ElementWaits.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ package com.qautomatron.kopi.library.element
55
import android.view.View
66
import androidx.test.espresso.Root
77
import androidx.test.espresso.ViewAssertion
8+
import androidx.test.espresso.assertion.ViewAssertions
89
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
910
import androidx.test.espresso.matcher.ViewMatchers
11+
import androidx.test.espresso.matcher.ViewMatchers.withText
1012
import com.qautomatron.kopi.library.wait.ViewMatcherWaiter
13+
import org.hamcrest.CoreMatchers
14+
import org.hamcrest.CoreMatchers.containsString
1115
import org.hamcrest.Matcher
1216
import org.hamcrest.Matchers
1317

@@ -54,6 +58,20 @@ interface ElementWaits<T> {
5458
return this as T
5559
}
5660

61+
fun waitToBeSelected() = this.waitFor(ViewAssertions.matches(ViewMatchers.isSelected()))
62+
fun waitToBeEnabled() = this.waitFor(ViewAssertions.matches(ViewMatchers.isEnabled()))
63+
fun waitToBeDisabled() = this.waitFor(
64+
ViewAssertions.matches(
65+
CoreMatchers.not(
66+
ViewMatchers.isEnabled()
67+
)
68+
)
69+
)
70+
fun waitToBeClickable() = this.waitFor(ViewAssertions.matches(ViewMatchers.isClickable()))
71+
fun waitForText(text: String) = this.waitFor(withText(text))
72+
fun waitForText(resId: Int) = this.waitFor(withText(resId))
73+
fun waitForTextContains(text: String) = this.waitFor(withText(containsString(text)))
74+
5775
/**
5876
* Wait for specific matcher
5977
*/
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.qautomatron.kopi.library.matcher
2+
3+
import android.view.View
4+
import android.view.ViewGroup
5+
import androidx.test.espresso.matcher.BoundedMatcher
6+
import org.hamcrest.Description
7+
import org.hamcrest.Matcher
8+
import org.hamcrest.TypeSafeMatcher
9+
10+
fun withChildViewCount(count: Int, childMatcher: Matcher<View?>): Matcher<View?> {
11+
return object : BoundedMatcher<View?, ViewGroup>(ViewGroup::class.java) {
12+
override fun matchesSafely(viewGroup: ViewGroup): Boolean {
13+
var matchCount = 0
14+
for (i in 0 until viewGroup.childCount) {
15+
if (childMatcher.matches(viewGroup.getChildAt(i))) {
16+
matchCount++
17+
}
18+
}
19+
return matchCount == count
20+
}
21+
22+
override fun describeTo(description: Description) {
23+
description.appendText("ViewGroup with child-count=$count and")
24+
childMatcher.describeTo(description)
25+
}
26+
}
27+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.qautomatron.kopi.library.matcher
2+
3+
import android.os.SystemClock
4+
import android.view.View
5+
import kotlinx.coroutines.delay
6+
import kotlinx.coroutines.runBlocking
7+
import org.hamcrest.Description
8+
import org.hamcrest.TypeSafeMatcher
9+
10+
class IsNotMovingMatcher(private val timeoutInMills: Int?) : TypeSafeMatcher<View>() {
11+
12+
private val defaultTimeoutInMills: Int = 1000
13+
private val pollingIntervalInMills: Int = 250
14+
15+
override fun matchesSafely(view: View): Boolean {
16+
val endTime = SystemClock.elapsedRealtime() + (timeoutInMills ?: defaultTimeoutInMills)
17+
val oldLocation = IntArray(2)
18+
val newLocation = IntArray(2)
19+
20+
do {
21+
view.getLocationOnScreen(oldLocation)
22+
runBlocking { delay(pollingIntervalInMills.toLong()) }
23+
view.getLocationOnScreen(newLocation)
24+
25+
if (newLocation.contentEquals(oldLocation)) {
26+
return true
27+
}
28+
} while (SystemClock.elapsedRealtime() < endTime)
29+
30+
return false
31+
}
32+
33+
override fun describeTo(description: Description) {
34+
description.appendText("which is stop moving before $timeoutInMills")
35+
}
36+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.qautomatron.kopi.library.matcher
2+
3+
import android.view.View
4+
import android.view.ViewGroup
5+
import org.hamcrest.Description
6+
import org.hamcrest.Matcher
7+
import org.hamcrest.TypeSafeMatcher
8+
9+
class NthChildOfMatcher(val parentMatcher: Matcher<View>, val childPosition: Int) : TypeSafeMatcher<View>() {
10+
override fun describeTo(description: Description) {
11+
description.appendText("with $childPosition child view of type parentMatcher")
12+
}
13+
14+
override fun matchesSafely(view: View): Boolean {
15+
if (view.parent !is ViewGroup) {
16+
return parentMatcher.matches(view.parent)
17+
}
18+
val group = view.parent as ViewGroup
19+
return parentMatcher.matches(view.parent) && group.getChildAt(childPosition) == view
20+
}
21+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.qautomatron.kopi.library.matcher
2+
3+
import android.view.WindowManager
4+
import androidx.test.espresso.Root
5+
import org.hamcrest.Description
6+
import org.hamcrest.Matcher
7+
import org.hamcrest.TypeSafeMatcher
8+
9+
class ToastMatcher : TypeSafeMatcher<Root>() {
10+
11+
override fun describeTo(description: Description) {
12+
description.appendText("is toast")
13+
}
14+
15+
public override fun matchesSafely(root: Root): Boolean {
16+
val type = root.windowLayoutParams.get().type
17+
if (type == WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY) {
18+
val windowToken = root.decorView.windowToken
19+
val appToken = root.decorView.applicationWindowToken
20+
if (windowToken === appToken) {
21+
// windowToken == appToken means this window isn't contained by any other windows.
22+
// if it was a window for an activity, it would have TYPE_BASE_APPLICATION.
23+
return true
24+
}
25+
}
26+
return false
27+
}
28+
29+
fun isToast(): Matcher<Root> {
30+
return ToastMatcher()
31+
}
32+
}

library/src/main/java/com/qautomatron/kopi/library/steps/DeviceSteps.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ object DeviceSteps {
2020
/**
2121
* Wait for activity by name
2222
*/
23-
fun waitForActivity(fullName: String, timeoutInMillis: Int?, pollingInMillis: Int?) =
23+
fun waitForActivity(fullName: String, timeoutInMillis: Int? = null, pollingInMillis: Int? = null) =
2424
Watcher.waitForCondition(WaitForActivity(fullName), timeoutInMillis, pollingInMillis)
2525

2626
/**
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.qautomatron.kopi.sample
22

3+
import androidx.test.espresso.assertion.ViewAssertions
34
import androidx.test.espresso.matcher.ViewMatchers.withId
45
import com.qautomatron.kopi.library.element.Element
6+
import com.qautomatron.kopi.library.matcher.IsNotMovingMatcher
57
import com.qautomatron.kopi.library.matcher.first
68
import org.junit.Test
79

@@ -10,10 +12,16 @@ import org.junit.Test
1012
*/
1113
class ElementMatcherTest: BaseTest() {
1214

13-
private val element = Element(first(withId(R.id.sameId1)))
14-
1515
@Test
1616
fun should_get_first_element() {
17+
val element = Element(first(withId(R.id.sameId1)))
1718
element.sameAs("Text1")
1819
}
20+
21+
@Test
22+
fun should_is_not_moving_matcher() {
23+
val element = Element(withId(R.id.message_text))
24+
val timeoutInMills = 1000
25+
element.isNotMoving(timeoutInMills)
26+
}
1927
}

0 commit comments

Comments
 (0)