docs: deux concepts pour ce que la journée a appris

sign-in — comment un utilisateur passe de rien à une identité qui agit. La
barrière, l'identifiant qui franchit une frontière de partition par l'URL, et
surtout : la redirection vers le broker appartient à @ng-org/web, vérifié dans
son bundle. Le polyfill ne la réimplémente pas ; ce qui lui revient est la seule
chose qu'init() ne peut pas faire, écrire l'identifiant dans l'URL avant qu'il
ne la lise.

La feuille centrale dit pourquoi régler l'identité et se connecter sont deux
actes séparés : l'un ne demande aucune session, l'autre en exige une, et les
confondre bloque dans un sens et casse le partage en silence dans l'autre. Le
piège encore vivant — une connexion abandonnée qui reste joignable — est
consigné comme tel, non corrigé.

e2e-harness — ce que chaque suite juge, et deux choses qu'un agent doit savoir
avant de diagnostiquer : le raccourci qui pré-injectait l'identifiant dans l'URL
a tenu deux défauts invisibles pendant des mois (un lien de téléchargement vers
un 404 que rien ne servait, et une barrière inatteignable pour tout nouvel
arrivant) ; et un tuyau devtools qui lâche tue une exécution sans que Playwright
n'émette d'événement, ce qui ressemble à un défaut produit et n'en est pas.

Le vocabulaire gagne settle, barrier, journey et batch. J'avais aussi introduit
« hand-over » pour la redirection : retiré, c'est le mot de NextGraph qui
l'emporte.
This commit is contained in:
Sylvain Duchesne
2026-08-11 19:24:00 +02:00
parent 16e24f67f9
commit 7c2e8d8f1f
10 changed files with 234 additions and 10 deletions
@@ -0,0 +1,27 @@
---
type: overview
summary: What each end-to-end suite judges, the shortcut that kept two defects invisible, and the failure mode that is not ours to fix
triggers:
keywords: [e2e, harness, playwright, chromium, journey, suite, profile, broker page, notebook, wallet profile]
paths:
- "packages/polyfill/e2e/**"
vocabulary:
- term: journey
gloss: one named end-to-end story in the applicative suite, reported pass or fail as a whole
not: [scenario, test case, spec]
- term: batch
gloss: one run of a suite, which mints its own physical user and discards the previous one
not: [session, campaign]
---
# e2e-harness — the suites, and what they can and cannot tell you
Three suites drive real browsers against the real broker and the real wallet application. They are the only place the emulation meets what it emulates, so a green unit suite says nothing about them.
They are also expensive and, on a machine whose network moves, unreliable in ways that look exactly like product defects. Most of what this concept holds exists so an agent does not spend hours diagnosing a product bug that is a transport failure — or, worse, trust a green run that measured nothing.
## Read first
- `knowledge_what-each-suite-judges` — which suite answers which question.
- `rule_never-shortcut-the-sign-in` — the shortcut that hid a 404 and a regression at once.
- `caveat_a-dropped-pipe-kills-a-run` — the failure mode that is not ours.
@@ -0,0 +1,23 @@
---
type: caveat
summary: Chromium's control pipe drops without Playwright emitting close or disconnected, so a run dies mid-flight — it looks like a product defect and is not one
last_checked: 2026-08-11
---
# A run can die from a dropped pipe, and it is not the product
Chromium's devtools pipe sometimes drops mid-run. It logs a terminated-pipe message and exits cleanly, and **Playwright emits neither `close` nor `disconnected`** — observed four times out of four. From the client's side the browser simply stops answering.
Before waits were bounded this was fatal in a specific way: the suite blocked in its own teardown, so it printed **neither its summary nor the failure already on its way out**. Hours went into diagnosing silence. Every wait is now bounded and names what it was waiting for, so a lost browser costs seconds and a report.
**How to recognize it.** The run dies without a coherent failure, or several unrelated interactions time out at once, or the summary is missing entirely. If the suite reports a named deadline on a browser operation rather than a failed assertion, suspect the transport before suspecting the code.
**It is not ours to fix.** It is not caused by how a child process is spawned, nor by a leftover holding the profile, nor by overlapping launches — all three were probed and ruled out. It looks like Playwright losing its file descriptors without telling its client. Worth reporting upstream.
## The host can be the cause too
A machine that reconfigures its network — a container in a crash-restart loop cycling its virtual interface, for instance — makes the applicative suite unmeasurable. The browser answers with a network-changed error, broker sockets fail, and every failure looks like a different product bug.
This has happened here: seven failures out of ten runs, all transport, none product. The check costs seconds — watch for repeated link events, and look for a container restarting.
**The discipline that follows:** a suite that fails for transport reasons has measured nothing. Do not read it as a red baseline, do not chase it as a regression, and do not commit against it. Restore a stable environment first, then measure.
@@ -0,0 +1,28 @@
---
type: knowledge
summary: Which suite answers which question, what a batch costs, and why two runs must never overlap
---
# What each suite judges
**The polyfill suite** exercises the published surface against the real broker: capabilities, reads, inboxes, reactivity. It is the one that judges whether the emulation behaves like the target.
**The applicative suite** drives the example application through a browser, as a person would — several identities, several pages, assertions on what is on screen rather than on what the library returns. It judges whether an application built on this package actually works, including the sign-in a person really performs.
**The reactivity suite** isolates document subscription.
A unit suite cannot replace any of them, and none of them replaces the unit suite: they are slow, they depend on live external services, and they cannot enumerate a case space.
## What a batch costs, and why
Every `batch` mints its own physical user by driving the wallet application's real interface, then discards the previous one. That is deliberate — identities must not leak between runs — and it puts a floor under every run that no test filter can remove.
Consequences worth knowing before optimizing anything: the setup runs before any `journey`, so a filter saves journey time only; and the wallet profile is a single directory shared by every suite.
## Two runs must never overlap
Because the profile is shared and each `batch` discards what it finds, a second run started while a first is alive destroys the first — which then fails in a way that reads as a product defect. Several measurements were lost to this before it was made structural.
It is now enforced by a lock rather than by discipline, and a browser left behind by a killed run is reclaimed. If you ever find yourself reasoning about "was another run going?", check the lock rather than your memory.
The related trap that made it self-perpetuating: discarding the profile was once conditioned on a marker written at the *end* of a batch, so a run killed before writing it left a profile the next run happily reused — and inherited its breakage. Discarding now keys on the profile itself.
@@ -0,0 +1,24 @@
---
type: rule
summary: An end-to-end journey must reach its starting state through the system, never by pre-seeding what the sign-in would have produced — the shortcut keeps whole features untested while staying green
---
# Never hand a journey the state the sign-in would have produced
**Rule:** An end-to-end `journey` must arrive at its starting state **through the system**. Never pre-seed the identifier in the URL, never pre-import a wallet into the profile under test, never set the acting identity directly. If reaching a state through the system is awkward, that awkwardness is the finding — not a reason to skip it.
**Why.** Every applicative `journey` used to append the identifier to the application URL before loading it. The identity then resolved from the URL, the `barrier` never appeared, and the suite was green for months. It was green over:
- a download link pointing at a file **nobody served** — the barrier handed out a 404, and no test had ever clicked it. Worse, the test server answered every unknown path with the application's own HTML, so a missing file could not fail even in principle;
- an ordering defect that made the barrier unreachable for any first-time user — the application handed the page to the broker before the barrier could appear, leaving a newcomer at a login page with no wallet and no way to get one;
- and, later, a regression in sharing.
The first journey that actually walked a newcomer's path — empty profile, no wallet, real download, real import — found all of it immediately. The coverage hole and the defects protected each other: the shortcut existed because the real path was tedious, and the real path stayed broken because nothing walked it.
**How to apply.** The tell is mechanical: a `journey` that *writes* a value the system is supposed to produce. Ask *"by which call would a real person obtain this?"* — no answer means the feature is incomplete, not that the test needs help.
This is not a blanket ban on fixtures. Shared setup — building the application, minting the `batch` user, serving files — is legitimate and belongs before the journeys. What is banned is seeding **the specific state under test**: a journey about sign-in may not start signed in.
Journeys that legitimately need an already-signed-in actor may use the shared sign-in helper. That is exactly why at least one journey must not: without it, the helper's own path is the untested one.
Read alongside the global rules on covering the case space and on each actor obtaining its inputs through the system — same failure shape, and it has now produced real defects here twice.