diff --git a/.project/concepts/bdd-testing/knowledge_multibrowser-harness.md b/.project/concepts/bdd-testing/knowledge_multibrowser-harness.md index 0068703..a461f57 100644 --- a/.project/concepts/bdd-testing/knowledge_multibrowser-harness.md +++ b/.project/concepts/bdd-testing/knowledge_multibrowser-harness.md @@ -38,7 +38,7 @@ Ne **pas** confondre `@multibrowser` (plusieurs navigateurs) avec `@shared-walle ## Parcours humain — e2e du mécanisme produit (vert) -Scénario `@humain` : valide le flux RÉEL de distribution du wallet **de bout en bout, via la vraie app**, pas l'injection de test. Un navigateur vierge ouvre l'app staging → l'`AccessGateScreen` propose le **fichier** + le **mot de passe** → on télécharge le fichier **depuis l'écran**, on vérifie que le mot de passe affiché **égale** celui du wallet → import sur `nextgraph.eu` « Import a Wallet File » → retour → clic « Entrer » → app connectée (`ConnexionScreen`). +Scénario `@humain` : valide le flux RÉEL de distribution du wallet **de bout en bout, via la vraie app**, pas l'injection de test. Un navigateur vierge ouvre l'app staging → l'`AccessGateScreen` propose le **fichier** + le **mot de passe** → on télécharge le fichier **depuis l'écran**, on vérifie que le mot de passe affiché **égale** celui du wallet → import sur `nextgraph.eu` « Import a Wallet File » → retour → on **saisit un identifiant** puis clic « Entrer » (nommer l'espace et ouvrir le wallet = un seul acte, cf. concept `app-security` [[decision_2026-07-06_identifier-at-access-barrier]]) → app connectée, arrivée directe sur l'accueil (plus d'écran « nom d'utilisateur » séparé). - **Wallet e2e** : un fichier `.ngw` (`festipod-e2e-tests`, mot de passe = identifiant) placé **à la racine du worktree** ; `findE2eWalletFile()` le localise (`*.ngw`). Gitignoré → chaque environnement doit l'ajouter (sinon erreur claire). - `pool.ensureStagingApp()` (`hooks.ts`) — build **isolé** `bun run build.ts --outdir=dist-staging` (barrière d'accès **ON par défaut** ; mot de passe gravé + **fichier copié** en `/shared-wallet.ngw`, cf. `build.ts`), servi statiquement. Mémoïsé, lazy (seul `@humain` le paie). diff --git a/src/app/AuthGate.tsx b/src/app/AuthGate.tsx index d7509d0..8b6a245 100644 --- a/src/app/AuthGate.tsx +++ b/src/app/AuthGate.tsx @@ -13,10 +13,11 @@ * screens, not the auth flow). Absent → gate ON. */ -import type { ReactNode } from 'react'; +import { useEffect, type ReactNode } from 'react'; import { useNextGraph } from '../shared/context/NextGraphContext'; import { useAccount } from '../shared/context/AccountContext'; import { AccessGateScreen } from '../modules/auth/screens/AccessGateScreen'; +import { useRouter, useNavigate } from './router'; declare global { // eslint-disable-next-line no-var @@ -27,6 +28,18 @@ const GATE_DISABLED = globalThis.__FESTIPOD_ACCESS_GATE_DISABLED__ === true; export function AuthGate({ children }: { children: ReactNode }) { const { status, error, connect } = useNextGraph(); const { username, login } = useAccount(); + const { route } = useRouter(); + const navigate = useNavigate(); + + // Once connected AND identified, leave the disconnected welcome screen for the + // app home. The identifier is now set at the barrier (before the broker + // round-trip), so on return the app can land on '/' with a session already + // open; the removed ConnexionScreen used to do this navigate on login. + useEffect(() => { + if (!GATE_DISABLED && status === 'connected' && username && route.page === 'welcome') { + navigate('/home'); + } + }, [status, username, route.page, navigate]); // Gate explicitly disabled (no-gate build / @e2e harness) → straight to app. if (GATE_DISABLED) { diff --git a/src/modules/workshop/features/multibrowser-harness.feature b/src/modules/workshop/features/multibrowser-harness.feature index c8e685e..cdd9a39 100644 --- a/src/modules/workshop/features/multibrowser-harness.feature +++ b/src/modules/workshop/features/multibrowser-harness.feature @@ -69,7 +69,5 @@ Fonctionnalité: Harness multi-navigateur — modèles private-wallet et shared- Étant donné un nouveau testeur ouvre Festipod en staging sur un navigateur vierge Alors Festipod affiche l'écran d'accès avec le portefeuille à télécharger Quand le testeur télécharge le portefeuille et l'importe sur nextgraph.eu - Et le testeur revient sur Festipod et clique « Entrer » - Alors Festipod est connecté et propose de choisir un nom d'utilisateur - Quand le testeur choisit un nom d'utilisateur + Et le testeur revient sur Festipod, saisit un identifiant et clique « Entrer » Alors il arrive sur l'accueil de l'application diff --git a/src/modules/workshop/steps/data/multibrowser.steps.ts b/src/modules/workshop/steps/data/multibrowser.steps.ts index 0c04a17..636ebce 100644 --- a/src/modules/workshop/steps/data/multibrowser.steps.ts +++ b/src/modules/workshop/steps/data/multibrowser.steps.ts @@ -57,34 +57,22 @@ When('le testeur télécharge le portefeuille et l\'importe sur nextgraph.eu', a await pool.importWalletViaFile(page, filePath!, (this as any).displayedPassword); }); -When('le testeur revient sur Festipod et clique « Entrer »', async function (this: FestipodWorld) { +When('le testeur revient sur Festipod, saisit un identifiant et clique « Entrer »', async function (this: FestipodWorld) { const handle = this.browser('H'); await handle.page.goto((this as any).stagingUrl, { waitUntil: 'domcontentloaded' }); + // Saisit son identifiant (il nomme l'espace virtuel) — « Entrer » reste désactivé + // tant qu'il est vide. Naming the space and opening the wallet are one act. + const idf = handle.page.locator('[data-testid=identifier-input]'); + await idf.waitFor({ state: 'visible', timeout: 15000 }); + await idf.fill('testeur'); const entrer = handle.page.getByText('Entrer', { exact: true }); - await entrer.waitFor({ state: 'visible', timeout: 15000 }); - await entrer.click(); // déclenche le redirect vers le broker + await entrer.click(); // enregistre l'identifiant PUIS déclenche le redirect broker await handle.page.waitForURL('**nextgraph**', { timeout: 20000 }).catch(() => {}); // Le broker demande de déverrouiller le wallet fraîchement importé → son mot de // passe (completeBrokerLogin attend la page de login wallet de façon robuste). handle.appFrame = await pool.completeBrokerLogin(handle.page, (this as any).stagingUrl, pool.sharedWalletPassword); }); -Then('Festipod est connecté et propose de choisir un nom d\'utilisateur', async function (this: FestipodWorld) { - const handle = this.browser('H'); - expect(handle.appFrame, 'l\'app doit être chargée dans l\'iframe broker').to.not.equal(null); - // Connecté → AuthGate passe l'AccessGateScreen et montre ConnexionScreen. - await handle.appFrame!.waitForFunction( - () => /Choisissez votre nom d'utilisateur|Connexion/.test(document.body?.innerText ?? ''), - { timeout: 30000 }, - ); -}); - -When('le testeur choisit un nom d\'utilisateur', async function (this: FestipodWorld) { - const frame = this.browser('H').appFrame!; - await frame.locator('input[placeholder="@votrepseudo"]').fill('@testeur'); - await frame.getByRole('button', { name: 'Se connecter' }).click(); -}); - Then('il arrive sur l\'accueil de l\'application', async function (this: FestipodWorld) { const frame = this.browser('H').appFrame!; // On atterrit sur /home (l'app), PAS sur l'onboarding hors-connexion ('/'