393 lines
16 KiB
HTML
393 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Feature Flow - Standardization Pipeline</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
|
|
background: #0f172a;
|
|
color: #e2e8f0;
|
|
min-height: 100vh;
|
|
}
|
|
.slide { display: none; min-height: 100vh; padding: 3rem; }
|
|
.slide.active { display: flex; flex-direction: column; }
|
|
.nav { position: fixed; bottom: 2rem; right: 2rem; display: flex; gap: 0.5rem; z-index: 100; }
|
|
.nav button { background: #334155; border: none; color: #e2e8f0; padding: 0.75rem 1.25rem; border-radius: 6px; cursor: pointer; font-size: 1rem; }
|
|
.nav button:hover { background: #475569; }
|
|
.nav .counter { background: transparent; padding: 0.75rem 1rem; color: #64748b; }
|
|
.slide-title { justify-content: center; align-items: center; text-align: center; }
|
|
.slide-title h1 { font-size: 3.5rem; font-weight: 700; margin-bottom: 1rem; background: linear-gradient(135deg, #6366f1, #a855f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
|
.slide-title p { font-size: 1.5rem; color: #94a3b8; }
|
|
.slide-title .subtitle { margin-top: 3rem; font-size: 1rem; color: #64748b; }
|
|
.pipeline { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem; margin: 2rem 0; }
|
|
.pipeline-step { background: #1e293b; border-radius: 12px; padding: 2rem; border-left: 4px solid; }
|
|
.pipeline-step.ops { border-color: #22c55e; }
|
|
.pipeline-step.bdd { border-color: #6366f1; }
|
|
.pipeline-step.tests { border-color: #f59e0b; }
|
|
.pipeline-step h3 { font-size: 1.25rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
.pipeline-step .num { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.85rem; font-weight: 700; }
|
|
.pipeline-step.ops .num { background: #22c55e; color: #0f172a; }
|
|
.pipeline-step.bdd .num { background: #6366f1; color: white; }
|
|
.pipeline-step.tests .num { background: #f59e0b; color: #0f172a; }
|
|
.pipeline-step ul { list-style: none; color: #94a3b8; font-size: 0.95rem; }
|
|
.pipeline-step li { padding: 0.4rem 0; padding-left: 1rem; border-left: 2px solid #334155; margin-bottom: 0.25rem; }
|
|
.slide h2 { font-size: 2rem; margin-bottom: 2rem; display: flex; align-items: center; gap: 1rem; }
|
|
.slide h2 .badge { padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
|
|
.slide h2 .badge.ops { background: #22c55e; color: #0f172a; }
|
|
.slide h2 .badge.bdd { background: #6366f1; color: white; }
|
|
.slide h2 .badge.tests { background: #f59e0b; color: #0f172a; }
|
|
pre { background: #1e293b; padding: 1.5rem; border-radius: 8px; overflow-x: auto; font-size: 0.8rem; line-height: 1.5; margin: 1rem 0; }
|
|
.keyword { color: #c084fc; }
|
|
.string { color: #4ade80; }
|
|
.comment { color: #64748b; }
|
|
.decorator { color: #f59e0b; }
|
|
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; flex: 1; }
|
|
.col { background: #1e293b; border-radius: 8px; padding: 1.5rem; }
|
|
.col h4 { font-size: 0.85rem; color: #94a3b8; margin-bottom: 1rem; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
.checklist { list-style: none; font-size: 1.1rem; }
|
|
.checklist li { padding: 0.75rem 0; display: flex; align-items: center; gap: 0.75rem; border-bottom: 1px solid #334155; }
|
|
.checklist .check { width: 20px; height: 20px; border: 2px solid #475569; border-radius: 4px; }
|
|
.flow-list { list-style: none; }
|
|
.flow-list li { padding: 0.4rem 0; color: #94a3b8; font-size: 0.9rem; }
|
|
.flow-list li strong { color: #e2e8f0; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<section class="slide slide-title active" data-slide="0">
|
|
<h1>Feature Flow</h1>
|
|
<p>Standardization Pipeline</p>
|
|
<div class="subtitle">Ops Templates → BDD/Gherkin → Backend + Frontend Tests</div>
|
|
</section>
|
|
|
|
<section class="slide" data-slide="1">
|
|
<h2>Pipeline Overview</h2>
|
|
<div class="pipeline">
|
|
<div class="pipeline-step ops">
|
|
<h3><span class="num">1</span> Ops Templates</h3>
|
|
<ul>
|
|
<li>Non-technical language</li>
|
|
<li>User perspective flows</li>
|
|
<li>From support/ops team</li>
|
|
<li>Captures edge cases</li>
|
|
<li>Documents known problems</li>
|
|
</ul>
|
|
</div>
|
|
<div class="pipeline-step bdd">
|
|
<h3><span class="num">2</span> BDD/Gherkin</h3>
|
|
<ul>
|
|
<li>.feature files</li>
|
|
<li>Given/When/Then syntax</li>
|
|
<li>Human readable specs</li>
|
|
<li>Single source of truth</li>
|
|
<li>Maps to both test types</li>
|
|
</ul>
|
|
</div>
|
|
<div class="pipeline-step tests">
|
|
<h3><span class="num">3</span> Tests</h3>
|
|
<ul>
|
|
<li><strong>Backend:</strong> API contracts</li>
|
|
<li><strong>Backend:</strong> Workflows (compositions)</li>
|
|
<li><strong>Frontend:</strong> Page Objects</li>
|
|
<li><strong>Frontend:</strong> E2E specs (Playwright)</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="slide" data-slide="2">
|
|
<h2><span class="badge ops">1</span> Ops Templates</h2>
|
|
<div class="two-col">
|
|
<div class="col">
|
|
<h4>Template Structure</h4>
|
|
<pre>
|
|
<span class="keyword">### [Flow Name]</span>
|
|
|
|
<span class="comment">User type:</span> Pet Owner / Vet / Admin
|
|
<span class="comment">Entry point:</span> Page/button/link
|
|
<span class="comment">Goal:</span> One sentence
|
|
|
|
<span class="keyword">Steps:</span>
|
|
1. First action
|
|
2. Second action
|
|
3. ...
|
|
|
|
<span class="keyword">Expected result:</span>
|
|
- What should happen
|
|
|
|
<span class="keyword">Common problems:</span>
|
|
- Problem 1
|
|
|
|
<span class="keyword">Edge cases:</span>
|
|
- Special case 1
|
|
</pre>
|
|
</div>
|
|
<div class="col">
|
|
<h4>Source</h4>
|
|
<ul class="flow-list">
|
|
<li><strong>Template:</strong> album/template/ops-flow/</li>
|
|
<li><strong>Reference:</strong> def/work_plan/21-plantilla</li>
|
|
</ul>
|
|
<h4 style="margin-top: 1.5rem;">Who Fills This</h4>
|
|
<ul class="flow-list">
|
|
<li>Support team (daily user contact)</li>
|
|
<li>Ops team (knows workarounds)</li>
|
|
<li>Product (requirements)</li>
|
|
</ul>
|
|
<h4 style="margin-top: 1.5rem;">Output</h4>
|
|
<ul class="flow-list">
|
|
<li>One .md per flow</li>
|
|
<li>Organized by user type</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="slide" data-slide="3">
|
|
<h2><span class="badge bdd">2</span> BDD/Gherkin</h2>
|
|
<div class="two-col">
|
|
<div class="col">
|
|
<h4>.feature File</h4>
|
|
<pre>
|
|
<span class="keyword">Feature:</span> Turnero - Book appointment
|
|
|
|
<span class="keyword">Scenario:</span> Book vaccination for cat
|
|
<span class="decorator">Given</span> I am on the turnero page
|
|
<span class="decorator">When</span> I enter address <span class="string">"Av Santa Fe 1234"</span>
|
|
<span class="decorator">And</span> I click <span class="string">"Next"</span>
|
|
<span class="decorator">Then</span> a guest user should be created
|
|
|
|
<span class="decorator">When</span> I add pet <span class="string">"Koshka"</span> type <span class="string">"Cat"</span>
|
|
<span class="decorator">And</span> I select <span class="string">"Vaccination"</span>
|
|
<span class="decorator">Then</span> <span class="string">"Clinical consult"</span> is auto-added
|
|
|
|
<span class="keyword">Scenario:</span> Services filtered by pet type
|
|
<span class="decorator">Given</span> I added a cat
|
|
<span class="decorator">Then</span> I see cat vaccines
|
|
<span class="decorator">And</span> I dont see dog vaccines
|
|
</pre>
|
|
</div>
|
|
<div class="col">
|
|
<h4>Keywords</h4>
|
|
<ul class="flow-list">
|
|
<li><strong>Feature</strong> = one capability</li>
|
|
<li><strong>Scenario</strong> = one behavior</li>
|
|
<li><strong>Given</strong> = precondition</li>
|
|
<li><strong>When</strong> = action</li>
|
|
<li><strong>Then</strong> = expected result</li>
|
|
</ul>
|
|
<h4 style="margin-top: 1.5rem;">Reference</h4>
|
|
<ul class="flow-list">
|
|
<li>def/work_plan/10-flow-turnero.md</li>
|
|
<li>Full example with Gherkin, API tests, Page Objects</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="slide" data-slide="4">
|
|
<h2><span class="badge bdd">2b</span> Gherkin File Organization</h2>
|
|
<div class="two-col">
|
|
<div class="col">
|
|
<h4>Correct: One Feature = One File</h4>
|
|
<pre>
|
|
<span class="keyword">features/</span>
|
|
├── pet-owner/
|
|
│ ├── registro.feature <span class="comment"># 6-8 scenarios</span>
|
|
│ ├── reservar-turno.feature <span class="comment"># 10-15 scenarios</span>
|
|
│ ├── gestion-mascotas.feature
|
|
│ └── pago.feature
|
|
├── veterinarian/
|
|
│ └── ...
|
|
└── backoffice/
|
|
└── ...
|
|
</pre>
|
|
<h4 style="margin-top: 1rem;">Anti-pattern: One Scenario = One File</h4>
|
|
<pre style="border-left: 3px solid #ef4444;">
|
|
<span class="comment"># DON'T do this</span>
|
|
features/pet-owner/registro/
|
|
├── registro-exitoso.feature
|
|
├── registro-email-invalido.feature
|
|
├── registro-password-corto.feature
|
|
└── <span class="comment">... (dozens of tiny files)</span>
|
|
</pre>
|
|
</div>
|
|
<div class="col">
|
|
<h4>Why Multiple Scenarios per File</h4>
|
|
<ul class="flow-list">
|
|
<li><strong>Feature = Capability</strong> - one file describes one capability with all its behaviors</li>
|
|
<li><strong>Context stays together</strong> - Background, Rules share context</li>
|
|
<li><strong>Tooling expects it</strong> - test runners, reports, IDE navigation</li>
|
|
</ul>
|
|
<h4 style="margin-top: 1.5rem;">When to Split</h4>
|
|
<pre>
|
|
<span class="comment"># Scenarios per file:</span>
|
|
5-20 <span class="string">Normal, keep as is</span>
|
|
20-40 <span class="string">Consider splitting</span>
|
|
40+ <span class="string">Definitely split</span>
|
|
</pre>
|
|
<h4 style="margin-top: 1rem;">Folder Depth</h4>
|
|
<ul class="flow-list">
|
|
<li><strong>Good:</strong> 1-2 levels max</li>
|
|
<li><strong>Avoid:</strong> deep nesting</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="slide" data-slide="5">
|
|
<h2><span class="badge tests">3a</span> Backend Tests</h2>
|
|
<div class="two-col">
|
|
<div class="col">
|
|
<h4>Structure (amar_django_back)</h4>
|
|
<pre>
|
|
tests/contracts/
|
|
├── base.py <span class="comment"># mode switcher</span>
|
|
├── endpoints.py <span class="comment"># API paths (single source)</span>
|
|
├── helpers.py <span class="comment"># test data</span>
|
|
│
|
|
├── mascotas/ <span class="comment"># app tests</span>
|
|
│ ├── test_pet_owners.py
|
|
│ ├── test_pets.py
|
|
│ └── test_coverage.py
|
|
├── productos/
|
|
│ ├── test_services.py
|
|
│ └── test_cart.py
|
|
├── solicitudes/
|
|
│ └── test_service_requests.py
|
|
│
|
|
└── <span class="keyword">workflows/</span> <span class="comment"># compositions</span>
|
|
└── test_turnero_general.py
|
|
</pre>
|
|
</div>
|
|
<div class="col">
|
|
<h4>Two Test Modes</h4>
|
|
<pre>
|
|
<span class="comment"># Fast (Django test client)</span>
|
|
pytest tests/contracts/
|
|
|
|
<span class="comment"># Live (real HTTP)</span>
|
|
CONTRACT_TEST_MODE=live pytest
|
|
</pre>
|
|
<h4 style="margin-top: 1rem;">Workflow = Composition</h4>
|
|
<pre>
|
|
<span class="comment"># Calls endpoints in sequence:</span>
|
|
1. Check coverage
|
|
2. Create pet owner
|
|
3. Create pet
|
|
4. Get services
|
|
5. Create request
|
|
</pre>
|
|
<h4 style="margin-top: 1rem;">Key Files</h4>
|
|
<ul class="flow-list">
|
|
<li><strong>endpoints.py</strong> - change paths here only</li>
|
|
<li><strong>helpers.py</strong> - sample data</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="slide" data-slide="6">
|
|
<h2><span class="badge tests">3b</span> Frontend Tests</h2>
|
|
<div class="two-col">
|
|
<div class="col">
|
|
<h4>Structure (amar_frontend)</h4>
|
|
<pre>
|
|
tests/e2e/
|
|
├── <span class="keyword">pages/</span> <span class="comment"># Page Objects</span>
|
|
│ ├── BasePage.ts
|
|
│ ├── LoginPage.ts
|
|
│ └── index.ts
|
|
│
|
|
└── login.spec.ts <span class="comment"># E2E test</span>
|
|
</pre>
|
|
<h4 style="margin-top: 1rem;">Page Object Pattern</h4>
|
|
<pre>
|
|
<span class="keyword">export class</span> LoginPage <span class="keyword">extends</span> BasePage {
|
|
<span class="keyword">get</span> emailInput() {
|
|
<span class="keyword">return</span> this.page.getByLabel(<span class="string">'Email'</span>);
|
|
}
|
|
|
|
<span class="keyword">async</span> login(email, password) {
|
|
<span class="keyword">await</span> this.emailInput.fill(email);
|
|
<span class="keyword">await</span> this.passwordInput.fill(password);
|
|
<span class="keyword">await</span> this.submitButton.click();
|
|
}
|
|
}
|
|
</pre>
|
|
</div>
|
|
<div class="col">
|
|
<h4>Running Tests</h4>
|
|
<pre>
|
|
<span class="comment"># All tests</span>
|
|
npx playwright test
|
|
|
|
<span class="comment"># With UI</span>
|
|
npx playwright test --ui
|
|
|
|
<span class="comment"># Specific file</span>
|
|
npx playwright test login.spec.ts
|
|
</pre>
|
|
<h4 style="margin-top: 1rem;">Locator Priority</h4>
|
|
<ul class="flow-list">
|
|
<li>1. getByRole() - buttons, links</li>
|
|
<li>2. getByLabel() - form fields</li>
|
|
<li>3. getByText() - visible text</li>
|
|
<li>4. getByTestId() - data-testid</li>
|
|
</ul>
|
|
<h4 style="margin-top: 1rem;">Avoid</h4>
|
|
<ul class="flow-list">
|
|
<li>CSS class selectors</li>
|
|
<li>Complex XPath</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="slide" data-slide="7">
|
|
<h2>Per-Feature Checklist</h2>
|
|
<ul class="checklist">
|
|
<li><span class="check"></span> Ops template filled (support team)</li>
|
|
<li><span class="check"></span> Convert to .feature file (Gherkin spec)</li>
|
|
<li><span class="check"></span> Backend: API contract tests per endpoint</li>
|
|
<li><span class="check"></span> Backend: Workflow test (composition)</li>
|
|
<li><span class="check"></span> Frontend: Page Object (if new page)</li>
|
|
<li><span class="check"></span> Frontend: E2E spec (Playwright)</li>
|
|
<li><span class="check"></span> Wire to CI</li>
|
|
</ul>
|
|
<div style="margin-top: 2rem; color: #64748b; font-size: 0.9rem;">
|
|
<p><strong>Full example:</strong> def/work_plan/10-flow-turnero.md</p>
|
|
<p><strong>Backend README:</strong> amar_django_back/tests/contracts/README.md</p>
|
|
<p><strong>Frontend README:</strong> amar_frontend/tests/README.md</p>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="nav">
|
|
<button onclick="prevSlide()">←</button>
|
|
<span class="counter"><span id="current">1</span>/<span id="total">8</span></span>
|
|
<button onclick="nextSlide()">→</button>
|
|
</div>
|
|
|
|
<script>
|
|
let current = 0;
|
|
const slides = document.querySelectorAll('.slide');
|
|
const total = slides.length;
|
|
document.getElementById('total').textContent = total;
|
|
function showSlide(n) {
|
|
slides.forEach(s => s.classList.remove('active'));
|
|
current = (n + total) % total;
|
|
slides[current].classList.add('active');
|
|
document.getElementById('current').textContent = current + 1;
|
|
}
|
|
function nextSlide() { showSlide(current + 1); }
|
|
function prevSlide() { showSlide(current - 1); }
|
|
document.addEventListener('keydown', e => {
|
|
if (e.key === 'ArrowRight' || e.key === ' ') nextSlide();
|
|
if (e.key === 'ArrowLeft') prevSlide();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|