- Fix US-6: Rename to "M'inscrire/me désinscrire à un atelier" and remove
non-atelier scenarios
- Fix US-9: Add missing scenario for "Ajouter une photo personnelle"
- Fix US-13: Add scenarios for "Modifier" and "Supprimer" événement,
plus "Retirer une organisation"
- Fix US-14: Mark all scenarios as non-implemented (no atelier screen)
- Fix US-15: Distinguish événement vs atelier scenarios, remove unclear ones
- Fix US-16: Implement 4 scenarios for MeetingPointsScreen
- Fix US-22: Implement 5 scenarios for ShareProfileScreen (parrainage)
- Fix US-23: Implement 3 scenarios for profile sharing
Add missing scenarios from user story descriptions:
- US-1: Programme détaillé, zone partage collective
- US-2: Programme détaillé des ateliers
- US-4: Icône ajouter commentaire
- US-5: Interactions Date/Heure/Lieu
- US-7: Recherche Mobilizon
- US-8: 4 scenarios for macro-événement
- US-12: Vue carte des événements
- US-20: Profils publiques
Add new step definitions:
- l'écran contient un bouton {string}
- l'écran contient un champ {string}
- l'écran contient un texte {string}
- l'écran contient un avatar
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
16 KiB
Cucumber BDD Integration
This document explains how the Cucumber BDD testing framework is integrated into Festipod.
Overview
Festipod uses Cucumber.js with TypeScript for Behavior-Driven Development testing. All feature files are written in French using Gherkin syntax. The integration uses static source code analysis rather than browser automation.
Architecture
Feature Files (French Gherkin)
↓
Cucumber Parser (language: "fr")
↓
Step Definition Matching
↓
World Instance (FestipodWorld)
↓
Inline Detection Logic (screen-specific regex patterns)
↓
Chai Assertions
↓
JSON + HTML Reports
Directory Structure
features/
├── support/
│ ├── world.ts # Custom World class with state management
│ └── hooks.ts # Before/After lifecycle hooks
├── step_definitions/
│ ├── navigation.steps.ts # Screen navigation steps
│ ├── form.steps.ts # Form validation steps
│ └── screen.steps.ts # Content verification steps
├── user/ # User-related features (9 files)
├── event/ # Event features (5 files)
├── workshop/ # Workshop features (6 files)
├── meeting/ # Meeting features (1 file)
└── notif/ # Notification features (3 files)
Configuration
The cucumber.json file configures the test runner:
{
"default": {
"import": [
"features/support/**/*.ts",
"features/step_definitions/**/*.ts"
],
"paths": ["features/**/*.feature"],
"format": [
"progress-bar",
"json:reports/cucumber-report.json",
"html:reports/cucumber-report.html"
],
"language": "fr",
"formatOptions": {
"snippetInterface": "async-await"
},
"strict": false
}
}
- language: "fr" - Uses French Gherkin keywords (Fonctionnalité, Scénario, Étant donné, Quand, Alors)
- strict: false - Allows pending scenarios to be reported without failing the test suite
- Reports - Generates both JSON (for CI) and HTML (human-readable) reports
World Class
The FestipodWorld class (features/support/world.ts) maintains test state:
Tracked State
| Property | Type | Description |
|---|---|---|
currentRoute |
string |
Current URL hash (e.g., #/demo/home) |
currentScreenId |
string |
Current screen identifier |
formFields |
Map |
Form fields with required flag and value |
navigationHistory |
string[] |
All visited routes |
isAuthenticated |
boolean |
Login state |
screenSourceContent |
string |
Raw TypeScript source of current screen |
Key Methods
navigateTo(route)- Navigate to a screen, load its source codehasField(fieldName)- Check if a semantic field exists using regex detectorshasText(text)- Check if text exists in screen sourcehasElement(selector)- Check for JSX elementsgetRenderedText()- Get the full source code for matching
Screen Name Mapping
French screen names are mapped to screen IDs:
screenNameMap = {
'accueil': 'home',
'créer un événement': 'create-event',
'détail événement': 'event-detail',
'mon profil': 'profile',
'profil utilisateur': 'user-profile',
// ... etc
}
Screen-Specific Field Detectors
Field detection is screen-specific, defined in screenFieldDetectors map. Each screen has its own set of regex patterns to identify UI elements:
event-detail screen:
| Field | Detection Pattern |
|---|---|
| Titre | <Title>content</Title> |
| Date | 📅 emoji + French month name + year |
| Heure | 🕓 emoji + time pattern (e.g., 14h30) |
| Lieu | 📍 emoji + capitalized location name |
| Description | "À propos" section with 50+ chars of text |
| Photo | <Avatar component |
user-profile / profile screens:
| Field | Detection Pattern |
|---|---|
| Nom | <Title> with capitalized first/last name |
| Pseudo | @username pattern |
| Photo / Photo de profil | <Avatar component |
These patterns match what currently exists in each screen's source code. When UI changes, patterns must be updated accordingly.
Step Definition Design Principles
Step definitions follow strict principles to ensure they are readable, specific, and verifiable:
1. Inline Detection Logic (No Abstraction)
All detection logic must be written directly in step definitions, not in separate utility functions. This is critical because:
- The step definition code is displayed in the app's "Définitions" view
- Users must see the exact detection logic when reviewing specs
- Abstraction hides the "how" and prevents quick verification
Correct approach:
Then('je peux annuler et revenir à l\'écran précédent', async function (this: FestipodWorld) {
expect(this.currentScreenId).to.equal('create-event');
const source = this.getRenderedText();
// Detect ✕ close button with onClick handler that calls navigate()
const found = /onClick\s*=\s*\{\s*\(\)\s*=>\s*navigate\s*\(['"]home['"]\)\s*\}[^>]*>✕</.test(source);
expect(found, 'Create event screen should have ✕ button with navigate("home")').to.be.true;
});
Avoid (abstraction hides logic):
// DON'T DO THIS - the function hides the detection logic from the UI
const result = hasBackNavigation(source);
expect(result.found).to.be.true;
2. Specific Patterns (No Future Anticipation)
Detection patterns must match exactly what exists in the codebase today. Do not use OR patterns or generic matching to anticipate future variations that don't exist yet.
Correct approach:
// CreateEventScreen.tsx has: onClick={() => navigate('home')}...>✕<
const found = /onClick\s*=\s*\{\s*\(\)\s*=>\s*navigate\s*\(['"]home['"]\)\s*\}[^>]*>✕</.test(source);
Avoid (anticipates variations that don't exist):
// DON'T DO THIS - the app doesn't have "Retour", "←", or "Annuler" buttons
const found = /✕|←|Retour|Annuler/.test(source);
3. Code Duplication is Acceptable
If multiple steps need similar detection logic, duplicate the code. This maintains readability and allows each step to evolve independently based on actual screen content.
4. Screen-Specific Detection
Each step should know exactly which screen it's testing and what specific patterns to look for in that screen's source code.
When Tests Fail
A failing test means a specific UI element was removed or changed:
AssertionError: Create event screen should have ✕ button with navigate("home")
This tells the developer exactly what's missing and where to look. The regex in the step definition shows exactly what pattern was expected.
Step Definitions
Navigation Steps (navigation.steps.ts)
# Given steps
Étant donné je suis sur la page "accueil"
Étant donné je suis connecté(e)
# When steps
Quand je navigue vers "détail événement"
Quand je clique sur {string}
Quand je clique sur un participant
# Then steps
Alors je suis redirigé vers "profil utilisateur"
Alors je vois l'écran "profile"
Alors l'écran contient une section "Photo de profil"
Form Steps (form.steps.ts)
# Validating required fields
Alors le formulaire contient le champ obligatoire "Titre"
# Multiple fields with DataTable
Alors le formulaire contient les champs obligatoires suivants:
| Titre |
| Date |
| Description |
# Form interaction
Quand je remplis le champ "Titre" avec "Mon événement"
Quand je laisse le champ "Date" vide
Alors une erreur de validation est affichée pour "Date"
Screen Steps (screen.steps.ts)
# Content verification
Alors je peux voir la liste des participants
Alors l'écran affiche les informations de l'événement
Alors je peux m'inscrire à l'événement
Alors je peux voir le QR code
Test Outcomes
Tests in this project serve to consolidate features and prevent regressions. This shapes the testing strategy.
1. Pass/Fail (Implemented Features)
When a feature exists and can be verified through static source analysis:
- The scenario has full steps (Given/When/Then)
- Step definitions run assertions (
expect(...).to.be.true) - If the assertion passes → test passes
- If the assertion fails → test fails with descriptive error message
Then('je peux voir la liste des participants', async function (this: FestipodWorld) {
expect(this.currentScreenId).to.equal('event-detail');
const source = this.getRenderedText();
// EventDetailScreen.tsx has: <Avatar components and "Participants (12)" text
const hasAvatars = /<Avatar/.test(source);
expect(hasAvatars, 'Event detail should have Avatar components for participants').to.be.true;
});
2. Skipped (Not Yet Implemented)
When a feature is not yet implemented, the scenario uses a placeholder step.
Rationale: Since tests exist to consolidate features and prevent regressions, there is no value in writing detailed steps for features that don't exist yet. The placeholder step * Scénario non implémenté makes it clear this is a planned feature while allowing Cucumber to properly report it as "skipped".
Format for skipped scenarios:
Scénario: Modifier ma photo de profil
* Scénario non implémenté
The * is a Gherkin keyword that matches any step type (Given/When/Then). The step definition returns 'skipped':
Given('Scénario non implémenté', async function (this: FestipodWorld) {
return 'skipped';
});
Do NOT write detailed steps for unimplemented features:
# DON'T DO THIS - speculation about future implementation
Scénario: Modifier ma photo de profil
Étant donné je suis sur la page "mon profil"
Quand je clique sur "Modifier la photo"
Alors je peux sélectionner une nouvelle image
UI Behavior for Skipped Scenarios
In the GherkinHighlighter component:
- Scenarios with skipped status appear with yellow/amber indicator
- They show only the scenario name (the placeholder step is hidden)
- This provides a clear visual distinction between tested and planned features
Hooks
Lifecycle hooks in features/support/hooks.ts:
| Hook | Purpose |
|---|---|
BeforeAll |
Log test suite start |
Before |
Reset World state |
After |
Attach debug info on failure, cleanup |
AfterAll |
Log test suite completion |
Skipped Scenarios
Scenarios use the placeholder step * Scénario non implémenté which returns 'skipped'. This is handled by the step definition in navigation.steps.ts, not by hooks.
Use * Scénario non implémenté for scenarios that represent features not yet implemented.
Debug Information on Failure
When a scenario fails, the After hook attaches:
- Current route
- Current screen ID
- Navigation history
- Form fields state
- Screen source snippet (first 500 chars)
Running Tests
# Run all tests end-to-end (runs tests + generates all data files)
bun run test:cucumber
# This runs: cucumber:run → cucumber:report → features:parse → steps:extract
# Sub-commands for individual steps:
bun run cucumber:run # Only run cucumber tests (generates HTML/JSON reports)
bun run cucumber:report # Parse results to generate testResults.ts
bun run features:parse # Parse .feature files to generate features.ts
bun run steps:extract # Extract step definitions to generate stepDefinitions.ts
# Run by category tag
bun run cucumber:run --tags "@USER"
bun run cucumber:run --tags "@EVENT"
bun run cucumber:run --tags "@NOTIF"
# Run by priority
bun run cucumber:run --tags "@priority-0"
Parsing Results
All parsing is included in bun run test:cucumber. For manual regeneration:
bun run cucumber:report # testResults.ts from cucumber-report.json
bun run steps:extract # stepDefinitions.ts from step definition files
bun run features:parse # features.ts from .feature files
Example Feature File
# language: fr
@USER @priority-0
Fonctionnalité: US-9 Visualiser la photo d'un individu
En tant qu'utilisateur
Je peux visualiser la photo d'un individu
Contexte:
Étant donné que je suis connecté en tant qu'utilisateur
Scénario: Accéder au profil pour voir la photo
Étant donné que je suis sur la page "mon profil"
Alors je vois l'écran "profile"
Et l'écran contient une section "Photo de profil"
Scénario: Naviguer vers le profil depuis la liste des participants
Étant donné que je suis sur la page "détail événement"
Quand je clique sur un participant
Alors je suis redirigé vers "profil utilisateur"
# Skipped scenarios use placeholder step
Scénario: Modifier ma photo de profil
* Scénario non implémenté
Key Design Decisions
Static Source Analysis
Instead of running the app in a browser, tests analyze TypeScript source files directly. This approach:
- Runs faster (no browser startup)
- Doesn't require a running server
- Validates code structure, not runtime behavior
French-First
All Gherkin keywords and step definitions use French:
Fonctionnalitéinstead ofFeatureScénarioinstead ofScenarioÉtant donnéinstead ofGivenQuandinstead ofWhenAlorsinstead ofThen
Specific Detection (Not Generic)
Tests use patterns that match exactly what exists in the codebase today:
- No OR patterns to anticipate future variations
- No abstraction functions that hide detection logic
- Tests break when UI changes - this is intentional to catch regressions
- When a test fails, the developer must update both the UI and the test pattern
UI Integration
The Specs page (#/specs) displays feature files using the GherkinHighlighter component.
GherkinHighlighter Component
Located at src/components/specs/GherkinHighlighter.tsx, this component renders Gherkin content in a card-based UI (not code/text style).
Key design decisions:
- System font: Uses system UI font instead of the sketchy handwritten font for better readability of specifications
- Card-based layout: Each scenario/background is a collapsible card with colored left border
- No header duplication: Feature metadata (title, priority, category) is shown in
FeatureView, not duplicated inGherkinHighlighter - Background always visible: The "Contexte" (Background) block is always expanded since it provides essential context
- Compact responsive design: Reduced padding/gaps for both mobile and desktop
Visual features:
- Toolbar with Expand/Collapse all and "Définitions" toggle
- Color-coded keywords:
- Blue: Given/Étant donné (preconditions)
- Amber: When/Quand (actions)
- Green: Then/Alors (assertions)
- Gray: And/Et (continuation)
- Test status icons per scenario (CheckCircle2/XCircle/AlertCircle)
- Error messages displayed inline for failed scenarios
- Step definition tooltips when "Définitions" mode is enabled
- Tables rendered as styled HTML tables with alternating row colors
French/English keyword support: The component detects both French and English Gherkin keywords for parsing steps (Étant donné/Given, Quand/When, Alors/Then, Et/And, Exemples/Examples).
Props:
interface GherkinHighlighterProps {
content: string; // Raw Gherkin file content
scenarioResults?: Array<{ // Test results per scenario
scenarioName: string;
status: 'passed' | 'failed' | 'skipped';
errorMessage?: string;
}>;
}
Data Layer
Data is generated by build-time scripts:
src/data/features.ts- Parsed feature file contentsrc/data/testResults.ts- Test execution resultssrc/data/stepDefinitions.ts- Step definition source code