Test Kit annotations
The Test Kit provides utility annotations that can be used to run tests for a specific posture or device type. If you need to test complex app flows that switch between multiple postures, then you should simulate a folding feature or use swipe gestures instead.
For simpler tests that only use one posture, you can use annotations to:
- run tests in single-screen or dual-screen mode
- run tests on specific target devices
- run tests in a specific device orientation
- run tests with a simulated folding feature
Important
These annotations only work with FoldableTestRule and FoldableJUnit4ClassRunner. In the Test Kit components section below, you can find information about how to use them with the annotations.
Setup
Create a new test class file in the androidTest directory. This is where you will later add in the code snippets for test rules and tests.
Make sure you have the
mavenCentral()
repository in your top-level build.gradle file:allprojects { repositories { google() mavenCentral() } }
Add the following dependencies to your module-level build.gradle file (current version may be different from what's shown here):
androidTestImplementation "com.microsoft.device.dualscreen.testing:testing-kotlin:1.0.0-alpha4" androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0" androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0" androidTestImplementation "androidx.test:runner:1.4.0" androidTestImplementation "androidx.test:rules:1.4.0"
Ensure the
compileSdkVersion
andtargetSdkVersion
are set to API 31 or newer in your module-level build.gradle file:android { compileSdkVersion 31 defaultConfig { targetSdkVersion 31 } ... }
Create a
TestRule
that can perform UI checks and simulate folding features. You can do this by chaining together two rules: aFoldableTestRule
rule and an ActivityScenarioRule or AndroidComposeTestRule.You can use the
foldableRuleChain
method to create the rule chain or you can create your own rule chain as shown in the following example:private val activityScenarioRule = activityScenarioRule<MainActivity>() private val foldableTestRule = FoldableTestRule() @get:Rule val testRule: TestRule = foldableRuleChain(activityScenarioRule, foldableTestRule)
or
private val uiDevice: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) private val activityScenarioRule = activityScenarioRule<MainActivity>() private val foldableTestRule = FoldableTestRule() @get:Rule val testRule: TestRule = if (uiDevice.isSurfaceDuo()) { RuleChain.outerRule(activityScenarioRule).around(foldableTestRule) } else { RuleChain.outerRule(foldableTestRule).around(activityScenarioRule) }
If you choose to create your own rule chain:
- For the
SurfaceDuo
andSurfaceDuo2
devices, theActivityScenarioRule
must be the outer rule because the app should be started before the span/unspan operations. - For other foldable devices, the
FoldableTestRule
must be the outer rule because the FoldingFeature should be mocked before app launch.
- For the
If you're using Espresso to test views, make sure to disable animations on your device.
Test Kit components
Important
Please see FoldableTestRule and FoldableJUnit4ClassRunner sections before you start using the following annotations.
SingleScreenTest/DualScreenTest
Add this annotation for the test method or test class if you want to run the test in single-screen/dual-screen mode. Also, using the orientation
parameter, you can run the test in the specified device orientation. The orientation
parameter can have the following values: UiAutomation.ROTATION_FREEZE_0
, UiAutomation.ROTATION_FREEZE_90
, UiAutomation.ROTATION_FREEZE_180
, UiAutomation.ROTATION_FREEZE_270
@RunWith(FoldableJUnit4ClassRunner::class)
@SingleScreenTest(orientation = UiAutomation.ROTATION_FREEZE_180)
class TestSample {
private val activityScenarioRule = activityScenarioRule<MainActivity>()
private val foldableTestRule = FoldableTestRule()
@get:Rule
val testRule: TestRule = foldableRuleChain(activityScenarioRule, foldableTestRule)
@Test
fun sampleTestMethod() {
}
}
or
@RunWith(FoldableJUnit4ClassRunner::class)
class TestSample {
private val activityScenarioRule = activityScenarioRule<MainActivity>()
private val foldableTestRule = FoldableTestRule()
@get:Rule
val testRule: TestRule = foldableRuleChain(activityScenarioRule, foldableTestRule)
@Test
@SingleScreenTest(orientation = UiAutomation.ROTATION_FREEZE_180)
fun sampleTestMethod() {
}
}
MockFoldingFeature
Use this annotation for the test method or test class if you want to mock the folding feature with the desired data.
The
windowBounds
parameter is an array of coordinates that represents some display area that will contain the FoldingFeature, for example [left, top, right, bottom].windowBounds
width and height must be greater than 0. By default, if you don't define your own windowBounds values, it will use the whole display area; more specifically, [0, 0, uiDevice.displayWidth, uiDevice.displayHeight].The
center
parameter is the center of the fold complementary to the orientation. For aHORIZONTAL
fold, this is the y-axis and for aVERTICAL
fold this is the x-axis. Default value is -1. If this parameter is not provided, then will be calculated based onwindowBounds
parameter as windowBounds.centerY() or windowBounds.centerX(), depending on the givenorientation
.The
size
parameter is the smaller dimension of the fold. The larger dimension always covers the entire window. Default value is 0.The
state
parameter represents the state of the fold. Possible values are:FoldingFeatureState.HALF_OPENED
andFoldingFeatureState.FLAT
. The default value isFoldingFeatureState.HALF_OPENED
. See the Posture section in the official documentation for visual samples and references.The
orientation
parameter is the orientation of the fold. Possible values are:FoldingFeatureOrientation.HORIZONTAL
andFoldingFeatureOrientation.VERTICAL
. The default value isFoldingFeatureOrientation.HORIZONTAL
.
@RunWith(FoldableJUnit4ClassRunner::class)
@MockFoldingFeature(
size = 2,
state = FoldingFeatureState.FLAT,
orientation = FoldingFeatureOrientation.HORIZONTAL
)
class TestSample {
private val activityScenarioRule = activityScenarioRule<MainActivity>()
private val foldableTestRule = FoldableTestRule()
@get:Rule
val testRule: TestRule = foldableRuleChain(activityScenarioRule, foldableTestRule)
@Test
fun sampleTestMethod() {
}
}
or
@RunWith(FoldableJUnit4ClassRunner::class)
class TestSample {
private val activityScenarioRule = activityScenarioRule<MainActivity>()
private val foldableTestRule = FoldableTestRule()
@get:Rule
val testRule: TestRule = foldableRuleChain(activityScenarioRule, foldableTestRule)
@Test
@MockFoldingFeature(
size = 2,
state = FoldingFeatureState.FLAT,
orientation = FoldingFeatureOrientation.HORIZONTAL
)
fun sampleTestMethod() {
}
}
TargetDevices
Use this annotation for the test method or test class if you want to run the test only on the specified devices or if you want to ignore some devices.
The
devices
parameter is an array of wanted devices and theignoreDevices
parameter is an array with the devices to be ignored.Example of usage:
@TargetDevices(devices = [DeviceModel.SurfaceDuo, DeviceModel.SurfaceDuo2]), @TargetDevices(ignoreDevices = [DeviceModel.SurfaceDuo])
You cannot use both parameters at the same time.
Possible values for device model:
DeviceModel.SurfaceDuo
- representation forSurfaceDuo1
device or emulatorDeviceModel.SurfaceDuo2
- representation forSurfaceDuo2
device and emulatorDeviceModel.HorizontalFoldIn
- representation for6.7" horizontal Fold-In
devices and emulatorsDeviceModel.FoldInOuterDisplay
- representation for7.6" Fold-In
with outer display devices and emulatorsDeviceModel.FoldOut
- representation for8" FoldOut
devices and emulatorsDeviceModel.Other
- representation for other foldable devices and emulators
@RunWith(FoldableJUnit4ClassRunner::class)
@TargetDevices(devices = [DeviceModel.SurfaceDuo])
class TestSample {
private val activityScenarioRule = activityScenarioRule<MainActivity>()
private val foldableTestRule = FoldableTestRule()
@get:Rule
val testRule: TestRule = foldableRuleChain(activityScenarioRule, foldableTestRule)
@Test
fun sampleTestMethod() {
}
}
or
@RunWith(FoldableJUnit4ClassRunner::class)
class TestSample {
private val activityScenarioRule = activityScenarioRule<MainActivity>()
private val foldableTestRule = FoldableTestRule()
@get:Rule
val testRule: TestRule = foldableRuleChain(activityScenarioRule, foldableTestRule)
@Test
@TargetDevices(devices = [DeviceModel.SurfaceDuo])
fun sampleTestMethod() {
}
}
DeviceOrientation
Use this annotation for the test method or test class if you want to run the test on the specified device orientation.
- The
orientation
parameter represents the device orientation and can have the following values:UiAutomation.ROTATION_FREEZE_0
,UiAutomation.ROTATION_FREEZE_90
,UiAutomation.ROTATION_FREEZE_180
,UiAutomation.ROTATION_FREEZE_270
@RunWith(FoldableJUnit4ClassRunner::class)
@DeviceOrientation(orientation = UiAutomation.ROTATION_FREEZE_180)
class TestSample {
private val activityScenarioRule = activityScenarioRule<MainActivity>()
private val foldableTestRule = FoldableTestRule()
@get:Rule
val testRule: TestRule = foldableRuleChain(activityScenarioRule, foldableTestRule)
@Test
fun sampleTestMethod() {
}
}
or
@RunWith(FoldableJUnit4ClassRunner::class)
class TestSample {
private val activityScenarioRule = activityScenarioRule<MainActivity>()
private val foldableTestRule = FoldableTestRule()
@get:Rule
val testRule: TestRule = foldableRuleChain(activityScenarioRule, foldableTestRule)
@Test
@DeviceOrientation(orientation = UiAutomation.ROTATION_FREEZE_180)
fun sampleTestMethod() {
}
}
FoldableTestRule
FoldableTestRule
is a custom TestRule
that must be used together with @SingleScreenTest
, @DualScreenTest
, @MockFoldingFeature
and @DeviceOrientation
annotations. The annotations will not work if this test rule is not used with them. This TestRule
uses reflection to retrieve these annotations and parameters in order to run the tests on the desired posture and device orientation.
FoldableJUnit4ClassRunner
FoldableJUnit4ClassRunner
is a custom AndroidJUnit4ClassRunner
that must be used together with @SingleScreenTest
, @DualScreenTest
, @MockFoldingFeature
, @DeviceOrientation
and @TargetDevices
annotations. This runner validates the parameters for the annotations. It will ignore tests when the current device is not in the target devices list, or is in the ignored devices list. If this Runner
is not used, the annotations will not be validated and the @TargetDevices
annotation will not have any effect.
Constraints for the annotations
@SingleScreenTest
,@DualScreenTest
,@MockFoldingFeature
cannot be used together. For example, you cannot have the same method or the same test class annotated with both@SingleScreenTest
and@DualScreenTest
annotations.@TargetDevices.devices
and@TargetDevices.ignoreDevices
cannot be used together. For example, you cannot have the same method or test class annotated with@TargetDevices(devices = [DeviceModel.SurfaceDuo, DeviceModel.SurfaceDuo2], ignoreDevices = [DeviceModel.Other])
.Correct usage:
@TargetDevices(devices = [DeviceModel.SurfaceDuo])
or@TargetDevices(ignoreDevices = [DeviceModel.SurfaceDuo])
@MockFoldingFeature.windowBounds
should be an array of four elements, representing the left, top, right, bottom coordinates of the display window. For example, you cannot use it like this:@MockFoldingFeature(windowBounds = [0, 0, 0])
or@MockFoldingFeature(windowBounds = [0, 0, 0, 0, 0])
.