Skip to content

Commit 192069d

Browse files
committed
Smoke test
1 parent b4ea696 commit 192069d

File tree

2 files changed

+128
-175
lines changed

2 files changed

+128
-175
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package com.darkrockstudios.app.securecamera
2+
3+
import android.app.Application
4+
import android.content.res.Resources
5+
import androidx.annotation.StringRes
6+
import androidx.compose.ui.semantics.Role
7+
import androidx.compose.ui.semantics.SemanticsProperties
8+
import androidx.compose.ui.test.SemanticsMatcher
9+
import androidx.compose.ui.test.assertIsDisplayed
10+
import androidx.compose.ui.test.hasSetTextAction
11+
import androidx.compose.ui.test.hasText
12+
import androidx.compose.ui.test.hasTextExactly
13+
import androidx.compose.ui.test.junit4.ComposeContentTestRule
14+
import androidx.compose.ui.test.performTextClearance
15+
import androidx.compose.ui.test.performTextInput
16+
import androidx.test.core.app.ApplicationProvider
17+
import kotlin.time.Duration
18+
import kotlin.time.Duration.Companion.seconds
19+
20+
/**
21+
* Waits for a node matching [matcher] to appear in either the unmerged or merged semantics tree.
22+
*
23+
* @return the matching SemanticsNodeInteraction (from the tree where it was found)
24+
*/
25+
fun ComposeContentTestRule.waitForEitherTree(
26+
matcher: SemanticsMatcher,
27+
timeout: Duration = 10.seconds,
28+
assertDisplayed: Boolean = true,
29+
preferUnmergedFirst: Boolean = true
30+
): androidx.compose.ui.test.SemanticsNodeInteraction {
31+
32+
fun hasAny(unmerged: Boolean) = onAllNodes(
33+
matcher,
34+
useUnmergedTree = unmerged
35+
).fetchSemanticsNodes().isNotEmpty()
36+
37+
val order = if (preferUnmergedFirst) listOf(true, false) else listOf(false, true)
38+
39+
waitUntil(timeout.inWholeMilliseconds) {
40+
hasAny(order[0]) || hasAny(order[1])
41+
}
42+
43+
// Choose the tree that actually has it now
44+
val chosenUnmerged = if (hasAny(order[0])) order[0] else order[1]
45+
46+
val node = onNode(
47+
matcher,
48+
useUnmergedTree = chosenUnmerged
49+
)
50+
51+
if (assertDisplayed) node.assertIsDisplayed() else node.assertExists()
52+
return node
53+
}
54+
55+
fun ComposeContentTestRule.waitForTextEitherTree(
56+
text: String,
57+
timeout: Duration = 10.seconds,
58+
substring: Boolean = true,
59+
ignoreCase: Boolean = false,
60+
assertDisplayed: Boolean = true,
61+
preferUnmergedFirst: Boolean = true
62+
) {
63+
fun hasAny(useUnmerged: Boolean) = onAllNodes(
64+
hasText(text, substring = substring, ignoreCase = ignoreCase),
65+
useUnmergedTree = useUnmerged
66+
).fetchSemanticsNodes().isNotEmpty()
67+
68+
val order = if (preferUnmergedFirst) listOf(true, false) else listOf(false, true)
69+
70+
waitUntil(timeout.inWholeMilliseconds) {
71+
hasAny(order[0]) || hasAny(order[1])
72+
}
73+
74+
// Pick the tree that actually has the node
75+
val chosenUnmerged = if (hasAny(order[0])) order[0] else order[1]
76+
77+
val node = onNode(
78+
hasText(text, substring = substring, ignoreCase = ignoreCase),
79+
useUnmergedTree = chosenUnmerged
80+
)
81+
if (assertDisplayed) node.assertIsDisplayed() else node.assertExists()
82+
}
83+
84+
fun ComposeContentTestRule.waitForTextEitherTree(
85+
@StringRes resId: Int,
86+
timeout: Duration = 10.seconds,
87+
substring: Boolean = true,
88+
ignoreCase: Boolean = false,
89+
assertDisplayed: Boolean = true,
90+
preferUnmergedFirst: Boolean = true
91+
) {
92+
val str = androidx.test.platform.app.InstrumentationRegistry
93+
.getInstrumentation().targetContext.getString(resId)
94+
waitForTextEitherTree(
95+
text = str,
96+
timeout = timeout,
97+
substring = substring,
98+
ignoreCase = ignoreCase,
99+
assertDisplayed = assertDisplayed,
100+
preferUnmergedFirst = preferUnmergedFirst
101+
)
102+
}
103+
104+
fun hasRole(role: Role): SemanticsMatcher =
105+
SemanticsMatcher.expectValue(SemanticsProperties.Role, role)
106+
107+
fun str(@StringRes id: Int): String = r.getString(id)
108+
private val r: Resources
109+
get() {
110+
val application = ApplicationProvider.getApplicationContext<Application>()
111+
return application.resources
112+
}
113+
114+
fun ComposeContentTestRule.setTextField(value: String, placeholder: Int) {
115+
onNode(
116+
hasSetTextAction() and hasTextExactly(
117+
str(placeholder),
118+
includeEditableText = false
119+
)
120+
).apply {
121+
performTextClearance()
122+
performTextInput(value)
123+
}
124+
}
Lines changed: 4 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,18 @@
11
package com.darkrockstudios.app.securecamera
22

33
import android.Manifest
4-
import android.app.Application
5-
import android.content.res.Resources
64
import android.os.Build
7-
import androidx.annotation.StringRes
85
import androidx.compose.ui.semantics.Role
9-
import androidx.compose.ui.semantics.SemanticsProperties
10-
import androidx.compose.ui.test.SemanticsMatcher
116
import androidx.compose.ui.test.assertIsDisplayed
127
import androidx.compose.ui.test.hasContentDescription
13-
import androidx.compose.ui.test.hasSetTextAction
148
import androidx.compose.ui.test.hasTestTag
15-
import androidx.compose.ui.test.hasText
16-
import androidx.compose.ui.test.hasTextExactly
179
import androidx.compose.ui.test.junit4.ComposeContentTestRule
1810
import androidx.compose.ui.test.junit4.createAndroidComposeRule
19-
import androidx.compose.ui.test.onNodeWithContentDescription
2011
import androidx.compose.ui.test.onNodeWithText
2112
import androidx.compose.ui.test.performClick
22-
import androidx.compose.ui.test.performTextClearance
23-
import androidx.compose.ui.test.performTextInput
24-
import androidx.test.core.app.ApplicationProvider
2513
import androidx.test.rule.GrantPermissionRule
2614
import org.junit.Rule
2715
import org.junit.Test
28-
import kotlin.time.Duration
29-
import kotlin.time.Duration.Companion.seconds
3016

3117

3218
class SmokeTestUiTest {
@@ -77,19 +63,20 @@ class SmokeTestUiTest {
7763

7864
waitForTextEitherTree(str(R.string.pin_creating_vault), assertDisplayed = false)
7965

80-
waitForButton(str(R.string.camera_shutter_button_desc))
66+
waitForEitherTree(hasRole(Role.Button) and hasContentDescription(str(R.string.camera_shutter_button_desc)))
8167

8268
onNode(
8369
hasRole(Role.Button) and hasContentDescription(str(R.string.camera_shutter_button_desc))
8470
).performClick()
8571

86-
waitForContentDescriptionSimple(str(R.string.camera_more_options_content_description))
72+
waitForEitherTree(hasContentDescription(str(R.string.camera_more_options_content_description)))
8773

8874
onNode(
8975
hasRole(Role.Button) and hasContentDescription(str(R.string.camera_more_options_content_description))
9076
).performClick()
9177

92-
waitForTestTagSimple("flash-switch")
78+
waitForEitherTree(hasTestTag("flash-switch"))
79+
9380
onNode(
9481
hasRole(Role.Switch) and hasTestTag("flash-switch")
9582
).performClick()
@@ -100,50 +87,6 @@ class SmokeTestUiTest {
10087
}
10188
}
10289

103-
private fun waitForButton(contentDescription: String) {
104-
composeTestRule.waitUntil(
105-
timeoutMillis = 5.seconds.inWholeMilliseconds
106-
) {
107-
composeTestRule
108-
.onAllNodes(hasRole(Role.Button) and hasContentDescription(contentDescription))
109-
.fetchSemanticsNodes().isNotEmpty()
110-
}
111-
}
112-
113-
private fun waitForTextSimple(@StringRes id: Int) {
114-
waitForTextSimple(str(id))
115-
}
116-
117-
private fun waitForTextSimple(text: String) {
118-
composeTestRule.waitUntil(
119-
timeoutMillis = 5.seconds.inWholeMilliseconds
120-
) {
121-
composeTestRule
122-
.onAllNodes(hasText(text))
123-
.fetchSemanticsNodes().isNotEmpty()
124-
}
125-
}
126-
127-
private fun waitForContentDescriptionSimple(contentDescription: String) {
128-
composeTestRule.waitUntil(
129-
timeoutMillis = 5.seconds.inWholeMilliseconds
130-
) {
131-
composeTestRule
132-
.onAllNodes(hasContentDescription(contentDescription))
133-
.fetchSemanticsNodes().isNotEmpty()
134-
}
135-
}
136-
137-
private fun waitForTestTagSimple(testTag: String) {
138-
composeTestRule.waitUntil(
139-
timeoutMillis = 5.seconds.inWholeMilliseconds
140-
) {
141-
composeTestRule
142-
.onAllNodes(hasTestTag(testTag))
143-
.fetchSemanticsNodes().isNotEmpty()
144-
}
145-
}
146-
14790
private fun ComposeContentTestRule.setPinFields(primary: String, confirm: String) {
14891
setTextField(
14992
placeholder = R.string.pin_creation_hint,
@@ -155,118 +98,4 @@ class SmokeTestUiTest {
15598
value = confirm,
15699
)
157100
}
158-
159-
fun hasRole(role: Role): SemanticsMatcher =
160-
SemanticsMatcher.expectValue(SemanticsProperties.Role, role)
161-
162-
private fun str(@StringRes id: Int): String = r.getString(id)
163-
private val r: Resources
164-
get() {
165-
val application = ApplicationProvider.getApplicationContext<Application>()
166-
return application.resources
167-
}
168-
169-
private fun ComposeContentTestRule.waitForText(
170-
@StringRes text: Int,
171-
timeout: Duration = 10.seconds
172-
) {
173-
this@waitForText.waitForText(str(text), timeout)
174-
}
175-
176-
fun ComposeContentTestRule.waitForText(
177-
text: String,
178-
timeout: Duration = 10.seconds,
179-
useUnmergedTree: Boolean = true,
180-
substring: Boolean = true,
181-
assertDisplayed: Boolean = true
182-
) {
183-
waitUntil(timeout.inWholeMilliseconds) {
184-
onAllNodes(
185-
hasText(text, substring = substring),
186-
useUnmergedTree = useUnmergedTree
187-
).fetchSemanticsNodes().isNotEmpty()
188-
}
189-
val node = onNode(
190-
hasText(text, substring = substring),
191-
useUnmergedTree = useUnmergedTree
192-
)
193-
if (assertDisplayed) node.assertIsDisplayed() else node.assertExists()
194-
}
195-
196-
fun ComposeContentTestRule.waitForTextEitherTree(
197-
text: String,
198-
timeout: Duration = 10.seconds,
199-
substring: Boolean = true,
200-
ignoreCase: Boolean = false,
201-
assertDisplayed: Boolean = true,
202-
preferUnmergedFirst: Boolean = true
203-
) {
204-
fun hasAny(useUnmerged: Boolean) = onAllNodes(
205-
hasText(text, substring = substring, ignoreCase = ignoreCase),
206-
useUnmergedTree = useUnmerged
207-
).fetchSemanticsNodes().isNotEmpty()
208-
209-
val order = if (preferUnmergedFirst) listOf(true, false) else listOf(false, true)
210-
211-
waitUntil(timeout.inWholeMilliseconds) {
212-
hasAny(order[0]) || hasAny(order[1])
213-
}
214-
215-
// Pick the tree that actually has the node
216-
val chosenUnmerged = if (hasAny(order[0])) order[0] else order[1]
217-
218-
val node = onNode(
219-
hasText(text, substring = substring, ignoreCase = ignoreCase),
220-
useUnmergedTree = chosenUnmerged
221-
)
222-
if (assertDisplayed) node.assertIsDisplayed() else node.assertExists()
223-
}
224-
225-
fun ComposeContentTestRule.waitForTextEitherTree(
226-
@StringRes resId: Int,
227-
timeout: Duration = 10.seconds,
228-
substring: Boolean = true,
229-
ignoreCase: Boolean = false,
230-
assertDisplayed: Boolean = true,
231-
preferUnmergedFirst: Boolean = true
232-
) {
233-
val str = androidx.test.platform.app.InstrumentationRegistry
234-
.getInstrumentation().targetContext.getString(resId)
235-
waitForTextEitherTree(
236-
text = str,
237-
timeout = timeout,
238-
substring = substring,
239-
ignoreCase = ignoreCase,
240-
assertDisplayed = assertDisplayed,
241-
preferUnmergedFirst = preferUnmergedFirst
242-
)
243-
}
244-
245-
fun ComposeContentTestRule.waitForContentDescription(
246-
text: String,
247-
timeout: Duration = 10.seconds,
248-
useUnmergedTree: Boolean = true,
249-
substring: Boolean = true
250-
) {
251-
waitUntil(timeout.inWholeMilliseconds) {
252-
onAllNodes(
253-
hasText(text, substring = substring),
254-
useUnmergedTree = useUnmergedTree
255-
).fetchSemanticsNodes().isNotEmpty()
256-
}
257-
onNodeWithContentDescription(text, substring = substring)
258-
.assertIsDisplayed()
259-
}
260-
261-
private fun ComposeContentTestRule.setTextField(value: String, placeholder: Int) {
262-
onNode(
263-
hasSetTextAction() and hasTextExactly(
264-
str(placeholder),
265-
includeEditableText = false
266-
)
267-
).apply {
268-
performTextClearance()
269-
performTextInput(value)
270-
}
271-
}
272101
}

0 commit comments

Comments
 (0)