saffron-ai 0.8.5 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,201 @@ All notable changes to Saffron, in one place: the `saffron-ai` runner (npm),
4
4
  the VS Code extension (`ChathurangaJayasinghe.saffron-vscode`) and the
5
5
  JetBrains plugin (`ai.saffron.jetbrains`). Dates are publish dates.
6
6
 
7
+ ## 0.9.0
8
+
9
+ Released 2026-09-27.
10
+
11
+ Runner
12
+
13
+ - **Execution replay: `saffron trace`.** Not a video. With `"trace":
14
+ "retain-on-failure"` (or `"on"`) in the config, or `--trace <mode>` for
15
+ one run, a scenario keeps an execution trace, and `saffron trace` opens
16
+ it as a replay in your browser: your Given, When and Then on the left,
17
+ each step opening onto the actions Saffron replayed and the Playwright
18
+ calls behind them; the page at the selected action in the middle,
19
+ rendered as a live DOM you can inspect, before, during or after the
20
+ action, with a filmstrip of the whole run; on the right the failure, the
21
+ retries, the call log, the network during the selection, and what the
22
+ agent changed. The failed step is selected when you arrive. Traces live
23
+ next to the screenshots under `.saffron/artifacts/`, are kept for the
24
+ latest run and pruned with the rest. The report names the command for
25
+ every traced scenario, as `feature:scenario`. The page opens on `127.0.0.1` or `localhost`, a
26
+ forwarded port included, and keeps what it loaded when a later run
27
+ replaces the files. A tab left open from before `saffron trace` was
28
+ started again says to reload the page instead of loading the new run's
29
+ files. The trace is Playwright's own format, rendered by
30
+ the snapshot engine of the Playwright Saffron runs with; the replay
31
+ around it is Saffron's. The Saucedemo example config ships with
32
+ `retain-on-failure`.
33
+ - **Secrets in a trace.** `{env:VAR}` values, including one a data file
34
+ holds, are masked back to their tokens before the trace is kept: in the
35
+ event log, the DOM snapshots and stylesheets, and every request and
36
+ response body, URL, header and WebSocket message, in the spellings the
37
+ value takes there (JSON escapes, percent-encoding as browsers and
38
+ servers write it, HTML entities, CSS escapes). The unmasked trace never
39
+ reaches `.saffron/artifacts/`, even when a run is killed while saving
40
+ it. The scheme, host and port of a URL are kept, so the replay can
41
+ still load the page's stylesheets and images; a secret anywhere else in
42
+ it, credentials included, is written `%7Benv:NAME%7D`. A value
43
+ shorter than three characters, one inside base64 or another binary
44
+ encoding, a value in a URL's host, and the filmstrip's pixels are not
45
+ masked, so look through a trace before attaching it to a public issue.
46
+ - **Review a heal from the replay.** The selected step shows what the
47
+ proposal changes for it, the elements Playwright actually matched are
48
+ outlined in the page, and the Saffron panel has **Accept recording** and
49
+ **Reject proposal**. Both act only on the exact proposal associated with
50
+ this run: if it was replaced, accepted or rejected since, the replay
51
+ refuses and says why. One that went stale against the scenario or the
52
+ committed recording, or can no longer be read, cannot be accepted but
53
+ can still be rejected. An unverified recording, or one this run showed
54
+ failing, needs an explicit acknowledgement first. Suggested feature
55
+ rewrites are listed with the command that applies them (`saffron accept
56
+ --with-feature-edit`); they and propagation to other recordings stay
57
+ with `saffron accept`.
58
+ - **Accept or reject the proposal you listed.** `saffron status --json`
59
+ gives each pending proposal a `revision`, and `saffron accept
60
+ <file>#<revision>` or `saffron reject <file>#<revision>` decides that
61
+ proposal only if it is still the one listed. One a later run replaced
62
+ is refused ("changed since it was listed. Refresh and review it
63
+ again."), exit 1, and left in place. A decision and a run filing a
64
+ proposal never interleave, so what is checked is what is promoted, with
65
+ or without a revision. Accept and reject act only on files under
66
+ `.saffron/proposals`: `saffron reject features/login.saffron` used to
67
+ delete the feature file.
68
+ - **Proof replays and the agent in the trace.** A recorded or healed
69
+ scenario also keeps a trace of each proof replay (`proof-1.zip`,
70
+ `proof-2.zip`) with its own step outcomes, and **Trace** at the top of
71
+ the page switches between them and the run itself. The agent's own
72
+ browser calls are recorded too, under the step it was working on and in
73
+ words ("click Login button", "read the page"); what it looked at before
74
+ naming a step is filed under the step it named next. A healed step is
75
+ marked ✦, and a healed run opens on it.
76
+ If that cannot start with the installed Playwright, Saffron says why,
77
+ once, and the agent works through the standard browser connection
78
+ without being traced.
79
+ - **Masking fixes outside the trace.** A secret whose value appears in
80
+ another variable's name or in a `{data:...}` key no longer corrupts the
81
+ recording (`ROLE=ADMIN` beside `{env:ADMIN_PASSWORD}` used to be
82
+ recorded as `{env:{env:ROLE}_PASSWORD}`, which never replayed).
83
+ Three-character values (a CVV, a short PIN) are now masked in
84
+ recordings, reports and history too, as in the trace; in a recording a
85
+ value shorter than six characters is masked where it stands alone or
86
+ where the step joins it to other text (`"qa{env:ID}"`), so `ENV=dev`
87
+ never turns `/devices` into `/{env:ENV}ices`. Reports mask
88
+ a value's URL-encoded and HTML-escaped forms. A recording masks a
89
+ secret in every field replay reads (labels, placeholders, test ids,
90
+ fallback selectors, frame names, drag targets, URL patterns, the text
91
+ for a prompt dialog), not only typed values and URLs, and replay
92
+ resolves the token in each of them: a selector recorded as
93
+ `td.{env:NAME}` still finds its element. A
94
+ `{env:VAR}` written as a value in a data file is masked in recordings
95
+ and reports like one written in the step.
96
+ - **The HTML report embeds only the run's own screenshots.** A merged
97
+ report (`saffron report --merge`) could be made to embed any file the
98
+ merging machine could read, such as `.env`; it now embeds pictures from
99
+ `.saffron/artifacts/` only, and escapes the setup and teardown lines.
100
+ - **Evidence from an interrupted run is not mixed in.** Screenshots and
101
+ traces are kept at fixed paths that the next run replaces. A run
102
+ stopped before it wrote its report used to leave the previous report
103
+ pointing at the new files; the IDE panels now leave out such a
104
+ screenshot and `saffron trace` refuses such a trace.
105
+ - **Playwright 1.63 and trace format 9.** The runner now depends on
106
+ Playwright 1.63, and the replay reads both trace formats: 8 (up to
107
+ 1.62) and 9 (1.63 and later), which files snapshots under the call by
108
+ phase and screencast frames under their own folder. A project that
109
+ resolved 1.63 saw broken filmstrip tiles and "No snapshot for this
110
+ selection".
111
+ - **The first snapshot shown no longer arrives unstyled.** The snapshot
112
+ engine forgets a trace while its first frame is still loading, so that
113
+ frame's stylesheets and fonts came back missing and the page looked
114
+ bare until the next step was selected. The replay now warms the engine
115
+ with a hidden frame first.
116
+ - **A step with no snapshot of its own shows the last page there was.** A
117
+ step the agent only inspected (the accessibility tree, the title, the
118
+ console) leaves no DOM snapshot, since Playwright takes them around
119
+ interactions. Instead of an empty pane, the replay shows the page after
120
+ the last interaction before that step and says so above it. A step
121
+ that never ran, after a failure, shows no page: "This step was never
122
+ reached." An action
123
+ that took no snapshot (reading the title, say) shows the page as the
124
+ last snapshot before it left it, not as its step began. With several
125
+ tabs open it only borrows from the same tab; Playwright 1.63 does not
126
+ record which tab such an action ran in, so when that is unknown the
127
+ replay says so, and the filmstrip marks no frame, rather than show
128
+ another tab. A tab opened later in the run changes nothing before it, and one
129
+ closed changes nothing after.
130
+ - **Every traced scenario in one replay.** `saffron trace` with no name
131
+ (or a name several scenarios share) opens them all, with a Scenario
132
+ picker in the header, starting on a red one, else a yellow one. A run
133
+ with `"trace": "on"` where everything passed opens too. `--json` with no
134
+ name prints the scenario the replay opens on. Other scenarios' traces
135
+ are read when first picked and checked against their digest; one that
136
+ cannot be read says why.
137
+ - **A step lists what happened in the order it happened.** Saffron's page
138
+ check, the recorded actions, the agent's and the calls outside any step
139
+ are interleaved by time, and the arrow keys follow that order. A call
140
+ outside every step is filed under the step running when it started. The
141
+ keys keep working after a click on a button or tab, leave Cmd, Ctrl and
142
+ Alt shortcuts to the browser, and the selected row scrolls into view.
143
+ - **✦ marks only a step the agent healed.** In a first recording, a step
144
+ the agent worked through reads "recorded by the agent". In a proof
145
+ replay, or a run that replayed a pending proposal without the agent, a
146
+ step the proposal changes reads "ran the proposed recording". Neither
147
+ is called healed, and Details names the kind of run: Heal review, First
148
+ recording, New recording or Proposed recording.
149
+ - **A pending proposal that failed is shown apart.** When a run replays a
150
+ pending proposal, it fails, and the run goes on to the committed
151
+ recording or records again, the proposal's attempt is one section,
152
+ "Earlier attempt: the pending proposal", instead of both executions
153
+ being merged into the steps.
154
+ - **A heal whose assertion does not hold as written says so on that
155
+ step.** The failed step shows the guard's "Assertion failed as written"
156
+ reason instead of what replay missed first, and the execution trace
157
+ shows the steps the agent carried after it.
158
+ - **A failed agent session no longer marks every step failed.** When a
159
+ heal did not succeed, the report and the replay showed all steps as
160
+ failed, including the ones replay had passed before the agent took
161
+ over. They now show what happened: passed up to the step replay stopped
162
+ on, that step failed with its reason, the rest skipped. When the agent
163
+ carried that step and failed on a later one, the later step is the
164
+ failed one, with the agent's reason. A failed recording shows the steps
165
+ the agent got through the same way.
166
+ - `saffron trace` and `saffron report` keep working on a machine with no
167
+ browser opener (a container, an SSH or WSL remote): they print the
168
+ address to open instead of crashing.
169
+
170
+ Editors (VS Code extension 0.3.0, JetBrains plugin 0.3.0)
171
+
172
+ - **Open Replay** on a scenario in the Last run panel runs `saffron trace`
173
+ for it, by feature and scenario name, so two features with a scenario
174
+ of the same name are told apart. JetBrains also gets `trace` as a run
175
+ configuration command: its *Scenario* field takes a name or
176
+ `feature:scenario` as it is or wrapped whole in double quotes, and blank
177
+ opens every traced scenario, starting on a failed one.
178
+ - **A Last run row stays with its run.** In VS Code, a row (its
179
+ screenshot, Open Replay, Open Scenario) acts on the project it came
180
+ from, even after the panel has moved to another one, and says so if
181
+ that project is no longer open.
182
+ In both editors, a screenshot is checked against the digest its run
183
+ recorded when you open it, so a picture a later run put at the same
184
+ path is refused instead of shown as this run's, and what opens is a copy
185
+ of the picture that was checked, named after the scenario. VS Code keeps
186
+ the copies in the extension's own storage, one folder per window,
187
+ removed when the window closes; JetBrains in the IDE's system folder,
188
+ removed after a day. Neither uses the shared temporary folder. In
189
+ JetBrains, double-clicking such a row no longer jumps to the scenario
190
+ over the message saying why. In VS Code, Open Scenario on a row whose
191
+ feature file was renamed or deleted since the run says so.
192
+ `saffron status --json` now reports each picture's `sha256`.
193
+ - **Accept and reject decide the proposal you saw.** Both Proposals
194
+ panels send the revision they listed, so a proposal a later run
195
+ replaced after the panel showed it is refused with a prompt to refresh,
196
+ not decided unseen. A replaced proposal comes back unticked in both.
197
+ With a runner that lists no revision, they send the file as before.
198
+ VS Code's Accept all tooltip now says what `--all` does: it accepts
199
+ what is pending when it runs, skipping UNVERIFIED proposals and
200
+ refusing STALE ones.
201
+
7
202
  ## 0.8.5
8
203
 
9
204
  Released 2026-09-24.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Saffron
2
2
 
3
- **Gherkin-native test runner with zero-token cached replay and runtime AI healing.**
3
+ **End-to-end tests written in plain language, with zero-token cached replay and runtime AI healing.**
4
4
 
5
5
  Free to use · [npm: saffron-ai](https://www.npmjs.com/package/saffron-ai) · [site](https://saffron-ai.io)
6
6
 
7
- Write tests as Gherkin feature files: `.saffron`, Saffron's superset of Gherkin with step sets, or standard `.feature`. On the first run, an AI agent (Claude Agent SDK + Playwright MCP) executes each scenario in a real browser and records what it did into a **cache**. A human-reviewable JSON action list committed to git. Every run after that, Saffron replays the cache in a real browser: **zero AI calls, zero tokens**, at the speed of the browser and your app. The cache is Saffron's own JSON, not a generated script; replay runs on Playwright's browser engine, and Playwright MCP is only how the agent operates the browser while it records or heals.
7
+ Write tests in plain language, as `.saffron` files or standard `.feature` files. `.saffron` is Saffron's own test language and goes further than Gherkin: step sets for reusable flows, `{env:VAR}` tokens that keep secrets out of recordings and reports, and `{data:...}` references to data files. There are no step definitions and no glue code. On the first run, an AI agent (Claude Agent SDK + Playwright MCP) executes each scenario in a real browser and records what it did into a **cache**. A human-reviewable JSON action list committed to git. Every run after that, Saffron replays the cache in a real browser: **zero AI calls, zero tokens**, at the speed of the browser and your app. The cache is Saffron's own JSON, not a generated script; replay runs on Playwright's browser engine, and Playwright MCP is only how the agent operates the browser while it records or heals.
8
8
 
9
9
  When the UI changes and a cached step fails at runtime, the agent takes over mid-execution with full scenario context, adapts, and finishes the run, then files a **cache proposal** you accept or reject, like a snapshot test update. Reports state exactly what was adapted and what it cost.
10
10
 
@@ -22,7 +22,7 @@ When the UI changes and a cached step fails at runtime, the agent takes over mid
22
22
 
23
23
  Like a maturing Cucumber suite, most of a new feature file is steps you already have. Saffron derives a **step index** from committed caches and, when recording a new scenario, replays every seeded island of known steps zero-AI. The agent records only the genuinely novel steps, wherever they sit. Exact step text is the identity (Cucumber-style); quoted values may differ (`"admin"` seeds from `"bob"`); data-table steps seed on matching keys. Measured on a live site: a 6-step scenario with one novel step recorded for **$0.15 / 6 AI calls**, vs $2.07 for a full unseeded recording of comparable length.
24
24
 
25
- **Step sets** (shipped): `.saffron` files, a superset dialect of Gherkin, add the `StepSet:` keyword for named, reusable step sequences invoked with `StepSet <name>` inside any scenario. Sets expand at parse time, so their steps cache and seed like ordinary steps; editing a set makes every invoking scenario honestly stale (re-recorded mostly seeded), and heal edits route to the set definition, one fix, every caller follows. Sets are **project-wide**: keep application-wide flows in a sets-only library file (convention: `features/shared.steps.saffron`) and invoke them from any feature.
25
+ **Step sets** (shipped): `.saffron` files add the `StepSet:` keyword for named, reusable step sequences invoked with `StepSet <name>` inside any scenario. Sets expand at parse time, so their steps cache and seed like ordinary steps; editing a set makes every invoking scenario honestly stale (re-recorded mostly seeded), and heal edits route to the set definition, one fix, every caller follows. Sets are **project-wide**: keep application-wide flows in a sets-only library file (convention: `features/shared.steps.saffron`) and invoke them from any feature.
26
26
 
27
27
  **IDE integration & authoring** (shipped): `saffron steps` lists the project vocabulary with recorded/divergent/unrecorded badges (`--snippets` for native VS Code completion), `saffron mcp` serves it to AI assistants, `saffron lsp` brings completion/navigation/diagnostics to **JetBrains** (incl. Community editions via LSP4IJ) and Neovim, `saffron author` drafts feature files from prose in your own vocabulary, and duplicate wordings that record identical actions get **behavior-proven rename proposals**. The **Saffron VS Code extension** is on the [Marketplace](https://marketplace.visualstudio.com/items?itemName=ChathurangaJayasinghe.saffron-vscode) (`code --install-extension ChathurangaJayasinghe.saffron-vscode`). JetBrains users get the same in one click from the **Saffron JetBrains plugin** ([Marketplace](https://plugins.jetbrains.com/plugin/34240-saffron), [source](https://github.com/s-chathuranga-j/saffron-jetbrains-plugin)): `.saffron` file type, bundled grammar, LSP4IJ wiring. Both editors also **run from the IDE**: right-click Run on `.saffron` files and folders, Saffron run configurations (JetBrains), and a Saffron panel with feature files, tags to run, proposals to accept or reject, vocabulary health, config, and the run report as an in-editor dashboard, all fed by `saffron status --json`.
28
28
 
@@ -72,7 +72,7 @@ node dist/cli/index.js -p examples accept --all
72
72
 
73
73
  | Command | What it does |
74
74
  |---|---|
75
- | `saffron run [paths] [--headed] [--filter @tags] [--rerecord] [--no-agent] [--strict] [--browser b] [--no-hooks] [--workers n] [--heal-model m] [--no-verify] [--no-reuse] [--model m] [--storage-state f]` | Run features. Cached replays are deterministic; misses/failures escalate to the agent (unless `--no-agent`). Replay cross-browser with `--browser firefox\|webkit`, parallelize with `--workers N`, heal on a cheaper model with `--heal-model`. Exit 1 on red (and on yellow with `--strict`). |
75
+ | `saffron run [paths] [--headed] [--filter @tags] [--rerecord] [--no-agent] [--strict] [--browser b] [--no-hooks] [--workers n] [--heal-model m] [--no-verify] [--no-reuse] [--model m] [--storage-state f] [--trace mode]` | Run features. Cached replays are deterministic; misses/failures escalate to the agent (unless `--no-agent`). Replay cross-browser with `--browser firefox\|webkit`, parallelize with `--workers N`, heal on a cheaper model with `--heal-model`, keep an execution trace with `--trace retain-on-failure`. Exit 1 on red (and on yellow with `--strict`). |
76
76
  | `saffron accept [files... \| --all] [--include-unverified] [--with-feature-edit] [--propagate]` | Promote cache proposals to committed caches (`--all` skips UNVERIFIED ones unless `--include-unverified`); `--with-feature-edit` also rewrites the adapted steps in the `.feature` file (and keeps the cache in sync); `--propagate` applies the heal's locator fixes to every other cache using the same locator. One heal repairs N scenarios before they ever fail. No args: list pending proposals. |
77
77
  | `saffron reject [files... \| --all]` | Discard proposals; the agent will try again next run. |
78
78
  | `saffron status [--json]` | Project overview for people, IDE panels and dashboards: feature files and scenarios with cache state, tags, pending proposals, last run, history, vocabulary health (divergent steps, duplicate wordings proven by identical recordings), effective config. |
@@ -82,6 +82,7 @@ node dist/cli/index.js -p examples accept --all
82
82
  | `saffron lsp` | Run the Saffron language server over stdio for JetBrains (LSP4IJ/Ultimate), Neovim, any LSP editor: badge completion, StepSet go-to-definition, hover, diagnostics. Highlighting for JetBrains comes from the bundled TextMate grammar (`textmate/saffron` in the package); run `npx saffron lsp` in a terminal for setup steps. |
83
83
  | `saffron init [--agents list] [--examples]` | Make the project agent-ready (`--examples` installs the Saucedemo example suite): installs the bundled **Saffron agent skill** into `.claude/skills` and `.agents/skills` (Copilot/Cursor via `--agents`), registers the `saffron mcp` server in `.mcp.json` / `.cursor/mcp.json` / `.vscode/mcp.json`, adds a managed block to `AGENTS.md`/`CLAUDE.md`, scaffolds config. |
84
84
  | `saffron report` | Open the latest HTML report. |
85
+ | `saffron trace [scenario] [--port n] [--no-open] [--json]` | Open the execution replay of the last run (every traced scenario, or the one you name): its Given / When / Then, the page inspectable at every action, the network alongside, and what the agent changed. Needs `"trace": "retain-on-failure"` (or `"on"`). |
85
86
 
86
87
  Configuration lives in `saffron.config.json` at your project root:
87
88
 
@@ -130,7 +131,7 @@ The full execution-model diagram is on the site: https://saffron-ai.io/docs
130
131
  proposals/ pending AI-generated cache updates (review these)
131
132
  history.jsonl one line per run: trends (commit recommended)
132
133
  reports/ latest.html / latest.json (gitignore these)
133
- artifacts/ failure screenshots from the latest run (gitignore these)
134
+ artifacts/ screenshots and traces from the latest run (gitignore these)
134
135
  features/ your .saffron and .feature files
135
136
  ```
136
137