Skip to content

Commit

Permalink
Merge pull request #12 from sergio-sastre/release/1.1.2
Browse files Browse the repository at this point in the history
Release/1.1.2
  • Loading branch information
sergio-sastre authored Jun 21, 2022
2 parents 5142437 + 408af31 commit 0d52122
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 90 deletions.
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,14 @@ allprojects {
Add a dependency to `build.gradle`
```groovy
dependencies {
androidTestImplementation 'com.github.sergio-sastre:AndroidUiTestingUtils:1.1.1'
androidTestImplementation 'com.github.sergio-sastre:AndroidUiTestingUtils:1.1.2'
}
```

# Usage
## Configuration
First, you need to add the following permission and activities to your `debug/manifest`
```xml
<!-- Required to change the Locale (for screenshot testing Activities only) -->
<uses-permission
android:name="android.permission.CHANGE_CONFIGURATION"
tools:ignore="ProtectedPermissions" />
...
<!-- Required for ActivityScenarios -->
<application
...
Expand All @@ -90,6 +85,16 @@ android {
}
```

### Pre 1.1.2
You also need to add the following permission and activities to your `debug/manifest` if not using version 1.1.2 or higher
```xml
<!-- Required to change the Locale via LocaleTestRule (required for snapshot testing Activities only) -->
<uses-permission
android:name="android.permission.CHANGE_CONFIGURATION"
tools:ignore="ProtectedPermissions" />
...
```

## Screenshot testing examples
The examples use [pedrovgs/Shot](https://github.com/pedrovgs/Shot). It'd also work with any other on-device screenshot testing framework, like Facebook [screenshot-tests-for-android](https://github.com/facebook/screenshot-tests-for-android), Dropbox [Dropshots](https://github.com/dropbox/dropshots) or with a custom screenshot testing solution.

Expand Down Expand Up @@ -136,7 +141,7 @@ fun snapViewHolderTest() {
val activity = activityScenario.waitForActivity()

val layout = waitForView {
// inflate layout inside the activity with its context -> inherits configuration
// IMPORTANT: To inherit the configuration, inflate layout inside the activity with its context
activity.inflate(R.layout.your_view_holder_layout)
}

Expand Down Expand Up @@ -230,9 +235,14 @@ val uiMode = DayNightRule(UiMode.NIGHT)

activity.rotateTo(Orientation.LANDSCAPE)
```
**WARNING**: When using DisplaySizeTestRule and FontSizeTesRule together in the same test, make sure your emulator has enough RAM and VM heap to avoid Exceptions when running the tests.
The recommended configuration is the following:
- RAM: 4GB
- VM heap: 1GB

# Code attributions
This library has been possible due to the work others have done previously. Most TestRules are based on code written by others:
- LocaleTestRule -> [Screengrab](https://github.com/fastlane/fastlane/tree/master/screengrab/screengrab-lib/src/main/java/tools.fastlane.screengrab/locale)
- LocaleTestRule -> [Screengrab](https://github.com/fastlane/fastlane/tree/master/screengrab/screengrab-lib/src/main/java/tools.fastlane.screengrab/locale) (pre 1.1.2 only)
- FontSizeTestRule -> [Novoda/espresso-support](https://github.com/novoda/espresso-support/tree/master/core/src/main/java/com/novoda/espresso)
- UiModeTestRule -> [AdevintaSpain/Barista](https://github.com/AdevintaSpain/Barista)
- Orientation change for activities -> [Shopify/android-testify](https://github.com/Shopify/android-testify/)
Expand Down
7 changes: 4 additions & 3 deletions utils/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ android {
defaultConfig {
minSdk 21
targetSdk 31
versionCode 6
versionName "1.1.1"
versionCode 7
versionName "1.1.2"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
Expand All @@ -33,6 +33,7 @@ android {
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.6.0-alpha04'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.test.ext:junit:1.1.3'
Expand All @@ -47,7 +48,7 @@ afterEvaluate {
from components.release
groupId = 'sergio.sastre'
artifactId = 'uitesting.utils'
version = '1.1.1'
version = '1.1.2'
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ object ActivityScenarioConfigurator {
locale?.run { newConfig.setLocale(this) }
uiMode?.run { newConfig.uiMode = this.configurationInt }
displaySize?.run {
val newDensityDpi = this.value.toFloat() * this@wrap.resources.configuration.densityDpi
val newDensityDpi = this.value.toFloat() * newConfig.densityDpi
newConfig.densityDpi = newDensityDpi.toInt()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package sergio.sastre.uitesting.utils.testrules.displaysize

import android.content.res.Resources
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import sergio.sastre.uitesting.utils.common.DisplaySize

class DisplayScaleSetting internal constructor(private val resources: Resources) {
class DisplayScaleSetting internal constructor() {

private val resources
get() = getInstrumentation().targetContext.resources

fun getDensityDpi(): Int {
return resources.configuration.densityDpi
}

fun resetDisplaySizeScale(originalDensity: Int) {
fun setDisplaySizeScale(originalDensity: Int) {
try {
getInstrumentation().uiAutomation.executeShellCommand("wm density reset")
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sergio.sastre.uitesting.utils.testrules.displaysize

import android.os.SystemClock
import android.util.Log
import androidx.annotation.IntRange

import org.junit.rules.TestRule
Expand All @@ -22,12 +23,16 @@ class DisplaySizeTestRule(
) : TestWatcher(), TestRule {

companion object {
fun smallDisplaySizeTestRule(): DisplaySizeTestRule = DisplaySizeTestRule(DisplaySize.SMALL)
fun smallDisplaySizeTestRule(): DisplaySizeTestRule = DisplaySizeTestRule(
DisplaySize.SMALL
)

fun normalDisplaySizeTestRule(): DisplaySizeTestRule =
DisplaySizeTestRule(DisplaySize.NORMAL)

fun largeDisplaySizeTestRule(): DisplaySizeTestRule = DisplaySizeTestRule(DisplaySize.LARGE)
fun largeDisplaySizeTestRule(): DisplaySizeTestRule = DisplaySizeTestRule(
DisplaySize.LARGE
)

fun largerDisplaySizeTestRule(): DisplaySizeTestRule =
DisplaySizeTestRule(DisplaySize.LARGER)
Expand All @@ -38,26 +43,25 @@ class DisplaySizeTestRule(

private var timeOutInMillis = MAX_RETRIES_TO_WAIT_FOR_SETTING * SLEEP_TO_WAIT_FOR_SETTING_MILLIS

private val displayScaleSetting: DisplayScaleSetting =
DisplayScaleSetting(getInstrumentation().targetContext.resources)
private val displayScaleSetting: DisplayScaleSetting = DisplayScaleSetting()

private var previousScale: Int = 0
private var previousDensityDpi: Int = 0

/**
* Since the Display Size setting is changed via adb, it might take longer than expected to
* take effect, and could be device dependent. One can use this method to adjust the default
* time out which is [MAX_RETRIES_TO_WAIT_FOR_SETTING] * [SLEEP_TO_WAIT_FOR_SETTING_MILLIS]
*/
fun withTimeOut(@IntRange(from = 0) inMillis: Int): DisplaySizeTestRule = apply {
this.timeOutInMillis = inMillis
this.timeOutInMillis = inMillis
}

override fun starting(description: Description?) {
previousScale = getInstrumentation().targetContext.resources.configuration.densityDpi
previousDensityDpi = getInstrumentation().targetContext.resources.configuration.densityDpi
}

override fun finished(description: Description?) {
displayScaleSetting.resetDisplaySizeScale(previousScale)
displayScaleSetting.setDisplaySizeScale(previousDensityDpi)
}

override fun apply(base: Statement, description: Description): Statement {
Expand All @@ -76,12 +80,12 @@ class DisplaySizeTestRule(
val initialDisplay = scaleSetting.getDensityDpi()
val expectedDisplay = (initialDisplay * scale.value.toFloat()).toInt()
scaleSetting.setDisplaySizeScale(scale)
sleepUntil(scaleMatches(expectedDisplay))
sleepUntil(scaleMatches(expectedDisplay), expectedDisplay)

baseStatement.evaluate()

scaleSetting.resetDisplaySizeScale(initialDisplay)
sleepUntil(scaleMatches(initialDisplay))
scaleSetting.setDisplaySizeScale(initialDisplay)
sleepUntil(scaleMatches(initialDisplay), initialDisplay)
}

private fun scaleMatches(densityDpi: Int): Condition {
Expand All @@ -92,20 +96,28 @@ class DisplaySizeTestRule(
}
}

private fun sleepUntil(condition: Condition) {
@Synchronized
private fun sleepUntil(condition: Condition, expectedDisplay: Int) {
var iterations = 0
var retries = 0
while (!condition.holds()) {
val retriesCount = timeOutInMillis / SLEEP_TO_WAIT_FOR_SETTING_MILLIS
iterations++
val iterationsCount = timeOutInMillis / SLEEP_TO_WAIT_FOR_SETTING_MILLIS
SystemClock.sleep(SLEEP_TO_WAIT_FOR_SETTING_MILLIS.toLong())
if (retries == retriesCount) {
throw timeoutError(retries)
val mustRetry = iterations % 10 == 0
if (mustRetry) {
retries++
scaleSetting.setDisplaySizeScale(expectedDisplay)
Log.d("DisplaySizeTestRule", "trying to set DisplaySize to $expectedDisplay, $retries retry")
}
if (iterations == iterationsCount) {
throw timeoutError()
}
retries++
}
}

private fun timeoutError(retries: Int): AssertionError {
return AssertionError("Spent too long waiting trying to set display scale.$retries retries")
private fun timeoutError(): AssertionError {
return AssertionError("Spent too long waiting trying to set display scale: $timeOutInMillis milliseconds")
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import sergio.sastre.uitesting.utils.common.FontSize

class FontScaleSetting internal constructor(private val resources: Resources) {
class FontScaleSetting internal constructor() {

private val resources
get() = getInstrumentation().targetContext.resources

fun get(): FontSize {
return FontSize.from(resources.configuration.fontScale)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sergio.sastre.uitesting.utils.testrules.fontsize

import android.os.SystemClock
import android.util.Log
import androidx.annotation.IntRange

import org.junit.rules.TestRule
Expand All @@ -11,7 +12,6 @@ import org.junit.runners.model.Statement
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import sergio.sastre.uitesting.utils.common.FontSize
import sergio.sastre.uitesting.utils.testrules.Condition
import sergio.sastre.uitesting.utils.testrules.displaysize.DisplaySizeTestRule
import sergio.sastre.uitesting.utils.testrules.fontsize.FontSizeTestRule.FontScaleStatement.Companion.MAX_RETRIES_TO_WAIT_FOR_SETTING
import sergio.sastre.uitesting.utils.testrules.fontsize.FontSizeTestRule.FontScaleStatement.Companion.SLEEP_TO_WAIT_FOR_SETTING_MILLIS

Expand Down Expand Up @@ -39,8 +39,7 @@ class FontSizeTestRule(

private var timeOutInMillis = MAX_RETRIES_TO_WAIT_FOR_SETTING * SLEEP_TO_WAIT_FOR_SETTING_MILLIS

private val fontScaleSetting: FontScaleSetting =
FontScaleSetting(getInstrumentation().targetContext.resources)
private val fontScaleSetting: FontScaleSetting = FontScaleSetting()

private var previousScale: Float = 0.toFloat()

Expand Down Expand Up @@ -76,12 +75,12 @@ class FontSizeTestRule(
override fun evaluate() {
val initialScale = scaleSetting.get()
scaleSetting.set(scale)
sleepUntil(scaleMatches(scale))
sleepUntil(scaleMatches(scale), scale)

baseStatement.evaluate()

scaleSetting.set(initialScale)
sleepUntil(scaleMatches(initialScale))
sleepUntil(scaleMatches(initialScale), initialScale)
}

private fun scaleMatches(scale: FontSize): Condition {
Expand All @@ -92,20 +91,28 @@ class FontSizeTestRule(
}
}

private fun sleepUntil(condition: Condition) {
@Synchronized
private fun sleepUntil(condition: Condition, expectedFontSize: FontSize) {
var iterations = 0
var retries = 0
while (!condition.holds()) {
iterations++
val retriesCount = timeOutInMillis / SLEEP_TO_WAIT_FOR_SETTING_MILLIS
SystemClock.sleep(SLEEP_TO_WAIT_FOR_SETTING_MILLIS.toLong())
if (retries == retriesCount) {
throw timeoutError(retries)
val mustRetry = iterations % 10 == 0
if (mustRetry) {
retries++
scaleSetting.set(expectedFontSize)
Log.d("FontSizeTestRule", "trying to set FontSize to ${expectedFontSize.name}, $retries retry")
}
if (iterations == retriesCount) {
throw timeoutError()
}
retries++
}
}

private fun timeoutError(retries: Int): AssertionError {
return AssertionError("Spent too long waiting trying to set font scale.$retries retries")
private fun timeoutError(): AssertionError {
return AssertionError("Spent too long waiting trying to set font scale: $timeOutInMillis milliseconds")
}

companion object {
Expand Down
Loading

0 comments on commit 0d52122

Please sign in to comment.