When authoring gets stuck
What happens when a step cannot be authored, and drafting a spec from a requirement document (author-from-doc) or from the app's code (author-from-code).
Choose the intended authoring backend
In auto mode, plain freeform UI text (for example, Smash the shiny button) is model intent. The model receives the live scene and must ground
its answer to one of the listed target tokens; it cannot invent a selector.
An explicit rules: step instead succeeds or fails against the grammar on
this page and names the accepted forms for that app. Use --author rules
or --author llm when the entire recording should force one backend.
When auto mode has no configured model (flowproof config ai, or
FLOWPROOF_AI_PROVIDER / FLOWPROOF_AI_API_KEY), the CLI emits a visible
warning before trying the deterministic grammar. This fallback is identified as
its own fallback route in
the per-step human and structured diagnostics, so ordinary prose is never
silently mistaken for deliberate rule syntax.
Resolve an ambiguous step
When a step is too ambiguous to author at all ("make required field
changes", which fields?, or Enter it with several remembered values),
recording fails with a structured clarification payload: the stuck step
plus the relevant live-scene fields or remembered-value candidates. It is
available via record --json, the MCP record tool, or Python's
ClarificationNeeded. The driving agent rewrites the step more precisely
and re-records; see The outside-in loop for the loop.
Whichever route authors a step, recording persists grounded selectors and
actions in the trace. flowproof run executes those deterministic artifacts
directly and makes zero authoring-model calls. See
getting-started.
Drafting a spec from a requirement document (author-from-doc)
QA teams already write test cases in a test-management tool and export them
as a document (today: an HP ALM/Quality Center PDF, with Step Name /
Description / Expected / Actual fields per step). flowproof author-from-doc reads that PDF and drafts a .flow.yaml spec from it,
instead of hand-translating the same steps twice:
flowproof author-from-doc uat-export.pdf --app sap --name "Manage purchasing info records" --out draft.flow.yaml--app: the target app id the draft'sapp:field is written with (sap,web, …).--name: the flow name written into the draft'sname:field.--out: where the draft.flow.yamlis written.
A draft is one test case. An ALM test-set report holds a run of each test
case in the set, so name the one to draft with --test-case: its number
as --check lists them, its name, or its ID (the part of the name before
-). A test case run more than once is read from its latest run.
flowproof author-from-doc test-set.pdf --check
flowproof author-from-doc test-set.pdf --test-case M-040-090-020-1 --app sap --name "TECO production order" --out teco.flow.yamlTo draft several test cases, repeat --test-case, or give --test-case all.
--out is then a folder, and each test case is drafted into its own flow
there. The flow is named after its test case without the ID, unless two
names would then be the same. Its file is named after the test case too, e.g.
m-040-090-020-1-teco-production-order.flow.yaml. Nothing is drafted if
any of those files already exists. A test case the model fails on is
reported and the others are still drafted. The exit code is 1 when some
failed and 2 when none were drafted. With --json, the result is
{"drafts": [...], "failed": [{"test_case", "error"}]}, where each draft
has the same fields as a single draft's JSON.
flowproof author-from-doc test-set.pdf --test-case all --app sap --out drafts/When the document contains concrete non-secret business data, such as a
material, supplier, plant, customer, or order id, the draft can use
${NAME} placeholders and author-from-doc writes a sibling values file
next to the flow:
draft.flow.yaml
draft.values.yamlflowproof record draft.flow.yaml and flowproof run draft.flow.yaml load
that values file automatically. Keep passwords, tokens, and login
credentials in flowproof config or the caller's secret environment, not in
the generated values file.
The PDF's text is extracted and segmented into per-step records, then each
Description/Expected pair is translated into this page's grammar by a
model call, the same "flag, don't guess" discipline as live authoring
above, extended to three distinct outcomes instead of a binary
action-or-not:
- A
Descriptionthat maps cleanly becomes a real step in the grammar above, and itsExpectedbecomes a realassert:, something a video recording never has, since a document already states a belief about correctness. - A
Descriptionthat implies an action within the app under test, but too ambiguously to map with confidence, is flagged as a# TODOplus a freeform step for the next liveflowproof recordpass to resolve against the real screen, never a forced guess. - A
Descriptionwhose action clearly targets a different app or tool entirely (e.g. reviewing a downloaded export in a spreadsheet program) is flagged as its own distinct# TODOrather than silently dropped or confused with in-app ambiguity: it isn't automatable here at all, and needs a human decision, not a screen to search.
The output is a draft: review every step, then run flowproof record
to resolve any flagged ones against the live app before trusting it.
For tools that call it (the desktop app does), two flags help:
--checkreads the PDF and prints how many steps it holds and how many have an expected result, and for a test-set report each test case with its step count. Nothing is sent to a model and nothing is written, so it is a fast way to confirm the export is readable. With--jsonit printsstepsandwith_expectedfor the whole document andtest_cases, each with itsname(ornull),run_id,stepsandwith_expected.--jsonprints one JSON object when the draft is written:flow,values(ornull),test_case(its name, ornull), andsteps, each with akind(action,assert,flagged,out_of_scope), the exacttextwritten to the file, and for the two flagged kinds theobserveddocument wording.
While drafting, drafting step N of M goes to stderr before each model
call. The model comes from flowproof config ai or the usual environment
variables.
Drafting a spec from an outcome and the app's code (author-from-code)
When nobody has written the steps yet, only the outcome a flow should prove,
flowproof author-from-code drafts them from the app's own source. The code
already says how the app is navigated: its routes, its link and field labels,
where a status is rendered. That beats exploring the live app by trial.
flowproof author-from-code ~/src/portal/apps/web \
--goal "the supplier list shows Acme Metals with status Active" \
--name "Find Acme Metals" --url https://portal.example.test --out draft.flow.yaml--goal: what the flow should prove, as the screen shows it when done.--url: the starting address, written into the draft'surl:field.--app: the target app id (defaultweb).--nameand--out: as forauthor-from-doc.
Two model calls: the first picks up to 12 files from the repository's list of
source files, the second drafts steps from those files with line numbers.
Every step ends up citing the path:line it rests on, so you check it against
the code rather than take it on trust. Dot-files (.env, .git), dependency
and build folders, and anything the root .gitignore names are never listed,
read or sent. Anything the code cannot settle, such as sign-in credentials
or a record that must already exist, is flagged as a # TODO, as above.
With --json, the report is flow and steps, each step with kind,
text, observed for a flagged one, and source (file, line) when the
model cited a file it was given. reading <path> for each picked file, then
drafting steps, go to stderr.