@@ -76,168 +76,237 @@ test(
7676 "Prompt Template component — renders on canvas with output handle" ,
7777 { tag : [ "@stable" , "@release" , "@regression" , "@components" ] } ,
7878 async ( { page } ) => {
79- await addPromptComponent ( page ) ;
79+ await test . step ( "Add Prompt Template to a blank flow" , async ( ) => {
80+ await addPromptComponent ( page ) ;
81+ } ) ;
8082
81- await expect ( page . getByTestId ( "title-Prompt Template" ) ) . toBeVisible ( {
82- timeout : 10000 ,
83+ await test . step ( "Node title is visible on the canvas" , async ( ) => {
84+ await expect ( page . getByTestId ( "title-Prompt Template" ) ) . toBeVisible ( {
85+ timeout : 10000 ,
86+ } ) ;
8387 } ) ;
8488
85- // Output handle: "prompt" port on the right side
86- await expect (
87- page . getByTestId ( "handle-prompt template-shownode-prompt-right" ) ,
88- ) . toBeVisible ( { timeout : 5000 } ) ;
89+ await test . step (
90+ "Right-side output handle for the `prompt` port is visible" ,
91+ async ( ) => {
92+ await expect (
93+ page . getByTestId ( "handle-prompt template-shownode-prompt-right" ) ,
94+ ) . toBeVisible ( { timeout : 5000 } ) ;
95+ } ,
96+ ) ;
8997
90- // Exactly one node on the canvas — no spurious duplicates
91- await expect ( page . locator ( ".react-flow__node" ) ) . toHaveCount ( 1 ) ;
98+ await test . step ( "Exactly one node is rendered on the canvas" , async ( ) => {
99+ await expect ( page . locator ( ".react-flow__node" ) ) . toHaveCount ( 1 ) ;
100+ } ) ;
92101 } ,
93102) ;
94103
95104test (
96105 "Prompt Template component — variables in curly braces generate dynamic input handles" ,
97106 { tag : [ "@stable" , "@release" , "@regression" , "@components" ] } ,
98107 async ( { page } ) => {
99- await addPromptComponent ( page ) ;
100-
101- await setPromptTemplate ( page , "Hello {name}, your job is {profession}." ) ;
102-
103- // The specific handle assertions are the contract under test — both must
104- // be rendered as left-side input handles on the node. Auto-retry covers
105- // the asynchronous canvas re-render after the modal closes.
106- await expect (
107- page . getByTestId ( "handle-prompt template-shownode-name-left" ) ,
108- ) . toBeVisible ( { timeout : 10000 } ) ;
109- await expect (
110- page . getByTestId ( "handle-prompt template-shownode-profession-left" ) ,
111- ) . toBeVisible ( { timeout : 10000 } ) ;
112-
113- // Sanity: dynamic-handle count matches the variable count exactly
114- await expect ( dynamicHandlesLocator ( page ) ) . toHaveCount ( 2 ) ;
108+ await test . step ( "Add Prompt Template to a blank flow" , async ( ) => {
109+ await addPromptComponent ( page ) ;
110+ } ) ;
111+
112+ await test . step (
113+ "Save template with two {variable} placeholders" ,
114+ async ( ) => {
115+ await setPromptTemplate ( page , "Hello {name}, your job is {profession}." ) ;
116+ } ,
117+ ) ;
118+
119+ await test . step (
120+ "Both variable handles are rendered on the left side of the node" ,
121+ async ( ) => {
122+ await expect (
123+ page . getByTestId ( "handle-prompt template-shownode-name-left" ) ,
124+ ) . toBeVisible ( { timeout : 10000 } ) ;
125+ await expect (
126+ page . getByTestId ( "handle-prompt template-shownode-profession-left" ) ,
127+ ) . toBeVisible ( { timeout : 10000 } ) ;
128+ } ,
129+ ) ;
130+
131+ await test . step (
132+ "Exactly 2 dynamic handles exist — no extras leaked in" ,
133+ async ( ) => {
134+ await expect ( dynamicHandlesLocator ( page ) ) . toHaveCount ( 2 ) ;
135+ } ,
136+ ) ;
115137 } ,
116138) ;
117139
118140test (
119141 "Prompt Template component — removing a variable removes its input handle" ,
120142 { tag : [ "@stable" , "@release" , "@regression" , "@components" ] } ,
121143 async ( { page } ) => {
122- await addPromptComponent ( page ) ;
123-
124- await setPromptTemplate ( page , "Hello {name}!" ) ;
125-
126144 const nameHandle = page . getByTestId (
127145 "handle-prompt template-shownode-name-left" ,
128146 ) ;
129- await expect ( nameHandle ) . toBeVisible ( { timeout : 10000 } ) ;
130- await expect ( dynamicHandlesLocator ( page ) ) . toHaveCount ( 1 ) ;
131147
132- await setPromptTemplate ( page , "Hello world!" ) ;
148+ await test . step ( "Add Prompt Template to a blank flow" , async ( ) => {
149+ await addPromptComponent ( page ) ;
150+ } ) ;
133151
134- await expect ( nameHandle ) . toHaveCount ( 0 , { timeout : 10000 } ) ;
135- await expect ( dynamicHandlesLocator ( page ) ) . toHaveCount ( 0 ) ;
152+ await test . step (
153+ "Save template `Hello {name}!` — expect 1 dynamic handle for {name}" ,
154+ async ( ) => {
155+ await setPromptTemplate ( page , "Hello {name}!" ) ;
156+ await expect ( nameHandle ) . toBeVisible ( { timeout : 10000 } ) ;
157+ await expect ( dynamicHandlesLocator ( page ) ) . toHaveCount ( 1 ) ;
158+ } ,
159+ ) ;
160+
161+ await test . step (
162+ "Save template without variables — expect 0 dynamic handles" ,
163+ async ( ) => {
164+ await setPromptTemplate ( page , "Hello world!" ) ;
165+ await expect ( nameHandle ) . toHaveCount ( 0 , { timeout : 10000 } ) ;
166+ await expect ( dynamicHandlesLocator ( page ) ) . toHaveCount ( 0 ) ;
167+ } ,
168+ ) ;
136169 } ,
137170) ;
138171
139172test (
140173 "Prompt Template component — replacing a variable updates handles accordingly" ,
141174 { tag : [ "@stable" , "@release" , "@regression" , "@components" ] } ,
142175 async ( { page } ) => {
143- await addPromptComponent ( page ) ;
144-
145- await setPromptTemplate ( page , "Hello {name}, you are {role}." ) ;
146-
147- await expect (
148- page . getByTestId ( "handle-prompt template-shownode-name-left" ) ,
149- ) . toBeVisible ( { timeout : 10000 } ) ;
150- await expect (
151- page . getByTestId ( "handle-prompt template-shownode-role-left" ) ,
152- ) . toBeVisible ( { timeout : 10000 } ) ;
153-
154- await setPromptTemplate ( page , "Hello {name}, you are {title}." ) ;
155-
156- await expect (
157- page . getByTestId ( "handle-prompt template-shownode-name-left" ) ,
158- ) . toBeVisible ( { timeout : 10000 } ) ;
159- await expect (
160- page . getByTestId ( "handle-prompt template-shownode-role-left" ) ,
161- ) . toHaveCount ( 0 , { timeout : 10000 } ) ;
162- await expect (
163- page . getByTestId ( "handle-prompt template-shownode-title-left" ) ,
164- ) . toBeVisible ( { timeout : 10000 } ) ;
176+ await test . step ( "Add Prompt Template to a blank flow" , async ( ) => {
177+ await addPromptComponent ( page ) ;
178+ } ) ;
179+
180+ await test . step (
181+ "Save template `Hello {name}, you are {role}.` — both handles render" ,
182+ async ( ) => {
183+ await setPromptTemplate ( page , "Hello {name}, you are {role}." ) ;
184+ await expect (
185+ page . getByTestId ( "handle-prompt template-shownode-name-left" ) ,
186+ ) . toBeVisible ( { timeout : 10000 } ) ;
187+ await expect (
188+ page . getByTestId ( "handle-prompt template-shownode-role-left" ) ,
189+ ) . toBeVisible ( { timeout : 10000 } ) ;
190+ } ,
191+ ) ;
192+
193+ await test . step (
194+ "Replace {role} with {title} — old handle is gone, new one appears, {name} stays" ,
195+ async ( ) => {
196+ await setPromptTemplate ( page , "Hello {name}, you are {title}." ) ;
197+ await expect (
198+ page . getByTestId ( "handle-prompt template-shownode-name-left" ) ,
199+ ) . toBeVisible ( { timeout : 10000 } ) ;
200+ await expect (
201+ page . getByTestId ( "handle-prompt template-shownode-role-left" ) ,
202+ ) . toHaveCount ( 0 , { timeout : 10000 } ) ;
203+ await expect (
204+ page . getByTestId ( "handle-prompt template-shownode-title-left" ) ,
205+ ) . toBeVisible ( { timeout : 10000 } ) ;
206+ } ,
207+ ) ;
165208 } ,
166209) ;
167210
168211test (
169212 "Prompt Template component — clearing the template removes all dynamic handles" ,
170213 { tag : [ "@stable" , "@release" , "@regression" , "@components" ] } ,
171214 async ( { page } ) => {
172- await addPromptComponent ( page ) ;
173-
174- await setPromptTemplate ( page , "{a} and {b} and {c}" ) ;
175-
176- await expect ( dynamicHandlesLocator ( page ) ) . toHaveCount ( 3 , {
177- timeout : 10000 ,
215+ await test . step ( "Add Prompt Template to a blank flow" , async ( ) => {
216+ await addPromptComponent ( page ) ;
178217 } ) ;
179218
180- await setPromptTemplate ( page , "No variables here." ) ;
219+ await test . step (
220+ "Save template with 3 variables — expect 3 dynamic handles" ,
221+ async ( ) => {
222+ await setPromptTemplate ( page , "{a} and {b} and {c}" ) ;
223+ await expect ( dynamicHandlesLocator ( page ) ) . toHaveCount ( 3 , {
224+ timeout : 10000 ,
225+ } ) ;
226+ } ,
227+ ) ;
181228
182- await expect ( dynamicHandlesLocator ( page ) ) . toHaveCount ( 0 , {
183- timeout : 10000 ,
184- } ) ;
229+ await test . step (
230+ "Save plain-text template — all dynamic handles disappear" ,
231+ async ( ) => {
232+ await setPromptTemplate ( page , "No variables here." ) ;
233+ await expect ( dynamicHandlesLocator ( page ) ) . toHaveCount ( 0 , {
234+ timeout : 10000 ,
235+ } ) ;
236+ } ,
237+ ) ;
185238 } ,
186239) ;
187240
188241test (
189242 "Prompt Template component — modal edits persist in UI and in saved flow" ,
190243 { tag : [ "@stable" , "@release" , "@regression" , "@components" ] } ,
191244 async ( { page } ) => {
192- await addPromptComponent ( page ) ;
193-
194245 const expected = "Persisted prompt text {topic}." ;
195- await setPromptTemplate ( page , expected ) ;
246+ let flowId = "" ;
196247
197- // Confirms the save was applied: the {topic} variable produced a handle
198- await expect (
199- page . getByTestId ( "handle-prompt template-shownode-topic-left" ) ,
200- ) . toBeVisible ( { timeout : 10000 } ) ;
248+ await test . step ( "Add Prompt Template to a blank flow" , async ( ) => {
249+ await addPromptComponent ( page ) ;
250+ flowId = page . url ( ) . split ( "/" ) . slice ( - 1 ) [ 0 ] ;
251+ expect ( flowId ) . toMatch ( / ^ [ 0 - 9 a - f - ] { 36 } $ / ) ;
252+ } ) ;
201253
202- // UI-layer persistence: reopening the modal must surface the saved value
203- // both in the sanitized preview and in the textarea after re-entering edit.
204- await page . getByTestId ( "button_open_prompt_modal" ) . click ( ) ;
205- const preview = page . getByTestId ( "edit-prompt-sanitized" ) ;
206- await expect ( preview ) . toBeVisible ( { timeout : 10000 } ) ;
207- await expect ( preview ) . toContainText ( "Persisted prompt text" ) ;
208- await expect ( preview ) . toContainText ( "topic" ) ;
254+ await test . step (
255+ "Save template — the {topic} handle confirms save was applied" ,
256+ async ( ) => {
257+ await setPromptTemplate ( page , expected ) ;
258+ await expect (
259+ page . getByTestId ( "handle-prompt template-shownode-topic-left" ) ,
260+ ) . toBeVisible ( { timeout : 10000 } ) ;
261+ } ,
262+ ) ;
209263
210- await preview . click ( ) ;
211- const textarea = page . getByTestId ( "modal-promptarea_prompt_template" ) ;
212- await expect ( textarea ) . toBeVisible ( { timeout : 5000 } ) ;
213- await expect ( textarea ) . toHaveValue ( expected ) ;
214- await page . keyboard . press ( "Escape" ) ;
215-
216- // Backend-layer persistence: the autosaved flow must contain the saved
217- // template string in the Prompt node. Without this check, a regression
218- // where the modal shows the value but never autosaves it would slip past.
219- // `page.request` is used (not the `request` fixture) so the call inherits
220- // the page's session cookies — `GET /api/v1/flows/{id}` requires session
221- // auth in Langflow's auto-login mode.
222- const flowId = page . url ( ) . split ( "/" ) . slice ( - 1 ) [ 0 ] ;
223- expect ( flowId ) . toMatch ( / ^ [ 0 - 9 a - f - ] { 36 } $ / ) ;
224-
225- await expect
226- . poll (
227- async ( ) => {
228- const res = await page . request . get ( `/api/v1/flows/${ flowId } ` ) ;
229- if ( ! res . ok ( ) ) return null ;
230- const flow = await res . json ( ) ;
231- // The frontend sets `node.data.type` to the human-readable name
232- // ("Prompt Template"), which is the display_name of the PromptComponent.
233- const promptNode = ( flow ?. data ?. nodes ?? [ ] ) . find (
234- ( n : { data ?: { type ?: string } } ) =>
235- n ?. data ?. type === "Prompt Template" ,
236- ) ;
237- return promptNode ?. data ?. node ?. template ?. template ?. value ?? null ;
238- } ,
239- { timeout : 15000 , intervals : [ 500 , 1000 , 2000 ] } ,
240- )
241- . toBe ( expected ) ;
264+ await test . step (
265+ "Reopen the modal — sanitized preview shows the saved value" ,
266+ async ( ) => {
267+ await page . getByTestId ( "button_open_prompt_modal" ) . click ( ) ;
268+ const preview = page . getByTestId ( "edit-prompt-sanitized" ) ;
269+ await expect ( preview ) . toBeVisible ( { timeout : 10000 } ) ;
270+ await expect ( preview ) . toContainText ( "Persisted prompt text" ) ;
271+ await expect ( preview ) . toContainText ( "topic" ) ;
272+ } ,
273+ ) ;
274+
275+ await test . step (
276+ "Re-enter edit mode — textarea holds the exact saved value" ,
277+ async ( ) => {
278+ await page . getByTestId ( "edit-prompt-sanitized" ) . click ( ) ;
279+ const textarea = page . getByTestId ( "modal-promptarea_prompt_template" ) ;
280+ await expect ( textarea ) . toBeVisible ( { timeout : 5000 } ) ;
281+ await expect ( textarea ) . toHaveValue ( expected ) ;
282+ await page . keyboard . press ( "Escape" ) ;
283+ } ,
284+ ) ;
285+
286+ await test . step (
287+ "Backend persistence — autosaved flow contains the template string" ,
288+ async ( ) => {
289+ // `page.request` inherits session cookies — `GET /api/v1/flows/{id}`
290+ // requires session auth in Langflow's auto-login mode.
291+ await expect
292+ . poll (
293+ async ( ) => {
294+ const res = await page . request . get ( `/api/v1/flows/${ flowId } ` ) ;
295+ if ( ! res . ok ( ) ) return null ;
296+ const flow = await res . json ( ) ;
297+ // The frontend sets `node.data.type` to the human-readable name
298+ // ("Prompt Template"), which is the `display_name` of the
299+ // PromptComponent on the upstream Langflow source.
300+ const promptNode = ( flow ?. data ?. nodes ?? [ ] ) . find (
301+ ( n : { data ?: { type ?: string } } ) =>
302+ n ?. data ?. type === "Prompt Template" ,
303+ ) ;
304+ return promptNode ?. data ?. node ?. template ?. template ?. value ?? null ;
305+ } ,
306+ { timeout : 15000 , intervals : [ 500 , 1000 , 2000 ] } ,
307+ )
308+ . toBe ( expected ) ;
309+ } ,
310+ ) ;
242311 } ,
243312) ;
0 commit comments