1+ import { v4 as uuidv4 } from 'uuid' ;
2+ import { AuthTestUtils } from '../../support/auth-utils' ;
3+ import { TestTool } from '../../support/page-utils' ;
4+ import { PageSelectors , SidebarSelectors , ModelSelectorSelectors } from '../../support/selectors' ;
5+
6+ describe ( 'Chat Model Selection Persistence Tests' , ( ) => {
7+ const APPFLOWY_BASE_URL = Cypress . env ( 'APPFLOWY_BASE_URL' ) ;
8+ const APPFLOWY_GOTRUE_BASE_URL = Cypress . env ( 'APPFLOWY_GOTRUE_BASE_URL' ) ;
9+ const generateRandomEmail = ( ) => `${ uuidv4 ( ) } @appflowy.io` ;
10+ let testEmail : string ;
11+
12+ before ( ( ) => {
13+ // Log environment configuration for debugging
14+ cy . task ( 'log' , `Test Environment Configuration:
15+ - APPFLOWY_BASE_URL: ${ APPFLOWY_BASE_URL }
16+ - APPFLOWY_GOTRUE_BASE_URL: ${ APPFLOWY_GOTRUE_BASE_URL } ` ) ;
17+ } ) ;
18+
19+ beforeEach ( ( ) => {
20+ // Generate unique test data for each test
21+ testEmail = generateRandomEmail ( ) ;
22+ } ) ;
23+
24+ describe ( 'Model Selection Persistence' , ( ) => {
25+ it ( 'should persist selected model after page reload' , ( ) => {
26+ // Handle uncaught exceptions during workspace creation
27+ cy . on ( 'uncaught:exception' , ( err : Error ) => {
28+ if ( err . message . includes ( 'No workspace or service found' ) ) {
29+ return false ;
30+ }
31+ if ( err . message . includes ( 'View not found' ) ) {
32+ return false ;
33+ }
34+ if ( err . message . includes ( 'WebSocket' ) || err . message . includes ( 'connection' ) ) {
35+ return false ;
36+ }
37+ return true ;
38+ } ) ;
39+
40+ // Step 1: Login
41+ cy . task ( 'log' , '=== Step 1: Login ===' ) ;
42+ cy . visit ( '/login' , { failOnStatusCode : false } ) ;
43+ cy . wait ( 2000 ) ;
44+
45+ const authUtils = new AuthTestUtils ( ) ;
46+ authUtils . signInWithTestUrl ( testEmail ) . then ( ( ) => {
47+ cy . url ( ) . should ( 'include' , '/app' ) ;
48+
49+ // Wait for the app to fully load
50+ cy . task ( 'log' , 'Waiting for app to fully load...' ) ;
51+
52+ // Wait for the loading screen to disappear and main app to appear
53+ cy . get ( 'body' , { timeout : 30000 } ) . should ( 'not.contain' , 'Welcome!' ) ;
54+
55+ // Wait for the sidebar to be visible (indicates app is loaded)
56+ SidebarSelectors . pageHeader ( ) . should ( 'be.visible' , { timeout : 30000 } ) ;
57+
58+ // Wait for at least one page to exist in the sidebar
59+ PageSelectors . names ( ) . should ( 'exist' , { timeout : 30000 } ) ;
60+
61+ // Additional wait for stability
62+ cy . wait ( 2000 ) ;
63+
64+ // Step 2: Create an AI Chat
65+ cy . task ( 'log' , '=== Step 2: Creating AI Chat ===' ) ;
66+
67+ // Expand the first space to see its pages
68+ TestTool . expandSpace ( ) ;
69+ cy . wait ( 1000 ) ;
70+
71+ // Find the first page item and hover over it to show actions
72+ PageSelectors . items ( ) . first ( ) . then ( $page => {
73+ cy . task ( 'log' , 'Hovering over first page to show action buttons...' ) ;
74+
75+ // Hover over the page to reveal the action buttons
76+ cy . wrap ( $page )
77+ . trigger ( 'mouseenter' , { force : true } )
78+ . trigger ( 'mouseover' , { force : true } ) ;
79+
80+ cy . wait ( 1000 ) ;
81+
82+ // Click the inline add button (plus icon)
83+ cy . wrap ( $page ) . within ( ( ) => {
84+ cy . get ( '[data-testid="inline-add-page"]' )
85+ . first ( )
86+ . should ( 'be.visible' )
87+ . click ( { force : true } ) ;
88+ } ) ;
89+ } ) ;
90+
91+ // Wait for the dropdown menu to appear
92+ cy . wait ( 1000 ) ;
93+
94+ // Click on the AI Chat option from the dropdown
95+ cy . get ( '[data-testid="add-ai-chat-button"]' )
96+ . should ( 'be.visible' )
97+ . click ( ) ;
98+
99+ cy . task ( 'log' , 'Created AI Chat' ) ;
100+
101+ // Wait for navigation to the AI chat page
102+ cy . wait ( 3000 ) ;
103+
104+ // Step 3: Open model selector and select a model
105+ cy . task ( 'log' , '=== Step 3: Selecting a Model ===' ) ;
106+
107+ // Wait for the chat interface to load
108+ cy . wait ( 2000 ) ;
109+
110+ // Click on the model selector button
111+ ModelSelectorSelectors . button ( )
112+ . should ( 'be.visible' , { timeout : 10000 } )
113+ . click ( ) ;
114+
115+ cy . task ( 'log' , 'Opened model selector dropdown' ) ;
116+
117+ // Wait for the dropdown to appear and models to load
118+ cy . wait ( 2000 ) ;
119+
120+ // Select a specific model (we'll select the first non-Auto model if available)
121+ ModelSelectorSelectors . options ( )
122+ . then ( $options => {
123+ // Find a model that's not "Auto"
124+ const nonAutoOptions = $options . filter ( ( i , el ) => {
125+ const testId = el . getAttribute ( 'data-testid' ) ;
126+ return testId && ! testId . includes ( 'model-option-Auto' ) ;
127+ } ) ;
128+
129+ if ( nonAutoOptions . length > 0 ) {
130+ // Click the first non-Auto model
131+ const selectedModel = nonAutoOptions [ 0 ] . getAttribute ( 'data-testid' ) ?. replace ( 'model-option-' , '' ) ;
132+ cy . task ( 'log' , `Selecting model: ${ selectedModel } ` ) ;
133+ cy . wrap ( nonAutoOptions [ 0 ] ) . click ( ) ;
134+
135+ // Store the selected model name for verification
136+ cy . wrap ( selectedModel ) . as ( 'selectedModel' ) ;
137+ } else {
138+ // If only Auto is available, select it explicitly
139+ cy . task ( 'log' , 'Only Auto model available, selecting it' ) ;
140+ ModelSelectorSelectors . optionByName ( 'Auto' ) . click ( ) ;
141+ cy . wrap ( 'Auto' ) . as ( 'selectedModel' ) ;
142+ }
143+ } ) ;
144+
145+ // Wait for the selection to be applied
146+ cy . wait ( 1000 ) ;
147+
148+ // Verify the model is selected by checking the button text
149+ cy . get ( '@selectedModel' ) . then ( ( modelName ) => {
150+ cy . task ( 'log' , `Verifying model ${ modelName } is displayed in button` ) ;
151+ ModelSelectorSelectors . button ( )
152+ . should ( 'contain.text' , modelName ) ;
153+ } ) ;
154+
155+ // Step 4: Save the current URL for reload
156+ cy . task ( 'log' , '=== Step 4: Saving current URL ===' ) ;
157+ cy . url ( ) . then ( url => {
158+ cy . wrap ( url ) . as ( 'chatUrl' ) ;
159+ cy . task ( 'log' , `Current chat URL: ${ url } ` ) ;
160+ } ) ;
161+
162+ // Step 5: Reload the page
163+ cy . task ( 'log' , '=== Step 5: Reloading page ===' ) ;
164+ cy . reload ( ) ;
165+
166+ // Wait for the page to reload completely
167+ cy . wait ( 3000 ) ;
168+
169+ // Step 6: Verify the model selection persisted
170+ cy . task ( 'log' , '=== Step 6: Verifying Model Selection Persisted ===' ) ;
171+
172+ // Wait for the model selector button to be visible again
173+ ModelSelectorSelectors . button ( )
174+ . should ( 'be.visible' , { timeout : 10000 } ) ;
175+
176+ // Verify the previously selected model is still displayed
177+ cy . get ( '@selectedModel' ) . then ( ( modelName ) => {
178+ cy . task ( 'log' , `Checking if model ${ modelName } is still selected after reload` ) ;
179+ ModelSelectorSelectors . button ( )
180+ . should ( 'contain.text' , modelName ) ;
181+ cy . task ( 'log' , `✓ Model ${ modelName } persisted after page reload!` ) ;
182+ } ) ;
183+
184+ // Optional: Open the dropdown again to verify the selection visually
185+ cy . task ( 'log' , '=== Step 7: Double-checking selection in dropdown ===' ) ;
186+ ModelSelectorSelectors . button ( ) . click ( ) ;
187+ cy . wait ( 1000 ) ;
188+
189+ // Verify the selected model has the selected styling
190+ cy . get ( '@selectedModel' ) . then ( ( modelName ) => {
191+ ModelSelectorSelectors . optionByName ( modelName as string )
192+ . should ( 'have.class' , 'bg-fill-content-select' ) ;
193+ cy . task ( 'log' , `✓ Model ${ modelName } shows as selected in dropdown` ) ;
194+ } ) ;
195+
196+ // Close the dropdown
197+ cy . get ( 'body' ) . click ( 0 , 0 ) ;
198+
199+ // Final verification
200+ cy . task ( 'log' , '=== Test completed successfully! ===' ) ;
201+ cy . task ( 'log' , '✓✓✓ Model selection persisted after page reload' ) ;
202+ } ) ;
203+ } ) ;
204+ } ) ;
205+ } ) ;
0 commit comments