refactor(e2e): la mécanique de test devient un paquet à part, ng-e2e-helpers
Créer un portefeuille, en obtenir le .ngw, traverser le broker : ce n'est pas du ressort du polyfill. C'est un besoin commun au polyfill et à toute application NextGraph — et surtout, ça SURVIT à la migration, alors que le polyfill est fait pour disparaître. L'y laisser, c'était le faire mourir avec lui ou rendre le polyfill indéracinable. Le paquet n'importe rien du polyfill — vérifié mécaniquement — et déclare Playwright et @ng-org/web en pairs, le consommateur devant maîtriser les versions. Sa surface : attentes bornées, mesure, navigateur, profils, portefeuille, traversée du broker, rapport d'exécution, et reconnaissance des modes de panne connus. La preuve qu'il est utilisable de l'extérieur : le polyfill le CONSOMME, sans garder de copie. Restent chez lui les parcours, la barrière et les identités virtuelles, qui lui sont propres. Le verrou entre exécutions disparaît, remplacé par un profil par exécution. Il ne traitait qu'un symptôme — un répertoire partagé que la création de portefeuille effaçait. Avec un profil par exécution il n'y a plus rien à sérialiser, les exécutions concurrentes deviennent indépendantes, et la collision entre deux dépôts s'évanouit au lieu d'être exportée. Six exécutions : aucun répertoire ni Chromium orphelin. Et la connaissance descriptive est séparée du pilotage : URL, sélecteurs et inventaire ordonné des écrans sont des données, passées DANS la page pour la reconnaissance — donc un échec nomme le même écran que celui sur lequel on dispatchait. Un échec de navigateur est désormais nommé comme tel — « the actors browser STOPPED ANSWERING » — au lieu de sortir sous le nom de l'opération innocente qui se trouvait en vol.
This commit is contained in:
@@ -17,19 +17,18 @@ import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import type { Frame, Page, BrowserContext } from "playwright";
|
||||
import {
|
||||
buildBundle,
|
||||
serveHarness,
|
||||
armSuiteDeadline,
|
||||
closeContext,
|
||||
ensureWallet,
|
||||
launchWalletContext,
|
||||
launchCleanProfileContext,
|
||||
importWalletViaFile,
|
||||
closeQuietly,
|
||||
emptyProfileContext,
|
||||
importWalletFile,
|
||||
launchWatchedContext,
|
||||
newPage,
|
||||
setupBrokerPage,
|
||||
PROFILE_DIR,
|
||||
} from "./broker";
|
||||
import { armSuiteDeadline, closeQuietly, within } from "./deadline";
|
||||
import { acquireRunLock } from "./run-lock";
|
||||
within,
|
||||
type RunProfile,
|
||||
} from "ng-e2e-helpers";
|
||||
import { WALLET, buildBundle, mintBatchWallet, serveHarness } from "./harness-page";
|
||||
|
||||
type Check = { name: string; ok: boolean; detail?: string };
|
||||
const results: Check[] = [];
|
||||
@@ -105,7 +104,7 @@ async function faithfulReconnect(
|
||||
p.on("console", (m) => {
|
||||
if (m.type() === "error") console.error("[iframe console:reconnect]", m.text());
|
||||
});
|
||||
const frame = await setupBrokerPage(p, url);
|
||||
const frame = await setupBrokerPage(p, url, WALLET.password);
|
||||
await frame.waitForFunction(() => (window as any).__sdk !== undefined, { timeout: 30000 });
|
||||
await frame.waitForFunction(() => (window as any).__sdk.status() === "connected", { timeout: 60000 });
|
||||
return { page: p, frame };
|
||||
@@ -161,21 +160,21 @@ function assertWithinBudget(): void {
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
// Before anything touches the shared profile: this batch is about to DELETE it (see
|
||||
// `ensureWallet`), so a second run alive right now would be destroyed by this one.
|
||||
acquireRunLock("the polyfill suite (e2e/run.ts)", PROFILE_DIR);
|
||||
armSuiteDeadline("the polyfill suite", BATCH_BUDGET_MS);
|
||||
console.log("[e2e] building SDK page bundle...");
|
||||
buildBundle();
|
||||
console.log("[e2e] ensuring dedicated lib wallet...");
|
||||
await ensureWallet();
|
||||
// This batch's own physical user, in a profile directory of its own. Nothing to serialise
|
||||
// against another run: there is no shared directory left for two runs to fight over, so a
|
||||
// suite from a consuming application can drive the same broker at the same time.
|
||||
console.log("[e2e] minting this batch's wallet...");
|
||||
const wallet: RunProfile = await mintBatchWallet("the polyfill suite (e2e/run.ts)");
|
||||
const { url, close: closeServer } = await serveHarness();
|
||||
console.log(`[e2e] harness served at ${url}`);
|
||||
|
||||
let ctx: BrowserContext | null = null;
|
||||
let page: Page | null = null;
|
||||
try {
|
||||
ctx = await launchWalletContext("sdk-harness");
|
||||
ctx = await launchWatchedContext("sdk-harness", wallet.dir);
|
||||
page = await newPage("the SDK harness", ctx);
|
||||
page.on("pageerror", (e) => console.error("[iframe error]", e.message));
|
||||
page.on("console", (m) => {
|
||||
@@ -183,7 +182,7 @@ async function main(): Promise<void> {
|
||||
});
|
||||
|
||||
console.log("[e2e] loading SDK page in broker iframe...");
|
||||
const frame = await setupBrokerPage(page, url);
|
||||
const frame = await setupBrokerPage(page, url, WALLET.password);
|
||||
|
||||
// Wait for the bridge to exist + the broker session to connect.
|
||||
await frame.waitForFunction(() => (window as any).__sdk !== undefined, { timeout: 30000 });
|
||||
@@ -496,20 +495,20 @@ async function main(): Promise<void> {
|
||||
fs.writeFileSync(ngwPath, Buffer.from(exp.b64, "base64"));
|
||||
|
||||
let cleanCtx: BrowserContext | null = null;
|
||||
let cleanDir: string | null = null;
|
||||
let cleanProfile: RunProfile | null = null;
|
||||
let cleanPage: Page | null = null;
|
||||
try {
|
||||
const launched = await launchCleanProfileContext();
|
||||
const launched = await emptyProfileContext("the clean-profile cold read");
|
||||
cleanCtx = launched.ctx;
|
||||
cleanDir = launched.dir;
|
||||
cleanProfile = launched.profile;
|
||||
cleanPage = await newPage("the clean-profile session", cleanCtx);
|
||||
cleanPage.on("pageerror", (e) => console.error("[iframe error:clean]", e.message));
|
||||
cleanPage.on("console", (m) => { if (m.type() === "error") console.error("[iframe console:clean]", m.text()); });
|
||||
|
||||
// Import the SAME wallet into the empty profile (broker-only repos), then open
|
||||
// the SDK page in a fresh broker session over it.
|
||||
await importWalletViaFile(cleanPage, ngwPath);
|
||||
const cleanFrame = await setupBrokerPage(cleanPage, url);
|
||||
await importWalletFile(cleanPage, ngwPath, WALLET.password);
|
||||
const cleanFrame = await setupBrokerPage(cleanPage, url, WALLET.password);
|
||||
await cleanFrame.waitForFunction(() => (window as any).__sdk !== undefined, { timeout: 30000 });
|
||||
await cleanFrame.waitForFunction(() => (window as any).__sdk.status() === "connected", { timeout: 60000 });
|
||||
const cleanInfo = await sdkGet<any>(cleanFrame, "sessionInfo");
|
||||
@@ -528,7 +527,7 @@ async function main(): Promise<void> {
|
||||
} finally {
|
||||
if (cleanPage) await closeQuietly("the clean-profile page", () => cleanPage!.close());
|
||||
if (cleanCtx) await closeContext("clean-profile", cleanCtx);
|
||||
try { if (cleanDir) fs.rmSync(cleanDir, { recursive: true, force: true }); } catch { /* ignore */ }
|
||||
cleanProfile?.discard();
|
||||
try { fs.rmSync(ngwPath, { force: true }); } catch { /* ignore */ }
|
||||
}
|
||||
});
|
||||
@@ -826,6 +825,10 @@ async function main(): Promise<void> {
|
||||
if (page) await closeQuietly("the SDK harness page", () => page!.close());
|
||||
if (ctx) await closeContext("sdk-harness", ctx);
|
||||
closeServer();
|
||||
// This run's physical user goes with it. Explicit here and also registered on the way
|
||||
// out, so a run that is killed mid-batch still takes its profile — and the Chromium
|
||||
// holding it — with it, instead of leaving both for a host that has to keep running.
|
||||
wallet.discard();
|
||||
}
|
||||
|
||||
// ── Summary ───────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user