Skip to content

Commit

Permalink
Merge pull request #85 from KakaoCup/clicks-improvements
Browse files Browse the repository at this point in the history
Clicks improvements FINAL
  • Loading branch information
Vacxe authored Jul 10, 2023
2 parents 8470da2 + 7f223ff commit deae63f
Show file tree
Hide file tree
Showing 52 changed files with 1,560 additions and 35 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/release-deploy-ext-clicks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Deploy Clicks Extension to Sonatype
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- uses: little-core-labs/[email protected]
- name: deploy-release-ext-clicks
run: ./gradlew :kakao-ext-clicks:publishDefaultPublicationToOSSHRRepository -PreleaseMode=RELEASE --stacktrace
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
notify:
needs: deploy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Notify Telegram
uses: appleboy/telegram-action@master
with:
to: ${{ secrets.TELEGRAM_TO }}
token: ${{ secrets.TELEGRAM_TOKEN }}
message: |
New extension version has been released: Clicks
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ class KMyView : KBaseView<KView>, MyActions, MyAssertions {
}
```

##### Custom clicks

See [Kakao-ext-clicks](kakao-ext-clicks/README.md)

##### Intercepting

If you need to add custom logic during the `Kakao -> Espresso` call chain (for example, logging) or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import org.gradle.plugins.signing.SigningExtension
import org.jetbrains.dokka.gradle.DokkaTask
import java.net.URI

object Deployment {
object KakaoDeployment {
val ghToken = System.getenv("GH_TOKEN")
val sonatypeUser = System.getenv("SONATYPE_USERNAME")
val sonatypePassword = System.getenv("SONATYPE_PASSWORD")
Expand All @@ -42,8 +42,8 @@ object Deployment {
else -> "-SNAPSHOT"
}

Deployment.releaseMode = releaseMode
Deployment.versionSuffix = versionSuffix
KakaoDeployment.releaseMode = releaseMode
KakaoDeployment.versionSuffix = versionSuffix
deployUrl = when (releaseMode) {
"RELEASE" -> releaseDeployUrl
else -> snapshotDeployUrl
Expand Down Expand Up @@ -100,7 +100,7 @@ object Deployment {
project.configure<PublishingExtension> {
publications {
create("default", MavenPublication::class.java) {
Deployment.customizePom(pom)
KakaoDeployment.customizePom(pom)
additionalArtifacts.forEach { it ->
artifact(it)
}
Expand Down
149 changes: 149 additions & 0 deletions buildSrc/src/main/kotlin/KakaoExtClicksDeployment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
@file:Suppress("DEPRECATION")

import com.android.build.gradle.LibraryExtension
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPom
import org.gradle.api.publish.maven.MavenPomContributorSpec
import org.gradle.api.publish.maven.MavenPomDeveloperSpec
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.bundling.Jar
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.creating
import org.gradle.kotlin.dsl.extra
import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.getValue
import org.gradle.kotlin.dsl.provideDelegate
import org.gradle.kotlin.dsl.the
import org.gradle.plugins.signing.SigningExtension
import org.jetbrains.dokka.gradle.DokkaTask
import java.net.URI

object KakaoExtClicksDeployment {
val ghToken = System.getenv("GH_TOKEN")
val sonatypeUser = System.getenv("SONATYPE_USERNAME")
val sonatypePassword = System.getenv("SONATYPE_PASSWORD")
var releaseMode: String? = null
var versionSuffix: String? = null
var deployUrl: String? = null

val snapshotDeployUrl = System.getenv("SONATYPE_SNAPSHOTS_URL")
?: "https://s01.oss.sonatype.org/content/repositories/snapshots/"
val releaseDeployUrl = System.getenv("SONATYPE_RELEASES_URL")
?: "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"

fun initialize(project: Project) {
val releaseMode: String? by project
val versionSuffix = when (releaseMode) {
"RELEASE" -> ""
else -> "-SNAPSHOT"
}

KakaoExtClicksDeployment.releaseMode = releaseMode
KakaoExtClicksDeployment.versionSuffix = versionSuffix
deployUrl = when (releaseMode) {
"RELEASE" -> releaseDeployUrl
else -> snapshotDeployUrl
}

initializePublishing(project)
initializeSigning(project)
}

private fun initializePublishing(project: Project) {
project.version = Versions.kakaoExtClicksVersion + versionSuffix

project.plugins.apply("maven-publish")

val (component, additionalArtifacts) = when {
project.extensions.findByType(LibraryExtension::class) != null -> {
val android = project.extensions.findByType(LibraryExtension::class)!!
val main = android.sourceSets.getByName("main")
val sourcesJar by project.tasks.creating(Jar::class) {
classifier = "sources"
from(main.java.srcDirs)
}

Pair(project.components["release"], listOf(sourcesJar))
}
project.the(JavaPluginConvention::class) != null -> {
val javaPlugin = project.the(JavaPluginConvention::class)

val sourcesJar by project.tasks.creating(Jar::class) {
classifier = "sources"
from(javaPlugin.sourceSets["main"].allSource)
}
Pair(project.components["java"], listOf(sourcesJar))
}
else -> {
throw RuntimeException("Unknown plugin")
}
}

project.configure<PublishingExtension> {
publications {
create("default", MavenPublication::class.java) {
KakaoExtClicksDeployment.customizePom(pom)
additionalArtifacts.forEach { it ->
artifact(it)
}
from(component)
}
}
repositories {
maven {
name = "Local"
setUrl("${project.rootDir}/build/repository")
}
maven {
name = "OSSHR"
credentials {
username = sonatypeUser
password = sonatypePassword
}
url = URI.create(deployUrl)
}
}
}
}

private fun initializeSigning(project: Project) {
val passphrase = System.getenv("GPG_PASSPHRASE")
passphrase?.let {
project.plugins.apply("signing")

val publishing = project.the(PublishingExtension::class)
project.configure<SigningExtension> {
sign(publishing.publications.getByName("default"))
}

project.extra.set("signing.keyId", "0110979F")
project.extra.set("signing.password", passphrase)
project.extra.set("signing.secretKeyRingFile", "${project.rootProject.rootDir}/buildsystem/secring.gpg")
}
}

fun customizePom(pom: MavenPom?) {
pom?.apply {
name.set("kakao-ext-clicks")
url.set("https://github.com/KakaoCup/Kakao/kakao-ext-clicks")
description.set("Clicks extension for Kakao")

licenses {
license {
name.set("The Apache License, Version 2.0")
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
}
}

scm {
url.set("https://github.com/KakaoCup/Kakao.git")
connection.set("scm:git:ssh://github.com/KakaoCup/Kakao")
developerConnection.set("scm:git:ssh://github.com/KakaoCup/Kakao")
}
}
}
}
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import java.io.File

object Versions {
val kakaoVersion = File("buildsystem/version").readText().trim()
val gmapsVersion = File("buildsystem/gmapsversion").readText().trim()
val kakaoExtClicksVersion = File("buildsystem/extclickversion").readText().trim()

val kotlin = "1.7.21"
val detekt = "1.17.1"
Expand Down
1 change: 1 addition & 0 deletions buildsystem/extclickversion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
1 change: 0 additions & 1 deletion buildsystem/gmapsversion

This file was deleted.

151 changes: 151 additions & 0 deletions kakao-ext-clicks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Kakao custom clicks

Espresso clicks implementation sometimes causes additional problems while devices are unstable.
It could be a click that was registered as long click. Click that is not registered at all. And so on.

From the [androidx.test.espresso.action.GeneralClickAction.perform] comment:

```
Native event injection is quite a tricky process. A tap is actually 2
seperate motion events which need to get injected into the system. Injection
makes an RPC call from our app under test to the Android system server, the
system server decides which window layer to deliver the event to, the system
server makes an RPC to that window layer, that window layer delivers the event
to the correct UI element, activity, or window object. Now we need to repeat
that 2x. for a simple down and up. Oh and the down event triggers timers to
detect whether or not the event is a long vs. short press. The timers are
removed the moment the up event is received (NOTE: the possibility of eventTime
being in the future is totally ignored by most motion event processors).
Phew.
The net result of this is sometimes we'll want to do a regular tap, and for
whatever reason the up event (last half) of the tap is delivered after long
press timeout (depending on system load) and the long press behaviour is
displayed (EG: show a context menu). There is no way to avoid or handle this more
gracefully. Also the longpress behavour is app/widget specific. So if you have
a seperate long press behaviour from your short press, you can pass in a
'RollBack' ViewAction which when executed will undo the effects of long press.
```

If you experience unreliable tap/click in UI tests you can try our naive but sometimes more reliable implementation, that dispatches events
directly to View.

## How to use

There are multiple ways to apply custom clicks:

### Apply KakaoClicksTestRule

If you want to apply it directly to single test class the TestRule is an obvious choice.

For example:
```
@Rule
@JvmField
var chain: TestRule = RuleChain.outerRule(ActivityScenarioRule(MyActivity::class.java))
.around(KakaoClicksTestRule())
```

### Override Kakao clicks behaviour

If you need to change it globally, you can override static variables of kakao, like that:

```
Kakao {
singleClickAction = KakaoSingleClick()
doubleClickAction = KakaoDoubleClick()
longClickAction = KakaoLongClick()
}
```

To revert this behavior you can set it back:

```
Kakao {
singleClickAction = EspressoSingleClick()
doubleClickAction = EspressoDoubleClick()
longClickAction = EspressoLongClick()
}
```

To make a precise change for single click you can implement your own function and execute your test code wrapped in it:

```kotlin
fun withCustomClicks(block: () -> Unit) {
Kakao {
singleClickAction = KakaoSingleClick()
doubleClickAction = KakaoDoubleClick()
longClickAction = KakaoLongClick()
}

block.invoke()

Kakao {
singleClickAction = EspressoSingleClick()
doubleClickAction = EspressoDoubleClick()
longClickAction = EspressoLongClick()
}
}

button {
withCustomClicks {
click() // clicked with custom mechanism
}
click() // clicked by espresso
}
```

## Click visualization

Click visualization is a useful debug tool. Usually it's enabled with Android option `Developer Options > Show taps`. Or using ADB:

```
adb shell settings put system show_touches 1
```

On different setups it can be impossible to set or it can simply not working.

To enable visual taps programmatically with custom clicks, use custom click constructor:

```kotlin
KakaoSingleClick(visualClicksConfig = VisualClicksConfig()) // null is the default argument
KakaoDoubleClick(visualClicksConfig = VisualClicksConfig())
KakaoLongClick(visualClicksConfig = VisualClicksConfig())
```

or if you are using TestRule:

```kotlin
KakaoClicksTestRule(visualClicksConfig = VisualClicksConfig())
```

to apply config to all types of clicks

`VisualClicksConfig` data class has some customization options: like color and radius of the tap circle.

## Global Center coordinates

There are some cases when standard espresso coordinates not working.
For example clicking on center of the view with applied property animations or transitions with help of `GeneralLocation.VISIBLE_CENTER`.
See explanation on why it happens [here](https://github.com/avito-tech/avito-android/pull/308).

`VisibleCenterGlobalCoordinatesProvider` to the rescue.

Pass it as a replacement for your click location like that:

```kotlin
tranformedView.click(VisibleCenterGlobalCoordinatesProvider())
```

## Customize emulator

You can also tune emulator a bit that could help with long click registration.

```
adb shell "settings put secure long_press_timeout 1500" // default can vary but usually it's 400ms
```

## Credits

Initial idea and implementation by [Avito](https://github.com/avito-tech/avito-android)
Loading

0 comments on commit deae63f

Please sign in to comment.