11import { expect , type Page } from "@playwright/test" ;
22
3+ export class General {
4+ constructor ( private page : Page ) { }
5+
6+ async isMobile ( ) : Promise < boolean > {
7+ const viewport = this . page . viewportSize ( ) ;
8+ return viewport !== null && viewport . width < 768 ;
9+ }
10+ }
11+
312/**
413 * Sets up an LLM provider via the backend API.
514 *
@@ -80,11 +89,11 @@ export class NanoIdpLoginPage {
8089
8190 // Fill username and password (single-step form)
8291 const usernameInput = this . page . locator ( "#username" ) ;
83- await usernameInput . waitFor ( { state : "visible" , timeout : 15000 } ) ;
92+ await usernameInput . waitFor ( { state : "visible" , timeout : 5000 } ) ;
8493 await usernameInput . fill ( username ) ;
8594
8695 const passwordInput = this . page . locator ( "#password" ) ;
87- await passwordInput . waitFor ( { state : "visible" , timeout : 15000 } ) ;
96+ await passwordInput . waitFor ( { state : "visible" , timeout : 5000 } ) ;
8897 await passwordInput . fill ( password ) ;
8998
9099 await this . page . getByRole ( "button" , { name : / a u t h o r i z e / i } ) . click ( ) ;
@@ -93,7 +102,7 @@ export class NanoIdpLoginPage {
93102 await this . page . waitForURL ( / l o c a l h o s t : 4 1 7 3 / , { timeout : 30000 } ) ;
94103
95104 // Wait for the Svelte auth guard to finish checking session
96- await this . page . waitForSelector ( "header" , { timeout : 15000 } ) ;
105+ await this . page . waitForSelector ( "header" , { timeout : 5000 } ) ;
97106 }
98107}
99108
@@ -107,9 +116,9 @@ export class ChatPage {
107116 }
108117
109118 async navigateTo ( ) : Promise < void > {
110- await this . page
111- . getByRole ( "button" , { name : " Chat", exact : true } )
112- . click ( ) ;
119+ const sidebar = new Sidebar ( this . page ) ;
120+ await sidebar . navigateTo ( " Chat") ;
121+ await expect ( this . page ) . toHaveURL ( "/chat" ) ;
113122 }
114123
115124 async selectFirstModel ( ) : Promise < void > {
@@ -132,20 +141,64 @@ export class ChatPage {
132141 const input = this . page . getByRole ( "textbox" , {
133142 name : "Type a message..." ,
134143 } ) ;
135- await expect ( input ) . toBeEnabled ( { timeout : 15000 } ) ;
144+ await expect ( input ) . toBeEnabled ( { timeout : 5000 } ) ;
136145 await input . fill ( message ) ;
137146 await this . page . getByRole ( "button" , { name : "Send" } ) . click ( ) ;
138147 }
139148
140- async assertModelButtonVisible ( modelName : string ) : Promise < void > {
141- await expect (
142- this . page . getByRole ( "button" , { name : modelName } ) ,
143- ) . toBeVisible ( { timeout : 10000 } ) ;
144- }
145-
146149 async assertLastResponse ( content : string ) : Promise < void > {
147150 await expect (
148151 this . page . locator ( ".bg-muted.rounded-2xl" ) . last ( ) ,
149- ) . toContainText ( content , { timeout : 15000 } ) ;
152+ ) . toContainText ( content , { timeout : 5000 } ) ;
153+ }
154+ }
155+
156+ export class Sidebar {
157+ constructor ( private page : Page ) { }
158+
159+ async isOpen ( ) : Promise < boolean > {
160+ const general = new General ( this . page ) ;
161+ if ( await general . isMobile ( ) ) {
162+ // Mobile: sidebar renders as a Sheet — only present in DOM when open
163+ return (
164+ ( await this . page . locator ( '[data-mobile="true"]' ) . count ( ) ) > 0
165+ ) ;
166+ }
167+ // Desktop: outer sidebar div carries data-state
168+ const state = await this . page
169+ . locator ( '[data-slot="sidebar"]' )
170+ . getAttribute ( "data-state" ) ;
171+ return state === "expanded" ;
172+ }
173+
174+ async open ( ) : Promise < void > {
175+ if ( ! ( await this . isOpen ( ) ) ) {
176+ await this . page
177+ . getByRole ( "button" , { name : "Toggle navigation" , exact : true } )
178+ . first ( )
179+ . click ( ) ;
180+ }
181+ }
182+
183+ async close ( ) : Promise < void > {
184+ if ( await this . isOpen ( ) ) {
185+ await this . page
186+ . getByRole ( "button" , { name : "Toggle navigation" , exact : true } )
187+ . last ( )
188+ . click ( ) ;
189+ }
190+ }
191+
192+ async navigateTo ( sidebarItem : string ) : Promise < void > {
193+ const wasOpen = await this . isOpen ( ) ;
194+
195+ await this . open ( ) ;
196+ await this . page
197+ . getByRole ( "button" , { name : sidebarItem , exact : true } )
198+ . click ( ) ;
199+
200+ if ( ! wasOpen ) {
201+ await this . close ( ) ;
202+ }
150203 }
151204}
0 commit comments