Examples of advanced test cases.
The Sample application includes a baseline for an emulator that’s compatible with GitHub Actions. To configure an AVD locally, create a new virtual device with the following settings in the Android Virtual Device (AVD) configuration:
Once the emulator is booted:
en_US
)off
It is often desirable to capture only a portion of your screen or to capture a single View
.
For these cases, you can use the setScreenshotViewProvider
on ScreenshotRule
to specify which View
to capture.
Using ScreenshotRule.setScreenshotViewProvider
, you myst return a View
reference which will be used by Testify to narrow the bitmap to only that View.
@TestifyLayout(R.layout.view_client_details)
@ScreenshotInstrumentation
@Test
fun default() {
rule
.setScreenshotViewProvider {
it.findViewById(R.id.info_card)
}
.assertSame()
}
It is often desirable to test your View or Activity in multiple locales. Testify allows you to dynamically change the locale on a per-test basis.
To begin, if you are targeting an emulator running Android API 24 or higher, your activity under test must implement the TestifyResourcesOverride interface. This allows Testify to attach a new Context
with the appropriate locale loaded. It is highly recommended that you employ a test harness activity for this purpose. Please see the TestHarnessActivity in the provided Sample.
With an Activity which implements TestifyResourcesOverride
, you can now invoke the setLocale method on the ScreenshotTestRule
. setLocale
accepts any valid Locale instance.
Example Test:
class TestLocaleActivityTest {
@get:Rule var rule = ScreenshotRule(
activityClass = TestLocaleHarnessActivity::class.java,
launchActivity = false,
rootViewId = R.id.harness_root
)
@ScreenshotInstrumentation
@TestifyLayout(R.layout.view_client_details)
@Test
fun testLocaleFrance() {
rule
.setLocale(Locale.FRANCE)
.assertSame()
}
}
Example Test Harness Activity
open class TestHarnessActivity : AppCompatActivity(), TestifyResourcesOverride {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(FrameLayout(this).apply {
layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
id = R.id.harness_root
})
}
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase?.wrap())
}
}
Please read this excellent blog post if you want to better understand how to dynamically adjust Locale in your app. Note that the Testify locale override support is intended for instrumentation testing only and does not provide a suitable solution for your production application.
On lower API levels, a test harness activity is not required. You are not required to implement TestifyResourcesOverride
, but doing so is not harmful.
To test with a provided locale, invoke the setLocale
method on ScreenshotRule
Example Test:
class MainActivityScreenshotTest {
@get:Rule var rule = ScreenshotRule(MainActivity::class.java)
@ScreenshotInstrumentation
@TestifyLayout(R.layout.view_client_details)
@Test
fun testLocaleFrance() {
rule
.setLocale(Locale.FRANCE)
.assertSame()
}
}
Testify allows you to change the current Activity scaling factor for fonts, relative to the base density scaling. This allows you to simulate the impact of a user modifying the default font size on their device, such as tiny, large or huge.
:warning: Please note that, similar to changing the Locale (above), you are required to implement TestifyResourcesOverride
when invoking setFontScale()
.
See Font size and display size
Example Test:
class MainActivityScreenshotTest {
@get:Rule var rule = ScreenshotRule(MainActivity::class.java)
@ScreenshotInstrumentation
@TestifyLayout(R.layout.view_client_details)
@Test
fun testHugeFontScale() {
rule
.setFontScale(2.0f)
.assertSame()
}
}
In some cases, the captured screenshot may inherently contain randomness. It may then be desirable to allow for an inexact matching. By default, Testify employs an exact, pixel-by-pixel matching algorithm. Alternatively, you may optionally reduce this exactness.
By providing a value less than 1 to setExactness
, a test will be more tolerant to color differences. The fuzzy matching algorithm maps the captured image into the HSV color space
and compares the Hue, Saturation and Lightness components of each pixel. If they are within the provided tolerance, the images are considered to be the same.
:warning: Note that the fuzzy matching is approximately 10x slower than the default matching. Use sparingly.
@TestifyLayout(R.layout.view_client_details)
@ScreenshotInstrumentation
@Test
fun setExactness() {
rule
.setExactness(0.9f)
.setViewModifications {
val r = Integer.toHexString(Random.nextInt(0, 25) + 230).padStart(2, '0')
it.findViewById<View>(R.id.info_card).setBackgroundColor(Color.parseColor("#${r}0000"))
}
.assertSame()
}
TestifyLayout
in library projectsThe TestifyLayout
annotation allows you to specify a layout resource to be automatically loaded into the host Activity for testing.
Unfortunately R fields are not constants in Android library projects and R.layout resource IDs cannot be used as annotations parameters.
Instead, you can specify a fully qualified resource name of the form “package:type/entry” as the layoutResName
argument on TestifyLayout
.
class MainActivityScreenshotTest {
@get:Rule var rule = ScreenshotRule(MainActivity::class.java)
@TestifyLayout(layoutResName = "com.shopify.testify.sample:layout/view_client_details")
@ScreenshotInstrumentation
@Test
fun default() {
rule.assertSame()
}
}
Some activities may require a Bundle
of additional information called extras. Extras can be used to provide extended information to the component. For example, if we have a action to send an e-mail message, we could also include extra pieces of data here to supply a subject, body, etc.
To provide extras to your Activity, you can implement the addIntentExtras
method on ScreenshotRule
and pass a lambda that can add to the provided Bundle
.
class MainActivityScreenshotTest {
@get:Rule var rule = ScreenshotRule(MainActivity::class.java)
@ScreenshotInstrumentation
@Test
fun default() {
rule
.addIntentExtras {
it.putString("TOOLBAR_TITLE", "addIntentExtras")
}
.assertSame()
}
}
As an alternative to using the TestifyLayout
annotation, you may also specific a layout file to be loaded programmatically.
You can pass a R.layout.*
resource ID to setTargetLayoutId
on the ScreenshotRule
.
class MainActivityScreenshotTest {
@get:Rule var rule = ScreenshotRule(MainActivity::class.java)
@ScreenshotInstrumentation
@Test
fun default() {
rule
.setTargetLayoutId(R.layout.view_client_details)
.assertSame()
}
}
ScreenshotRule.setEspressoActions
accepts a lambda of type EspressoActions
in which you may define any number of Espresso actions. These actions are executed after the activity is fully inflated and any view modifications have been applied.
Testify will synchronize with the Espresso event loop and ensure that all Espresso actions are complete before capturing a screenshot.
Note that it’s not generally recommended to use complex Espresso actions with your screenshot tests. Espresso test are an order of magnitude slower to run and are more susceptible to flakiness.
Please check here for more information about Espresso testing.
class MainActivityScreenshotTest {
@get:Rule var rule = ScreenshotRule(MainActivity::class.java)
@TestifyLayout(R.layout.view_edit_text)
@ScreenshotInstrumentation
@Test
fun setEspressoActions() {
rule
.setEspressoActions {
onView(withId(R.id.edit_text)).perform(typeText("Testify"))
}
.assertSame()
}
}
public class MainActivityScreenshotTest {
@Rule
public ScreenshotRule rule = new ScreenshotRule<>(MainActivity.class);
@ScreenshotInstrumentation
@Test
public void testDefault() {
rule.assertSame();
}
}
Use the setOrientation
method to select between portrait and landscape mode.
@TestifyLayout(R.layout.view_client_details)
@ScreenshotInstrumentation
@Test
fun setOrientation() {
rule
.setOrientation(requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE)
.assertSame()
}
You may use Android Studio’s Layout Inspector in conjunction with your screenshot test. It can sometimes be useful to pause your test so that you can capture the layout hierarchy for further debugging in Android Studio. In order to do so, invoke the setLayoutInspectionModeEnabled
method on the test rule. This will pause the test after all ViewModifications have been applied and prior to the screenshot being taken. The test is paused for 5 minutes, allowing plenty of time to capture the layout.
@ScreenshotInstrumentation
@Test
fun testDefault() {
rule
.setLayoutInspectionModeEnabled(true)
.assertSame()
}
Testify provides three bitmap capture method. Each method will capture slightly different results based primarily on API level.
The three capture methods available are:
(1) Canvas: Render the view (and all of its children) to a given Canvas, using View.draw (2) DrawingCache: Pulls the view’s drawing cache bitmap using the deprecated View.getDrawingCache (3) PixelCopy: Use Android’s recommended PixelCopy API to capture the full screen, including elevation.
For legacy compatibility reasons, DrawingCache
mode is the default Testify capture method.
If you wish to select an alternative capture method, you can enable the experimental feature either in code, or in your manifest. Available features can be found in TestifyFeatures
Code:
@ScreenshotInstrumentation
@Test
fun testDefault() {
rule
.withExperimentalFeatureEnabled(TestifyFeatures.CanvasCapture)
.assertSame()
}
Manifest:
<manifest package="com.shopify.testify.sample"
xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<meta-data android:name="testify-canvas-capture" android:value="true" />
</application>
</manifest>
In some instances it may be desirable to use the software renderer, not Android’s default hardware renderer. Differences in GPU hardware from device to device (and emulators running on different architectures) may cause flakiness in rendering.
Please read more about Hardware acceleration for more information.
@ScreenshotInstrumentation
@Test
fun default() {
rule
.setUseSoftwareRenderer(true)
.assertSame()
}
For some Views, it may be impossible to guarantee a stable, consistent rendering. For instance, if the content is dynamic or randomized. For this reason, Testify provides the option to specify a series of rectangles to exclude from the comparison. All pixels in these rectangles are ignored and only pixels not contained will be compared.
Note that this comparison mechanism is slower than the default.
@ScreenshotInstrumentation
@Test
fun default() {
rule
.defineExclusionRects { rootView, exclusionRects ->
val card = rootView.findViewById<View>(R.id.info_card)
exclusionRects.add(card.boundingBox)
}
.assertSame()
}
It’s possible that users can navigate your app using a keyboard, because the Android system enables most of the necessary behaviors by default.
In order to place the keyboard focus on a specific View, use the setFocusTarget
method.
See https://developer.android.com/training/keyboard-input/navigation
@ScreenshotInstrumentation
@Test
fun default() {
rule
.setFocusTarget(enabled = true, focusTargetId = R.id.fab)
.assertSame()
}