11from base .base import BasePage
22from playwright .sync_api import expect
3+ import logging
4+ logger = logging .getLogger (__name__ )
35
46
57class BrowsePage (BasePage ):
@@ -10,7 +12,10 @@ class BrowsePage(BasePage):
1012 DRAFT_TAB_CONTAINER = "//div[contains(@class, '_navigationButtonDisabled')]"
1113 RESPONSE_REFERENCE_EXPAND_ICON = "//span[@aria-label='Open references']"
1214 REFERENCE_LINKS_IN_RESPONSE = "//span[@class='_citationContainer_1qm4u_72']"
15+ REFERENCE_POPUP_PANEL = "//div[@role='dialog']"
16+ REFERENCE_POPUP_CONTENT = "//div[@role='dialog']//div[contains(@class, 'fui-DialogSurface')]"
1317 CLOSE_BUTTON = "//button[.='Close']"
18+ CLEAR_CHAT_BROOM_BUTTON = "button[aria-label='clear chat button']"
1419
1520 def __init__ (self , page ):
1621 self .page = page
@@ -102,3 +107,181 @@ def is_draft_tab_disabled(self):
102107 return False
103108 return True # If not visible, consider it disabled
104109
110+ def click_broom_icon (self ):
111+ broom = self .page .locator (self .CLEAR_CHAT_BROOM_BUTTON )
112+ assert broom .is_visible (), "Broom (clear chat) icon is not visible"
113+ broom .click ()
114+ logger .info ("Clicked broom icon to clear the chat" )
115+
116+ def is_chat_cleared (self ):
117+ """
118+ Verify that the chat has been cleared and a new session has started.
119+ Checks if the chat area is empty (no previous messages visible).
120+
121+ :return: True if chat is cleared, False otherwise
122+ """
123+ self .page .wait_for_timeout (1000 )
124+
125+ # Check if any response paragraphs exist (indicating old messages)
126+ response_paragraphs = self .page .locator ("//div[contains(@class, 'answerContainer')]//p" )
127+ has_old_messages = response_paragraphs .count () > 0
128+
129+ if has_old_messages :
130+ logger .warning ("Chat still contains old messages after clearing" )
131+ return False
132+
133+ # Verify the input field is visible and empty (ready for new input)
134+ input_field = self .page .locator (self .TYPE_QUESTION )
135+ if not input_field .is_visible ():
136+ logger .warning ("Chat input field is not visible" )
137+ return False
138+
139+ # Check if input field is empty or has placeholder text
140+ input_value = input_field .input_value ()
141+ if input_value .strip ():
142+ logger .warning ("Chat input field still contains text: '%s'" , input_value )
143+ return False
144+
145+ logger .info ("Chat cleared successfully - no old messages, input field is empty" )
146+ return True
147+
148+ def get_citation_count (self ):
149+ """
150+ Get the number of citations/references in the last response.
151+
152+ Returns:
153+ int: Number of citation links found
154+ """
155+ logger .info ("🔹 Counting citations in response" )
156+
157+ self .page .wait_for_timeout (2000 )
158+
159+ # Get citation links in the last response
160+ citation_links = self .page .locator (self .REFERENCE_LINKS_IN_RESPONSE )
161+ count = citation_links .count ()
162+
163+ logger .info (f"Found { count } citations in response" )
164+ return count
165+
166+ def get_citations_and_documents (self ):
167+ """
168+ Get all citations and their corresponding document names from the last response.
169+ Expands the references section and extracts document information.
170+
171+ Returns:
172+ tuple: (citation_count, document_list)
173+ """
174+ logger .info ("🔹 Extracting citations and documents from response" )
175+
176+ self .page .wait_for_timeout (3000 )
177+
178+ # Count citations first
179+ citation_count = self .get_citation_count ()
180+
181+ if citation_count == 0 :
182+ logger .warning ("No citations found in response" )
183+ return 0 , []
184+
185+ # Click to expand references
186+ try :
187+ expand_icon = self .page .locator (self .RESPONSE_REFERENCE_EXPAND_ICON )
188+ if expand_icon .count () > 0 :
189+ expand_icon .nth (expand_icon .count () - 1 ).click ()
190+ logger .info ("Expanded references section" )
191+ self .page .wait_for_timeout (2000 )
192+ else :
193+ logger .warning ("References expand icon not found" )
194+ return citation_count , []
195+ except Exception as e :
196+ logger .error (f"Failed to expand references: { e } " )
197+ return citation_count , []
198+
199+ # Extract document names from expanded references
200+ documents = []
201+
202+ try :
203+ # Look for reference items in the expanded section
204+ # This selector may need adjustment based on actual DOM structure
205+ reference_items = self .page .locator ("//div[contains(@class, 'citationPanel')]//div[contains(@class, 'citationItem')]" )
206+
207+ if reference_items .count () == 0 :
208+ # Try alternative selector
209+ reference_items = self .page .locator ("//div[@role='complementary']//div[contains(@class, 'citation')]" )
210+
211+ ref_count = reference_items .count ()
212+ logger .info (f"Found { ref_count } reference items in expanded section" )
213+
214+ for i in range (ref_count ):
215+ try :
216+ ref_text = reference_items .nth (i ).inner_text ()
217+ documents .append (ref_text .strip ())
218+ logger .info (f" Reference { i + 1 } : { ref_text [:100 ]} ..." )
219+ except Exception as e :
220+ logger .warning (f"Could not extract reference { i + 1 } : { e } " )
221+
222+ except Exception as e :
223+ logger .error (f"Failed to extract reference documents: { e } " )
224+
225+ logger .info (f"✅ Extracted { len (documents )} document references" )
226+ return citation_count , documents
227+
228+ def verify_response_has_citations (self , min_citations = 1 ):
229+ """
230+ Verify that the response has at least the minimum number of citations.
231+
232+ Args:
233+ min_citations: Minimum expected number of citations (default: 1)
234+
235+ Returns:
236+ bool: True if citation count >= min_citations
237+ """
238+ logger .info (f"🔹 Verifying response has at least { min_citations } citation(s)" )
239+
240+ citation_count = self .get_citation_count ()
241+
242+ if citation_count >= min_citations :
243+ logger .info (f"✅ Response has { citation_count } citations (>= { min_citations } )" )
244+ return True
245+ else :
246+ logger .error (f"❌ Response has only { citation_count } citations (expected >= { min_citations } )" )
247+ return False
248+
249+ def verify_response_generated_with_citations (self , timeout = 60000 ):
250+ """
251+ Verify that a response is generated with citations/references.
252+
253+ Args:
254+ timeout: Maximum wait time in milliseconds
255+
256+ Returns:
257+ tuple: (response_text, citation_count)
258+ """
259+ logger .info ("🔹 Verifying response generated with citations" )
260+
261+ # Wait for response container
262+ self .page .wait_for_timeout (5000 )
263+
264+ answer_container = self .page .locator ("//div[contains(@class, 'answerContainer')]" ).last
265+
266+ try :
267+ # Wait for answer to be visible
268+ expect (answer_container ).to_be_visible (timeout = timeout )
269+
270+ # Get response text
271+ response_text = answer_container .inner_text ()
272+ logger .info (f"Response length: { len (response_text )} characters" )
273+
274+ # Verify response is not empty
275+ assert response_text .strip (), "Response text is empty"
276+
277+ # Count citations
278+ citation_count = self .get_citation_count ()
279+ logger .info (f"Response has { citation_count } citations" )
280+
281+ logger .info ("✅ Response generated successfully with citations" )
282+ return response_text , citation_count
283+
284+ except Exception as e :
285+ logger .error (f"❌ Failed to verify response with citations: { e } " )
286+ raise
287+
0 commit comments