saffron-ai 0.5.0 → 0.5.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,23 @@ 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.5.1
8
+
9
+ Released 2026-09-14.
10
+
11
+ Runner
12
+
13
+ - Example suite: `.saffron` files only, with StepSets invoked both at the
14
+ start of scenarios and in the middle ("Recover from a locked-out
15
+ login", and a checkout set that carries a data table).
16
+
17
+ Editors
18
+
19
+ - Grammar: quoted arguments, `<placeholders>` and `{env:…}` / `{date…}`
20
+ tokens get their own scopes, so editors colour them like Cucumber
21
+ parameters instead of the whole step line. Ships in the VS Code
22
+ extension, the JetBrains plugin and the package's TextMate bundle.
23
+
7
24
  ## 0.5.0
8
25
 
9
26
  Released 2026-09-14.
package/README.md CHANGED
@@ -37,10 +37,11 @@ npx saffron init # config + agent skill; offers the Saucedemo example sui
37
37
  npx saffron run
38
38
  ```
39
39
 
40
- No app of your own yet? `npx saffron init --examples` installs five short
41
- scenarios against the public demo shop at saucedemo.com (login, cart,
42
- checkout, a Scenario Outline, a StepSet) so the first run records and
43
- replays something real.
40
+ No app of your own yet? `npx saffron init --examples` installs a handful
41
+ of short `.saffron` scenarios against the public demo shop at saucedemo.com
42
+ (login, a Scenario Outline, cart, checkout, StepSets at the start and in
43
+ the middle of scenarios) so the first run records and replays something
44
+ real.
44
45
 
45
46
  **Built for the AI era:** the package ships an [Agent Skill](https://agentskills.io) that teaches Claude Code, Codex, Cursor, Copilot and any skill-aware agent to write Saffron tests efficiently. Reuse recorded wordings, keep assertions sacred, secrets as `{env:VAR}`, so the tests your agents write replay at zero tokens instead of paying for near-duplicate recordings.
46
47
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saffron-ai",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
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",
@@ -23,3 +23,15 @@ Feature: Login
23
23
  | username | password | message |
24
24
  | locked_out_user | secret_sauce | Epic sadface: Sorry, this user has been locked out. |
25
25
  | standard_user | wrong | Epic sadface: Username and password do not match |
26
+
27
+ @login @recover
28
+ Scenario: Recover from a locked-out login
29
+ # A StepSet in the middle of a scenario: the set's guard step takes the
30
+ # browser back to the login page before it logs the standard user in.
31
+ Given I am on the login page
32
+ When I enter "locked_out_user" in the username field
33
+ And I enter "secret_sauce" in the password field
34
+ And I click the "Login" button
35
+ Then I should see the error message "Epic sadface: Sorry, this user has been locked out."
36
+ StepSet Log in as the standard user
37
+ Then I should see the products page
@@ -1,7 +1,10 @@
1
1
  Feature: Shared step sets for the Saucedemo example suite
2
2
 
3
- # A sets-only library file: no scenarios, only StepSet definitions that any
4
- # .saffron file in the project can invoke with "StepSet <name>".
3
+ # A sets-only library file: no scenarios, only StepSet definitions. Any
4
+ # .saffron file in the project invokes them with "StepSet <name>", at the
5
+ # start of a scenario or anywhere in the middle. Each set opens with a
6
+ # guard step and closes with an exit assertion, so every invocation checks
7
+ # itself.
5
8
 
6
9
  StepSet: Log in as the standard user
7
10
  Given I am on the login page
@@ -9,3 +12,14 @@ StepSet: Log in as the standard user
9
12
  And I enter "{env:SAUCE_PASSWORD}" in the password field
10
13
  And I click the "Login" button
11
14
  Then I should see the products page
15
+
16
+ StepSet: Proceed to checkout with the standard details
17
+ Given I am on the products page
18
+ When I open the cart
19
+ And I click the "Checkout" button
20
+ And I enter the following checkout information
21
+ | firstName | Ada |
22
+ | lastName | Lovelace |
23
+ | postalCode | 10117 |
24
+ And I click the "Continue" button
25
+ Then I should see the checkout overview
@@ -1,8 +1,8 @@
1
1
  Feature: Shopping
2
2
 
3
- # .saffron is Gherkin plus the StepSet keyword. The login flow is defined
4
- # once in shared.steps.saffron and invoked here; its steps are inlined at
5
- # parse time, so they record once and replay everywhere.
3
+ # .saffron is Gherkin plus the StepSet keyword. The flows below are
4
+ # defined once in shared.steps.saffron; their steps are inlined at parse
5
+ # time, so they record once and replay everywhere.
6
6
 
7
7
  @smoke @cart
8
8
  Scenario: Add a product to the cart
@@ -14,12 +14,7 @@ Feature: Shopping
14
14
  Scenario: Complete a checkout
15
15
  StepSet Log in as the standard user
16
16
  When I add "Sauce Labs Bike Light" to the cart
17
- And I open the cart
18
- And I click the "Checkout" button
19
- And I enter the following checkout information
20
- | firstName | Ada |
21
- | lastName | Lovelace |
22
- | postalCode | 10117 |
23
- And I click the "Continue" button
24
- And I click the "Finish" button
17
+ StepSet Proceed to checkout with the standard details
18
+ Then the overview should list "Sauce Labs Bike Light"
19
+ When I click the "Finish" button
25
20
  Then I should see "Thank you for your order!"
@@ -72,56 +72,78 @@
72
72
  "name": "keyword.control.stepset.saffron"
73
73
  },
74
74
  "2": {
75
- "name": "entity.name.function.stepset.saffron"
76
- }
77
- }
78
- },
79
- "step": {
80
- "match": "^\\s*(Given|When|Then|And|But|\\*)\\s(.*)$",
81
- "captures": {
82
- "1": {
83
- "name": "keyword.other.step.saffron"
84
- },
85
- "2": {
86
- "name": "string.unquoted.step-text.saffron",
75
+ "name": "entity.name.function.stepset.saffron",
87
76
  "patterns": [
88
77
  {
89
- "match": "\"[^\"]*\"",
90
- "name": "string.quoted.double.saffron"
78
+ "include": "#argument"
79
+ },
80
+ {
81
+ "include": "#placeholder"
91
82
  },
92
83
  {
93
- "match": "<[^<>]+>",
94
- "name": "variable.parameter.saffron"
84
+ "include": "#token"
95
85
  }
96
86
  ]
97
87
  }
98
88
  }
99
89
  },
90
+ "step": {
91
+ "begin": "^\\s*(Given|When|Then|And|But|\\*)\\b",
92
+ "beginCaptures": {
93
+ "1": {
94
+ "name": "keyword.other.step.saffron"
95
+ }
96
+ },
97
+ "end": "$",
98
+ "name": "meta.step.saffron",
99
+ "patterns": [
100
+ {
101
+ "include": "#argument"
102
+ },
103
+ {
104
+ "include": "#placeholder"
105
+ },
106
+ {
107
+ "include": "#token"
108
+ }
109
+ ]
110
+ },
100
111
  "docstring": {
101
112
  "begin": "^\\s*\"\"\"",
102
113
  "end": "^\\s*\"\"\"",
103
114
  "name": "string.quoted.docstring.saffron"
104
115
  },
105
116
  "table": {
106
- "match": "^\\s*\\|.*\\|\\s*$",
117
+ "begin": "^\\s*(?=\\|)",
118
+ "end": "$",
107
119
  "name": "meta.table.saffron",
108
- "captures": {
109
- "0": {
110
- "patterns": [
111
- {
112
- "match": "\\|",
113
- "name": "punctuation.separator.table.saffron"
114
- },
115
- {
116
- "match": "<[^<>]+>",
117
- "name": "variable.parameter.saffron"
118
- }
119
- ]
120
+ "patterns": [
121
+ {
122
+ "match": "\\|",
123
+ "name": "punctuation.separator.table.saffron"
124
+ },
125
+ {
126
+ "include": "#placeholder"
127
+ },
128
+ {
129
+ "include": "#token"
120
130
  }
121
- }
131
+ ]
132
+ },
133
+ "argument": {
134
+ "match": "\"[^\"]*\"",
135
+ "name": "string.quoted.double.argument.saffron"
136
+ },
137
+ "placeholder": {
138
+ "match": "<[^<>\\s]+>",
139
+ "name": "variable.parameter.placeholder.saffron"
140
+ },
141
+ "token": {
142
+ "match": "\\{(env:[A-Za-z_][A-Za-z0-9_]*|date(?:[+-]\\d+)?(?::[^}]+)?)\\}",
143
+ "name": "constant.other.token.saffron"
122
144
  }
123
145
  },
124
146
  "fileTypes": [
125
147
  "saffron"
126
148
  ]
127
- }
149
+ }