saffron-ai 0.8.5 → 0.9.1

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,265 @@ 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.1
8
+
9
+ Released 2026-09-29.
10
+
11
+ Runner
12
+
13
+ - **Tag expressions in `--filter`.** `--filter "@e2e and not @wip"`,
14
+ `"(@smoke or @sanity) and not @flaky"`: `and`, `or`, `not` and
15
+ parentheses, in the shape Cucumber and pytest use, with `and` binding
16
+ tighter than `or`. The list forms keep their meaning: `@a,@b`, `@a @b`,
17
+ `@a|@b` and a repeated flag all mean `or`, and the `@` may be left off.
18
+ A malformed expression stops the run with exit 2 and names the problem.
19
+ - **Replay fixes.** A network assertion checked without the run's
20
+ response log (the check after a heal) now checks the response body too:
21
+ a heal could pass while the API answered `{"ok":false}`. A file upload
22
+ armed for the next file chooser is set by the click that opens it,
23
+ which waits for a chooser that opens late up to the action timeout. If
24
+ no chooser opens, or the files cannot be set (a missing file), that
25
+ click fails, and so does each retry of it, instead of the runner ending
26
+ or the scenario passing with nothing uploaded. `{env:VAR}` in the
27
+ pattern of `expectMatches` and `expectAttribute` is resolved like
28
+ every other field.
29
+ - **A click through a locator chain records the element it clicked.**
30
+ When the agent targets an element with a selector string, Playwright
31
+ MCP reports a chain such as `locator('article:has(...)')
32
+ .locator('role=button[name="Select room"]')`. The recorder kept only the
33
+ first link, so replay clicked the whole card instead of its button, and
34
+ a refine pass then re-clicked the room under the next step. Chains of
35
+ locators, getBy calls, `filter({ hasText })`, `nth`, `first` and `last`
36
+ are now kept whole as one selector; a chain part with no selector form
37
+ is not recorded rather than recorded as a different element. The agent
38
+ is also told to act through snapshot refs, which record as role and
39
+ name.
40
+ - **Dates picked through a selector string move with the calendar.** A
41
+ date in a fallback selector or test id (`role=button[name="2026-10-11"]`)
42
+ is now templated to `{date+N}` like one in a name or label, and replay
43
+ resolves it there, so the recording does not expire the next day.
44
+ - **Open Replay opens the run its row lists.** `saffron trace --run
45
+ <startedAt>` refuses a later run ("The last run changed since this was
46
+ listed"), and both editors pass it, so an older row no longer opens a
47
+ newer run's trace.
48
+ - **A decision under way keeps its lock.** A proposal lock held by a live
49
+ process on this machine is never broken for its age, so a paused
50
+ decision cannot be overtaken.
51
+
52
+ Editors (VS Code extension 0.3.2, JetBrains plugin 0.3.2)
53
+
54
+ - **VS Code: a project nested inside a bigger workspace folder finds its
55
+ data folder.** `{data:...}` completion, hover and diagnostics resolve
56
+ against the nearest `saffron.config.json` above the file, not the
57
+ workspace root.
58
+ - **JetBrains: the Run Tagged tooltip** says the CLI also takes tag
59
+ expressions.
60
+ - **Open Replay opens the run its row lists**, with saffron-ai 0.9.1 or
61
+ later; with an older runner it opens the scenario as before.
62
+ - **JetBrains: the tool window opens on IntelliJ 2026.2.** The Dashboard
63
+ tab crashed the whole Saffron tool window there (NoClassDefFoundError:
64
+ JBCefApp), because 2026.2 moved the embedded browser into its own
65
+ plugin. The plugin now depends on it where it exists, and without it
66
+ the Dashboard falls back to Open in Browser.
67
+ - **VS Code: `.saffron` files show the Saffron mark** in the explorer and
68
+ tabs, under any file icon theme that has no icon of its own for them
69
+ (Seti, the default, included).
70
+
71
+ ## 0.9.0
72
+
73
+ Released 2026-09-27.
74
+
75
+ Runner
76
+
77
+ - **Execution replay: `saffron trace`.** Not a video. With `"trace":
78
+ "retain-on-failure"` (or `"on"`) in the config, or `--trace <mode>` for
79
+ one run, a scenario keeps an execution trace, and `saffron trace` opens
80
+ it as a replay in your browser: your Given, When and Then on the left,
81
+ each step opening onto the actions Saffron replayed and the Playwright
82
+ calls behind them; the page at the selected action in the middle,
83
+ rendered as a live DOM you can inspect, before, during or after the
84
+ action, with a filmstrip of the whole run; on the right the failure, the
85
+ retries, the call log, the network during the selection, and what the
86
+ agent changed. The failed step is selected when you arrive. Traces live
87
+ next to the screenshots under `.saffron/artifacts/`, are kept for the
88
+ latest run and pruned with the rest. The report names the command for
89
+ every traced scenario, as `feature:scenario`. The page opens on `127.0.0.1` or `localhost`, a
90
+ forwarded port included, and keeps what it loaded when a later run
91
+ replaces the files. A tab left open from before `saffron trace` was
92
+ started again says to reload the page instead of loading the new run's
93
+ files. The trace is Playwright's own format, rendered by
94
+ the snapshot engine of the Playwright Saffron runs with; the replay
95
+ around it is Saffron's. The Saucedemo example config ships with
96
+ `retain-on-failure`.
97
+ - **Secrets in a trace.** `{env:VAR}` values, including one a data file
98
+ holds, are masked back to their tokens before the trace is kept: in the
99
+ event log, the DOM snapshots and stylesheets, and every request and
100
+ response body, URL, header and WebSocket message, in the spellings the
101
+ value takes there (JSON escapes, percent-encoding as browsers and
102
+ servers write it, HTML entities, CSS escapes). The unmasked trace never
103
+ reaches `.saffron/artifacts/`, even when a run is killed while saving
104
+ it. The scheme, host and port of a URL are kept, so the replay can
105
+ still load the page's stylesheets and images; a secret anywhere else in
106
+ it, credentials included, is written `%7Benv:NAME%7D`. A value
107
+ shorter than three characters, one inside base64 or another binary
108
+ encoding, a value in a URL's host, and the filmstrip's pixels are not
109
+ masked, so look through a trace before attaching it to a public issue.
110
+ - **Review a heal from the replay.** The selected step shows what the
111
+ proposal changes for it, the elements Playwright actually matched are
112
+ outlined in the page, and the Saffron panel has **Accept recording** and
113
+ **Reject proposal**. Both act only on the exact proposal associated with
114
+ this run: if it was replaced, accepted or rejected since, the replay
115
+ refuses and says why. One that went stale against the scenario or the
116
+ committed recording, or can no longer be read, cannot be accepted but
117
+ can still be rejected. An unverified recording, or one this run showed
118
+ failing, needs an explicit acknowledgement first. Suggested feature
119
+ rewrites are listed with the command that applies them (`saffron accept
120
+ --with-feature-edit`); they and propagation to other recordings stay
121
+ with `saffron accept`.
122
+ - **Accept or reject the proposal you listed.** `saffron status --json`
123
+ gives each pending proposal a `revision`, and `saffron accept
124
+ <file>#<revision>` or `saffron reject <file>#<revision>` decides that
125
+ proposal only if it is still the one listed. One a later run replaced
126
+ is refused ("changed since it was listed. Refresh and review it
127
+ again."), exit 1, and left in place. A decision and a run filing a
128
+ proposal never interleave, so what is checked is what is promoted, with
129
+ or without a revision. Accept and reject act only on files under
130
+ `.saffron/proposals`: `saffron reject features/login.saffron` used to
131
+ delete the feature file.
132
+ - **Proof replays and the agent in the trace.** A recorded or healed
133
+ scenario also keeps a trace of each proof replay (`proof-1.zip`,
134
+ `proof-2.zip`) with its own step outcomes, and **Trace** at the top of
135
+ the page switches between them and the run itself. The agent's own
136
+ browser calls are recorded too, under the step it was working on and in
137
+ words ("click Login button", "read the page"); what it looked at before
138
+ naming a step is filed under the step it named next. A healed step is
139
+ marked ✦, and a healed run opens on it.
140
+ If that cannot start with the installed Playwright, Saffron says why,
141
+ once, and the agent works through the standard browser connection
142
+ without being traced.
143
+ - **Masking fixes outside the trace.** A secret whose value appears in
144
+ another variable's name or in a `{data:...}` key no longer corrupts the
145
+ recording (`ROLE=ADMIN` beside `{env:ADMIN_PASSWORD}` used to be
146
+ recorded as `{env:{env:ROLE}_PASSWORD}`, which never replayed).
147
+ Three-character values (a CVV, a short PIN) are now masked in
148
+ recordings, reports and history too, as in the trace; in a recording a
149
+ value shorter than six characters is masked where it stands alone or
150
+ where the step joins it to other text (`"qa{env:ID}"`), so `ENV=dev`
151
+ never turns `/devices` into `/{env:ENV}ices`. Reports mask
152
+ a value's URL-encoded and HTML-escaped forms. A recording masks a
153
+ secret in every field replay reads (labels, placeholders, test ids,
154
+ fallback selectors, frame names, drag targets, URL patterns, the text
155
+ for a prompt dialog), not only typed values and URLs, and replay
156
+ resolves the token in each of them: a selector recorded as
157
+ `td.{env:NAME}` still finds its element. A
158
+ `{env:VAR}` written as a value in a data file is masked in recordings
159
+ and reports like one written in the step.
160
+ - **The HTML report embeds only the run's own screenshots.** A merged
161
+ report (`saffron report --merge`) could be made to embed any file the
162
+ merging machine could read, such as `.env`; it now embeds pictures from
163
+ `.saffron/artifacts/` only, and escapes the setup and teardown lines.
164
+ - **Evidence from an interrupted run is not mixed in.** Screenshots and
165
+ traces are kept at fixed paths that the next run replaces. A run
166
+ stopped before it wrote its report used to leave the previous report
167
+ pointing at the new files; the IDE panels now leave out such a
168
+ screenshot and `saffron trace` refuses such a trace.
169
+ - **Playwright 1.63 and trace format 9.** The runner now depends on
170
+ Playwright 1.63, and the replay reads both trace formats: 8 (up to
171
+ 1.62) and 9 (1.63 and later), which files snapshots under the call by
172
+ phase and screencast frames under their own folder. A project that
173
+ resolved 1.63 saw broken filmstrip tiles and "No snapshot for this
174
+ selection".
175
+ - **The first snapshot shown no longer arrives unstyled.** The snapshot
176
+ engine forgets a trace while its first frame is still loading, so that
177
+ frame's stylesheets and fonts came back missing and the page looked
178
+ bare until the next step was selected. The replay now warms the engine
179
+ with a hidden frame first.
180
+ - **A step with no snapshot of its own shows the last page there was.** A
181
+ step the agent only inspected (the accessibility tree, the title, the
182
+ console) leaves no DOM snapshot, since Playwright takes them around
183
+ interactions. Instead of an empty pane, the replay shows the page after
184
+ the last interaction before that step and says so above it. A step
185
+ that never ran, after a failure, shows no page: "This step was never
186
+ reached." An action
187
+ that took no snapshot (reading the title, say) shows the page as the
188
+ last snapshot before it left it, not as its step began. With several
189
+ tabs open it only borrows from the same tab; Playwright 1.63 does not
190
+ record which tab such an action ran in, so when that is unknown the
191
+ replay says so, and the filmstrip marks no frame, rather than show
192
+ another tab. A tab opened later in the run changes nothing before it, and one
193
+ closed changes nothing after.
194
+ - **Every traced scenario in one replay.** `saffron trace` with no name
195
+ (or a name several scenarios share) opens them all, with a Scenario
196
+ picker in the header, starting on a red one, else a yellow one. A run
197
+ with `"trace": "on"` where everything passed opens too. `--json` with no
198
+ name prints the scenario the replay opens on. Other scenarios' traces
199
+ are read when first picked and checked against their digest; one that
200
+ cannot be read says why.
201
+ - **A step lists what happened in the order it happened.** Saffron's page
202
+ check, the recorded actions, the agent's and the calls outside any step
203
+ are interleaved by time, and the arrow keys follow that order. A call
204
+ outside every step is filed under the step running when it started. The
205
+ keys keep working after a click on a button or tab, leave Cmd, Ctrl and
206
+ Alt shortcuts to the browser, and the selected row scrolls into view.
207
+ - **✦ marks only a step the agent healed.** In a first recording, a step
208
+ the agent worked through reads "recorded by the agent". In a proof
209
+ replay, or a run that replayed a pending proposal without the agent, a
210
+ step the proposal changes reads "ran the proposed recording". Neither
211
+ is called healed, and Details names the kind of run: Heal review, First
212
+ recording, New recording or Proposed recording.
213
+ - **A pending proposal that failed is shown apart.** When a run replays a
214
+ pending proposal, it fails, and the run goes on to the committed
215
+ recording or records again, the proposal's attempt is one section,
216
+ "Earlier attempt: the pending proposal", instead of both executions
217
+ being merged into the steps.
218
+ - **A heal whose assertion does not hold as written says so on that
219
+ step.** The failed step shows the guard's "Assertion failed as written"
220
+ reason instead of what replay missed first, and the execution trace
221
+ shows the steps the agent carried after it.
222
+ - **A failed agent session no longer marks every step failed.** When a
223
+ heal did not succeed, the report and the replay showed all steps as
224
+ failed, including the ones replay had passed before the agent took
225
+ over. They now show what happened: passed up to the step replay stopped
226
+ on, that step failed with its reason, the rest skipped. When the agent
227
+ carried that step and failed on a later one, the later step is the
228
+ failed one, with the agent's reason. A failed recording shows the steps
229
+ the agent got through the same way.
230
+ - `saffron trace` and `saffron report` keep working on a machine with no
231
+ browser opener (a container, an SSH or WSL remote): they print the
232
+ address to open instead of crashing.
233
+
234
+ Editors (VS Code extension 0.3.0, JetBrains plugin 0.3.0)
235
+
236
+ - **Open Replay** on a scenario in the Last run panel runs `saffron trace`
237
+ for it, by feature and scenario name, so two features with a scenario
238
+ of the same name are told apart. JetBrains also gets `trace` as a run
239
+ configuration command: its *Scenario* field takes a name or
240
+ `feature:scenario` as it is or wrapped whole in double quotes, and blank
241
+ opens every traced scenario, starting on a failed one.
242
+ - **A Last run row stays with its run.** In VS Code, a row (its
243
+ screenshot, Open Replay, Open Scenario) acts on the project it came
244
+ from, even after the panel has moved to another one, and says so if
245
+ that project is no longer open.
246
+ In both editors, a screenshot is checked against the digest its run
247
+ recorded when you open it, so a picture a later run put at the same
248
+ path is refused instead of shown as this run's, and what opens is a copy
249
+ of the picture that was checked, named after the scenario. VS Code keeps
250
+ the copies in the extension's own storage, one folder per window,
251
+ removed when the window closes; JetBrains in the IDE's system folder,
252
+ removed after a day. Neither uses the shared temporary folder. In
253
+ JetBrains, double-clicking such a row no longer jumps to the scenario
254
+ over the message saying why. In VS Code, Open Scenario on a row whose
255
+ feature file was renamed or deleted since the run says so.
256
+ `saffron status --json` now reports each picture's `sha256`.
257
+ - **Accept and reject decide the proposal you saw.** Both Proposals
258
+ panels send the revision they listed, so a proposal a later run
259
+ replaced after the panel showed it is refused with a prompt to refresh,
260
+ not decided unseen. A replaced proposal comes back unticked in both.
261
+ With a runner that lists no revision, they send the file as before.
262
+ VS Code's Accept all tooltip now says what `--all` does: it accepts
263
+ what is pending when it runs, skipping UNVERIFIED proposals and
264
+ refusing STALE ones.
265
+
7
266
  ## 0.8.5
8
267
 
9
268
  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 expr] [--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