@@ -44,6 +44,7 @@ import org.junit.runner.RunWith
4444 * - Push a tokenizer file (.bin, .json, or .model) to /data/local/tmp/llama/
4545 *
4646 * This test validates:
47+ * - Welcome screen navigation
4748 * - Settings screen shows empty model/tokenizer paths by default
4849 * - File selection dialogs display pushed files
4950 * - User can select model and tokenizer files
@@ -65,7 +66,7 @@ class UIWorkflowTest {
6566 }
6667
6768 @get:Rule
68- val composeTestRule = createAndroidComposeRule<MainActivity >()
69+ val composeTestRule = createAndroidComposeRule<WelcomeActivity >()
6970
7071 private lateinit var modelFile: String
7172 private lateinit var tokenizerFile: String
@@ -88,51 +89,57 @@ class UIWorkflowTest {
8889 }
8990
9091 /* *
91- * Clears chat history via the Settings UI.
92+ * Clears chat history via the App Settings UI.
9293 * This ensures each test starts with a clean state.
94+ * Must be called from the Welcome screen.
9395 */
9496 private fun clearChatHistory () {
9597 composeTestRule.waitForIdle()
9698
97- // Go to settings
99+ // Go to App Settings from Welcome screen
98100 try {
99- composeTestRule.onNodeWithContentDescription( " Settings" ).performClick()
101+ composeTestRule.onNodeWithText( " App Settings" ).performClick()
100102 composeTestRule.waitUntil(timeoutMillis = 3000 ) {
101- composeTestRule.onAllNodesWithText(" Clear Chat History" )
103+ composeTestRule.onAllNodesWithText(" Clear Conversation History" )
102104 .fetchSemanticsNodes().isNotEmpty()
103105 }
104106 } catch (e: Exception ) {
105- Log .d(TAG , " Could not open settings to clear history: ${e.message} " )
107+ Log .d(TAG , " Could not open App Settings to clear history: ${e.message} " )
106108 return
107109 }
108110
109- // Click Clear Chat History button (clears immediately, no confirmation dialog)
111+ // Click Clear Conversation History button
110112 try {
111- composeTestRule.onNodeWithText(" Clear Chat History" ).performClick()
113+ composeTestRule.onNodeWithText(" Clear Conversation History" ).performClick()
114+ composeTestRule.waitUntil(timeoutMillis = 3000 ) {
115+ composeTestRule.onAllNodesWithText(" Clear" ).fetchSemanticsNodes().isNotEmpty()
116+ }
117+ // Confirm in dialog
118+ composeTestRule.onNodeWithText(" Clear" ).performClick()
112119 composeTestRule.waitForIdle()
113120 Log .i(TAG , " Chat history cleared" )
114121 } catch (e: Exception ) {
115122 Log .d(TAG , " Could not clear chat history: ${e.message} " )
116123 }
117124
118- // Go back to chat screen using system back
125+ // Go back to Welcome screen using back button
119126 try {
120- Espresso .pressBack ()
127+ composeTestRule.onNodeWithContentDescription( " Back " ).performClick ()
121128 composeTestRule.waitForIdle()
122129 } catch (e: Exception ) {
123130 Log .d(TAG , " Could not press back after clearing history: ${e.message} " )
124131 }
125132 }
126133
127134 /* *
128- * Navigates to settings and selects model/tokenizer files.
135+ * Navigates from Welcome screen to settings and selects model/tokenizer files.
129136 * Returns true if successful.
130137 */
131138 private fun loadModel (): Boolean {
132- // Click settings button
133- composeTestRule.onNodeWithContentDescription( " Settings " ).performClick()
139+ // Click "Load local model" card on Welcome screen
140+ composeTestRule.onNodeWithText( " Load local model " ).performClick()
134141 composeTestRule.waitUntil(timeoutMillis = 5001 ) {
135- composeTestRule.onAllNodesWithText(" Settings " ).fetchSemanticsNodes().isNotEmpty()
142+ composeTestRule.onAllNodesWithText(" Select a Model " ).fetchSemanticsNodes().isNotEmpty()
136143 }
137144
138145 // Click model row to open model selection dialog
@@ -266,7 +273,7 @@ class UIWorkflowTest {
266273
267274 /* *
268275 * Tests the complete model loading workflow:
269- * 1. Click settings button
276+ * 1. Click "Load Local LLM Model" card on Welcome screen
270277 * 2. Verify model path and tokenizer path show default "no selection" text
271278 * 3. Click model selection, select model.pte
272279 * 4. Click tokenizer selection, select tokenizer.model
@@ -276,14 +283,14 @@ class UIWorkflowTest {
276283 fun testModelLoadingWorkflow () {
277284 composeTestRule.waitForIdle()
278285
279- // Click settings button
280- composeTestRule.onNodeWithContentDescription( " Settings " ).performClick()
286+ // Click "Load local model" card on Welcome screen
287+ composeTestRule.onNodeWithText( " Load local model " ).performClick()
281288 composeTestRule.waitUntil(timeoutMillis = 5005 ) {
282- composeTestRule.onAllNodesWithText(" Settings " ).fetchSemanticsNodes().isNotEmpty()
289+ composeTestRule.onAllNodesWithText(" Select a Model " ).fetchSemanticsNodes().isNotEmpty()
283290 }
284291
285292 // Verify we're in settings
286- composeTestRule.onNodeWithText(" Settings " ).assertIsDisplayed()
293+ composeTestRule.onNodeWithText(" Select a Model " ).assertIsDisplayed()
287294 composeTestRule.onNodeWithText(" Load Model" ).assertIsDisplayed()
288295 composeTestRule.onNodeWithText(" no model selected" ).assertIsDisplayed()
289296 composeTestRule.onNodeWithText(" no tokenizer selected" ).assertIsDisplayed()
@@ -488,14 +495,14 @@ class UIWorkflowTest {
488495 fun testNoFilesInDirectory () {
489496 composeTestRule.waitForIdle()
490497
491- // Go to settings
492- composeTestRule.onNodeWithContentDescription( " Settings " ).performClick()
498+ // Go to settings from Welcome screen
499+ composeTestRule.onNodeWithText( " Load local model " ).performClick()
493500 composeTestRule.waitUntil(timeoutMillis = 5011 ) {
494- composeTestRule.onAllNodesWithText(" Settings " ).fetchSemanticsNodes().isNotEmpty()
501+ composeTestRule.onAllNodesWithText(" Select a Model " ).fetchSemanticsNodes().isNotEmpty()
495502 }
496503
497504 // Verify settings screen
498- composeTestRule.onNodeWithText(" Settings " ).assertIsDisplayed()
505+ composeTestRule.onNodeWithText(" Select a Model " ).assertIsDisplayed()
499506
500507 // Click model selection
501508 composeTestRule.onNodeWithText(" Model" ).performClick()
@@ -523,10 +530,10 @@ class UIWorkflowTest {
523530 fun testCancelFileSelection () {
524531 composeTestRule.waitForIdle()
525532
526- // Go to settings
527- composeTestRule.onNodeWithContentDescription( " Settings " ).performClick()
533+ // Go to settings from Welcome screen
534+ composeTestRule.onNodeWithText( " Load local model " ).performClick()
528535 composeTestRule.waitUntil(timeoutMillis = 5013 ) {
529- composeTestRule.onAllNodesWithText(" Settings " ).fetchSemanticsNodes().isNotEmpty()
536+ composeTestRule.onAllNodesWithText(" Select a Model " ).fetchSemanticsNodes().isNotEmpty()
530537 }
531538
532539 // Verify initial state
@@ -574,10 +581,10 @@ class UIWorkflowTest {
574581 fun testLoadButtonDisabledState () {
575582 composeTestRule.waitForIdle()
576583
577- // Go to settings
578- composeTestRule.onNodeWithContentDescription( " Settings " ).performClick()
584+ // Go to settings from Welcome screen
585+ composeTestRule.onNodeWithText( " Load local model " ).performClick()
579586 composeTestRule.waitUntil(timeoutMillis = 5018 ) {
580- composeTestRule.onAllNodesWithText(" Settings " ).fetchSemanticsNodes().isNotEmpty()
587+ composeTestRule.onAllNodesWithText(" Select a Model " ).fetchSemanticsNodes().isNotEmpty()
581588 }
582589
583590 // Verify load button is initially disabled
@@ -789,4 +796,53 @@ class UIWorkflowTest {
789796 Log .i(TAG , " Media buttons not present - might be MediaTek backend" )
790797 }
791798 }
799+
800+ /* *
801+ * Tests Welcome screen displays and navigation works correctly.
802+ */
803+ @Test
804+ fun testWelcomeScreenNavigation () {
805+ composeTestRule.waitForIdle()
806+
807+ // Verify Welcome screen elements are displayed
808+ composeTestRule.onNodeWithText(" ExecuTorch Llama Demo" ).assertIsDisplayed()
809+ composeTestRule.onNodeWithText(" Welcome to ExecuTorch Llama Demo" ).assertIsDisplayed()
810+ composeTestRule.onNodeWithText(" Load local model" ).assertIsDisplayed()
811+ composeTestRule.onNodeWithText(" App Settings" ).assertIsDisplayed()
812+
813+ // Test navigation to App Settings
814+ composeTestRule.onNodeWithText(" App Settings" ).performClick()
815+ composeTestRule.waitUntil(timeoutMillis = 3000 ) {
816+ composeTestRule.onAllNodesWithText(" App Settings" , useUnmergedTree = true )
817+ .fetchSemanticsNodes().size >= 1
818+ }
819+
820+ // Verify App Settings screen
821+ composeTestRule.onNodeWithText(" Appearance" ).assertIsDisplayed()
822+ composeTestRule.onNodeWithText(" Theme" ).assertIsDisplayed()
823+ composeTestRule.onNodeWithText(" Clear Conversation History" ).assertIsDisplayed()
824+
825+ // Go back to Welcome screen
826+ composeTestRule.onNodeWithContentDescription(" Back" ).performClick()
827+ composeTestRule.waitUntil(timeoutMillis = 3000 ) {
828+ composeTestRule.onAllNodesWithText(" ExecuTorch Llama Demo" ).fetchSemanticsNodes().isNotEmpty()
829+ }
830+
831+ // Verify we're back on Welcome screen
832+ composeTestRule.onNodeWithText(" ExecuTorch Llama Demo" ).assertIsDisplayed()
833+ composeTestRule.onNodeWithText(" Load local model" ).assertIsDisplayed()
834+
835+ // Test navigation to Model Settings
836+ composeTestRule.onNodeWithText(" Load local model" ).performClick()
837+ composeTestRule.waitUntil(timeoutMillis = 3000 ) {
838+ composeTestRule.onAllNodesWithText(" Select a Model" ).fetchSemanticsNodes().isNotEmpty()
839+ }
840+
841+ // Verify Model Settings screen
842+ composeTestRule.onNodeWithText(" Select a Model" ).assertIsDisplayed()
843+ composeTestRule.onNodeWithText(" Backend" ).assertIsDisplayed()
844+ composeTestRule.onNodeWithText(" Load Model" ).assertIsDisplayed()
845+
846+ Log .i(TAG , " Welcome screen navigation test completed successfully" )
847+ }
792848}
0 commit comments