Skip to content

Commit

Permalink
Add query API
Browse files Browse the repository at this point in the history
  • Loading branch information
takahirom committed Mar 6, 2023
1 parent f951930 commit 495dabe
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.github.takahirom.roborazzi.CaptureOptions
import com.github.takahirom.roborazzi.DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH
import com.github.takahirom.roborazzi.captureRoboAllImage
import com.github.takahirom.roborazzi.captureRoboGif
import com.github.takahirom.roborazzi.captureRoboImage
import com.github.takahirom.roborazzi.captureRoboLastImage
import com.github.takahirom.roborazzi.withComposeTestTag
import com.github.takahirom.roborazzi.withViewId
import java.io.File
import org.junit.Rule
import org.junit.Test
Expand Down Expand Up @@ -53,6 +56,31 @@ class ManualTest {
.captureRoboImage("${PATH_AND_PREFIX_FOR_FILE}_second_screen.png")
}

@Test
fun captureRoboImageSampleWithQuery() {
onView(ViewMatchers.isRoot())
.captureRoboImage(
filePath = "${PATH_AND_PREFIX_FOR_FILE}_view_first_screen_with_query_view.png",
captureOptions = CaptureOptions(query = withViewId(R.id.textview_first))
)


composeTestRule.onNodeWithTag("MyComposeButton")
.performClick()

composeTestRule.onNodeWithTag("MyComposeButton")
.performClick()
composeTestRule.waitForIdle()


onView(ViewMatchers.isRoot())
.captureRoboImage(
filePath = "${PATH_AND_PREFIX_FOR_FILE}_view_first_screen_with_query_compose.png",
captureOptions = CaptureOptions(query = withComposeTestTag("child:0"))
)
}


@Test
fun captureRoboGifSample() {
onView(ViewMatchers.isRoot())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ fun ViewInteraction.captureRoboImage(
filePath: String,
captureOptions: CaptureOptions = CaptureOptions(),
) {
captureRoboImage(File(filePath))
captureRoboImage(
file = File(filePath),
captureOptions = captureOptions
)
}

fun ViewInteraction.captureRoboImage(
Expand Down Expand Up @@ -147,7 +150,10 @@ class CaptureResult(
)

// Only for library, please don't use this directly
fun ViewInteraction.captureAndroidView(captureOptions: CaptureOptions, block: () -> Unit): CaptureResult {
fun ViewInteraction.captureAndroidView(
captureOptions: CaptureOptions,
block: () -> Unit
): CaptureResult {
var removeListener = {}

val canvases = mutableListOf<RoboCanvas>()
Expand Down Expand Up @@ -352,7 +358,10 @@ private fun saveAllImage(
}
}

private class ImageCaptureViewAction(val captureOptions: CaptureOptions, val saveAction: (RoboCanvas) -> Unit) :
private class ImageCaptureViewAction(
val captureOptions: CaptureOptions,
val saveAction: (RoboCanvas) -> Unit
) :
ViewAction {
override fun getConstraints(): Matcher<View> {
return Matchers.any(View::class.java)
Expand Down
76 changes: 63 additions & 13 deletions roborazzi/src/main/java/com/github/takahirom/roborazzi/capture.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import android.graphics.Paint
import android.graphics.Rect
import android.text.TextPaint
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.compose.ui.graphics.toAndroidRect
import androidx.compose.ui.platform.AbstractComposeView
import androidx.compose.ui.platform.ViewRootForTest
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.getOrNull
import androidx.test.espresso.util.HumanReadables
import kotlin.math.abs

Expand All @@ -25,13 +27,20 @@ internal val colors = listOf(
0xeb5a00
)

internal enum class Visibility {
enum class Visibility {
Visible,
Gone,
Invisible;
}

internal sealed interface RoboComponent {
val hasCompose = try{
Class.forName("androidx.compose.ui.platform.AbstractComposeView")
true
} catch (e:Exception) {
false
}

sealed interface RoboComponent {

class View(view: android.view.View) : RoboComponent {
override val width: Int = view.width
Expand All @@ -42,8 +51,7 @@ internal sealed interface RoboComponent {
rect
}
override val children: List<RoboComponent> = when {
view::class.java.name == "androidx.compose.ui.platform.AbstractComposeView" -> {
view as AbstractComposeView
hasCompose && view is androidx.compose.ui.platform.AbstractComposeView -> {
(view.getChildAt(0) as? ViewRootForTest)
?.semanticsOwner
?.rootSemanticsNode
Expand All @@ -62,14 +70,16 @@ internal sealed interface RoboComponent {
}
}

private val id: String =
val id: Int = view.id

val idResourceName: String? =
if (0xFFFFFFFF.toInt() == view.id) {
""
null
} else {
try {
view.resources.getResourceName(view.id)
} catch (e: Exception) {
""
null
}
}
override val text: String = HumanReadables.describe(view)
Expand All @@ -88,6 +98,7 @@ internal sealed interface RoboComponent {
}
override val text: String = node.printToString()
override val visibility: Visibility = Visibility.Visible
val testTag: String? = node.config.getOrNull(SemanticsProperties.TestTag)

override val rect: Rect = run {
val rect = Rect()
Expand Down Expand Up @@ -117,11 +128,42 @@ internal sealed interface RoboComponent {
}
}

fun withViewId(@IdRes id: Int): (RoboComponent) -> Boolean {
return { roboComponent ->
when (roboComponent) {
is RoboComponent.Compose -> false
is RoboComponent.View -> roboComponent.id == id
}
}
}

fun withComposeTestTag(testTag: String): (RoboComponent) -> Boolean {
return { roboComponent ->
when (roboComponent) {
is RoboComponent.Compose -> testTag == roboComponent.testTag
is RoboComponent.View -> false
}
}
}

class CaptureOptions(
val basicSize: Int = 600,
val depthSlideSize: Int = 30
val depthSlideSize: Int = 30,
val query: ((RoboComponent) -> Boolean)? = null,
)

internal sealed interface QueryResult {
object Disabled : QueryResult
data class Enabled(val matched: Boolean) : QueryResult

companion object {
fun of(component: RoboComponent, query: ((RoboComponent) -> Boolean)?): QueryResult {
if (query == null) return Disabled
return Enabled(query(component))
}
}
}

internal fun capture(
rootComponent: RoboComponent,
captureOptions: CaptureOptions,
Expand Down Expand Up @@ -152,10 +194,18 @@ internal fun capture(
val depthAndComponentQueue = ArrayDeque<Pair<Int, RoboComponent>>()
depthAndComponentQueue.add(0 to rootComponent)


fun bfs() {
while (depthAndComponentQueue.isNotEmpty()) {
val (depth, component) = depthAndComponentQueue.removeFirst()
val queryResult = QueryResult.of(component, captureOptions.query)
fun Int.overrideByQuery(queryResult: QueryResult): Int = when (queryResult) {
QueryResult.Disabled -> this
is QueryResult.Enabled -> if (queryResult.matched) {
RoboCanvas.TRANSPARENT_BIT
} else {
RoboCanvas.TRANSPARENT_STRONG
}
}
canvas.addBaseDraw {
val rect = component.rect
val canvasRect = Rect(
Expand All @@ -166,9 +216,9 @@ internal fun capture(
)
val boxColor = colors[depth % colors.size]
val boxAlpha = when (component.visibility) {
Visibility.Visible -> RoboCanvas.TRANSPARENT_BIT // alpha EE / FF
Visibility.Gone -> RoboCanvas.TRANSPARENT_MEDIUM // alpha 88 / FF
Visibility.Invisible -> RoboCanvas.TRANSPARENT_STRONG // alpha BB / FF
Visibility.Visible -> RoboCanvas.TRANSPARENT_BIT.overrideByQuery(queryResult) // alpha EE / FF
Visibility.Invisible -> RoboCanvas.TRANSPARENT_MEDIUM.overrideByQuery(queryResult) // alpha BB / FF
Visibility.Gone -> RoboCanvas.TRANSPARENT_STRONG.overrideByQuery(queryResult) // alpha 88 / FF
}

val alphaBoxColor = boxColor + boxAlpha
Expand Down

0 comments on commit 495dabe

Please sign in to comment.