|
| 1 | +# Prompt Template Component — Regression |
| 2 | + |
| 3 | +**Last validated:** Langflow 1.10.x |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## What this test validates *(required)* |
| 8 | + |
| 9 | +Validates the **Prompt Template** component end-to-end via 6 scenarios: |
| 10 | + |
| 11 | +1. **Canvas rendering** — the node renders on a blank flow with the correct `prompt` output handle. |
| 12 | +2. **Dynamic input handles from `{variable}` placeholders** — typing `{name}` and `{profession}` in the template creates one input handle per placeholder, named after the variable. |
| 13 | +3. **Removing a variable removes its handle** — replacing `Hello {name}!` with `Hello world!` drops the `name` handle and decreases the total handle count. |
| 14 | +4. **Replacing a variable updates handles in place** — switching `{role}` for `{title}` while keeping `{name}` causes the old handle to disappear and the new one to appear. |
| 15 | +5. **Clearing all variables removes every dynamic handle** — replacing `{a} and {b} and {c}` with a variable-free string drops the dynamic handle count to zero. |
| 16 | +6. **Modal persistence (UI + backend)** — text entered in the prompt modal and saved via `genericModalBtnSave` is still present (in the sanitized preview and in the textarea after re-entering edit mode) when the modal is reopened, **and** the autosaved flow at `GET /api/v1/flows/{id}` contains the same string in `node.data.node.template.template.value` for the Prompt Template node. |
| 17 | + |
| 18 | +If any of these tests fails, the Prompt Template component is broken in one of its core contracts: rendering on the canvas, the regex that extracts `{variable}` placeholders from the template, the dynamic handle generation, or the modal's save-and-persist flow. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Tags *(required)* |
| 23 | + |
| 24 | +All 6 tests: `@stable` `@release` `@regression` `@components` |
| 25 | + |
| 26 | +--- |
| 27 | + |
| 28 | +## Step by step *(required)* |
| 29 | + |
| 30 | +Every test starts with `addPromptComponent(page)` which: |
| 31 | +1. Bootstraps the app (`awaitBootstrapTest`) |
| 32 | +2. Clicks `blank-flow` |
| 33 | +3. Fills `sidebar-search-input` with `prompt` and waits for `add-component-button-prompt-template` |
| 34 | +4. Clicks the add button |
| 35 | +5. Calls `adjustScreenView(page)` |
| 36 | +6. Asserts exactly one node is on the canvas |
| 37 | + |
| 38 | +Tests 2–6 use the helper `setPromptTemplate(page, value)` which: |
| 39 | +1. Clicks `button_open_prompt_modal` |
| 40 | +2. If the sanitized preview `edit-prompt-sanitized` is visible (post-save state), clicks it to re-enter edit mode |
| 41 | +3. Waits for the textarea `modal-promptarea_prompt_template` (unique to the prompt modal, used as anchor instead of `[role="dialog"]`) to be visible |
| 42 | +4. Selects all in the textarea (`Ctrl+A`) and fills `value` |
| 43 | +5. Clicks `genericModalBtnSave` and waits for the textarea testid to be hidden — that disappearance is the reliable signal that the modal closed. Downstream assertions then auto-retry on the expected handle state. |
| 44 | + |
| 45 | +### 1. `renders on canvas with output handle` |
| 46 | +- Asserts `title-Prompt Template` is visible. |
| 47 | +- Asserts the right-side `handle-prompt template-shownode-prompt-right` handle is visible. |
| 48 | +- Asserts exactly one node on the canvas (`react-flow__node` count === 1). |
| 49 | + |
| 50 | +### 2. `variables in curly braces generate dynamic input handles` |
| 51 | +- Calls `setPromptTemplate` with `Hello {name}, your job is {profession}.`. |
| 52 | +- Asserts both `handle-prompt template-shownode-name-left` and `handle-prompt template-shownode-profession-left` are visible. |
| 53 | +- Asserts the dynamic-handle locator (`-shownode-*-left` only) has count exactly 2 — sanity check that no extra handles leaked in. |
| 54 | + |
| 55 | +### 3. `removing a variable removes its input handle` |
| 56 | +- Sets the template to `Hello {name}!` and asserts the `name` handle is visible and the dynamic-handle count is exactly 1. |
| 57 | +- Sets the template to `Hello world!`. |
| 58 | +- Asserts the `name` handle has zero matches and the dynamic-handle count is exactly 0. |
| 59 | + |
| 60 | +### 4. `replacing a variable updates handles accordingly` |
| 61 | +- Sets the template to `Hello {name}, you are {role}.` and asserts both `name` and `role` handles are visible. |
| 62 | +- Sets the template to `Hello {name}, you are {title}.`. |
| 63 | +- Asserts `name` is still visible, `role` has zero matches, and `title` is visible. |
| 64 | + |
| 65 | +### 5. `clearing the template removes all dynamic handles` |
| 66 | +- Sets the template to `{a} and {b} and {c}` and asserts the dynamic-handle count is exactly 3. |
| 67 | +- Sets the template to `No variables here.`. |
| 68 | +- Asserts the dynamic-handle count is exactly 0. |
| 69 | + |
| 70 | +### 6. `modal edits persist in UI and in saved flow` |
| 71 | +- Sets the template to `Persisted prompt text {topic}.` via `setPromptTemplate`. |
| 72 | +- Asserts `handle-prompt template-shownode-topic-left` is visible (confirms save succeeded). |
| 73 | +- **UI layer:** reopens the modal, asserts `edit-prompt-sanitized` contains the saved text, clicks the preview to re-enter edit mode, asserts the textarea has the exact saved value via `toHaveValue`. |
| 74 | +- **Backend layer:** extracts the flow id from the URL, then polls `GET /api/v1/flows/{id}` via `page.request` (inherits session cookies — the endpoint requires session auth) until the Prompt Template node's `template.template.value` equals the saved string. Catches regressions where the modal shows the value but autosave does not flush it to the database. |
| 75 | + |
| 76 | +--- |
| 77 | + |
| 78 | +## Validation criterion *(required)* |
| 79 | + |
| 80 | +- `title-Prompt Template` is visible after adding the component to a blank flow |
| 81 | +- The right-side output handle `handle-prompt template-shownode-prompt-right` is visible |
| 82 | +- For each `{variable}` saved in the template, a corresponding left-side handle `handle-prompt template-shownode-{variable}-left` is rendered |
| 83 | +- Removing a `{variable}` from the template removes the corresponding handle |
| 84 | +- Replacing one variable with another removes the old handle and creates a new one |
| 85 | +- Saving a template via `genericModalBtnSave` makes the value retrievable on the next modal open — both in the sanitized preview and in the textarea after re-entering edit mode |
| 86 | +- The autosaved flow at `GET /api/v1/flows/{id}` contains the saved template string at `node.data.node.template.template.value` for the Prompt Template node |
| 87 | + |
| 88 | +--- |
| 89 | + |
| 90 | +## External dependencies *(required)* |
| 91 | + |
| 92 | +- `src/frontend/src/modals/promptModal/` — `genericModalBtnSave` button, `edit-prompt-sanitized` preview, and the textarea that holds the editable template; changes here break tests 2–6 |
| 93 | +- `src/frontend/src/CustomNodes/GenericNode/components/parameterRenderComponent/components/promptAreaComponent/` — `button_open_prompt_modal` trigger on the node inspector; breaks tests 2–6 |
| 94 | +- `src/lfx/src/lfx/interface/utils.py` — `extract_input_variables_from_prompt()`: derives the variable list from the template string using Python's `string.Formatter().parse()` (not a regex); breaks tests 2–5 |
| 95 | +- `src/lfx/src/lfx/base/prompts/api_utils.py` — `validate_prompt()` and `_check_input_variables()`: validation layer around the extracted variables; breaks tests 2–5 |
| 96 | +- `src/lfx/src/lfx/components/models_and_agents/prompt.py` — `PromptComponent` definition (`display_name="Prompt Template"`, template field) and `update_build_config()` that synchronizes template ↔ input fields; breaks tests 2–6 and the backend assertion in test 6 |
| 97 | +- `src/frontend/src/CustomNodes/GenericNode/` — dynamic handle rendering for `handle-{component}-shownode-{var}-left`; breaks tests 1–5 |
| 98 | +- `GET /api/v1/flows/{id}` — flow read endpoint backing the autosave round-trip; the response shape `data.nodes[].data.node.template.template.value` is what test 6 asserts. A rename of the inner `template.template` nesting, or a change to `node.data.type` away from `"Prompt Template"`, breaks the backend assertion. |
| 99 | + |
| 100 | +--- |
| 101 | + |
| 102 | +## What this test does not cover *(optional)* |
| 103 | + |
| 104 | +- Prompt rendering/execution behind an LLM (covered by `llm-agents` specs such as `memory-history-regression.spec.ts`) |
| 105 | +- Tool Mode interaction (covered by `tool-mode.spec.ts`) |
| 106 | +- Cross-component data flow (covered by `flow-functionality/` specs) |
| 107 | +- Variable name validation (e.g., reserved keywords, special characters) |
| 108 | + |
| 109 | +--- |
| 110 | + |
| 111 | +## Preconditions *(optional)* |
| 112 | + |
| 113 | +- Langflow running and accessible at `PLAYWRIGHT_BASE_URL` |
| 114 | +- No API key required — the Prompt Template component is a pure templating layer with no LLM calls |
| 115 | +- Auto-login mode is assumed: test 6 uses `page.request.get` so the backend call inherits the page's session cookies. In an environment with explicit auth, the test should still work because the page is authenticated via the normal login flow before the assertion runs. |
| 116 | + |
| 117 | +--- |
| 118 | + |
| 119 | +## When to review this test *(optional)* |
| 120 | + |
| 121 | +- If the `button_open_prompt_modal`, `genericModalBtnSave`, or `edit-prompt-sanitized` testids are renamed in the prompt modal frontend |
| 122 | +- If the dynamic handle testid pattern (`handle-prompt template-shownode-{var}-left`) changes — e.g., if `"prompt template"` (with space) is replaced by `"prompt-template"` (with dash) or a different node-type slug |
| 123 | +- If the `{variable}` extraction regex in `extract_input_variables_from_prompt` changes its handling of escape sequences, whitespace, or nested braces |
| 124 | + |
| 125 | +--- |
| 126 | + |
| 127 | +## Notes *(optional)* |
| 128 | + |
| 129 | +- Test 6 (modal persistence) asserts persistence at three layers: the sanitized preview (`edit-prompt-sanitized`, which is the post-save render), the textarea value reached by clicking back into edit mode, and the autosaved flow JSON fetched via `GET /api/v1/flows/{id}`. All three must match the saved string for the test to pass. |
| 130 | +- The `setPromptTemplate` helper deliberately handles the post-save "preview" state by detecting `edit-prompt-sanitized` and clicking through it. This is what makes the helper safe to call multiple times in a row (tests 3, 4, 5 all rely on this). |
| 131 | +- All assertions use the literal node-type slug `"prompt template"` (with space) in the testid, matching how the frontend renders the type. The leading space inside `handle-prompt template-...` is intentional and not a typo. |
0 commit comments