Feature Integration Checklist — Verifying Every User Story Works

The capstone verification is a structured walkthrough of every user story in the blog application, confirming that every feature works correctly from the browser — not just in unit tests. Use the Playwright test suite from Chapter 46 to automate the critical paths and verify them manually for the nuanced interactions (WebSocket live updates, drag-and-drop uploads, mobile responsiveness). This is the final quality gate before marking the application as production-ready.

Full Feature Verification Checklist

── AUTHENTICATION ───────────────────────────────────────────────
☐ Register with valid data → redirected to dashboard
☐ Register with duplicate email → specific error shown
☐ Login with valid credentials → redirected to intended page
☐ Login with wrong password → generic "Invalid email or password"
☐ Access /dashboard while logged out → redirected to /login
☐ After login, /login redirects to /dashboard (not shown again)
☐ JWT token refresh: expire access token manually, make API call
    → transparent refresh, no logout
☐ Logout → tokens cleared, redirected to /, RTK cache cleared
☐ Refresh page → auth state restored from localStorage

── POST MANAGEMENT ──────────────────────────────────────────────
☐ Create post: title, body, slug (auto-generated), tags, cover image
☐ Slug auto-generates from title, stops auto-updating after manual edit
☐ Post visible in feed after creation (cache invalidated)
☐ Edit own post: form pre-filled with existing data
☐ Edit post: changing title does NOT auto-update slug (already set)
☐ Delete own post: confirmation dialog, redirect to dashboard
☐ Edit/Delete buttons NOT shown on posts you do not own
☐ Post editor: cannot submit while image is uploading

── POST FEED ────────────────────────────────────────────────────
☐ Home page shows paginated post list (10 per page)
☐ Pagination controls navigate correctly (prev/next/page numbers)
☐ Tag filter changes page to 1 and reloads filtered results
☐ Search input debounced (300ms): not every keystroke fires API call
☐ URL updates on filter/pagination change (bookmarkable, shareable)
☐ Browser back button restores previous filter state

── REAL-TIME FEATURES ───────────────────────────────────────────
☐ Post detail page: WebSocket connection shown as green dot
☐ Second browser tab comments on the post → first tab sees it live
☐ Viewer count shows "N people viewing this" when >1 viewer
☐ Viewer count decrements when second tab closes
☐ Notification bell increments when someone likes your post
☐ Notification bell increments when someone comments on your post
☐ Notification dropdown shows the notification details
☐ Notification badge clears when dropdown opened

── UPLOADS ──────────────────────────────────────────────────────
☐ Avatar upload: preview shown before upload
☐ Avatar upload: header shows new avatar immediately after save
☐ Cover image upload: upload on selection, URL stored in form
☐ Cover image upload: existing cover shown when editing
☐ Drag-and-drop upload zone: visual feedback on drag-over
☐ Invalid file type rejected with error message
☐ File > 5MB rejected with size error message

── MOBILE ───────────────────────────────────────────────────────
☐ Navigation collapses to hamburger menu on small screens
☐ Post cards readable and clickable on mobile
☐ Forms usable on mobile (input zoom, button tap targets)
☐ Image uploads work on iOS/Android (camera roll access)
Note: The WebSocket test (second tab sees comments live) is the hardest to automate and the most important to verify manually. Open the post detail page in two browser windows side-by-side. Type a comment in one window and watch it appear in the other without refreshing. This confirms the full round-trip: HTTP POST → FastAPI handler → room_manager.broadcast_to_room() → WebSocket → React state update. If any part of the chain is broken, this test will catch it.
Tip: Use browser DevTools to simulate key scenarios during verification: throttle the network to “Slow 3G” to test loading states and skeletons; use the Application tab to manually expire the JWT access token and verify the silent refresh; use device emulation to test mobile layouts; disable JavaScript to ensure Nginx’s SPA fallback returns the correct status code (it should return 200 with index.html, not 404). These are the scenarios users encounter in the real world.
Warning: Do not skip the mobile verification. Approximately 60% of web traffic is from mobile devices, and issues on mobile are often invisible during desktop-first development. The most common mobile-specific issues in this stack: inputs that trigger unwanted zoom (font-size < 16px on iOS), buttons that are too small to tap (WCAG recommends 44×44px minimum touch target), and images that overflow their containers on small screens. Test on a real device or an accurate browser simulator, not just a narrow desktop window.

Playwright Automated Verification

// e2e/capstone.spec.ts — runs the most critical user stories end-to-end
import { test, expect } from "@playwright/test";

test.describe("Capstone: Complete User Journey", () => {
    const email    = `capstone-${Date.now()}@example.com`;
    const password = "Password1";
    let postId: string;

    test("1. Register and reach dashboard", async ({ page }) => {
        await page.goto("/register");
        await page.getByLabel("Name").fill("Capstone User");
        await page.getByLabel("Email").fill(email);
        await page.getByLabel(/^Password$/).fill(password);
        await page.getByLabel("Confirm Password").fill(password);
        await page.getByRole("button", { name: "Sign Up" }).click();
        await page.waitForURL("/dashboard");
        await expect(page.getByText("Capstone User")).toBeVisible();
    });

    test("2. Create a post and see it in the feed", async ({ page }) => {
        test.use({ storageState: "playwright/.auth/capstone.json" });
        await page.goto("/posts/new");
        await page.getByLabel("Title").fill("My Capstone Post");
        await page.getByLabel("Body").fill("This is the body of the capstone post.");
        await page.getByRole("button", { name: "Publish Post" }).click();
        await page.waitForURL(/\/posts\/(\d+)/);

        // Capture post ID from URL
        postId = page.url().match(/\/posts\/(\d+)/)?.[1]!;
        await expect(page.getByRole("heading", { name: "My Capstone Post" })).toBeVisible();

        // Verify in feed
        await page.goto("/");
        await expect(page.getByText("My Capstone Post")).toBeVisible();
    });

    test("3. Delete the post and verify removal from feed", async ({ page }) => {
        test.use({ storageState: "playwright/.auth/capstone.json" });
        await page.goto(`/posts/${postId}`);
        page.on("dialog", dialog => dialog.accept());
        await page.getByRole("button", { name: "Delete Post" }).click();
        await page.waitForURL("/dashboard");

        await page.goto("/");
        await expect(page.getByText("My Capstone Post")).not.toBeVisible();
    });
});

🧠 Test Yourself

During verification you notice that the notification bell does not update when someone likes your post, even though the like is saved correctly in the database. What are the three most likely causes to investigate in order?