1
+ import { Page , Locator , expect } from '@playwright/test' ;
2
+
3
+ export class SubgraphsPage {
4
+ readonly page : Page ;
5
+ readonly travelPlannerButton : Locator ;
6
+ readonly chatInput : Locator ;
7
+ readonly sendButton : Locator ;
8
+ readonly agentGreeting : Locator ;
9
+ readonly agentMessage : Locator ;
10
+ readonly userMessage : Locator ;
11
+
12
+ // Flight-related elements
13
+ readonly flightOptions : Locator ;
14
+ readonly klmFlightOption : Locator ;
15
+ readonly unitedFlightOption : Locator ;
16
+ readonly flightSelectionInterface : Locator ;
17
+
18
+ // Hotel-related elements
19
+ readonly hotelOptions : Locator ;
20
+ readonly hotelZephyrOption : Locator ;
21
+ readonly ritzCarltonOption : Locator ;
22
+ readonly hotelZoeOption : Locator ;
23
+ readonly hotelSelectionInterface : Locator ;
24
+
25
+ // Itinerary and state elements
26
+ readonly itineraryDisplay : Locator ;
27
+ readonly selectedFlight : Locator ;
28
+ readonly selectedHotel : Locator ;
29
+ readonly experienceRecommendations : Locator ;
30
+
31
+ // Subgraph activity indicators
32
+ readonly activeAgent : Locator ;
33
+ readonly supervisorIndicator : Locator ;
34
+ readonly flightsAgentIndicator : Locator ;
35
+ readonly hotelsAgentIndicator : Locator ;
36
+ readonly experiencesAgentIndicator : Locator ;
37
+
38
+ constructor ( page : Page ) {
39
+ this . page = page ;
40
+ this . travelPlannerButton = page . getByRole ( 'button' , { name : / t r a v e l .* p l a n n e r | s u b g r a p h s / i } ) ;
41
+ this . agentGreeting = page . getByText ( / t r a v e l .* p l a n n i n g | s u p e r v i s o r .* c o o r d i n a t e / i) ;
42
+ this . chatInput = page . getByRole ( 'textbox' , { name : 'Type a message...' } ) ;
43
+ this . sendButton = page . locator ( '[data-test-id="copilot-chat-ready"]' ) ;
44
+ this . agentMessage = page . locator ( '.copilotKitAssistantMessage' ) ;
45
+ this . userMessage = page . locator ( '.copilotKitUserMessage' ) ;
46
+
47
+ // Flight selection elements
48
+ this . flightOptions = page . locator ( '[data-testid*="flight"], .flight-option' ) ;
49
+ this . klmFlightOption = page . getByText ( / K L M .* \$ 6 5 0 .* 1 1 h 3 0 m / ) ;
50
+ this . unitedFlightOption = page . getByText ( / U n i t e d .* \$ 7 2 0 .* 1 2 h 1 5 m / ) ;
51
+ this . flightSelectionInterface = page . locator ( '[data-testid*="flight-select"], .flight-selection' ) ;
52
+
53
+ // Hotel selection elements
54
+ this . hotelOptions = page . locator ( '[data-testid*="hotel"], .hotel-option' ) ;
55
+ this . hotelZephyrOption = page . getByText ( / H o t e l Z e p h y r .* F i s h e r m a n \' s W h a r f .* \$ 2 8 0 / ) ;
56
+ this . ritzCarltonOption = page . getByText ( / R i t z - C a r l t o n .* N o b H i l l .* \$ 5 5 0 / ) ;
57
+ this . hotelZoeOption = page . getByText ( / H o t e l Z o e .* U n i o n S q u a r e .* \$ 3 2 0 / ) ;
58
+ this . hotelSelectionInterface = page . locator ( '[data-testid*="hotel-select"], .hotel-selection' ) ;
59
+
60
+ // Itinerary elements
61
+ this . itineraryDisplay = page . locator ( '[data-testid*="itinerary"], .itinerary' ) ;
62
+ this . selectedFlight = page . locator ( '[data-testid*="selected-flight"], .selected-flight' ) ;
63
+ this . selectedHotel = page . locator ( '[data-testid*="selected-hotel"], .selected-hotel' ) ;
64
+ this . experienceRecommendations = page . locator ( '[data-testid*="experience"], .experience' ) ;
65
+
66
+ // Agent activity indicators
67
+ this . activeAgent = page . locator ( '[data-testid*="active-agent"], .active-agent' ) ;
68
+ this . supervisorIndicator = page . locator ( '[data-testid*="supervisor"], .supervisor-active' ) ;
69
+ this . flightsAgentIndicator = page . locator ( '[data-testid*="flights-agent"], .flights-agent-active' ) ;
70
+ this . hotelsAgentIndicator = page . locator ( '[data-testid*="hotels-agent"], .hotels-agent-active' ) ;
71
+ this . experiencesAgentIndicator = page . locator ( '[data-testid*="experiences-agent"], .experiences-agent-active' ) ;
72
+ }
73
+
74
+ async openChat ( ) {
75
+ await this . travelPlannerButton . click ( ) ;
76
+ }
77
+
78
+ async sendMessage ( message : string ) {
79
+ await this . chatInput . click ( ) ;
80
+ await this . chatInput . fill ( message ) ;
81
+ await this . sendButton . click ( ) ;
82
+ }
83
+
84
+ async selectFlight ( airline : 'KLM' | 'United' ) {
85
+ const flightOption = airline === 'KLM' ? this . klmFlightOption : this . unitedFlightOption ;
86
+
87
+ // Wait for flight options to be presented
88
+ await expect ( this . flightOptions . first ( ) ) . toBeVisible ( { timeout : 15000 } ) ;
89
+
90
+ // Click on the desired flight option
91
+ await flightOption . click ( ) ;
92
+ }
93
+
94
+ async selectHotel ( hotel : 'Zephyr' | 'Ritz-Carlton' | 'Zoe' ) {
95
+ let hotelOption : Locator ;
96
+
97
+ switch ( hotel ) {
98
+ case 'Zephyr' :
99
+ hotelOption = this . hotelZephyrOption ;
100
+ break ;
101
+ case 'Ritz-Carlton' :
102
+ hotelOption = this . ritzCarltonOption ;
103
+ break ;
104
+ case 'Zoe' :
105
+ hotelOption = this . hotelZoeOption ;
106
+ break ;
107
+ }
108
+
109
+ // Wait for hotel options to be presented
110
+ await expect ( this . hotelOptions . first ( ) ) . toBeVisible ( { timeout : 15000 } ) ;
111
+
112
+ // Click on the desired hotel option
113
+ await hotelOption . click ( ) ;
114
+ }
115
+
116
+ async waitForFlightsAgent ( ) {
117
+ // Wait for flights agent to become active (or look for flight-related content)
118
+ // Use .first() to handle multiple matches in strict mode
119
+ await expect (
120
+ this . page . getByText ( / f l i g h t .* o p t i o n s | A m s t e r d a m .* S a n F r a n c i s c o | K L M | U n i t e d / i) . first ( )
121
+ ) . toBeVisible ( { timeout : 20000 } ) ;
122
+ }
123
+
124
+ async waitForHotelsAgent ( ) {
125
+ // Wait for hotels agent to become active (or look for hotel-related content)
126
+ // Use .first() to handle multiple matches in strict mode
127
+ await expect (
128
+ this . page . getByText ( / h o t e l .* o p t i o n s | a c c o m m o d a t i o n | Z e p h y r | R i t z - C a r l t o n | H o t e l Z o e / i) . first ( )
129
+ ) . toBeVisible ( { timeout : 20000 } ) ;
130
+ }
131
+
132
+ async waitForExperiencesAgent ( ) {
133
+ // Wait for experiences agent to become active (or look for experience-related content)
134
+ // Use .first() to handle multiple matches in strict mode
135
+ await expect (
136
+ this . page . getByText ( / e x p e r i e n c e | a c t i v i t i e s | r e s t a u r a n t | P i e r 3 9 | G o l d e n G a t e | S w a n O y s t e r | T a r t i n e / i) . first ( )
137
+ ) . toBeVisible ( { timeout : 20000 } ) ;
138
+ }
139
+
140
+ async verifyStaticFlightData ( ) {
141
+ // Verify the hardcoded flight options are present
142
+ await expect ( this . page . getByText ( / K L M .* \$ 6 5 0 .* 1 1 h 3 0 m / ) . first ( ) ) . toBeVisible ( ) ;
143
+ await expect ( this . page . getByText ( / U n i t e d .* \$ 7 2 0 .* 1 2 h 1 5 m / ) . first ( ) ) . toBeVisible ( ) ;
144
+ }
145
+
146
+ async verifyStaticHotelData ( ) {
147
+ // Verify the hardcoded hotel options are present
148
+ await expect ( this . page . getByText ( / H o t e l Z e p h y r .* \$ 2 8 0 / ) . first ( ) ) . toBeVisible ( ) ;
149
+ await expect ( this . page . getByText ( / R i t z - C a r l t o n .* \$ 5 5 0 / ) . first ( ) ) . toBeVisible ( ) ;
150
+ await expect ( this . page . getByText ( / H o t e l Z o e .* \$ 3 2 0 / ) . first ( ) ) . toBeVisible ( ) ;
151
+ }
152
+
153
+ async verifyStaticExperienceData ( ) {
154
+ // Wait for experiences to load - this can take time as it's the final step in the agent flow
155
+ // First ensure we're not stuck in "No experiences planned yet" state
156
+ await expect ( this . page . getByText ( 'No experiences planned yet' ) ) . not . toBeVisible ( { timeout : 20000 } ) . catch ( ( ) => {
157
+ console . log ( 'Still waiting for experiences to load...' ) ;
158
+ } ) ;
159
+
160
+ // Wait for actual experience content to appear
161
+ await expect ( this . page . locator ( '.activity-name' ) . first ( ) ) . toBeVisible ( { timeout : 15000 } ) ;
162
+
163
+ // Verify we have meaningful experience content (either static or AI-generated)
164
+ const experienceContent = this . page . locator ( '.activity-name' ) . first ( ) . or (
165
+ this . page . getByText ( / P i e r 3 9 | G o l d e n G a t e B r i d g e | S w a n O y s t e r D e p o t | T a r t i n e B a k e r y / i) . first ( )
166
+ ) ;
167
+ await expect ( experienceContent ) . toBeVisible ( ) ;
168
+ }
169
+
170
+ async verifyItineraryContainsFlight ( airline : 'KLM' | 'United' ) {
171
+ // Check that the selected flight appears in the itinerary or conversation
172
+ await expect ( this . page . getByText ( new RegExp ( airline , 'i' ) ) ) . toBeVisible ( ) ;
173
+ }
174
+
175
+ async verifyItineraryContainsHotel ( hotel : 'Zephyr' | 'Ritz-Carlton' | 'Zoe' ) {
176
+ // Check that the selected hotel appears in the itinerary or conversation
177
+ const hotelName = hotel === 'Ritz-Carlton' ? 'Ritz-Carlton' : `Hotel ${ hotel } ` ;
178
+ await expect ( this . page . getByText ( new RegExp ( hotelName , 'i' ) ) ) . toBeVisible ( ) ;
179
+ }
180
+
181
+ async assertAgentReplyVisible ( expectedText : RegExp ) {
182
+ await expect ( this . agentMessage . last ( ) . getByText ( expectedText ) ) . toBeVisible ( ) ;
183
+ }
184
+
185
+ async assertUserMessageVisible ( message : string ) {
186
+ await expect ( this . page . getByText ( message ) ) . toBeVisible ( ) ;
187
+ }
188
+
189
+ async waitForSupervisorCoordination ( ) {
190
+ // Wait for supervisor to appear in the conversation
191
+ await expect (
192
+ this . page . getByText ( / s u p e r v i s o r | c o o r d i n a t e | s p e c i a l i s t | r o u t i n g / i) . first ( )
193
+ ) . toBeVisible ( { timeout : 15000 } ) ;
194
+ }
195
+
196
+ async waitForAgentCompletion ( ) {
197
+ // Wait for the travel planning process to complete
198
+ await expect (
199
+ this . page . getByText ( / c o m p l e t e | f i n i s h e d | p l a n n i n g .* d o n e | i t i n e r a r y .* r e a d y / i) . first ( )
200
+ ) . toBeVisible ( { timeout : 30000 } ) ;
201
+ }
202
+ }
0 commit comments