Skip to content

Commit cedd48f

Browse files
authored
Run selected instrumented tests (#5214)
Add ability to run selected instrumented tests. Fix iOS version check. Unmute ignored tests.
1 parent b463a44 commit cedd48f

File tree

8 files changed

+74
-35
lines changed

8 files changed

+74
-35
lines changed

instrumented-test/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This project is a Compose Multiplatform module that implements instrumented UI t
77
## Requirements
88

99
- Kotlin >= 2.1.0
10-
- Compose Multiplatform 1.8.0-alpha01
10+
- Compose Multiplatform 1.8.0-alpha02
1111
- iOS 12+
1212

1313
## Testing
@@ -16,5 +16,5 @@ To execute XCTest cases on an iOS Simulator, use:
1616

1717
```shell
1818
cd launcher
19-
xcrun xcodebuild test -scheme Launcher -destination "platform=iOS Simulator,name=iPhone 16 Pro"
19+
xcodebuild test -scheme Launcher -destination "platform=iOS Simulator,name=iPhone 16 Pro"
2020
```

instrumented-test/gradle/libs.versions.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
[versions]
22
androidx-lifecycle = "2.8.4"
3-
compose-multiplatform = "1.8.0-alpha01"
3+
compose-multiplatform = "1.8.0-alpha02"
44
junit = "4.13.2"
55
kotlin = "2.1.0"
66

77
[libraries]
8-
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
9-
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
10-
junit = { group = "junit", name = "junit", version.ref = "junit" }
118
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
129
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
1310

instrumented-test/ui-instrumented-test/src/iosMain/kotlin/androidx/compose/test/accessibility/ComponentsAccessibilitySemanticTest.kt

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import androidx.compose.runtime.mutableStateOf
1717
import androidx.compose.runtime.setValue
1818
import androidx.compose.test.utils.assertAccessibilityTree
1919
import androidx.compose.test.utils.available
20-
import androidx.compose.test.utils.findNode
20+
import androidx.compose.test.utils.findNodeWithTag
2121
import androidx.compose.test.utils.runUIKitInstrumentedTest
2222
import androidx.compose.ui.ExperimentalComposeUiApi
2323
import androidx.compose.ui.Modifier
@@ -35,7 +35,6 @@ import androidx.compose.ui.viewinterop.UIKitView
3535
import platform.UIKit.*
3636
import kotlin.test.*
3737

38-
@Ignore // TODO: Uncomment when switching to 1.8.0-alpha02
3938
class ComponentsAccessibilitySemanticTest {
4039
@OptIn(ExperimentalMaterialApi::class)
4140
@Test
@@ -97,7 +96,7 @@ class ComponentsAccessibilitySemanticTest {
9796
}
9897

9998
var oldValue = sliderValue
100-
val sliderNode = findNode("Slider")
99+
val sliderNode = findNodeWithTag("Slider")
101100
sliderNode.element?.accessibilityIncrement()
102101
assertTrue(oldValue < sliderValue)
103102

@@ -177,19 +176,19 @@ class ComponentsAccessibilitySemanticTest {
177176
}
178177
}
179178

180-
findNode("Switch").element?.accessibilityActivate()
179+
findNodeWithTag("Switch").element?.accessibilityActivate()
181180
assertTrue(switch)
182181
waitForIdle()
183-
findNode("Switch").element?.accessibilityActivate()
182+
findNodeWithTag("Switch").element?.accessibilityActivate()
184183
assertFalse(switch)
185184

186-
findNode("Checkbox").element?.accessibilityActivate()
185+
findNodeWithTag("Checkbox").element?.accessibilityActivate()
187186
assertTrue(checkbox)
188187
waitForIdle()
189-
findNode("Checkbox").element?.accessibilityActivate()
188+
findNodeWithTag("Checkbox").element?.accessibilityActivate()
190189
assertFalse(checkbox)
191190

192-
findNode("TriStateCheckbox").element?.accessibilityActivate()
191+
findNodeWithTag("TriStateCheckbox").element?.accessibilityActivate()
193192
assertEquals(ToggleableState.On, triStateCheckbox)
194193
}
195194

@@ -227,7 +226,7 @@ class ComponentsAccessibilitySemanticTest {
227226
}
228227
}
229228

230-
findNode("RadioButton").element?.accessibilityActivate()
229+
findNodeWithTag("RadioButton").element?.accessibilityActivate()
231230
assertAccessibilityTree {
232231
node {
233232
isAccessibilityElement = true

instrumented-test/ui-instrumented-test/src/iosMain/kotlin/androidx/compose/test/configuration.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import kotlinx.cinterop.ExperimentalForeignApi
99
import androidx.compose.xctest.*
1010
import platform.XCTest.XCTestSuite
1111

12-
// TODO: create a configuration setup procedure with test selection and reporting
12+
@Suppress("unused")
1313
@OptIn(ExperimentalForeignApi::class)
14-
fun testSuite(): XCTestSuite = setupXCTestSuite()
14+
fun testSuite(): XCTestSuite = setupXCTestSuite(
15+
// Run all test cases from the tests
16+
// BasicInteractionTest::class,
17+
// LayersAccessibilityTest::class,
18+
19+
// Run test cases from a test
20+
// BasicInteractionTest::testButtonClick,
21+
// LayersAccessibilityTest::testLayersAppearanceOrder
22+
)

instrumented-test/ui-instrumented-test/src/iosMain/kotlin/androidx/compose/test/utils/AccessibilityTestNode.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ internal fun UIKitInstrumentedTest.getAccessibilityTree(): AccessibilityTestNode
4242
if (count == NSIntegerMax) {
4343
when (element) {
4444
is UITableView -> {
45-
TODO("Unused in tests. Implement correct table view traversal.")
45+
println("warning: UITableView is currently unsupported")
4646
}
4747

4848
is UICollectionView -> {
49-
TODO("Unused in tests. Implement correct collection view traversal.")
49+
println("warning: UICollectionView is currently unsupported")
5050
}
5151

5252
is UIView -> {
@@ -244,9 +244,9 @@ internal fun UIKitInstrumentedTest.assertAccessibilityTree(
244244
assertAccessibilityTree(validator)
245245
}
246246

247-
internal fun UIKitInstrumentedTest.findNode(identifier: String) = findNodeOrNull {
248-
it.identifier == identifier
249-
} ?: fail("Unable to find node with identifier: $identifier")
247+
internal fun UIKitInstrumentedTest.findNodeWithTag(tag: String) = findNodeOrNull {
248+
it.identifier == tag
249+
} ?: fail("Unable to find node with identifier: $tag")
250250

251251
internal fun UIKitInstrumentedTest.findNodeWithLabel(label: String) = findNodeOrNull {
252252
it.label == label

instrumented-test/ui-instrumented-test/src/iosMain/kotlin/androidx/compose/test/utils/OsVersion.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import platform.Foundation.NSProcessInfo
1313
internal fun available(iosMajorVersion: Int, iosMinorVersion: Int = 0): Boolean {
1414
return NSProcessInfo.processInfo.operatingSystemVersion.useContents {
1515
when {
16-
majorVersion.toInt() > iosMajorVersion -> false
17-
majorVersion.toInt() < iosMajorVersion -> true
18-
minorVersion.toInt() > iosMinorVersion -> false
16+
majorVersion.toInt() < iosMajorVersion -> false
17+
majorVersion.toInt() > iosMajorVersion -> true
18+
minorVersion.toInt() < iosMinorVersion -> false
1919
else -> true
2020
}
2121
}

instrumented-test/ui-instrumented-test/src/iosMain/kotlin/androidx/compose/test/utils/UIKitInstrumentedTest.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ internal class UIKitInstrumentedTest {
104104
}
105105
}
106106

107+
fun delay(timeoutMillis: Long) {
108+
val runLoop = NSRunLoop.currentRunLoop()
109+
runLoop.runUntilDate(NSDate.dateWithTimeIntervalSinceNow(timeoutMillis.toDouble() / 1000.0))
110+
}
111+
107112
fun waitUntil(
108113
conditionDescription: String? = null,
109114
timeoutMillis: Long = 5_000,

instrumented-test/ui-xctest/src/iosMain/kotlin/androidx/compose/xctest/configuration.kt

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import platform.XCTest.XCTest
1212
import platform.XCTest.XCTestObservationCenter
1313
import platform.XCTest.XCTestSuite
1414
import kotlin.native.internal.test.*
15+
import kotlin.reflect.KClass
16+
import kotlin.reflect.KFunction
17+
import kotlin.reflect.KFunction1
18+
1519

1620
// Top level suite name used to hold all Native tests
1721
internal const val TOP_LEVEL_SUITE = "Kotlin/Native test suite"
@@ -25,14 +29,22 @@ private const val TEST_ARGUMENTS_KEY = "KotlinNativeTestArgs"
2529
*/
2630
private lateinit var testSettings: TestSettings
2731

32+
@Suppress("unused")
33+
fun setupXCTestSuite(vararg tests: KClass<*>): XCTestSuite =
34+
setupXCTestSuite(tests = tests.toSet(), testCases = null)
35+
36+
@Suppress("unused")
37+
inline fun <reified Class>setupXCTestSuite(vararg tests: KFunction1<Class, *>): XCTestSuite =
38+
setupXCTestSuite(tests = null, testCases = mapOf(Class::class.qualifiedName to tests.toSet()))
39+
2840
/**
2941
* This is an entry-point of XCTestSuites and XCTestCases generation.
3042
* Function returns the XCTest's top level TestSuite that holds all the test cases
3143
* with K/N tests.
3244
* This test suite can be run by either native launcher compiled to bundle or
3345
* by the other test suite (e.g. compiled as a framework).
3446
*/
35-
fun setupXCTestSuite(): XCTestSuite {
47+
fun setupXCTestSuite(tests: Set<KClass<*>>? = null, testCases: Map<String?, Set<KFunction<*>>>? = null): XCTestSuite {
3648
val nativeTestSuite = XCTestSuite.testSuiteWithName(TOP_LEVEL_SUITE)
3749

3850
// Initialize settings with the given args
@@ -52,16 +64,31 @@ fun setupXCTestSuite(): XCTestSuite {
5264
)
5365

5466
if (testSettings.runTests) {
55-
// Generate and add tests to the main suite
56-
testSettings.testSuites.generate().forEach {
67+
val includeAllTests = tests == null && testCases == null
68+
val testSuiteNames = tests?.map { it.qualifiedName }.orEmpty() + testCases?.keys.orEmpty()
69+
fun includeTestSuite(testSuite: TestSuite) =
70+
includeAllTests || testSuiteNames.contains(testSuite.name)
5771

72+
// Generate and add tests to the main suite
73+
testSettings.testSuites.generate(
74+
addTestSuite = { testSuite ->
75+
includeTestSuite(testSuite)
76+
},
77+
addTestCase = { testCase ->
78+
includeTestSuite(testCase.suite) &&
79+
(testCases == null ||
80+
testCases[testCase.suite.name]?.firstOrNull { it.name == testCase.name } != null)
81+
}
82+
).forEach {
5883
nativeTestSuite.addTest(it)
5984
}
6085

61-
// Tests created (self-check)
62-
@Suppress("UNCHECKED_CAST")
63-
check(testSettings.testSuites.size == (nativeTestSuite.tests as List<XCTest>).size) {
64-
"The amount of generated XCTest suites should be equal to Kotlin test suites"
86+
if (includeAllTests) {
87+
// Tests created (self-check)
88+
@Suppress("UNCHECKED_CAST")
89+
check(testSettings.testSuites.size == (nativeTestSuite.tests as List<XCTest>).size) {
90+
"The amount of generated XCTest suites should be equal to Kotlin test suites"
91+
}
6592
}
6693
}
6794

@@ -104,10 +131,13 @@ private fun testArguments(key: String): Array<String> {
104131

105132
internal val TestCase.fullName get() = "${suite.name}.$name"
106133

107-
private fun Collection<TestSuite>.generate(): List<XCTestSuite> {
108-
return this.map { suite ->
134+
private fun Collection<TestSuite>.generate(
135+
addTestSuite: (TestSuite) -> Boolean,
136+
addTestCase: (TestCase) -> Boolean
137+
): List<XCTestSuite> {
138+
return this.filter(addTestSuite).map { suite ->
109139
val xcSuite = XCTestSuiteWrapper(suite)
110-
suite.testCases.values.map { testCase ->
140+
suite.testCases.values.filter(addTestCase).map { testCase ->
111141
XCTestCaseWrapper(testCase)
112142
}.forEach {
113143
// add test to its test suite wrapper

0 commit comments

Comments
 (0)