Dinoer

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, then dernier_code_http.
  • A fast succes: true on a workflow you expected to be slow is a warning, not a result.