|
1 | 1 | package com.pega.constellation.sdk.kmp.samples.androidcmpapp.test.cases |
2 | 2 |
|
| 3 | +import androidx.compose.ui.test.ComposeUiTest |
3 | 4 | import androidx.compose.ui.test.ExperimentalTestApi |
| 5 | +import androidx.compose.ui.test.SemanticsMatcher |
| 6 | +import androidx.compose.ui.test.SemanticsNodeInteractionCollection |
| 7 | +import androidx.compose.ui.test.filter |
| 8 | +import androidx.compose.ui.test.hasAnyAncestor |
4 | 9 | import androidx.compose.ui.test.hasContentDescription |
5 | | -import androidx.compose.ui.test.hasSetTextAction |
| 10 | +import androidx.compose.ui.test.hasTestTag |
6 | 11 | import androidx.compose.ui.test.hasText |
| 12 | +import androidx.compose.ui.test.onFirst |
7 | 13 | import androidx.compose.ui.test.onNodeWithContentDescription |
8 | 14 | import androidx.compose.ui.test.onNodeWithText |
| 15 | +import androidx.compose.ui.test.onSiblings |
9 | 16 | import androidx.compose.ui.test.performClick |
| 17 | +import androidx.compose.ui.test.performScrollTo |
10 | 18 | import androidx.compose.ui.test.performTextInput |
11 | 19 | import androidx.compose.ui.test.runComposeUiTest |
12 | | -import androidx.compose.ui.test.waitUntilDoesNotExist |
13 | 20 | import androidx.compose.ui.test.waitUntilNodeCount |
14 | 21 | import com.pega.constellation.sdk.kmp.samples.androidcmpapp.test.ComposeTest |
15 | 22 | import com.pega.constellation.sdk.kmp.samples.androidcmpapp.test.waitForNode |
16 | 23 | import com.pega.constellation.sdk.kmp.samples.androidcmpapp.test.waitForNodes |
17 | 24 | import com.pega.constellation.sdk.kmp.test.mock.PegaVersion |
| 25 | +import java.time.LocalDateTime |
18 | 26 | import kotlin.test.Test |
19 | 27 |
|
20 | 28 | @OptIn(ExperimentalTestApi::class) |
21 | | -class EmbeddedDataTest : ComposeTest(PegaVersion.v24_1_0) { |
| 29 | +class EmbeddedDataTest : ComposeTest(PegaVersion.v24_2_2) { |
| 30 | + |
22 | 31 | @Test |
23 | | - fun test_embedded_data() = runComposeUiTest { |
24 | | - setupApp("DIXL-MediaCo-Work-EmbeddedData") |
| 32 | + fun test_embedded_data_repeating_view() = runComposeUiTest { |
| 33 | + setupApp("O40M3A-MarekCo-Work-EmbeddedDataTest") |
25 | 34 |
|
26 | 35 | // create case |
27 | 36 | onNodeWithText("New Service").performClick() |
28 | 37 |
|
29 | 38 | // verify form title and instruction |
30 | | - waitForNode("Create EmbeddedData (E-", substring = true) |
31 | | - waitForNode("Embedded Data use-case", substring = true) |
32 | | - waitForNode("EmbeddedData cars editable") |
33 | | - waitForNode("EmbeddedData cars readonly") |
| 39 | + waitForNode("ED repeating view editable (", substring = true) |
| 40 | + waitForNode("ED repeating view editable & readonly instruction") |
| 41 | + |
| 42 | + // verify repeating views presence |
| 43 | + waitForNode("Cars repeating view editable") |
| 44 | + waitForNode("Cars repeating view readonly") |
34 | 45 |
|
35 | 46 | // remove and verify empty list |
36 | 47 | onNodeWithContentDescription("Delete item 1").performClick() |
37 | 48 | waitForNodes("No items", count = 2) |
38 | 49 | waitUntilNodeCount(hasContentDescription("No items"), count = 2) |
39 | | - |
40 | 50 | onNodeWithText("Add", substring = true).performClick() |
41 | 51 |
|
42 | | - // verify empty record |
43 | | - waitForNodes("Row 1", 2) |
44 | | - waitForNode("Details") |
45 | | - waitForNodes("Brand", 2) |
46 | | - waitForNodes("Model", 2) |
47 | | - waitForNodes("---", 2) |
48 | | - |
49 | | - // enter data |
50 | | - onNodeWithText("Client name").performTextInput("Lukasz") |
51 | | - onNode(hasText("Brand") and hasSetTextAction()).performTextInput("Audi") |
52 | | - onNode(hasText("Model") and hasSetTextAction()).performTextInput("A5") |
53 | | - onNodeWithText("Row 1").performClick() // remove focus to propagate data |
54 | | - |
55 | | - // verify data propagation |
56 | | - waitForNodes("Lukasz", 2) |
57 | | - waitForNodes("Audi", 2) |
58 | | - waitForNodes("A5", 2) |
59 | | - |
60 | | - // adding 2nd record |
| 52 | + // verify error banner |
| 53 | + onNodeWithText("Next").performClick() |
| 54 | + waitForNode("brand: Cannot be blank", substring = true) |
| 55 | + |
| 56 | + // enter data in row 1 and verify |
| 57 | + onAllNodes(hasAnyAncestor(hasTestTag("field_group_template_[Cars repeating view editable]"))).let { |
| 58 | + it.findFirstWithText("brand").performTextInput("Audi") |
| 59 | + it.findFirstWithText("model").performTextInput("A5") |
| 60 | + it.findFirstWithText("Price").performTextInput("123000") |
| 61 | + it.findFirstWithText("IsFirstOwner").onSiblings().onFirst().performClick() |
| 62 | + it.findFirstWithText("interior").performClick() |
| 63 | + onNodeWithText("comfort").performClick() |
| 64 | + it.findFirstWithText("Insurance").performClick() |
| 65 | + onNodeWithText("gold").performClick() |
| 66 | + it.findFirstWithText("client meeting date").performClick() |
| 67 | + onNodeWithText("Today, ", substring = true).performClick() |
| 68 | + onNodeWithText("OK").performClick() |
| 69 | + it.findFirstWithText("Client meeting time").performScrollTo().performClick() |
| 70 | + onNodeWithText("OK").performClick() |
| 71 | + it.findFirstWithText("Transaction date time").performScrollTo().performScrollTo() |
| 72 | + .performClick() |
| 73 | + onNodeWithText("Today, ", substring = true).performClick() |
| 74 | + onNodeWithText("OK").performClick() |
| 75 | + onNodeWithText("OK").performClick() |
| 76 | + it.findFirstWithText("Notes").performScrollTo().performTextInput("This is a note") |
| 77 | + // remove focus to propagate data |
| 78 | + onNodeWithText("Cars repeating view readonly").performScrollTo().performClick() |
| 79 | + |
| 80 | + // verify editable data |
| 81 | + verifyEmbeddedDataRecord( |
| 82 | + nodes = it, |
| 83 | + expectedDate = LocalDateTime.now().toString().substring(0, 10), |
| 84 | + isEditable = true |
| 85 | + ) |
| 86 | + } |
| 87 | + // verify if row 1 data propagated to readonly duplicated view |
| 88 | + onAllNodes(hasAnyAncestor(hasTestTag("field_group_template_[Cars repeating view readonly]"))).let { |
| 89 | + verifyEmbeddedDataRecord( |
| 90 | + nodes = it, |
| 91 | + expectedDate = LocalDateTime.now().toString().substring(0, 10), |
| 92 | + isEditable = false |
| 93 | + ) |
| 94 | + } |
| 95 | + |
| 96 | + // adding row 2 |
61 | 97 | onNodeWithText("Add", substring = true).performClick() |
62 | 98 |
|
63 | | - // enter data in Row 2 |
64 | | - waitForNodes("Row 2", count = 2) |
65 | | - onNode(hasText("Client name") and !hasText("Lukasz") and hasSetTextAction()).performTextInput( |
66 | | - "Marek" |
67 | | - ) |
68 | | - onNode(hasText("Brand") and !hasText("Audi") and hasSetTextAction()).performTextInput("Ford") |
69 | | - onNode(hasText("Model") and !hasText("A5") and hasSetTextAction()).performTextInput("Focus") |
70 | | - onNodeWithText("Row 2").performClick() // remove focus to propagate data |
71 | | - |
72 | | - // verify data propagation for Row 2 |
73 | | - waitForNodes("Marek", 2) |
74 | | - waitForNodes("Ford", 2) |
75 | | - waitForNodes("Focus", 2) |
76 | | - |
77 | | - // remove Row 1 and verify |
78 | | - onNodeWithContentDescription("Delete item 2").performClick() |
79 | | - |
80 | | - waitUntilDoesNotExist(hasText("Row 2")) |
81 | | - waitUntilDoesNotExist(hasText("Marek")) |
82 | | - waitUntilDoesNotExist(hasText("Ford")) |
83 | | - waitUntilDoesNotExist(hasText("Focus")) |
84 | | - |
85 | | - // go to next step |
| 99 | + // enter data in row 2 |
| 100 | + waitForNodes("cars 2", count = 2) |
| 101 | + onAllNodes(hasAnyAncestor(hasTestTag("field_group_template_[Cars repeating view editable]"))).let { nodes -> |
| 102 | + nodes.filter(hasAnyAncestor(hasTestTag("field_group_item_2"))).let { |
| 103 | + it.findFirstWithText("brand").performTextInput("Ford") |
| 104 | + nodes.findFirstWithText("cars 2").performClick() // remove focus to propagate data |
| 105 | + it.findFirstWithText("Ford").assertExists() |
| 106 | + } |
| 107 | + } |
| 108 | + // verify data in row 2 propagated to duplicated |
| 109 | + onAllNodes(hasAnyAncestor(hasTestTag("field_group_template_[Cars repeating view readonly]"))).let { nodes -> |
| 110 | + nodes.filter(hasAnyAncestor(hasTestTag("field_group_item_2"))).let { |
| 111 | + it.findFirstWithText("Ford").assertExists() |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + // 2nd step |
86 | 116 | onNodeWithText("Next").performClick() |
| 117 | + waitForNode("ED repeating view readonly (", substring = true) |
| 118 | + |
| 119 | + // verify row 1 on second step |
| 120 | + onAllNodes(hasAnyAncestor(hasTestTag("field_group_template_[Cars repeating view readonly]"))).let { |
| 121 | + verifyEmbeddedDataRecord(it, expectedDate = "2025-12-16", isEditable = false) |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + fun ComposeUiTest.verifyEmbeddedDataRecord( |
| 126 | + nodes: SemanticsNodeInteractionCollection, |
| 127 | + expectedDate: String, |
| 128 | + isEditable: Boolean |
| 129 | + ) { |
| 130 | + with(nodes) { |
| 131 | + findFirstWithText("Audi").assertExists() |
| 132 | + findFirstWithText("A5").assertExists() |
| 133 | + findFirstWithText("123000").assertExists() |
| 134 | + if (!isEditable) { |
| 135 | + findFirstWithText("Yes").assertExists() |
| 136 | + } |
| 137 | + findFirstWithText("comfort").assertExists() |
| 138 | + findFirstWithText("gold").assertExists() |
| 139 | + findFirstWithText(expectedDate).assertExists() |
| 140 | + findFirstWithText("12:00 AM").assertExists() |
| 141 | + findFirstWithText("$expectedDate 12:00 AM").assertExists() |
| 142 | + findFirstWithText("Notes").performScrollTo() |
| 143 | + waitUntilAtLeastOneExists(nodes, hasText("This is a note"), timeoutMillis = 5000L) |
| 144 | + } |
| 145 | + } |
87 | 146 |
|
88 | | - // verify form components on 2nd step |
89 | | - waitForNode("Verify EmbeddedData (E-", substring = true) |
90 | | - waitForNode("EmbeddedData cars readonly") |
91 | | - waitForNode("Lukasz") |
92 | | - waitForNode("Details") |
93 | | - waitForNode("Brand") |
94 | | - waitForNode("Audi") |
95 | | - waitForNode("Model") |
96 | | - waitForNode("A5") |
| 147 | + fun SemanticsNodeInteractionCollection.findFirstWithText(text: String) = |
| 148 | + this.filter(hasText(text)).onFirst() |
| 149 | + |
| 150 | + fun ComposeUiTest.waitUntilAtLeastOneExists( |
| 151 | + nodes: SemanticsNodeInteractionCollection, |
| 152 | + matcher: SemanticsMatcher, |
| 153 | + timeoutMillis: Long = 5000L |
| 154 | + ) { |
| 155 | + waitUntil("exactly 1 nodes match (${matcher.description})", timeoutMillis) { |
| 156 | + nodes.filter(matcher).fetchSemanticsNodes().size == 1 |
| 157 | + } |
97 | 158 | } |
98 | 159 | } |
0 commit comments