Test data
Values a scenario needs do not have to be typed into the feature file. Put
them in JSON files under data/, and point at them from a step:
data/
users.json
{
"admin": { "email": "admin@test.com", "role": "Admin" },
"roles": ["Admin", "Editor", "Viewer"]
}
When I sign in as {data:users.admin.email}
Then the profile should show the role {data:users.admin.role}
And the first role offered should be {data:users.roles.0}
A reference is the file name without .json, then the path to the value,
separated by dots. A number picks an entry from a list.
| You write | It means |
|---|---|
{data:users.admin.email} |
one value from data/users.json |
every {data:users.roles} in a Then step |
check each value of a list |
Examples: {data:roles} |
outline rows from data/roles.csv or a JSON list (.saffron only) |
{unique:id} |
a value that is new on every run |
--env staging |
lay data/*.staging.json over the base files |
What does not belong in data/. Secrets. Data files are committed like
the rest of your tests, so passwords and keys stay in {env:VAR}.
Where it works. Step text, data table cells and doc strings, in
.saffron and .feature files alike, and inside step sets. The folder
is data by default; "dataDir" in saffron.config.json moves it.
Single values
What happens to it. {data:...} is in the same family as {env:VAR}
and {date+1}. While recording, the agent is given the real value; before
the recording is saved, that value is turned back into the token. Every
replay then reads the file again. So:
- Change a value in the file, and the next replay uses it, at zero tokens. Nothing is recorded again.
- If a recording still has the OLD value written out somewhere (the agent matched a sentence that contained it, say), changing the value makes that scenario stale and it records again, rather than quietly typing or checking yesterday's data. The same rule data tables follow.
- A file or key that does not exist stops the run before any browser opens, naming the reference, what is missing and the scenario using it.
- Where a step uses a value (typing it, matching it), the reference must point at ONE value; pointing at an object is an error that suggests the entry to use. A list is for the step below.
Checking a whole list: "every"
Point a Then step at a list and say
that every value should be there:
Then the status filter should list every {data:enums.OrderStatus}
Then the roles table should show every {data:users.roles}
The recording holds ONE assertion that names the list and the place
(dropdown, table, list, menu), not one check per value. Replay reads the
list from the file each time, so adding "Refunded" to the JSON makes the
next run check for it, at zero tokens, and a missing one fails by name:
"Refunded" from {data:enums.OrderStatus} is not shown in the target (4 of 5 found). It checks that every listed value is present, matched as whole
text ("Paid" is not satisfied by "Unpaid"); extra entries such as "All" or
"Select…" are fine. It works on a closed dropdown too.
Examples from a file
In a .saffron file, a Scenario Outline can take its rows from the data
folder instead of an inline table:
Scenario Outline: Role sees its menu
Given I sign in as <role>
Then I should see the <menu> menu
Examples: {data:roles}
{data:roles} reads data/roles.csv, whose header row names the
placeholders, or a JSON list of records ({data:users.accounts} works
too). An outline records once and replays once per row, so adding a row
to the file is one more zero-token replay, with nothing recorded. Rows
are numbered "(example 1)", "(example 2)" like inline ones. A placeholder
the file has no column for is an error naming both; a .feature file that
tries this is told to rename itself to .saffron.
Values that must be new on every run: {unique:name}
A scenario that creates something (a user, an order, a project) collides with its own leftovers the second time it runs, unless what it types is new each time:
When I register as user-{unique:id}@test.com
Then the welcome banner should greet user-{unique:id}@test.com
{unique:id} is different on every run and the same everywhere inside one
run, so a later step finds what an earlier step created. It is 10
lowercase letters and digits, starting with a letter; {unique:id:digits}
is 9 digits for numeric fields. While recording, the agent types the
generated value and Saffron turns it back into the token. When a run is not
green, the report lists what each token stood for, so you can find the
record it left behind.
Data per environment
--env staging (or SAFFRON_ENV=staging, or "env": "staging" in the
config) lays data/users.staging.json over data/users.json: objects
merge key by key, lists and plain values are replaced, and a file that has
no staging version is used as it is. The step keeps saying
{data:users.admin.email} on every target. For Examples, a
roles.staging.csv replaces roles.csv whole. saffron status shows the
selected environment and any reference that does not resolve in it.
In the editor
Both IDE integrations complete {data: (files first, then keys, showing
each value), show the current value on hover, jump to the key in the data
file, and underline a reference that does not resolve. {data:...} and
{unique:...} are highlighted like {env:VAR}.