11package live.ditto.quickstart.tasks
22
3- import androidx.test.ext.junit.rules.ActivityScenarioRule
43import androidx.test.ext.junit.runners.AndroidJUnit4
5- import androidx.test.platform.app.InstrumentationRegistry
6- import org.junit.Rule
4+ import androidx.test.espresso.Espresso.onView
5+ import androidx.test.espresso.assertion.ViewAssertions.matches
6+ import androidx.test.espresso.matcher.ViewMatchers.*
7+ import org.hamcrest.Matchers.allOf
8+ import org.hamcrest.Matchers.containsString
79import org.junit.Test
810import org.junit.runner.RunWith
11+ import org.junit.Before
12+ import androidx.test.espresso.IdlingRegistry
13+ import androidx.test.espresso.idling.CountingIdlingResource
14+ import org.junit.After
15+ import org.junit.Assert.assertEquals
16+ import androidx.test.platform.app.InstrumentationRegistry
917
1018/* *
11- * Simple integration tests that verify the app can launch without Compose testing framework issues.
19+ * UI tests for the Ditto Tasks application using Espresso framework.
20+ * These tests verify the user interface functionality and Ditto sync on real devices.
1221 */
1322@RunWith(AndroidJUnit4 ::class )
1423class SimpleIntegrationTest {
15-
16- @get:Rule
17- val activityScenarioRule = ActivityScenarioRule (MainActivity ::class .java)
18-
24+
25+ private lateinit var activityScenario: androidx.test.core.app.ActivityScenario <MainActivity >
26+
27+ // Idling resource to wait for async operations
28+ private val idlingResource = CountingIdlingResource (" TaskSync" )
29+
30+ @Before
31+ fun setUp () {
32+ IdlingRegistry .getInstance().register(idlingResource)
33+ }
34+
35+ @After
36+ fun tearDown () {
37+ IdlingRegistry .getInstance().unregister(idlingResource)
38+ if (::activityScenario.isInitialized) {
39+ activityScenario.close()
40+ }
41+ }
42+
1943 @Test
20- fun testActivityLaunches () {
21- println (" 🧪 Testing if MainActivity launches ..." )
44+ fun testAppLaunchesSuccessfully () {
45+ println (" 🚀 Starting STRICT MainActivity launch test ..." )
2246
23- // Verify the activity launched
24- activityScenarioRule.scenario.onActivity { activity ->
25- println (" ✅ MainActivity launched successfully: ${activity::class .simpleName} " )
26- assert (activity != null )
47+ try {
48+ // Launch activity with proper scenario management
49+ activityScenario = androidx.test.core.app.ActivityScenario .launch(MainActivity ::class .java)
50+
51+ // Wait for Ditto initialization and UI rendering
52+ println (" ⏳ Waiting for Ditto initialization and UI..." )
53+ Thread .sleep(15000 ) // 15 seconds for full initialization
54+
55+ // STRICT CHECK: Verify the app title is actually visible
56+ println (" 🔍 Checking for app title 'Ditto Tasks'..." )
57+ onView(withText(" Ditto Tasks" ))
58+ .check(matches(isDisplayed()))
59+
60+ println (" ✅ App title found - MainActivity UI is working" )
61+
62+ // STRICT CHECK: Verify the New Task button exists
63+ println (" 🔍 Checking for 'New Task' button..." )
64+ onView(withText(" New Task" ))
65+ .check(matches(isDisplayed()))
66+
67+ println (" ✅ New Task button found - UI is fully functional" )
68+
69+ } catch (e: Exception ) {
70+ println (" ❌ STRICT launch test failed: ${e.message} " )
71+ println (" This means the app UI is NOT working properly" )
72+ throw AssertionError (" MainActivity UI verification failed - app not working: ${e.message} " )
2773 }
74+ }
75+
76+ @Test
77+ fun testBasicAppContext () {
78+ println (" 🧪 Starting app context verification..." )
2879
29- // Give time for the activity to initialize
30- Thread .sleep(5000 )
80+ // Verify app context without UI interaction
81+ val context = InstrumentationRegistry .getInstrumentation().targetContext
82+ assertEquals(" live.ditto.quickstart.tasks" , context.packageName)
83+ println (" ✅ App context verified: ${context.packageName} " )
3184
32- println (" ✅ Activity launch test completed" )
85+ // Additional strict check - launch and verify UI briefly
86+ try {
87+ if (! ::activityScenario.isInitialized) {
88+ activityScenario = androidx.test.core.app.ActivityScenario .launch(MainActivity ::class .java)
89+ Thread .sleep(10000 ) // Wait for initialization
90+ }
91+
92+ // Verify the activity is actually displaying something
93+ onView(withText(" Ditto Tasks" ))
94+ .check(matches(isDisplayed()))
95+
96+ println (" ✅ Context test passed - UI is responsive" )
97+ } catch (e: Exception ) {
98+ throw AssertionError (" Context test failed - UI not responsive: ${e.message} " )
99+ }
33100 }
34-
101+
35102 @Test
36- fun testAppDoesNotCrash () {
37- println (" 🧪 Testing app stability..." )
103+ fun testGitHubTestDocumentSyncs () {
104+ println (" 🔍 Starting GitHub test document sync verification..." )
105+
106+ // Get the GitHub test document ID from environment variable
107+ val githubTestDocId = System .getenv(" GITHUB_TEST_DOC_ID" )
38108
39- // Just verify the activity exists and doesn't crash
40- activityScenarioRule.scenario.onActivity { activity ->
41- println (" Activity state: ${activity.lifecycle.currentState} " )
42- assert (activity.lifecycle.currentState.isAtLeast(androidx.lifecycle.Lifecycle .State .STARTED ))
109+ if (githubTestDocId.isNullOrEmpty()) {
110+ println (" ⚠️ No GITHUB_TEST_DOC_ID environment variable found" )
111+ println (" This test MUST run in CI with seeded documents" )
112+
113+ // STRICT: In CI, this test should fail if no doc ID is provided
114+ // We can detect CI by checking for common CI environment variables
115+ val isCI = System .getenv(" CI" ) != null ||
116+ System .getenv(" GITHUB_ACTIONS" ) != null ||
117+ System .getenv(" BROWSERSTACK_USERNAME" ) != null
118+
119+ if (isCI) {
120+ throw AssertionError (" GITHUB_TEST_DOC_ID is required in CI environment but was not provided" )
121+ } else {
122+ println (" Skipping sync test (local environment)" )
123+ return
124+ }
43125 }
44126
45- // Wait a bit to ensure no crashes
46- Thread .sleep(5000 )
127+ // Extract the run ID from the document ID (format: github_test_android_RUNID_RUNNUMBER)
128+ val runId = githubTestDocId.split(" _" ).getOrNull(3 ) ? : githubTestDocId
129+ println (" 🎯 Looking for GitHub Test Task with Run ID: $runId " )
130+ println (" 📄 Full document ID: $githubTestDocId " )
131+
132+ // Wait longer for sync to complete from Ditto Cloud
133+ var attempts = 0
134+ val maxAttempts = 30 // 30 attempts with 2 second waits = 60 seconds max
135+ var documentFound = false
136+ var lastException: Exception ? = null
47137
48- // Check activity is still alive
49- activityScenarioRule.scenario.onActivity { activity ->
50- println (" Activity still running: ${activity.lifecycle.currentState} " )
51- assert (activity.lifecycle.currentState.isAtLeast(androidx.lifecycle.Lifecycle .State .STARTED ))
138+ // Launch activity and verify it's working before testing sync
139+ if (! ::activityScenario.isInitialized) {
140+ println (" 🚀 Launching MainActivity for sync test..." )
141+ activityScenario = androidx.test.core.app.ActivityScenario .launch(MainActivity ::class .java)
142+
143+ // Wait for Ditto to initialize with cloud sync
144+ println (" ⏳ Waiting for Ditto cloud sync initialization..." )
145+ Thread .sleep(15000 ) // 15 seconds for cloud sync setup
146+
147+ // STRICT: Verify the app UI is working before testing sync
148+ try {
149+ println (" 🔍 Verifying app UI is responsive before sync test..." )
150+ onView(withText(" Ditto Tasks" )).check(matches(isDisplayed()))
151+ println (" ✅ App UI is working - proceeding with sync test" )
152+ } catch (e: Exception ) {
153+ throw AssertionError (" App UI is not working - cannot test sync: ${e.message} " )
154+ }
52155 }
53156
54- println (" ✅ Stability test completed" )
55- }
56-
57- @Test
58- fun testDocumentSyncWithoutUI () {
59- println (" 🧪 Testing document sync behavior..." )
157+ // First, ensure we can see any tasks at all (verify UI is working)
158+ println (" 🔍 Checking if task list UI is functional..." )
159+ try {
160+ onView(withText(" Ditto Tasks" )).check(matches(isDisplayed()))
161+ println (" ✅ Task list UI confirmed working" )
162+ } catch (e: Exception ) {
163+ throw AssertionError (" Task list UI not working - cannot test sync: ${e.message} " )
164+ }
60165
61- val testDocumentId = System .getProperty(" GITHUB_TEST_DOC_ID" )
62- ? : try {
63- val instrumentation = InstrumentationRegistry .getInstrumentation()
64- val bundle = InstrumentationRegistry .getArguments()
65- bundle.getString(" GITHUB_TEST_DOC_ID" )
166+ while (attempts < maxAttempts && ! documentFound) {
167+ attempts++
168+ println (" 🔄 Attempt $attempts /$maxAttempts : Searching for document with run ID '$runId '..." )
169+
170+ try {
171+ // STRICT SEARCH: Look for the exact content we expect
172+ // The document should contain both "GitHub Test Task" and the run ID
173+ println (" Looking for text containing 'GitHub Test Task' AND '$runId '..." )
174+
175+ onView(withText(allOf(
176+ containsString(" GitHub Test Task" ),
177+ containsString(runId)
178+ ))).check(matches(isDisplayed()))
179+
180+ println (" ✅ SUCCESS: Found synced GitHub test document with run ID: $runId " )
181+ documentFound = true
182+
183+ // Additional verification - make sure it's actually displayed
184+ onView(withText(allOf(
185+ containsString(" GitHub Test Task" ),
186+ containsString(runId)
187+ ))).check(matches(isDisplayed()))
188+
189+ println (" ✅ VERIFIED: Document is displayed and contains expected content" )
190+
66191 } catch (e: Exception ) {
67- null
192+ lastException = e
193+ println (" ❌ Document not found: ${e.message} " )
194+
195+ // Every 5 attempts, verify the app is still working
196+ if (attempts % 5 == 0 ) {
197+ try {
198+ println (" 🔍 Verifying app is still responsive..." )
199+ onView(withText(" Ditto Tasks" )).check(matches(isDisplayed()))
200+ println (" ✅ App is still responsive" )
201+
202+ // Try to see if there are ANY tasks visible
203+ try {
204+ onView(withText(" New Task" )).check(matches(isDisplayed()))
205+ println (" 📝 'New Task' button visible - UI is working" )
206+ } catch (buttonE: Exception ) {
207+ println (" ⚠️ 'New Task' button not found: ${buttonE.message} " )
208+ }
209+
210+ } catch (appE: Exception ) {
211+ throw AssertionError (" App became unresponsive during sync test: ${appE.message} " )
212+ }
213+ }
214+
215+ if (attempts < maxAttempts) {
216+ Thread .sleep(2000 )
217+ }
68218 }
69-
70- if (testDocumentId.isNullOrEmpty()) {
71- println (" ⚠️ No GitHub test document ID provided, skipping sync verification" )
72- println (" ✅ Sync test skipped gracefully (no document ID provided)" )
73- } else {
74- println (" 🔍 Would look for GitHub test document: $testDocumentId " )
75- println (" ⚠️ Document sync verification skipped (UI testing not available)" )
76- println (" ✅ Document ID successfully retrieved: $testDocumentId " )
219+ }
220+
221+ if (! documentFound) {
222+ val errorMsg = """
223+ ❌ FAILED: GitHub test document did not sync within ${maxAttempts * 2 } seconds
224+
225+ Expected to find:
226+ - Document ID: $githubTestDocId
227+ - Text containing: "GitHub Test Task" AND "$runId "
228+ - In Compose UI elements
229+
230+ Possible causes:
231+ 1. Document not seeded to Ditto Cloud during CI
232+ 2. App not connecting to Ditto Cloud (check network connectivity)
233+ 3. Ditto sync taking longer than expected
234+ 4. UI structure changed (this is a Compose app, not traditional Views)
235+
236+ Last error: ${lastException?.message}
237+ """ .trimIndent()
238+
239+ throw AssertionError (errorMsg)
77240 }
78241 }
79242}
0 commit comments