9.0 KiB
padhle — Frontend Manual Testing Plan
Goal: test the application through the browser (frontend only) and verify every feature that was built, changed, or hardened. Setup: backend on
:3001, frontend dev server on:5173, local Supabase running (npx supabase start). Recommended browsers: test in a normal window and an incognito window side-by-side (incognito = clean anonymous identity).
0. Before you start — launch
| Step | Do this | Expected |
|---|---|---|
| 0.1 | Terminal 1: cd backend && npm run dev |
Backend on :3001 |
| 0.2 | Terminal 2: cd frontend && npm run dev |
Frontend opens http://localhost:5173 |
| 0.3 | Open http://localhost:5173 in incognito |
App loads; no sign-in wall — you're in the app (sidebar, selectors, chat input visible) |
⚠️ If you don't have a Supabase/backend running, backend calls for sign-in/chat will fail with a connection error banner. The anonymous flow also needs the backend for chat. If a real AI key has no quota, streams may end with an in-chat error (expected, not a code bug).
1. Anonymous 5-message trial (FREE-TIER — this is the headline feature)
This is the core new behavior. Everything here works without an account.
| # | Do this | Expected |
|---|---|---|
| 1.1 | In incognito, notice the TopNav + Sidebar | Both show a "Sign in" button (no avatar) |
| 1.2 | Pick a grade → subject → chapter, then send a message | Streaming answer appears; chat thread builds |
| 1.3 | Send 4 more messages (5 total) | Each works; counter quietly builds up (you see the conversation continue) |
| 1.4 | Send a 6th message | Composer is replaced by a card: "You've used your 5 free messages — Sign in to continue" |
| 1.5 | If you've closed/reopened between messages: the URL stays on / the whole time |
Anonymous chat does NOT reflect a chat id into the browser URL |
| 1.6 | Click "Sign in to continue" on the card | The sign-in modal opens |
| 1.7 | Close the modal (× / "Maybe later" / click the dimmer) | Modal closes; you can keep navigating, still capped at 5 |
| 1.8 | Refresh the page after using some messages | Conversation resets to a fresh 5-message budget (by design) |
| 1.9 | Secure: open DevTools → Application → Cookies | There is no padhle.anon cookie; your anonymous state is 100% in-memory (refresh wipes it) |
Cross-browser isolation:
| # | Do this | Expected |
|---|---|---|
| 1.10 | Use 2 different browsers (or normal + incognito), both anonymous | Each has its own separate budget; their chats are not visible to each other |
2. Sign-up / Sign-in / Auth
| # | Do this | Expected |
|---|---|---|
| 2.1 | Click "Sign in" (TopNav or Sidebar) | Modal opens cleanly |
| 2.2 | Sign up with a valid email + password (≥6 chars) | Account created; you are now signed in; avatar appears in TopNav; sessions sidebar appears |
| 2.3 | Reload the page (F5) |
You stay signed in (session restored from the httpOnly cookie) |
| 2.4 | Try to sign up with a 5-character password (via the UI if it lets you, or note): | Server must reject (password ≥ 6). The UI enforces it too — if the UI blocks it, that's fine |
| 2.5 | Sign out (Sidebar → sign out) | You are logged out; back to anonymous app with a fresh 5-message budget; "Sign in" buttons return |
| 2.6 | Sign back in with the same email/password | Your previous signed-in conversation is still there in the sidebar |
3. Sessions (persisted conversations, signed-in only)
| # | Do this | Expected |
|---|---|---|
| 3.1 | While signed in, start a new chat and send a few messages | When you send, the URL becomes /chat/<id> (signed-in chats ARE deep-linkable) |
| 3.2 | Open another chat from the sidebar | URL switches to that chat's /chat/<id>; back/forward browser buttons work |
| 3.3 | Copy the /chat/<id> URL, open it in another tab (signed in there) |
The conversation loads directly (deep link works) |
| 3.4 | New chat button | Resets to /, clears composer, back to a fresh conversation |
| 3.5 | Delete a session from the sidebar | Removed; no longer in the list |
| 3.6 | Clear messages in a session (if available) | Conversation empties but the session stays |
| 3.7 | Secure — ownership: in browser A (user 1) note a session URL; in incognito (user 2) paste that exact URL | Two possibilities: if you are NOT signed in there → app shows clean "could not load" / stays on /; if you ARE signed in as user 2 → NOT user 1's messages (must be 404 — you should NOT see user 1's chat) |
4. Routing / URLs (added this session)
| # | Do this | Expected |
|---|---|---|
| 4.1 | While anonymous, send a message | URL stays / (anonymous trial id is never written to the URL — security decision) |
| 4.2 | While signed in, open a chat | URL is /chat/<id> — shareable and refreshable |
| 4.3 | Type a random path, e.g. http://localhost:5173/whatever |
App still loads at the root shell (SPA fallback) — no 404 page yet (documented) |
| 4.4 | Browser back/forward buttons between chats | Moves between conversations as expected |
5. AI streaming + rendering
| # | Do this | Expected |
|---|---|---|
| 5.1 | Send a question and watch the reply | Answer streams word by word (token by token), typing indicator during |
| 5.2 | Ask for something with Markdown (e.g. "give me a bulleted list, bold text, a code block") | Renders as formatted Markdown (not raw ** / backticks) |
| 5.3 | Ask for math (e.g. "what is x^2 + y^2") |
LaTeX renders via KaTeX |
| 5.4 | Secure — XSS: ask the AI to output raw HTML like <script>alert(1)</script> (or intentionally craft a message) |
It renders as text, NOT executable HTML — no alert pops |
| 5.5 | Long answer | Auto-scrolls to the new content |
6. Errors, validation, robustness (secure boundary checks)
These verify the backend still protects you even if the frontend is bypassed. Easiest way: open DevTools Console and run
fetch(...)from the page (same-origin) — or just reason through the UI where possible.
| # | Try | Expected |
|---|---|---|
| 6.1 | fetch('http://localhost:3001/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text:123})}) (no session cookie) |
400 with a clean JSON error — NOT a 500 / stack trace |
| 6.2 | fetch('http://localhost:3001/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text:'hi',chapter:'<script>'})}) (no cookie) |
400 (chapter with </> rejected) |
| 6.3 | fetch('http://localhost:3001/api/sessions') (no cookie) |
401 |
| 6.4 | fetch('http://localhost:3001/api/chat/sessions') (no cookie) |
401 |
| 6.5 | Shut down the backend, then send a message | Frontend shows a "Connection error… make sure backend is running" message — app doesn't hang |
| 6.6 | Open DevTools → Network, and confirm the chat requests carry cookies with credentials: include |
Requests are same-origin via the Vite proxy; cookies sent automatically |
The rate limits (5 auth/15min, 30 chat/min, 20 sessions/min per IP) won't usually trip you in manual testing — they're per-IP and reset. Don't try to exhaust them in the UI; they're verified server-side.
7. Security-review smoke test (quick pass)
| # | Do this | Expected |
|---|---|---|
| 7.1 | Sign in; open DevTools → Application → Cookies for localhost |
padhle.token and padhle.user are marked HttpOnly; SameSite = Lax |
| 7.2 | Open any response in DevTools → Network → Headers | Headers include X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: no-referrer, Cross-Origin-Opener-Policy: same-origin; no X-Powered-By |
| 7.3 | After signing out, note your old chat URL and reload it (signed out) | You can't load a signed-in-only chat while anonymous (clean, no data leak) |
8. Final sign-off checklist
- Anonymous user can send 5 messages, then is blocked gracefully with a Sign in to continue card.
- Anonymous URL stays on
/. - Sign in/up works; session survives reload.
- Signed-in chats are deep-linkable at
/chat/<id>. - Two identities never see each other's data.
- Streamed Markdown + math renders; raw HTML is not executed.
- Malformed JSON (non-string
text) → clean 400, no crash/stack trace. - Unauthenticated calls to
/api/sessionsand/api/chat/sessions→ 401. - Backend + frontend both build;
cd backend && node --test→ 38 pass.
Notes / expectations
- You will NOT be able to exceed 5 anonymous messages in normal UI use — the card blocks you. That's correct.
- Markdown/LaTeX demo depends on the AI model actually producing them; a generic question works either way.
- If a real AI key has no quota, streaming may show an in-chat error after a short delay — that's the vendor, not the app. The frontend still handles errors gracefully.
- This plan tests the frontend behavior. The full backend regression suite is
cd backend && node --test(38 tests) and runs independently.