From f38db6a7e36b32f65422ae20291cbe6b61ed1241 Mon Sep 17 00:00:00 2001 From: "promptless[bot]" Date: Tue, 28 Jul 2026 00:18:54 +0000 Subject: [PATCH 1/3] docs: refresh Environment Variables screenshots and correct control names Replace both dashboard screenshots on the Manage environment variables page with fresh captures from the live product, and correct the prose the stale images had drifted away from: - Settings now opens on Environment Variables, so the old "find the section" step folds into step 1. - The add-a-variable flow submits with Add Variable, not Save. Save only appears in the per-row Update flow, which the update section now describes. - The Secret control is a toggle, not a checkbox. - Name the Update and Delete buttons in the Actions column. Add a Doc Detective spec asserting the documented controls still exist, so the next UI drift is caught rather than discovered by a reader. --- .../environment-variables-settings.spec.json | 122 ++++++++++++++++++ .../docs/docs/scale/environment-variables.mdx | 20 ++- 2 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 .doc-detective/tests/environment-variables-settings.spec.json diff --git a/.doc-detective/tests/environment-variables-settings.spec.json b/.doc-detective/tests/environment-variables-settings.spec.json new file mode 100644 index 000000000..20f2b5b84 --- /dev/null +++ b/.doc-detective/tests/environment-variables-settings.spec.json @@ -0,0 +1,122 @@ +{ + "specId": "environment-variables-settings", + "description": "Environment Variables settings page in the Promptless dashboard (https://app.gopromptless.ai/settings), covering the control names that src/content/docs/docs/scale/environment-variables.mdx documents in its `## Add environment variables` steps and its `## Update and delete variables` section. This page's screenshots and control names silently drifted out of date once already, so this spec pins the exact names the prose tells readers to look for -- Key Name, Value, Secret, Add Variable, Update, Delete -- and fails when one is renamed instead of letting the docs quietly go stale.\n\nNON-DESTRUCTIVE BY CONSTRUCTION. Nothing here creates, changes, or deletes a real environment variable in the test account. The Add Variable form is filled but never submitted: no step clicks Add Variable, no step clicks a row's Update, Save, or Delete, and no step types $ENTER$ or $RETURN$ into a form field. The only writes are keystrokes into two unsaved inputs, using the inert sentinel DOC_DETECTIVE_ENV_VARS_SPEC_NOT_SAVED as the key so that an unexpected save would be obvious in the dashboard. The last step asserts that sentinel never reached the variables table. Every runBrowserScript below only reads the DOM -- none clicks, submits, assigns to a value, dispatches an event, or issues a request.\n\nAuthentication reuses the existing Clerk sign-in flow rather than a new one: each test sets `before` to login.spec.json, whose steps are prepended into this test's own browser context. That spec reads $PROMPTLESS_TEST_EMAIL and $PROMPTLESS_TEST_PASSWORD from .doc-detective/.env via the config's `loadVariables`, so no credential and no resolved internal host appears here. `before` resolves relative to this spec file because `relativePathBase` defaults to `file`; under `relativePathBase: cwd` Doc Detective skips this spec rather than failing it. login.spec.json opens with a `record` step that is unsupported headless and that skips when its fixed output path already exists, so that step often reports SKIPPED here -- a skipped step does not fail its context, since only an all-skipped context is itself skipped.\n\nDATA PREREQUISITE for env-vars-settings-page-and-variables-table: the test account must already hold at least one saved environment variable, which is the state the page's own figure shows. With an empty table the Update/Delete assertion has nothing to check, so the probe reports rowActions=none and the step fails rather than passing vacuously. Fix that by adding a variable to the test account, not by loosening the assertion.\n\nRunning it: neither config lists .doc-detective/tests/ in `input` (.doc-detective.json scans src/content/docs for inline tests), so pass this file explicitly -- `npx doc-detective -c .doc-detective.json -i .doc-detective/tests/environment-variables-settings.spec.json`. This spec is deliberately NOT added to .doc-detective.ci.json: Clerk-dependent flows were dropped from CI in fb89a47 and intentionally left out again in 0e72027 for being unstable, and reversing that is a maintainer call rather than this spec's to make.\n\nURLs are absolute because the config's `origin` points at the docs site (https://promptless.ai), not the dashboard.\n\nAssertion style follows homepage-product-switcher.spec.json: `runBrowserScript` probes report several related facts in one string, so a renamed control shows up as a readable diff instead of an opaque element-not-found. The division of labour between step types is deliberate. `find` steps are waits and interactions, and they match text by regex so that incidental whitespace or a nested wrapper does not fail a step whose job is only to reach the control. The exact documented names are asserted inside the probes instead, compared against each element's own direct text with whitespace collapsed, so a container's aggregated text cannot satisfy an assertion by accident. `output` is a substring match on the serialized return value, which is why the probes also report unasserted context such as row counts. Comparisons upper-case before matching wherever the UI applies a CSS text-transform: the submit control's DOM text is `Add Variable`, rendered uppercase.", + "tests": [ + { + "testId": "env-vars-settings-page-and-variables-table", + "description": "Documented step 1 (\"Open Settings\"): the /settings link the page hands readers lands on the Environment Variables view, that view's tab row carries the documented Environment Variables name, and the page shows the variables table described by the figure and by `## Update and delete variables` -- Key, Value, Created, and Actions columns, with an Update and a Delete control in every row. Also pins that Save and Cancel are absent until a row's Update is clicked, which is what makes the documented Update-then-Save order correct.", + "before": "login.spec.json", + "steps": [ + { + "description": "Open the Settings page the docs link to. It 307-redirects to the Environment Variables view.", + "goTo": "https://app.gopromptless.ai/settings" + }, + { + "description": "Wait for the Environment Variables view to render", + "find": { + "elementText": "/Environment Variables/", + "timeout": 20000 + } + }, + { + "description": "Settings opened on the Environment Variables view rather than another tab, which is what the docs promise for a bare /settings link, and the documented Environment Variables name is present as some element's own text", + "runBrowserScript": { + "script": "const own = (el) => Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent).join(' ').replace(/\\s+/g, ' ').trim(); const named = Array.from(document.querySelectorAll('body *')).some((el) => own(el) === 'Environment Variables'); return ['path=' + window.location.pathname, 'envVarsTabName=' + (named ? 'present' : 'missing')].join(' ');", + "output": "path=/settings/env-vars envVarsTabName=present", + "timeout": 15000 + } + }, + { + "description": "The variables table has exactly the four columns the figure's alt text documents, in the documented order. Falls back to ARIA column headers so a non- rewrite is still checked instead of being reported as a missing table.", + "runBrowserScript": { + "script": "const cells = document.querySelectorAll('thead th').length ? document.querySelectorAll('thead th') : document.querySelectorAll('[role=columnheader]'); if (!cells.length) return 'headers=none-found'; const headers = Array.from(cells).map((c) => (c.textContent || '').replace(/\\s+/g, ' ').trim()).filter(Boolean); return 'headers=' + headers.join('|');", + "output": "headers=Key|Value|Created|Actions", + "timeout": 15000 + } + }, + { + "description": "Every row of the variables table carries both an Update and a Delete control, and neither Save nor Cancel is on the page before any Update is clicked. The row count is reported for debugging but is not part of the asserted substring; rowActions reads none on an empty table, which fails the assertion on purpose -- see the DATA PREREQUISITE note in the spec description.", + "runBrowserScript": { + "script": "const table = document.querySelector('table'); if (!table) return 'table=missing'; const label = (el) => (el.textContent || '').replace(/\\s+/g, ' ').trim().toLowerCase(); const controls = (root) => Array.from(root.querySelectorAll('button, a, [role=button]')); const rows = Array.from(table.querySelectorAll('tbody tr')); const has = (row, name) => controls(row).some((el) => label(el) === name); const complete = rows.length > 0 && rows.every((row) => has(row, 'update') && has(row, 'delete')); const anyIn = (name) => controls(table).some((el) => label(el) === name); return ['rows=' + rows.length, 'rowActions=' + (rows.length === 0 ? 'none' : complete ? 'complete' : 'incomplete'), 'saveOnLoad=' + (anyIn('save') ? 'present' : 'absent'), 'cancelOnLoad=' + (anyIn('cancel') ? 'present' : 'absent')].join(' ');", + "output": "rowActions=complete saveOnLoad=absent cancelOnLoad=absent", + "timeout": 15000 + } + } + ] + }, + { + "testId": "env-vars-add-variable-form-secret-toggle", + "description": "Documented steps 2 and 3 (\"Enter a key and value\", \"Mark sensitive values as secret\"): the Add Variable form sits inline below the variables table with no disclosure to open, labels its two inputs Key Name and Value as the docs call them, and turning on Secret masks the Value field as you type. The step-4 submit control is asserted to exist by its Add Variable label but is never clicked, so the run creates no variable.", + "before": "login.spec.json", + "steps": [ + { + "description": "Open the Environment Variables settings page", + "goTo": "https://app.gopromptless.ai/settings" + }, + { + "description": "Wait for the Add Variable form's Key Name field to render. Waiting on the placeholder rather than the label keeps this step off the name the next probe asserts, so a rename fails with a readable diff there instead of timing out here.", + "find": { + "selector": "input[placeholder='e.g. TEST_ACCOUNT_USERNAME']", + "timeout": 20000 + } + }, + { + "description": "The Add Variable form is already present with nothing to expand: both documented inputs exist under their user-visible placeholders, the Value field starts unmasked, the Secret control is a checkbox inside the label the UI shows, and the submit control reads Add Variable once its CSS uppercasing is normalized away. The Key Name and Value label names are looked for only inside the form -- the smallest subtree containing both inputs -- so the table's own Key and Value column headers cannot satisfy them.", + "runBrowserScript": { + "script": "const key = document.querySelector('input[placeholder=\"e.g. TEST_ACCOUNT_USERNAME\"]'); const value = document.querySelector('input[placeholder=\"Variable value\"]'); const own = (el) => Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent).join(' ').replace(/\\s+/g, ' ').trim(); let form = null; if (key && value) { const ancestors = new Set(); for (let el = key; el; el = el.parentElement) ancestors.add(el); for (let el = value; el; el = el.parentElement) { if (ancestors.has(el)) { form = el; break; } } } const labelled = (name) => form ? (Array.from(form.querySelectorAll('*')).some((el) => own(el) === name) ? 'present' : 'missing') : 'no-form'; const secret = Array.from(document.querySelectorAll('label')).find((l) => (l.textContent || '').replace(/\\s+/g, ' ').trim() === 'Secret (value will be hidden after saving)'); const submit = Array.from(document.querySelectorAll('button, input[type=submit]')).find((el) => (el.textContent || el.value || '').replace(/\\s+/g, ' ').trim().toUpperCase() === 'ADD VARIABLE'); return ['keyInput=' + (key ? 'present' : 'missing'), 'valueInput=' + (value ? 'present' : 'missing'), 'keyLabel=' + labelled('Key Name'), 'valueLabel=' + labelled('Value'), 'valueType=' + (value ? value.type : 'n-a'), 'secretLabel=' + (secret ? 'present' : 'missing'), 'secretToggle=' + (secret && secret.querySelector('input[type=checkbox], [role=switch], [role=checkbox]') ? 'present' : 'missing'), 'submit=' + (submit ? 'present' : 'missing')].join(' ');", + "output": "keyInput=present valueInput=present keyLabel=present valueLabel=present valueType=text secretLabel=present secretToggle=present submit=present", + "timeout": 15000 + } + }, + { + "description": "Focus the Key Name input. Clicking scrolls it into view, so no separate scroll step is needed.", + "find": { + "selector": "input[placeholder='e.g. TEST_ACCOUNT_USERNAME']", + "click": true, + "timeout": 15000 + } + }, + { + "description": "Type an inert sentinel key into the focused input. It is never submitted; it exists so that an unexpected save would be unmistakable in the dashboard.", + "type": "DOC_DETECTIVE_ENV_VARS_SPEC_NOT_SAVED" + }, + { + "description": "Turn on the Secret toggle by clicking its label, which is how the switch is reachable in the UI", + "find": { + "elementText": "/^Secret \\(value will be hidden after saving\\)$/", + "click": true, + "timeout": 15000 + } + }, + { + "description": "Secret is on and the Value field became a masked input, which is the masking the docs promise. The toggle's on-state is read from a checkbox's `checked` or from aria-checked, whichever the control uses, because the docs promise the masking rather than a particular markup for the switch.", + "runBrowserScript": { + "script": "const value = document.querySelector('input[placeholder=\"Variable value\"]'); const secret = Array.from(document.querySelectorAll('label')).find((l) => (l.textContent || '').replace(/\\s+/g, ' ').trim() === 'Secret (value will be hidden after saving)'); const box = secret ? secret.querySelector('input[type=checkbox]') : null; const aria = secret ? secret.querySelector('[role=switch], [role=checkbox]') : null; const on = box ? String(box.checked) : aria ? String(aria.getAttribute('aria-checked') === 'true') : 'missing'; return ['secretChecked=' + on, 'valueType=' + (value ? value.type : 'missing')].join(' ');", + "output": "secretChecked=true valueType=password", + "timeout": 15000 + } + }, + { + "description": "Focus the now-masked Value field", + "find": { + "selector": "input[placeholder='Variable value']", + "click": true, + "timeout": 15000 + } + }, + { + "description": "Type an inert value into the focused field. Nothing here submits the form: no click on Add Variable and no $ENTER$.", + "type": "doc-detective-not-saved" + }, + { + "description": "The value stayed masked while being typed, and the sentinel key never reached the variables table -- so this run created no environment variable", + "runBrowserScript": { + "script": "const value = document.querySelector('input[placeholder=\"Variable value\"]'); const table = document.querySelector('table'); const cells = table ? Array.from(table.querySelectorAll('tbody td')) : []; const leaked = cells.some((td) => (td.textContent || '').includes('DOC_DETECTIVE_ENV_VARS_SPEC_NOT_SAVED')); return ['valueStillMasked=' + (value ? (value.type === 'password' ? 'yes' : 'no') : 'missing'), 'sentinelInTable=' + (leaked ? 'yes' : 'no')].join(' ');", + "output": "valueStillMasked=yes sentinelInTable=no", + "timeout": 15000 + } + } + ] + } + ] +} diff --git a/src/content/docs/docs/scale/environment-variables.mdx b/src/content/docs/docs/scale/environment-variables.mdx index 80940fca1..74bb358fb 100644 --- a/src/content/docs/docs/scale/environment-variables.mdx +++ b/src/content/docs/docs/scale/environment-variables.mdx @@ -6,7 +6,7 @@ type: reference tags: - scale - reference -timestamp: "2026-07-10T17:59:48Z" +timestamp: "2026-07-27T00:00:00Z" sidebar: hidden: false order: 34 @@ -20,19 +20,17 @@ Environment variables store credentials, API keys, and configuration values that ## Add environment variables - 1. **Open Settings.** Navigate to the [Settings page](https://app.gopromptless.ai/settings) in your Promptless dashboard. + 1. **Open Settings.** Navigate to the [Settings page](https://app.gopromptless.ai/settings) in your Promptless dashboard. Settings opens on **Environment Variables**, which also appears in the tab row. - 2. **Find Environment Variables.** Select the **Environment Variables** section to view and manage your variables. + 2. **Enter a key and value.** The **Add Variable** form sits on the page below your existing variables. Type a **Key Name** and a **Value**. Use clear, descriptive names that indicate the variable's purpose. - 3. **Add a Variable.** Click the **Add Variable** button. Enter a name and value for your variable. Use clear, descriptive names that indicate the variable's purpose. + 3. **Mark sensitive values as secret.** For passwords, API keys, and other sensitive values, turn on the **Secret** toggle, which masks the **Value** field as you type. - 4. **Mark Sensitive Values as Secret.** For passwords, API keys, and other sensitive values, check the **Secret** option. Secret values are encrypted and hidden in the dashboard after saving. - - 5. **Save Changes.** Click **Save** to store your environment variables. + 4. **Submit the variable.** Click **Add Variable**. The new variable appears in the variables table above the form.
- Environment Variables settings page showing existing variables and the Add Variable form + Promptless Settings page with Environment Variables selected, showing the variables table with Key, Value, Created, and Actions columns, Update and Delete buttons on each row, and an empty Add Variable form below the table
Environment Variables settings page
@@ -48,7 +46,7 @@ Environment variables store credentials, API keys, and configuration values that
- Add Variable form with Secret checkbox enabled + Add Variable form, outlined in red, with the Key Name field set to MY_API_TOKEN, a masked value, and the Secret toggle turned on
Adding a secret environment variable
@@ -71,8 +69,8 @@ For [Promptless Capture](/docs/get-the-most-out/screenshots), add these variable ## Update and delete variables -To update an existing variable, locate it in the Environment Variables section and edit its value. For secret variables, you'll need to re-enter the entire value since the original is not displayed. +To update an existing variable, locate it in the variables table, click the **Update** button in the **Actions** column, edit the value, and click **Save**. For secret variables, re-enter the entire value — Promptless doesn't display the original. -To delete a variable, click the delete icon next to the variable. Deleted variables are immediately removed and will no longer be available to the Promptless agent. +To delete a variable, click the **Delete** button in its row of the **Actions** column. Deleted variables are immediately removed and are no longer available to the Promptless agent. Need help? Contact us at [help@gopromptless.ai](mailto\:help@gopromptless.ai). From d890eee35b7ea9fcb4b50dedeec3d4a50d1f5d08 Mon Sep 17 00:00:00 2001 From: "promptless[bot]" Date: Tue, 28 Jul 2026 17:38:05 +0000 Subject: [PATCH 2/3] docs: reframe the env-vars Doc Detective spec around the user journey it protects The spec's description narrated how the test was written -- authoring decisions, CI-history commit hashes, framework semantics, and a justification of each matcher choice. An engineer could not tell from it which product behavior was under test or what breaks when it fails. It now leads with the journey it protects (cuj-screenshots, step "Authenticate to the app being captured"), what breaks for users if the test fails, and what is and is not covered. The operational facts an engineer needs to run it -- auth, data prerequisite, invocation, CI status -- are kept as subordinate notes. The same episodic voice is removed from the per-test and step descriptions. Two assertions are corrected while here, because they described a DOM that does not exist. The Secret control is a CSS-styled toggle: its label contains no checkbox and no switch role, and aria-checked is null. The spec asserted secretToggle=present and secretChecked=true, so two steps failed against a healthy dashboard, and the claim contradicted the page shipping beside it, whose prose correctly calls Secret a toggle. Both now assert the masking the docs actually promise -- the Value input flipping to password -- which fails if the toggle stops working. The toggle's on-state has no machine-readable signal beyond computed CSS, which the docs never promise, so it is not asserted. The spec stays non-destructive: nothing was added that submits the form or deletes a variable. All six probes now pass against the live dashboard, up from four. --- .../environment-variables-settings.spec.json | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.doc-detective/tests/environment-variables-settings.spec.json b/.doc-detective/tests/environment-variables-settings.spec.json index 20f2b5b84..d22ead85e 100644 --- a/.doc-detective/tests/environment-variables-settings.spec.json +++ b/.doc-detective/tests/environment-variables-settings.spec.json @@ -1,10 +1,10 @@ { "specId": "environment-variables-settings", - "description": "Environment Variables settings page in the Promptless dashboard (https://app.gopromptless.ai/settings), covering the control names that src/content/docs/docs/scale/environment-variables.mdx documents in its `## Add environment variables` steps and its `## Update and delete variables` section. This page's screenshots and control names silently drifted out of date once already, so this spec pins the exact names the prose tells readers to look for -- Key Name, Value, Secret, Add Variable, Update, Delete -- and fails when one is renamed instead of letting the docs quietly go stale.\n\nNON-DESTRUCTIVE BY CONSTRUCTION. Nothing here creates, changes, or deletes a real environment variable in the test account. The Add Variable form is filled but never submitted: no step clicks Add Variable, no step clicks a row's Update, Save, or Delete, and no step types $ENTER$ or $RETURN$ into a form field. The only writes are keystrokes into two unsaved inputs, using the inert sentinel DOC_DETECTIVE_ENV_VARS_SPEC_NOT_SAVED as the key so that an unexpected save would be obvious in the dashboard. The last step asserts that sentinel never reached the variables table. Every runBrowserScript below only reads the DOM -- none clicks, submits, assigns to a value, dispatches an event, or issues a request.\n\nAuthentication reuses the existing Clerk sign-in flow rather than a new one: each test sets `before` to login.spec.json, whose steps are prepended into this test's own browser context. That spec reads $PROMPTLESS_TEST_EMAIL and $PROMPTLESS_TEST_PASSWORD from .doc-detective/.env via the config's `loadVariables`, so no credential and no resolved internal host appears here. `before` resolves relative to this spec file because `relativePathBase` defaults to `file`; under `relativePathBase: cwd` Doc Detective skips this spec rather than failing it. login.spec.json opens with a `record` step that is unsupported headless and that skips when its fixed output path already exists, so that step often reports SKIPPED here -- a skipped step does not fail its context, since only an all-skipped context is itself skipped.\n\nDATA PREREQUISITE for env-vars-settings-page-and-variables-table: the test account must already hold at least one saved environment variable, which is the state the page's own figure shows. With an empty table the Update/Delete assertion has nothing to check, so the probe reports rowActions=none and the step fails rather than passing vacuously. Fix that by adding a variable to the test account, not by loosening the assertion.\n\nRunning it: neither config lists .doc-detective/tests/ in `input` (.doc-detective.json scans src/content/docs for inline tests), so pass this file explicitly -- `npx doc-detective -c .doc-detective.json -i .doc-detective/tests/environment-variables-settings.spec.json`. This spec is deliberately NOT added to .doc-detective.ci.json: Clerk-dependent flows were dropped from CI in fb89a47 and intentionally left out again in 0e72027 for being unstable, and reversing that is a maintainer call rather than this spec's to make.\n\nURLs are absolute because the config's `origin` points at the docs site (https://promptless.ai), not the dashboard.\n\nAssertion style follows homepage-product-switcher.spec.json: `runBrowserScript` probes report several related facts in one string, so a renamed control shows up as a readable diff instead of an opaque element-not-found. The division of labour between step types is deliberate. `find` steps are waits and interactions, and they match text by regex so that incidental whitespace or a nested wrapper does not fail a step whose job is only to reach the control. The exact documented names are asserted inside the probes instead, compared against each element's own direct text with whitespace collapsed, so a container's aggregated text cannot satisfy an assertion by accident. `output` is a substring match on the serialized return value, which is why the probes also report unasserted context such as row counts. Comparisons upper-case before matching wherever the UI applies a CSS text-transform: the submit control's DOM text is `Add Variable`, rendered uppercase.", + "description": "Journey: cuj-screenshots, \"Set up automated screenshot capture and updates\" (docs/content_strategy/journeys/cuj-screenshots.md), step \"Authenticate to the app being captured\": this page stores the test-account credentials Promptless Capture logs in with. Page: src/content/docs/docs/scale/environment-variables.mdx (/docs/scale/environment-variables). Scenario: a reader stores a credential and marks it Secret. Success: they reach the view, recognize every documented control name (Key Name, Value, Secret, Add Variable, Update, Delete), and see Value masked once Secret is on.\n\nA renamed or moved control stalls them mid-`## Add environment variables`, blocking credential setup for Promptless Capture, the CUJ's most-requested capability. These names and screenshots went stale once before.\n\nCovered: reaching the view; the four columns; per-row Update and Delete; no Save or Cancel on load; the Add Variable form's two labeled inputs; the Secret label; Value going masked. Not covered: the submit and delete paths -- Add Variable, Update, and Delete are checked for presence and labeling, never effects. Nothing is submitted: no Add Variable, Update, Save, or Delete click, no $ENTER$, only keystrokes into two unsaved inputs bearing inert sentinel DOC_DETECTIVE_ENV_VARS_SPEC_NOT_SAVED, whose absence from the table the last step asserts.\n\n- Auth: each `before` is login.spec.json, reading credentials from .doc-detective/.env via `loadVariables`; none appear here.\n- Data prerequisite: the account needs one saved variable, else the row-actions assertion has nothing to check -- add one; do not loosen the assertion.\n- Running it: no config lists .doc-detective/tests/ in `input`, so pass it explicitly: `npx doc-detective -c .doc-detective.json -i .doc-detective/tests/environment-variables-settings.spec.json`\n- CI: not in .doc-detective.ci.json (homepage spec only); adding it is a maintainer call.\n- URLs: absolute, because `origin` is the docs site, not the dashboard.", "tests": [ { "testId": "env-vars-settings-page-and-variables-table", - "description": "Documented step 1 (\"Open Settings\"): the /settings link the page hands readers lands on the Environment Variables view, that view's tab row carries the documented Environment Variables name, and the page shows the variables table described by the figure and by `## Update and delete variables` -- Key, Value, Created, and Actions columns, with an Update and a Delete control in every row. Also pins that Save and Cancel are absent until a row's Update is clicked, which is what makes the documented Update-then-Save order correct.", + "description": "A bare /settings link lands on the Environment Variables view, which shows the variables table with Key, Value, Created, and Actions columns and both an Update and a Delete control on every row. Save and Cancel stay hidden on load (`## Update and delete variables`).", "before": "login.spec.json", "steps": [ { @@ -19,7 +19,7 @@ } }, { - "description": "Settings opened on the Environment Variables view rather than another tab, which is what the docs promise for a bare /settings link, and the documented Environment Variables name is present as some element's own text", + "description": "Settings opens on the Environment Variables view and the documented name is present", "runBrowserScript": { "script": "const own = (el) => Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent).join(' ').replace(/\\s+/g, ' ').trim(); const named = Array.from(document.querySelectorAll('body *')).some((el) => own(el) === 'Environment Variables'); return ['path=' + window.location.pathname, 'envVarsTabName=' + (named ? 'present' : 'missing')].join(' ');", "output": "path=/settings/env-vars envVarsTabName=present", @@ -27,7 +27,7 @@ } }, { - "description": "The variables table has exactly the four columns the figure's alt text documents, in the documented order. Falls back to ARIA column headers so a non-
rewrite is still checked instead of being reported as a missing table.", + "description": "The variables table has the four documented columns in the documented order", "runBrowserScript": { "script": "const cells = document.querySelectorAll('thead th').length ? document.querySelectorAll('thead th') : document.querySelectorAll('[role=columnheader]'); if (!cells.length) return 'headers=none-found'; const headers = Array.from(cells).map((c) => (c.textContent || '').replace(/\\s+/g, ' ').trim()).filter(Boolean); return 'headers=' + headers.join('|');", "output": "headers=Key|Value|Created|Actions", @@ -35,7 +35,7 @@ } }, { - "description": "Every row of the variables table carries both an Update and a Delete control, and neither Save nor Cancel is on the page before any Update is clicked. The row count is reported for debugging but is not part of the asserted substring; rowActions reads none on an empty table, which fails the assertion on purpose -- see the DATA PREREQUISITE note in the spec description.", + "description": "Every row carries an Update and a Delete control, and neither Save nor Cancel is present before an Update is clicked", "runBrowserScript": { "script": "const table = document.querySelector('table'); if (!table) return 'table=missing'; const label = (el) => (el.textContent || '').replace(/\\s+/g, ' ').trim().toLowerCase(); const controls = (root) => Array.from(root.querySelectorAll('button, a, [role=button]')); const rows = Array.from(table.querySelectorAll('tbody tr')); const has = (row, name) => controls(row).some((el) => label(el) === name); const complete = rows.length > 0 && rows.every((row) => has(row, 'update') && has(row, 'delete')); const anyIn = (name) => controls(table).some((el) => label(el) === name); return ['rows=' + rows.length, 'rowActions=' + (rows.length === 0 ? 'none' : complete ? 'complete' : 'incomplete'), 'saveOnLoad=' + (anyIn('save') ? 'present' : 'absent'), 'cancelOnLoad=' + (anyIn('cancel') ? 'present' : 'absent')].join(' ');", "output": "rowActions=complete saveOnLoad=absent cancelOnLoad=absent", @@ -46,7 +46,7 @@ }, { "testId": "env-vars-add-variable-form-secret-toggle", - "description": "Documented steps 2 and 3 (\"Enter a key and value\", \"Mark sensitive values as secret\"): the Add Variable form sits inline below the variables table with no disclosure to open, labels its two inputs Key Name and Value as the docs call them, and turning on Secret masks the Value field as you type. The step-4 submit control is asserted to exist by its Add Variable label but is never clicked, so the run creates no variable.", + "description": "The Add Variable form sits inline below the variables table with nothing to expand, labels its two inputs Key Name and Value as the docs call them, and masks the Value field as you type once the Secret toggle is on. The submit control is checked for existence by its Add Variable label and never clicked, so the run creates no variable.", "before": "login.spec.json", "steps": [ { @@ -54,22 +54,22 @@ "goTo": "https://app.gopromptless.ai/settings" }, { - "description": "Wait for the Add Variable form's Key Name field to render. Waiting on the placeholder rather than the label keeps this step off the name the next probe asserts, so a rename fails with a readable diff there instead of timing out here.", + "description": "Wait for the Add Variable form's Key Name input to render", "find": { "selector": "input[placeholder='e.g. TEST_ACCOUNT_USERNAME']", "timeout": 20000 } }, { - "description": "The Add Variable form is already present with nothing to expand: both documented inputs exist under their user-visible placeholders, the Value field starts unmasked, the Secret control is a checkbox inside the label the UI shows, and the submit control reads Add Variable once its CSS uppercasing is normalized away. The Key Name and Value label names are looked for only inside the form -- the smallest subtree containing both inputs -- so the table's own Key and Value column headers cannot satisfy them.", + "description": "The Add Variable form is already present with nothing to expand: both documented inputs exist, the Value field starts unmasked, the Secret label matches its documented wording, and the submit control reads Add Variable", "runBrowserScript": { - "script": "const key = document.querySelector('input[placeholder=\"e.g. TEST_ACCOUNT_USERNAME\"]'); const value = document.querySelector('input[placeholder=\"Variable value\"]'); const own = (el) => Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent).join(' ').replace(/\\s+/g, ' ').trim(); let form = null; if (key && value) { const ancestors = new Set(); for (let el = key; el; el = el.parentElement) ancestors.add(el); for (let el = value; el; el = el.parentElement) { if (ancestors.has(el)) { form = el; break; } } } const labelled = (name) => form ? (Array.from(form.querySelectorAll('*')).some((el) => own(el) === name) ? 'present' : 'missing') : 'no-form'; const secret = Array.from(document.querySelectorAll('label')).find((l) => (l.textContent || '').replace(/\\s+/g, ' ').trim() === 'Secret (value will be hidden after saving)'); const submit = Array.from(document.querySelectorAll('button, input[type=submit]')).find((el) => (el.textContent || el.value || '').replace(/\\s+/g, ' ').trim().toUpperCase() === 'ADD VARIABLE'); return ['keyInput=' + (key ? 'present' : 'missing'), 'valueInput=' + (value ? 'present' : 'missing'), 'keyLabel=' + labelled('Key Name'), 'valueLabel=' + labelled('Value'), 'valueType=' + (value ? value.type : 'n-a'), 'secretLabel=' + (secret ? 'present' : 'missing'), 'secretToggle=' + (secret && secret.querySelector('input[type=checkbox], [role=switch], [role=checkbox]') ? 'present' : 'missing'), 'submit=' + (submit ? 'present' : 'missing')].join(' ');", - "output": "keyInput=present valueInput=present keyLabel=present valueLabel=present valueType=text secretLabel=present secretToggle=present submit=present", + "script": "const key = document.querySelector('input[placeholder=\"e.g. TEST_ACCOUNT_USERNAME\"]'); const value = document.querySelector('input[placeholder=\"Variable value\"]'); const own = (el) => Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent).join(' ').replace(/\\s+/g, ' ').trim(); let form = null; if (key && value) { const ancestors = new Set(); for (let el = key; el; el = el.parentElement) ancestors.add(el); for (let el = value; el; el = el.parentElement) { if (ancestors.has(el)) { form = el; break; } } } const labelled = (name) => form ? (Array.from(form.querySelectorAll('*')).some((el) => own(el) === name) ? 'present' : 'missing') : 'no-form'; const secret = Array.from(document.querySelectorAll('label')).find((l) => (l.textContent || '').replace(/\\s+/g, ' ').trim() === 'Secret (value will be hidden after saving)'); const submit = Array.from(document.querySelectorAll('button, input[type=submit]')).find((el) => (el.textContent || el.value || '').replace(/\\s+/g, ' ').trim().toUpperCase() === 'ADD VARIABLE'); return ['keyInput=' + (key ? 'present' : 'missing'), 'valueInput=' + (value ? 'present' : 'missing'), 'keyLabel=' + labelled('Key Name'), 'valueLabel=' + labelled('Value'), 'valueType=' + (value ? value.type : 'n-a'), 'secretLabel=' + (secret ? 'present' : 'missing'), 'submit=' + (submit ? 'present' : 'missing')].join(' ');", + "output": "keyInput=present valueInput=present keyLabel=present valueLabel=present valueType=text secretLabel=present submit=present", "timeout": 15000 } }, { - "description": "Focus the Key Name input. Clicking scrolls it into view, so no separate scroll step is needed.", + "description": "Focus the Key Name input", "find": { "selector": "input[placeholder='e.g. TEST_ACCOUNT_USERNAME']", "click": true, @@ -77,11 +77,11 @@ } }, { - "description": "Type an inert sentinel key into the focused input. It is never submitted; it exists so that an unexpected save would be unmistakable in the dashboard.", + "description": "Type an inert sentinel key that is never submitted", "type": "DOC_DETECTIVE_ENV_VARS_SPEC_NOT_SAVED" }, { - "description": "Turn on the Secret toggle by clicking its label, which is how the switch is reachable in the UI", + "description": "Turn on the Secret toggle by clicking its label", "find": { "elementText": "/^Secret \\(value will be hidden after saving\\)$/", "click": true, @@ -89,10 +89,10 @@ } }, { - "description": "Secret is on and the Value field became a masked input, which is the masking the docs promise. The toggle's on-state is read from a checkbox's `checked` or from aria-checked, whichever the control uses, because the docs promise the masking rather than a particular markup for the switch.", + "description": "The Value field became a masked input, which matches what the docs promise", "runBrowserScript": { - "script": "const value = document.querySelector('input[placeholder=\"Variable value\"]'); const secret = Array.from(document.querySelectorAll('label')).find((l) => (l.textContent || '').replace(/\\s+/g, ' ').trim() === 'Secret (value will be hidden after saving)'); const box = secret ? secret.querySelector('input[type=checkbox]') : null; const aria = secret ? secret.querySelector('[role=switch], [role=checkbox]') : null; const on = box ? String(box.checked) : aria ? String(aria.getAttribute('aria-checked') === 'true') : 'missing'; return ['secretChecked=' + on, 'valueType=' + (value ? value.type : 'missing')].join(' ');", - "output": "secretChecked=true valueType=password", + "script": "const value = document.querySelector('input[placeholder=\"Variable value\"]'); return 'valueType=' + (value ? value.type : 'missing');", + "output": "valueType=password", "timeout": 15000 } }, @@ -105,7 +105,7 @@ } }, { - "description": "Type an inert value into the focused field. Nothing here submits the form: no click on Add Variable and no $ENTER$.", + "description": "Type an inert value; nothing here submits the form", "type": "doc-detective-not-saved" }, { From 3e1b8e044518a6685406a8cb9a2f056bae3321a5 Mon Sep 17 00:00:00 2001 From: "promptless[bot]" Date: Wed, 5 Aug 2026 18:03:33 +0000 Subject: [PATCH 3/3] docs: shorten the env-vars Doc Detective spec description to intent only The spec-level description narrated a covered/not-covered inventory and operational notes (auth, data prerequisite, run command, CI, URLs) that belong in config, not spec prose. Rewrite it to state what the spec guards and why, per doc_workflow/doc_detective_test_descriptions.md. Test steps and assertions are unchanged. --- .doc-detective/tests/environment-variables-settings.spec.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.doc-detective/tests/environment-variables-settings.spec.json b/.doc-detective/tests/environment-variables-settings.spec.json index d22ead85e..d39b2171c 100644 --- a/.doc-detective/tests/environment-variables-settings.spec.json +++ b/.doc-detective/tests/environment-variables-settings.spec.json @@ -1,6 +1,6 @@ { "specId": "environment-variables-settings", - "description": "Journey: cuj-screenshots, \"Set up automated screenshot capture and updates\" (docs/content_strategy/journeys/cuj-screenshots.md), step \"Authenticate to the app being captured\": this page stores the test-account credentials Promptless Capture logs in with. Page: src/content/docs/docs/scale/environment-variables.mdx (/docs/scale/environment-variables). Scenario: a reader stores a credential and marks it Secret. Success: they reach the view, recognize every documented control name (Key Name, Value, Secret, Add Variable, Update, Delete), and see Value masked once Secret is on.\n\nA renamed or moved control stalls them mid-`## Add environment variables`, blocking credential setup for Promptless Capture, the CUJ's most-requested capability. These names and screenshots went stale once before.\n\nCovered: reaching the view; the four columns; per-row Update and Delete; no Save or Cancel on load; the Add Variable form's two labeled inputs; the Secret label; Value going masked. Not covered: the submit and delete paths -- Add Variable, Update, and Delete are checked for presence and labeling, never effects. Nothing is submitted: no Add Variable, Update, Save, or Delete click, no $ENTER$, only keystrokes into two unsaved inputs bearing inert sentinel DOC_DETECTIVE_ENV_VARS_SPEC_NOT_SAVED, whose absence from the table the last step asserts.\n\n- Auth: each `before` is login.spec.json, reading credentials from .doc-detective/.env via `loadVariables`; none appear here.\n- Data prerequisite: the account needs one saved variable, else the row-actions assertion has nothing to check -- add one; do not loosen the assertion.\n- Running it: no config lists .doc-detective/tests/ in `input`, so pass it explicitly: `npx doc-detective -c .doc-detective.json -i .doc-detective/tests/environment-variables-settings.spec.json`\n- CI: not in .doc-detective.ci.json (homepage spec only); adding it is a maintainer call.\n- URLs: absolute, because `origin` is the docs site, not the dashboard.", + "description": "Guards the Environment Variables settings view against UI drift. The `## Add environment variables` and `## Update and delete variables` walkthroughs in src/content/docs/docs/scale/environment-variables.mdx name each control by its visible label -- Key Name, Value, Secret, Add Variable, Update, Delete -- and promise the Value field masks once Secret is on; these labels and the page's screenshots went stale once before, so this spec asserts them. Non-destructive: it checks presence, labels, and masking and never submits the form or deletes a variable.", "tests": [ { "testId": "env-vars-settings-page-and-variables-table",