Writing a scenario
A JSON file, a list of actions, eighteen verbs — none of them numbered. How to build one that still works next month, and the two habits that decide whether it does.
A scenario is a JSON file: where to go, and what to do there. It is the retained browser core’s unit of work — you will write them, reuse them, and put them under version control.
{
"nom": "check-dashboard",
"url": "https://target.local/login",
"actions": [
{"type": "remplir", "selecteur": "input[name=\"username\"]", "valeur": "depuis_secrets", "secret_cle": "username"},
{"type": "remplir", "selecteur": "input[name=\"password\"]", "valeur": "depuis_secrets", "secret_cle": "password"},
{"type": "cliquer", "selecteur": "button[type=\"submit\"]"},
{"type": "attendre_selecteur_present", "selecteur": ".user-menu"}
]
}
Two fields are required: url and actions. Everything else is optional,
and the file is validated against scenarios/schema.json before a browser
starts — a typo in a key name stops the run immediately rather than halfway
through.
Start by looking, without mutating
You cannot write actions for a page you have not read. Map it first:
/opt/dinoer/venv/bin/python3 /opt/dinoer/shot.py \
--url https://target.local/login --a11y --guide-version 1.6
a11y_tree gives you the structure and the selectors. Write the scenario
from that output, not from memory of what the page probably looks like — the
guide’s own reconnaissance-before-mutation rule, not a suggestion.
The reconnaissance scenario, run for real →
The eighteen verbs
| Family | Actions |
|---|---|
| Act | cliquer, remplir, defiler |
| Inside a frame | cliquer_iframe, remplir_iframe |
| Move | naviguer |
| Wait | attendre, attendre_absence, attendre_selecteur_present, attendre_navigation, attendre_url, attendre_reseau_calme, pause |
| Observe | evaluer, extraire_texte |
| MFA | attendre_mfa_ntfy |
| Compose | declencher_scenario, nettoyer_overlay |
Every action targets a CSS selector — there is no numbered overlay to target instead, on purpose. Why →
Each verb with its required and optional keys, and the exit codes, are on the cheat sheet.
Habit one — wait for a signal, never for a duration
A
pauseis a bet on how long something takes. You will lose it, in both directions.
{"type": "cliquer", "selecteur": "button[type=\"submit\"]"},
{"type": "attendre_absence", "selecteur": ".spinner"},
{"type": "attendre_selecteur_present", "selecteur": ".result"}
Set a pause to ten seconds and an operation taking fifteen gives you an answer read while the job is still running — success reported, nothing verified. One taking two wastes eight seconds on every single run. Waiting for a DOM signal makes the scenario take exactly as long as the work does.
Habit two — assert before you act
Put a check at the top. If the page is not the one you expect, the scenario stops before typing anything anywhere:
{"type": "evaluer", "script": "document.title", "contient": "Sign in"}
This costs nothing and it is what stands between a redirect you did not
notice and a password typed into a stranger’s form. auth_indicator does
the same at the file level — a selector that only exists when authenticated,
checked automatically:
{"auth_indicator": ".user-menu", "url": "…", "actions": [...]}
Options the file can carry itself
Some flags belong to the target, not to whoever runs the scenario. Put them in the file so it stays self-contained:
| Property | For |
|---|---|
wait_until | a target that never goes network-idle |
http_credentials | HTTP Basic authentication |
auth_indicator | an automatic authentication check |
intention | a sentence recorded in the operations journal |
Whoever reuses your scenario then does not need to know the target’s quirks.
Never write a credential
{"type": "remplir", "selecteur": "input[name=\"password\"]",
"valeur": "depuis_secrets", "secret_cle": "password"}
The scenario names a key; the encrypted directory holds the value. This is
what makes a scenario safe to commit, and the publication check refuses any
run that finds a password in clear text in one — in a remplir field or
inside an evaluer script.
In short
- Map the page first with
--a11y; write the scenario from that output. urlandactionsare required — everything else is optional.- Every target is a CSS selector — there is no other way to name one.
- Wait for signals, not durations.
- Assert the page before acting on it.
- A credential is always a
secret_cle, never a value, anywhere in the file.