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/CHANGELOG.md +61 -0
- package/README.md +1 -0
- package/dist-pkg/cli.js +116 -109
- package/package.json +1 -1
- package/skills/saffron/SKILL.md +1 -0
- package/skills/saffron/references/config.md +2 -0
- package/skills/saffron/references/syntax.md +54 -0
- package/templates/saucedemo/data/catalog.json +11 -0
- package/templates/saucedemo/features/shopping.saffron +9 -0
- package/textmate/saffron/syntaxes/saffron.tmLanguage.json +7 -2
package/package.json
CHANGED
package/skills/saffron/SKILL.md
CHANGED
|
@@ -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
|
|
@@ -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
|
},
|