Skip to content

Commit 8a347f5

Browse files
committed
feat: add UI tests and CI workflow for example app
Introduce Compose-based UI tests for MainScreen and ColorVotingScreen. Add GitHub Actions workflow to run tests on pull requests and pushes to `main`.
1 parent a7c6b55 commit 8a347f5

5 files changed

Lines changed: 121 additions & 2 deletions

File tree

.github/workflows/example-app.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Example App
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
check:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
android-api-level: [ 29 ]
16+
17+
steps:
18+
- name: checkout
19+
uses: actions/checkout@v4
20+
21+
- name: Set up the JDK
22+
uses: actions/setup-java@v3
23+
with:
24+
java-version: '17'
25+
distribution: 'temurin'
26+
27+
- name: Set up Gradle
28+
uses: gradle/actions/setup-gradle@v3
29+
30+
- name: Enable KVM
31+
run: |
32+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
33+
sudo udevadm control --reload-rules
34+
sudo udevadm trigger --name-match=kvm
35+
36+
- uses: reactivecircus/android-emulator-runner@v2
37+
with:
38+
api-level: ${{ matrix.android-api-level }}
39+
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
40+
disable-animations: true
41+
# Print emulator logs if tests fail
42+
script: ./gradlew :examples:connectedAndroidTest || (adb logcat -d System.out:I && exit 1)

examples/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ android {
1414

1515
defaultConfig {
1616
applicationId = "com.ably.example"
17-
minSdk = 30
17+
minSdk = 29
1818
targetSdk = 35
1919
versionCode = 1
2020
versionName = "1.0"
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.ably.example
2+
3+
import androidx.compose.ui.semantics.SemanticsProperties
4+
import androidx.compose.ui.test.assertIsDisplayed
5+
import androidx.compose.ui.test.junit4.createAndroidComposeRule
6+
import androidx.compose.ui.test.onAllNodesWithText
7+
import androidx.compose.ui.test.onNodeWithTag
8+
import androidx.compose.ui.test.onNodeWithText
9+
import androidx.compose.ui.test.performClick
10+
import androidx.test.ext.junit.runners.AndroidJUnit4
11+
import org.junit.Rule
12+
import org.junit.Test
13+
import org.junit.runner.RunWith
14+
15+
@RunWith(AndroidJUnit4::class)
16+
class ColorVotingScreenTest {
17+
18+
@get:Rule
19+
val composeTestRule = createAndroidComposeRule<MainActivity>()
20+
21+
@Test
22+
fun incrementRedColor() {
23+
// Navigate to Color Voting tab
24+
composeTestRule.onNodeWithText("Color Voting").performClick()
25+
26+
// Wait for the screen to load
27+
composeTestRule.waitForIdle()
28+
29+
// Find and click the Vote button for Red color
30+
val redVoteButton = composeTestRule.onNodeWithTag("vote_button_red")
31+
32+
// Capture initial count
33+
val initial = composeTestRule.onNodeWithTag("counter_red")
34+
.fetchSemanticsNode()
35+
.config[SemanticsProperties.Text].first().text.toInt()
36+
37+
composeTestRule.waitUntil(timeoutMillis = 10_000) {
38+
SemanticsProperties.Disabled !in redVoteButton.fetchSemanticsNode().config
39+
}
40+
41+
redVoteButton.performClick()
42+
43+
// Wait for the counter to update with 5-seconds timeout
44+
composeTestRule.waitUntil(timeoutMillis = 5_000) {
45+
val updated = composeTestRule.onNodeWithTag("counter_red")
46+
.fetchSemanticsNode()
47+
.config[SemanticsProperties.Text].first().text.toInt()
48+
updated == initial + 1
49+
}
50+
}
51+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.ably.example
2+
3+
import androidx.compose.ui.test.assertIsDisplayed
4+
import androidx.compose.ui.test.junit4.createAndroidComposeRule
5+
import androidx.compose.ui.test.onNodeWithText
6+
import androidx.test.ext.junit.runners.AndroidJUnit4
7+
import org.junit.Rule
8+
import org.junit.Test
9+
import org.junit.runner.RunWith
10+
11+
@RunWith(AndroidJUnit4::class)
12+
class MainScreenTest {
13+
14+
@get:Rule
15+
val composeTestRule = createAndroidComposeRule<MainActivity>()
16+
17+
@Test
18+
fun tabsAreDisplayed() {
19+
// Verify both tabs are displayed
20+
composeTestRule.onNodeWithText("Color Voting").assertIsDisplayed()
21+
composeTestRule.onNodeWithText("Task Management").assertIsDisplayed()
22+
}
23+
}

examples/src/main/kotlin/com/ably/example/screen/ColorVotingScreen.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import androidx.compose.runtime.*
88
import androidx.compose.ui.Alignment
99
import androidx.compose.ui.Modifier
1010
import androidx.compose.ui.graphics.Color
11+
import androidx.compose.ui.platform.testTag
1112
import androidx.compose.ui.text.font.FontWeight
1213
import androidx.compose.ui.text.style.TextAlign
1314
import androidx.compose.ui.unit.dp
@@ -146,11 +147,13 @@ fun ColorVoteCard(
146147
Text(
147148
text = count.toString(),
148149
fontSize = 24.sp,
149-
fontWeight = FontWeight.Bold
150+
fontWeight = FontWeight.Bold,
151+
modifier = Modifier.testTag("counter_${colorName.lowercase()}")
150152
)
151153
OutlinedButton(
152154
onClick = onVote,
153155
enabled = enabled,
156+
modifier = Modifier.testTag("vote_button_${colorName.lowercase()}")
154157
) {
155158
Text(
156159
text = "Vote",

0 commit comments

Comments
 (0)