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:
@@ -72,15 +72,38 @@ export function serveHarness(): Promise<{ url: string; close: () => void }> {
|
||||
|
||||
/**
|
||||
* Create the dedicated lib wallet once (headless UI flow on nextgraph.eu),
|
||||
* persisted in PROFILE_DIR. Mirrors Festipod's ensureAuth but with the lib's own
|
||||
* wallet name + profile. Idempotent via the ready marker.
|
||||
* persisted in PROFILE_DIR for the duration of the batch.
|
||||
*
|
||||
* ── One PHYSICAL user per batch, not one forever ──────────────────────────
|
||||
* This used to reuse a single wallet across every run, guarded by a ready marker. That
|
||||
* made the suite slow itself down, monotonically: each batch mints ~11 FRESH virtual
|
||||
* identities (`@alice-…`, `@owner-…`, `@recon-…`), each with three scope documents and
|
||||
* an inbox, and they all land in the SAME physical user. Nothing ever removed them. A
|
||||
* cold resynchronisation is O(the physical user's size) — which this library's own docs
|
||||
* state — so the wallet created on 2026-07-10 had grown enough to take 286s on a single
|
||||
* sync step, against 250s a week earlier, and the drift was invisible because no one
|
||||
* measured it.
|
||||
*
|
||||
* The fresh identities are not the mistake — they are what makes a batch reproducible
|
||||
* (a stable inbox accumulates its past runs' deposits otherwise). The mistake was
|
||||
* keeping the physical user that holds them. So: a new one per batch, which also makes
|
||||
* the cold-sync duration comparable from one run to the next instead of being a number
|
||||
* that only ever grows.
|
||||
*
|
||||
* What this does NOT change: the profile stays persistent WITHIN a batch, because
|
||||
* CONTRACT 1 and 2 test exactly that (a faithful reconnect over the same profile, and
|
||||
* the absence of an account fork across it).
|
||||
*/
|
||||
export async function ensureWallet(): Promise<void> {
|
||||
if (fs.existsSync(WALLET_READY_MARKER)) {
|
||||
console.log("[e2e] dedicated lib wallet present — skipping creation");
|
||||
return;
|
||||
const age = Date.now() - fs.statSync(WALLET_READY_MARKER).mtimeMs;
|
||||
console.log(
|
||||
`[e2e] discarding the previous batch's wallet (${Math.round(age / 60000)} min old) — ` +
|
||||
"a physical user is per-batch, see ensureWallet",
|
||||
);
|
||||
fs.rmSync(PROFILE_DIR, { recursive: true, force: true });
|
||||
}
|
||||
console.log("[e2e] creating dedicated lib wallet on nextgraph.eu...");
|
||||
console.log("[e2e] creating this batch's wallet on nextgraph.eu...");
|
||||
fs.mkdirSync(PROFILE_DIR, { recursive: true });
|
||||
const ctx = await chromium.launchPersistentContext(PROFILE_DIR, {
|
||||
headless: true,
|
||||
|
||||
@@ -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 ?? ""}`);
|
||||
|
||||
Reference in New Issue
Block a user