Skip to content

Commit 2b409a4

Browse files
test(core-components): wrap prompt template tests in test.step() blocks
CONTRIBUTING.md requires every test to have test.step() describing each block. The spec was previously flat; this commit groups setup, action, and assertion phases into named steps so traces are readable and failures point at the right phase. Validation performed (per CONTRIBUTING.md): - npx playwright test --trace=on --reporter=html: 6/6 pass, traces generated for every test - Force-fail per test: each test fails when its main assertion is inverted (no false positives) - Clean re-run: 6/6 pass in ~27s, zero backend errors
1 parent 2758a02 commit 2b409a4

1 file changed

Lines changed: 181 additions & 112 deletions

File tree

tests/tests-automations/regression/core-components/prompt-template-component-regression.spec.ts

Lines changed: 181 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -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

95104
test(
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

118140
test(
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

139172
test(
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

168211
test(
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

188241
test(
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-9a-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-9a-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

Comments
 (0)