test(e2e): un user physique par batterie — 20 min et 286s de synchro tombent à 3,6 min et 30s

La suite se ralentissait elle-même, de façon monotone. Chaque batterie crée ~11
identités virtuelles FRAÎCHES (`@alice-…`, `@owner-…`, `@recon-…`), chacune avec
ses trois documents de scope et son inbox, et toutes atterrissaient dans le MÊME
user physique — un wallet créé le 10 juillet et réutilisé depuis, que rien ne
nettoyait. Or une resynchronisation à froid est O(taille du user physique), ce
que la doc de cette bibliothèque énonce elle-même. D'où 250s il y a une semaine,
286s avant-hier, et une batterie qui a fini par dépasser les 20 minutes.

Les identités fraîches ne sont pas la faute : ce sont elles qui rendent une
batterie reproductible, une inbox stable accumulant sinon les dépôts des runs
précédents. La faute était de conserver le user physique qui les héberge.

Mesuré : 42/42 en **3,6 min** au lieu de 20+, synchro à froid la plus lente à
**30s** au lieu de 286s.

Le profil reste persistant À L'INTÉRIEUR d'une batterie — CONTRACT 1 et 2
testent précisément cela (reconnexion fidèle sur le même profil, absence de fork
de compte au travers).

Deux garde-fous pour que la prochaine dérive se voie :

- **Le budget appartient au runner**, qui échoue en nommant la cause probable.
  Un `timeout` posé autour de la commande tuait le navigateur, et la suite
  rapportait « Target page, context or browser has been closed » — un message
  qui se lit comme un défaut applicatif, et que j'ai diagnostiqué deux fois de
  travers avant de comparer les durées.
- **La synchro à froid remonte dans le résumé.** C'est le nombre qui a dérivé
  pendant un mois sans que personne le regarde, parce qu'il n'apparaissait qu'au
  détour de la ligne de détail d'une étape.
This commit is contained in:
Sylvain Duchesne
2026-08-06 11:22:04 +02:00
parent ebf866b1f2
commit da6ef4b8b8
2 changed files with 69 additions and 7 deletions
+41 -2
View File
@@ -91,6 +91,37 @@ async function faithfulReconnect(
return { page: p, frame };
}
/**
* The batch's own budget, and the measurement that explains an overrun.
*
* Not a `timeout` wrapped around the command from outside: when that fired it killed the
* browser, and the suite reported `Target page, context or browser has been closed` —
* which reads as an application bug and was twice diagnosed as one. A budget belongs to
* the thing that knows what it is spending it on, and it must say so when it runs out.
*/
const BATCH_BUDGET_MS = 45 * 60 * 1000;
const batchStart = Date.now();
/**
* The slowest cold resynchronisation of the batch — the number that drifted from 250s to
* 286s over a month without anyone looking, because it only ever appeared inside one
* step's detail line. It is the health indicator of the physical user, so it is reported
* with the summary.
*/
let coldSyncMs = 0;
/** Fail with the cause named, rather than letting a killed browser look like a defect. */
function assertWithinBudget(): void {
const spent = Date.now() - batchStart;
if (spent > BATCH_BUDGET_MS) {
throw new Error(
`[e2e] batch budget exceeded (${Math.round(spent / 60000)} min > ` +
`${BATCH_BUDGET_MS / 60000} min). This is almost always the physical user having ` +
"grown: a cold resync is O(its size). Check the cold-sync figure printed above — " +
"it should be stable from batch to batch now that each gets a fresh wallet.",
);
}
}
async function main(): Promise<void> {
console.log("[e2e] building SDK page bundle...");
buildBundle();
@@ -522,7 +553,8 @@ async function main(): Promise<void> {
`[SYNC] reconnect re-reads its OWN persisted ${scope} marker (cold-read)`,
found,
found
? `synced in ${syncMs}ms (reconnect-login ${loginMs}ms) — ${lastDetail}`
? ((coldSyncMs = Math.max(coldSyncMs, syncMs)),
`synced in ${syncMs}ms (reconnect-login ${loginMs}ms) — ${lastDetail}`)
: `NEVER synced within 120s (reconnect-login ${loginMs}ms) — ${lastDetail}`,
);
}
@@ -744,7 +776,14 @@ async function main(): Promise<void> {
// ── Summary ───────────────────────────────────────────────────────────────
const passed = results.filter((r) => r.ok).length;
const failed = results.length - passed;
console.log(`\n══ SDK e2e summary: ${passed} passed, ${failed} failed, ${results.length} total ══`);
const batchMin = ((Date.now() - batchStart) / 60000).toFixed(1);
console.log(
`\n══ SDK e2e summary: ${passed} passed, ${failed} failed, ${results.length} total ` +
`— batch ${batchMin} min, slowest cold sync ${Math.round(coldSyncMs / 1000)}s ══`,
);
// A fresh wallet per batch is what should keep the cold sync flat; if it climbs from
// one batch to the next, the per-batch wallet is not being discarded.
assertWithinBudget();
if (failed > 0) {
console.log("Failures:");
for (const r of results.filter((x) => !x.ok)) console.log(` - ${r.name}: ${r.detail ?? ""}`);