Writing a scenario
A JSON file, a list of actions, eighteen verbs. How to write one from a page you have read, and the two habits that keep it working.
A scenario is a JSON file: where to go, and what to do there. It is the unit of work of the browser core.
{
"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. The file is checked against
scenarios/schema.json before the browser starts: a misspelt key stops the
run at once, not halfway through.
Start by reading the page
You cannot write actions for a page you have not read. Read 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; the reconnaissance scenario gives you the
selectors. Write the scenario from that output, not from what the page
probably looks like.
The reconnaissance scenario →
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 |
| Second factor | attendre_mfa_ntfy |
| Compose | declencher_scenario, nettoyer_overlay |
An action that targets an element takes a CSS selector; there is no numbered overlay to use instead. Why →
The required and optional keys of each verb, 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, and it can be lost either way.
{"type": "cliquer", "selecteur": "button[type=\"submit\"]"},
{"type": "attendre_absence", "selecteur": ".spinner"},
{"type": "attendre_selecteur_present", "selecteur": ".result"}
With a ten-second pause, an operation that takes fifteen is read while it is still running: success reported, nothing verified. One that takes two wastes eight seconds on every run. Waiting for a change in the page makes the scenario last exactly as long as the work.
Habit two: check the page before acting
Put a check at the top. If the page is not the one you expect, the scenario stops before typing anything:
{"type": "evaluer", "script": "document.title", "contient": "Sign in"}
This check is what stands between a redirect you did not notice and a
password typed into someone else’s form. auth_indicator names a selector
that only exists once signed in; the answer then reports whether it was found
(auth_status):
{"auth_indicator": ".user-menu", "url": "…", "actions": [...]}
Options the file can carry
Some settings belong to the target, not to whoever runs the scenario. Put them in the file:
| Property | For |
|---|---|
wait_until | a target whose network never goes quiet |
http_credentials | HTTP Basic authentication |
auth_indicator | checking that the session is signed in |
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. That is
what lets you commit a scenario. Dinoer refuses to run a scenario that
contains a secret written in clear, in a remplir on a field that looks
sensitive or inside an evaluer script.
In short
- Read the page first with
--a11y; write the scenario from that output. urlandactionsare required; everything else is optional.- An element is always targeted by a CSS selector.
- Wait for signals, not durations.
- Check the page before acting on it.
- A credential is always a
secret_cle, never a value, anywhere in the file.