Skip to content

Commit 09e4c83

Browse files
committed
feat: Add test tags for UI automation
This commit introduces `TestTag` objects across various feature modules (`common-feature`, `dashboard-feature`, `issuance-feature`, `ui-logic`) to provide unique identifiers for UI components. These tags are crucial for enabling robust UI automation and testing. The changes include: - Creating centralized `TestTag.kt` files in each relevant module to define test tags for different screens and components. - Applying these tags to various Composables such as buttons, text fields, list items, and screen content headers. - Adding an `optionalTestTag` modifier extension to conditionally apply test tags. - Updating UI models and interactors to include necessary identifiers (`configurationId`) for creating unique test tags.
1 parent dabb9a3 commit 09e4c83

File tree

22 files changed

+265
-16
lines changed

22 files changed

+265
-16
lines changed

common-feature/src/main/java/eu/europa/ec/commonfeature/ui/biometric/BiometricScreen.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import androidx.compose.runtime.getValue
3434
import androidx.compose.ui.Alignment
3535
import androidx.compose.ui.Modifier
3636
import androidx.compose.ui.platform.LocalContext
37+
import androidx.compose.ui.platform.testTag
3738
import androidx.compose.ui.res.stringResource
3839
import androidx.compose.ui.text.input.PasswordVisualTransformation
3940
import androidx.compose.ui.unit.dp
@@ -42,6 +43,7 @@ import androidx.navigation.NavController
4243
import eu.europa.ec.commonfeature.config.BiometricMode
4344
import eu.europa.ec.commonfeature.config.BiometricUiConfig
4445
import eu.europa.ec.commonfeature.config.OnBackNavigationConfig
46+
import eu.europa.ec.commonfeature.util.TestTag
4547
import eu.europa.ec.resourceslogic.R
4648
import eu.europa.ec.uilogic.component.AppIconAndText
4749
import eu.europa.ec.uilogic.component.AppIconAndTextDataUi
@@ -267,6 +269,7 @@ private fun MainContent(
267269
) {
268270
Text(
269271
modifier = Modifier
272+
.testTag(TestTag.BiometricScreen.PIN_TEXT)
270273
.fillMaxWidth()
271274
.padding(vertical = SPACING_SMALL.dp),
272275
text = mode.textAbovePin,
@@ -301,6 +304,7 @@ private fun MainContent(
301304
) {
302305
Text(
303306
text = mode.title,
307+
modifier = Modifier.testTag(TestTag.BiometricScreen.PIN_TITLE),
304308
style = MaterialTheme.typography.headlineMedium.copy(
305309
color = MaterialTheme.colorScheme.onSurface
306310
)

common-feature/src/main/java/eu/europa/ec/commonfeature/ui/document_success/DocumentSuccessScreen.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ import androidx.compose.runtime.LaunchedEffect
3232
import androidx.compose.runtime.getValue
3333
import androidx.compose.ui.Modifier
3434
import androidx.compose.ui.platform.LocalContext
35+
import androidx.compose.ui.platform.testTag
3536
import androidx.compose.ui.res.stringResource
3637
import androidx.compose.ui.unit.dp
3738
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3839
import androidx.navigation.NavController
40+
import eu.europa.ec.commonfeature.util.TestTag
3941
import eu.europa.ec.resourceslogic.R
4042
import eu.europa.ec.uilogic.component.content.ContentHeader
4143
import eu.europa.ec.uilogic.component.content.ContentScreen
@@ -67,6 +69,7 @@ fun DocumentSuccessScreen(
6769
stickyBottom = { paddingValues ->
6870
WrapStickyBottomContent(
6971
modifier = Modifier
72+
.testTag(TestTag.DocumentSuccessScreen.BUTTON)
7073
.fillMaxWidth()
7174
.padding(paddingValues),
7275
stickyBottomConfig = StickyBottomConfig(
@@ -146,6 +149,7 @@ private fun Content(
146149
ContentHeader(
147150
modifier = Modifier.fillMaxWidth(),
148151
config = state.headerConfig,
152+
descriptionTestTag = TestTag.DocumentSuccessScreen.CONTENT_HEADER_DESCRIPTION,
149153
)
150154

151155
Column(

common-feature/src/main/java/eu/europa/ec/commonfeature/ui/pin/PinScreen.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ import androidx.compose.runtime.rememberCoroutineScope
3737
import androidx.compose.ui.Alignment
3838
import androidx.compose.ui.Modifier
3939
import androidx.compose.ui.platform.LocalContext
40+
import androidx.compose.ui.platform.testTag
4041
import androidx.compose.ui.res.stringResource
4142
import androidx.compose.ui.text.input.PasswordVisualTransformation
4243
import androidx.compose.ui.unit.dp
4344
import androidx.lifecycle.compose.collectAsStateWithLifecycle
4445
import androidx.navigation.NavController
4546
import eu.europa.ec.commonfeature.model.PinFlow
47+
import eu.europa.ec.commonfeature.util.TestTag
4648
import eu.europa.ec.resourceslogic.R
4749
import eu.europa.ec.uilogic.component.AppIconAndText
4850
import eu.europa.ec.uilogic.component.AppIconAndTextDataUi
@@ -94,6 +96,7 @@ fun PinScreen(
9496
stickyBottom = { paddingValues ->
9597
WrapStickyBottomContent(
9698
modifier = Modifier
99+
.testTag(TestTag.PinScreen.BUTTON)
97100
.fillMaxWidth()
98101
.padding(paddingValues),
99102
stickyBottomConfig = StickyBottomConfig(
@@ -202,6 +205,7 @@ private fun Content(
202205
) {
203206
Text(
204207
text = state.title,
208+
modifier = Modifier.testTag(TestTag.PinScreen.TITLE),
205209
style = MaterialTheme.typography.headlineMedium.copy(
206210
color = MaterialTheme.colorScheme.onSurface
207211
)

common-feature/src/main/java/eu/europa/ec/commonfeature/ui/request/RequestScreen.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@ import androidx.compose.runtime.LaunchedEffect
3434
import androidx.compose.runtime.getValue
3535
import androidx.compose.runtime.rememberCoroutineScope
3636
import androidx.compose.ui.Modifier
37+
import androidx.compose.ui.platform.testTag
3738
import androidx.compose.ui.res.stringResource
3839
import androidx.compose.ui.unit.dp
3940
import androidx.lifecycle.compose.collectAsStateWithLifecycle
4041
import androidx.navigation.NavController
4142
import eu.europa.ec.commonfeature.ui.request.model.DocumentPayloadDomain
4243
import eu.europa.ec.commonfeature.ui.request.model.DomainDocumentFormat
4344
import eu.europa.ec.commonfeature.ui.request.model.RequestDocumentItemUi
45+
import eu.europa.ec.commonfeature.util.TestTag
4446
import eu.europa.ec.corelogic.model.ClaimDomain
4547
import eu.europa.ec.corelogic.model.ClaimPathDomain
4648
import eu.europa.ec.resourceslogic.R
@@ -102,6 +104,7 @@ fun RequestScreen(
102104
stickyBottom = { paddingValues ->
103105
WrapStickyBottomContent(
104106
modifier = Modifier
107+
.testTag(TestTag.RequestScreen.BUTTON)
105108
.fillMaxWidth()
106109
.padding(paddingValues),
107110
stickyBottomConfig = StickyBottomConfig(
@@ -196,6 +199,7 @@ private fun Content(
196199
ContentHeader(
197200
modifier = Modifier.fillMaxWidth(),
198201
config = state.headerConfig,
202+
descriptionTestTag = TestTag.RequestScreen.CONTENT_HEADER_DESCRIPTION,
199203
)
200204

201205
// Screen Main Content.
@@ -252,9 +256,11 @@ private fun DisplayRequestItems(
252256
modifier = Modifier.fillMaxSize(),
253257
verticalArrangement = Arrangement.spacedBy(SPACING_MEDIUM.dp)
254258
) {
255-
requestDocuments.forEach { requestDocument ->
259+
requestDocuments.forEachIndexed { index, requestDocument ->
256260
WrapExpandableListItem(
257-
modifier = Modifier.fillMaxWidth(),
261+
modifier = Modifier
262+
.testTag(TestTag.RequestScreen.requestedDocument(index = index))
263+
.fillMaxWidth(),
258264
header = requestDocument.headerUi.header,
259265
data = requestDocument.headerUi.nestedItems,
260266
onItemClick = { item ->

common-feature/src/main/java/eu/europa/ec/commonfeature/ui/success/SuccessScreen.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ import androidx.compose.ui.Modifier
3333
import androidx.compose.ui.graphics.ColorFilter
3434
import androidx.compose.ui.layout.ContentScale
3535
import androidx.compose.ui.platform.LocalContext
36+
import androidx.compose.ui.platform.testTag
3637
import androidx.compose.ui.res.stringResource
3738
import androidx.compose.ui.text.style.TextAlign
3839
import androidx.compose.ui.unit.dp
3940
import androidx.lifecycle.compose.collectAsStateWithLifecycle
4041
import androidx.navigation.NavController
4142
import eu.europa.ec.commonfeature.config.SuccessUIConfig
43+
import eu.europa.ec.commonfeature.util.TestTag
4244
import eu.europa.ec.resourceslogic.R
4345
import eu.europa.ec.resourceslogic.theme.values.ThemeColors
4446
import eu.europa.ec.resourceslogic.theme.values.success
@@ -220,7 +222,9 @@ private fun Button(
220222
type = ButtonType.PRIMARY,
221223
onClick = { onEventSent(Event.ButtonClicked(config)) },
222224
),
223-
modifier = Modifier.fillMaxWidth(),
225+
modifier = Modifier
226+
.testTag(TestTag.SuccessScreen.PRIMARY_BUTTON)
227+
.fillMaxWidth(),
224228
) {
225229
ButtonRow(text = config.text)
226230
}
@@ -232,7 +236,9 @@ private fun Button(
232236
type = ButtonType.SECONDARY,
233237
onClick = { onEventSent(Event.ButtonClicked(config)) },
234238
),
235-
modifier = Modifier.fillMaxWidth()
239+
modifier = Modifier
240+
.testTag(TestTag.SuccessScreen.SECONDARY_BUTTON)
241+
.fillMaxWidth(),
236242
) {
237243
ButtonRow(text = config.text)
238244
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2025 European Commission
3+
*
4+
* Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European
5+
* Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work
6+
* except in compliance with the Licence.
7+
*
8+
* You may obtain a copy of the Licence at:
9+
* https://joinup.ec.europa.eu/software/page/eupl
10+
*
11+
* Unless required by applicable law or agreed to in writing, software distributed under
12+
* the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF
13+
* ANY KIND, either express or implied. See the Licence for the specific language
14+
* governing permissions and limitations under the Licence.
15+
*/
16+
17+
package eu.europa.ec.commonfeature.util
18+
19+
object TestTag {
20+
21+
object PinScreen {
22+
const val TITLE = "pin_screen_title"
23+
const val BUTTON = "pin_screen_button"
24+
}
25+
26+
object SuccessScreen {
27+
const val PRIMARY_BUTTON = "success_screen_primary_button"
28+
const val SECONDARY_BUTTON = "success_screen_secondary_button"
29+
}
30+
31+
object DocumentSuccessScreen {
32+
const val CONTENT_HEADER_DESCRIPTION = "document_success_screen_content_header_description"
33+
const val BUTTON = "document_success_screen_button"
34+
}
35+
36+
object RequestScreen {
37+
const val CONTENT_HEADER_DESCRIPTION = "request_screen_content_header_description"
38+
const val BUTTON = "request_screen_button"
39+
40+
fun requestedDocument(index: Int) = "request_screen_requested_document_$index"
41+
}
42+
43+
object BiometricScreen {
44+
const val PIN_TEXT = "biometric_screen_pin_text"
45+
const val PIN_TITLE = "biometric_screen_title"
46+
}
47+
}

dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/component/BottomNavigation.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ import androidx.compose.material3.NavigationBarItemDefaults
2424
import androidx.compose.material3.Text
2525
import androidx.compose.runtime.Composable
2626
import androidx.compose.runtime.getValue
27+
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.platform.testTag
2729
import androidx.compose.ui.res.stringResource
2830
import androidx.navigation.NavController
2931
import androidx.navigation.NavDestination.Companion.hierarchy
3032
import androidx.navigation.NavGraph.Companion.findStartDestination
3133
import androidx.navigation.compose.currentBackStackEntryAsState
3234
import androidx.navigation.compose.rememberNavController
35+
import eu.europa.ec.dashboardfeature.util.TestTag
3336
import eu.europa.ec.resourceslogic.R
3437
import eu.europa.ec.uilogic.component.AppIcons
3538
import eu.europa.ec.uilogic.component.IconDataUi
@@ -77,6 +80,11 @@ fun BottomNavigationBar(navController: NavController) {
7780
) {
7881
navItems.forEach { screen ->
7982
NavigationBarItem(
83+
modifier = Modifier.testTag(
84+
TestTag.DashboardScreen.bottomNavigationItem(
85+
navItem = screen.route.lowercase()
86+
)
87+
),
8088
icon = {
8189
WrapIcon(
8290
iconData = screen.icon,

dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/documents/detail/DocumentDetailsScreen.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import androidx.compose.runtime.rememberCoroutineScope
4949
import androidx.compose.ui.Alignment
5050
import androidx.compose.ui.Modifier
5151
import androidx.compose.ui.graphics.Color
52+
import androidx.compose.ui.platform.testTag
5253
import androidx.compose.ui.res.stringResource
5354
import androidx.compose.ui.text.style.TextDecoration
5455
import androidx.compose.ui.unit.dp
@@ -61,6 +62,7 @@ import eu.europa.ec.corelogic.util.CoreActions
6162
import eu.europa.ec.dashboardfeature.ui.documents.detail.model.DocumentDetailsUi
6263
import eu.europa.ec.dashboardfeature.ui.documents.detail.model.DocumentIssuanceStateUi
6364
import eu.europa.ec.dashboardfeature.ui.documents.model.DocumentCredentialsInfoUi
65+
import eu.europa.ec.dashboardfeature.util.TestTag
6466
import eu.europa.ec.resourceslogic.R
6567
import eu.europa.ec.resourceslogic.theme.values.success
6668
import eu.europa.ec.resourceslogic.theme.values.warning
@@ -377,7 +379,9 @@ private fun SheetContent(
377379
leadingIcon = AppIcons.Delete,
378380
leadingIconTint = MaterialTheme.colorScheme.error,
379381
onPositiveClick = { onEventSent(Event.BottomSheet.Delete.PrimaryButtonPressed) },
380-
onNegativeClick = { onEventSent(Event.BottomSheet.Delete.SecondaryButtonPressed) }
382+
positiveButtonTestTag = TestTag.DocumentDetailsScreen.BOTTOM_SHEET_DELETE_DOCUMENT_POSITIVE_BUTTON,
383+
onNegativeClick = { onEventSent(Event.BottomSheet.Delete.SecondaryButtonPressed) },
384+
negativeButtonTestTag = TestTag.DocumentDetailsScreen.BOTTOM_SHEET_DELETE_DOCUMENT_NEGATIVE_BUTTON,
381385
)
382386

383387
is DocumentDetailsBottomSheetContent.BookmarkStoredInfo -> {
@@ -649,7 +653,9 @@ private fun ButtonsSection(onEventSend: (Event) -> Unit) {
649653
.navigationBarsPadding()
650654
) {
651655
WrapButton(
652-
modifier = Modifier.fillMaxWidth(),
656+
modifier = Modifier
657+
.testTag(TestTag.DocumentDetailsScreen.DELETE_BUTTON)
658+
.fillMaxWidth(),
653659
buttonConfig = ButtonConfig(
654660
type = ButtonType.SECONDARY,
655661
onClick = { onEventSend(Event.SecondaryButtonPressed) },

dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/documents/list/DocumentsScreen.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import androidx.compose.ui.Modifier
4949
import androidx.compose.ui.layout.onGloballyPositioned
5050
import androidx.compose.ui.platform.LocalContext
5151
import androidx.compose.ui.platform.LocalDensity
52+
import androidx.compose.ui.platform.testTag
5253
import androidx.compose.ui.res.stringResource
5354
import androidx.compose.ui.text.style.TextAlign
5455
import androidx.compose.ui.unit.dp
@@ -60,8 +61,10 @@ import eu.europa.ec.corelogic.model.DocumentCategory
6061
import eu.europa.ec.corelogic.model.DocumentIdentifier
6162
import eu.europa.ec.corelogic.util.CoreActions
6263
import eu.europa.ec.dashboardfeature.model.SearchItemUi
64+
import eu.europa.ec.dashboardfeature.ui.component.BottomNavigationItem
6365
import eu.europa.ec.dashboardfeature.ui.documents.detail.model.DocumentIssuanceStateUi
6466
import eu.europa.ec.dashboardfeature.ui.documents.list.model.DocumentUi
67+
import eu.europa.ec.dashboardfeature.util.TestTag
6568
import eu.europa.ec.resourceslogic.R
6669
import eu.europa.ec.resourceslogic.theme.values.warning
6770
import eu.europa.ec.uilogic.component.AppIcons
@@ -229,7 +232,9 @@ private fun TopBar(
229232
)
230233

231234
WrapIconButton(
232-
modifier = Modifier.align(Alignment.CenterEnd),
235+
modifier = Modifier
236+
.testTag(TestTag.DocumentsScreen.PLUS_BUTTON)
237+
.align(Alignment.CenterEnd),
233238
iconData = AppIcons.Add,
234239
customTint = MaterialTheme.colorScheme.onSurfaceVariant,
235240
) {
@@ -525,7 +530,8 @@ private fun DocumentsSheetContent(
525530
event = Event.BottomSheet.AddDocument.ScanQr,
526531
)
527532
),
528-
onEventSent = onEventSent
533+
onEventSent = onEventSent,
534+
hostTab = BottomNavigationItem.Documents.route.lowercase(),
529535
)
530536
}
531537

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright (c) 2025 European Commission
3+
*
4+
* Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European
5+
* Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work
6+
* except in compliance with the Licence.
7+
*
8+
* You may obtain a copy of the Licence at:
9+
* https://joinup.ec.europa.eu/software/page/eupl
10+
*
11+
* Unless required by applicable law or agreed to in writing, software distributed under
12+
* the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF
13+
* ANY KIND, either express or implied. See the Licence for the specific language
14+
* governing permissions and limitations under the Licence.
15+
*/
16+
17+
package eu.europa.ec.dashboardfeature.util
18+
19+
object TestTag {
20+
21+
object DashboardScreen {
22+
fun bottomNavigationItem(navItem: String) =
23+
"dashboard_screen_bottom_navigation_item_$navItem"
24+
}
25+
26+
object DocumentsScreen {
27+
const val PLUS_BUTTON = "documents_screen_plus_button"
28+
}
29+
30+
object DocumentDetailsScreen {
31+
const val DELETE_BUTTON = "document_details_screen_delete_button"
32+
const val BOTTOM_SHEET_DELETE_DOCUMENT_POSITIVE_BUTTON =
33+
"document_details_screen_dialogue_delete_document_positive_button"
34+
const val BOTTOM_SHEET_DELETE_DOCUMENT_NEGATIVE_BUTTON =
35+
"document_details_screen_dialogue_delete_document_negative_button"
36+
}
37+
}

0 commit comments

Comments
 (0)