saffron-ai 0.7.2 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saffron-ai",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Gherkin-native AI-fallback test runner: zero-token cached replay, runtime AI healing, honest reports",
5
5
  "scripts": {
6
6
  "test": "vitest run",
@@ -68,6 +68,7 @@ strict under every policy. Use `Given`/`When` for actions.
68
68
  ## 3. Never bake volatile or secret values into files
69
69
 
70
70
  - Secrets: `{env:VAR}`, as in `When I enter "{env:ADMIN_PASSWORD}" in the password field`.
71
+ - Shared test data (accounts, names, enum values): put it in `data/*.json` and write `{data:users.admin.email}` (file, then dotted path; one value, not a list). The recording stores the token, so editing the file changes the next replay at zero tokens. Never for secrets: data files are committed. To check a whole list (an enum), point a Then step at it: `Then the status filter should list every {data:enums.OrderStatus}`; replay iterates the file's list, so adding a value needs no re-record. `Examples: {data:roles}` reads outline rows from `data/roles.csv` or a JSON list (`.saffron` only). A value that must be new on every run (an email for a sign-up) is `{unique:name}`: fresh per run, the same within it.
71
72
  - Dates: write intent, not literals: "1 day from today" records as `{date+1}`.
72
73
  - Dynamic display values: capture and compare, e.g. `I record the total as "first"` … `"first" should differ from the displayed total`.
73
74
 
@@ -20,6 +20,7 @@ your-project/
20
20
  {
21
21
  "baseURL": "https://stage.your-app.com",
22
22
  "features": "features",
23
+ "dataDir": "data",
23
24
  "actionTimeoutMs": 5000,
24
25
  "pollIntervalMs": 100,
25
26
  "retries": 1,
@@ -40,6 +41,7 @@ your-project/
40
41
 
41
42
  | Key | Meaning |
42
43
  |---|---|
44
+ | `dataDir` | Folder of JSON files read by `{data:file.key}` tokens (default `data`). Committed; not for secrets |
43
45
  | `baseURL` | App under test; steps say "the login page", not full URLs |
44
46
  | `storageState` | Playwright storage-state JSON so replays and the agent start authenticated (`npx playwright open --save-storage=.auth/state.json <url>`) |
45
47
  | `actionTimeoutMs` | Budget for one action: Playwright's actionability wait and the deadline for a polled assertion (default 5000) |
@@ -84,6 +84,60 @@ a missing variable fails fast by name. Honest note: during the *first*
84
84
  recording the agent types the real value once: use rotatable staging
85
85
  credentials.
86
86
 
87
+ ## Test data: `{data:...}`
88
+
89
+ Values that several scenarios share (accounts, names, enum values) live in
90
+ JSON files under `data/`, not in the feature file. Committed, so never
91
+ secrets.
92
+
93
+ ```json
94
+ // data/users.json
95
+ { "admin": { "email": "admin@test.com" }, "roles": ["Admin", "Editor", "Viewer"] }
96
+ ```
97
+
98
+ ```gherkin
99
+ When I sign in as {data:users.admin.email}
100
+ Then the role filter should list every {data:users.roles}
101
+ ```
102
+
103
+ - A reference is the file name without `.json`, then a dotted path; a number
104
+ picks a list entry (`{data:users.roles.0}`).
105
+ - The recording stores the token and replay reads the file: editing a value
106
+ changes the next replay at zero tokens. A recording that still spells out
107
+ the OLD value goes stale and records again.
108
+ - `every {data:list}` in a `Then` step records ONE assertion that names the
109
+ list; replay checks each value, so adding one to the file needs no
110
+ re-record. Every listed value must be present; extras like "All" are fine.
111
+ - A missing file or key stops the run before a browser opens, by name.
112
+ - Per environment: `data/users.staging.json` is laid over `data/users.json`
113
+ with `--env staging` (or `SAFFRON_ENV`, or `"env"` in the config).
114
+
115
+ ### Examples from a file (`.saffron` only)
116
+
117
+ ```gherkin
118
+ Scenario Outline: Role sees its menu
119
+ Given I sign in as <role>
120
+ Then I should see the <menu> menu
121
+
122
+ Examples: {data:roles}
123
+ ```
124
+
125
+ Reads `data/roles.csv` (header row = placeholder names) or a JSON list of
126
+ records. One recording, one zero-token replay per row; adding a row records
127
+ nothing. A placeholder with no column is a parse error naming both.
128
+
129
+ ### Unique per run: `{unique:name}`
130
+
131
+ ```gherkin
132
+ When I register as user-{unique:id}@test.com
133
+ Then the welcome banner should greet user-{unique:id}@test.com
134
+ ```
135
+
136
+ New on every run, the same within one run, so a later step can find what an
137
+ earlier step created and reruns do not collide with leftovers.
138
+ `{unique:name}` is 10 lowercase letters and digits; `{unique:name:digits}`
139
+ is 9 digits.
140
+
87
141
  ## Dates and dynamic values
88
142
 
89
143
  - Say the intent: "select a check-in date 1 day from today" → recorded
@@ -0,0 +1,11 @@
1
+ {
2
+ "products": [
3
+ "Sauce Labs Backpack",
4
+ "Sauce Labs Bike Light",
5
+ "Sauce Labs Bolt T-Shirt",
6
+ "Sauce Labs Fleece Jacket",
7
+ "Sauce Labs Onesie",
8
+ "Test.allTheThings() T-Shirt (Red)"
9
+ ],
10
+ "featured": "Sauce Labs Backpack"
11
+ }
@@ -18,3 +18,12 @@ Feature: Shopping
18
18
  Then the overview should list "Sauce Labs Bike Light"
19
19
  When I click the "Finish" button
20
20
  Then I should see "Thank you for your order!"
21
+
22
+ # Test data lives in data/catalog.json, not in this file. {data:...} is
23
+ # read again on every replay: add a product to the JSON and the next run
24
+ # checks for it, with nothing recorded again.
25
+ @catalog
26
+ Scenario: The catalog lists every product in the data folder
27
+ StepSet Log in as the standard user
28
+ Then the product list should show every {data:catalog.products}
29
+ And the product list should show "{data:catalog.featured}"
@@ -61,7 +61,12 @@
61
61
  "name": "punctuation.separator.saffron"
62
62
  },
63
63
  "3": {
64
- "name": "entity.name.section.saffron"
64
+ "name": "entity.name.section.saffron",
65
+ "patterns": [
66
+ {
67
+ "include": "#token"
68
+ }
69
+ ]
65
70
  }
66
71
  }
67
72
  },
@@ -139,7 +144,7 @@
139
144
  "name": "variable.parameter.placeholder.saffron"
140
145
  },
141
146
  "token": {
142
- "match": "\\{(env:[A-Za-z_][A-Za-z0-9_]*|date(?:[+-]\\d+)?(?::[^}]+)?)\\}",
147
+ "match": "\\{(env:[A-Za-z_][A-Za-z0-9_]*|data:[A-Za-z0-9_.-]+|unique:[A-Za-z0-9_-]+(?::digits)?|date(?:[+-]\\d+)?(?::[^}]+)?)\\}",
143
148
  "name": "constant.other.token.saffron"
144
149
  }
145
150
  },