The session does not survive between two calls
You log in, the answer proves it worked, and the next call lands back on the login page — or worse, reports success and does nothing. What is actually happening, and the rule that avoids it.
The symptom. You split a workflow across two calls. The first
authenticates and saves the session; the second reuses it. Either the second
call lands back on the login page, or — and this one is worse — it returns
succes: true in about a second and changes nothing on the target.
What is actually happening
A saved session is a Playwright storage_state: cookies and localStorage,
nothing else. Two things are not in it, and they are the two that break
you.
The DOM state is not in it. A checkbox you ticked, a <select> you set,
a dialog you opened — all gone on resume. The page comes back clean. Your
scenario then runs against a fresh page, hits whatever element the selector
now matches, and reports success. Nothing errors, because nothing went wrong
from Dinoer’s point of view: it clicked the selector, and the selector
matched something.
The server-side session is not in it either. Many applications call
something equivalent to session_regenerate_id() right after login. The
cookie you saved is the one from before the regeneration. The client-side
snapshot is perfectly valid and perfectly useless.
The rule
Never split a stateful workflow. Log in, act and confirm in a single call, even when that means logging in again.
A single call takes an array of actions — that is the whole point of the array:
{"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"},
{"type": "cliquer", "selecteur": "#next-step"}
]}
One browser launch, one final answer. The split version costs a Playwright start and a storage reload per call — it is slower per step and it loses your state between them.
When persistence is the right tool
Session persistence is not a trap, it is a tool with a narrow purpose: reading several authenticated pages that do not depend on each other. A dashboard, then a settings page, then a report — each one self-contained.
# Authenticate once
/opt/dinoer/venv/bin/python3 /opt/dinoer/shot.py \
--url https://target.local/login --guide-version 1.6 \
--actions login.json --sauver-session ~/session.json
# Read as many independent pages as you like
/opt/dinoer/venv/bin/python3 /opt/dinoer/shot.py \
--url https://target.local/dashboard --guide-version 1.6 \
--reprendre-session ~/session.json
The line to hold: persistence carries what you are, not what you were doing.
Read the answer, not just the verdict
Every call after --reprendre-session tells you whether the session held:
"boussole": {
"session_derive": true,
"url_courante": "https://target.local/login",
"dernier_code_http": 302
}
session_derive: true means you are no longer where you thought you were.
Re-run the full login, without --reprendre-session.
Then read dernier_code_http, because two very different failures look
identical. A genuinely expired session and an application error hidden
behind display_errors=0 both dump you on the login page with
session_derive: true. A 302 or 200 points to a real expiry — log in
again. A 500 or a 4xx means the application broke and your session was
never the problem; logging in again will waste your time.
One nuance: on a run with several naviguer actions, this field reflects
the last navigation only, which is not necessarily the one that explains
the drift.
In short
- Stateful — login, act, confirm: one call, one array of actions.
- Stateless — reading independent authenticated pages: persistence is fine.
- After every resume: check
session_derive, thendernier_code_http. - A fast
succes: trueon a workflow you expected to be slow is a warning, not a result.