QA Plan¶
Quality strategy for the Yam3at MVP across the Laravel API, Next.js web app, and Flutter mobile apps. QA is embedded from Sprint 1 — acceptance criteria live in the SRS modules and the backlog; this plan defines how they are verified.
Test pyramid¶
flowchart TB
E["Exploratory & release testing<br/>manual, bilingual, on real devices"]
P["E2E: Playwright (web) + Flutter integration tests (mobile)<br/>~60–80 scenarios, staging nightly"]
A["API/feature tests: Pest (Laravel HTTP tests)<br/>every endpoint, auth matrix, state machines"]
U["Unit tests: Pest/PHPUnit + Dart test<br/>money math, validators, policies, services"]
E --- P --- A --- U
| Layer | Tooling | Owner | Gate |
|---|---|---|---|
| Unit | Pest/PHPUnit (PHP), package:test (Dart), Vitest (web utils) |
Developers | Required in CI per PR; money/state-machine logic 100% branch-covered |
| API / feature | Pest HTTP tests against SQLite/MySQL test DB, PSP + FCM + SMS faked | Backend devs | Required in CI per PR |
| Contract | OpenAPI schema validation of responses in Pest; generated client types for web | Backend + web | CI per PR |
| E2E web | Playwright (Chromium + WebKit), ar and en projects |
QA | Nightly on staging; smoke subset on every deploy |
| E2E mobile | Flutter integration_test on Firebase Test Lab (Android matrix + iOS) |
QA + mobile dev | Nightly; pre-release full run |
| Exploratory | Session-based test charters, bilingual testers | QA | Per sprint + pre-release |
Rules: no test may call the real PSP, SMS gateway, or FCM outside the dedicated sandbox suites. Every bug fixed gets a regression test at the lowest layer that can catch it.
Environments¶
| Env | Purpose | Data | Payments | Notes |
|---|---|---|---|---|
local |
Development | Seeders + factories | PSP mock (in-process fake) | Docker Compose, Mailpit for email |
ci |
Per-PR pipeline | Ephemeral, migrated fresh | PSP mock | GitHub Actions |
staging |
Integration, E2E, UAT | Synthetic seed set (see below), reset weekly | MyFatoorah/Tap sandbox (KNET test cards) | FCM real w/ test app IDs; SMS to whitelisted numbers only |
production |
Live | Real | Live KNET/cards | Feature flags for dark launches; smoke tests post-deploy |
Test-data strategy¶
- Factories/seeders produce a deterministic staging world: 40 vendors across 8 categories and 4 plan tiers (incl. one at each quotation limit boundary), 20 customers, events at every lifecycle stage, RFQs in all 8 states, bookings in all 5 states, reviews pending moderation.
- Bilingual by default: every seeded entity has Arabic and English content, including long Arabic strings, mixed-direction text (
قاعة ABC هال), and emoji, to exercise RTL/truncation. - Money fixtures intentionally use awkward KWD values:
0.750,12.345,999.999, and per-line totals that only reconcile with half-up 3dp rounding. - OTP bypass: staging accepts a fixed OTP (
000000) for whitelisted test numbers; never enabled in production (guarded by env + config test). - PSP sandbox cards: documented set for success, decline, 3-DS challenge, and timeout; webhook replay tool in the admin dev menu on staging.
- No production data is ever copied to lower environments. Support reproductions use synthetic look-alikes.
Highest-value test cases¶
Phase: S = smoke (every deploy), R = regression (nightly), P = pre-release full pass.
| ID | Area | Scenario | Expected | Phase |
|---|---|---|---|---|
| TC-001 | Auth/OTP | Register with valid +965 number, correct OTP | Account created, session issued, profile step shown | S |
| TC-002 | Auth/OTP | Wrong OTP 5 times | Code invalidated, lockout message, no session | R |
| TC-003 | Auth/OTP | Request 4th OTP within 15 min | 429 with retry-after; only 3 SMS sent | R |
| TC-004 | Auth/OTP | Expired OTP (>5 min) submitted | Rejected with localized error | R |
| TC-005 | Auth | Google and Apple sign-in, new + returning user | Account linked once; no duplicate accounts by email | P |
| TC-006 | Auth | Access vendor-only endpoint as customer | 403; audit not required, no data leak in body | S |
| TC-007 | Vendor approval | Submit application with license PDF | Status Pending review; admin queue shows item with license preview |
S |
| TC-008 | Vendor approval | Admin approves application | Vendor notified (push+email); profile editable; audit log has admin ID + timestamp | S |
| TC-009 | Vendor approval | Admin rejects without reason | Blocked by validation; reason mandatory | R |
| TC-010 | Vendor approval | Vendor resubmits after "request changes" | Returns to queue flagged as resubmission with diff of changed fields | R |
| TC-011 | Vendor approval | Unapproved vendor tries to publish profile | Blocked with explanatory state; not in search index | R |
| TC-012 | Subscription limits | Basic vendor sends quotes up to monthly limit | Quote N allowed; quote N+1 shows upgrade prompt, API returns 422 entitlement error | S |
| TC-013 | Subscription limits | Limit counter resets at billing-cycle boundary (not calendar month) | New cycle starts count at 0 | R |
| TC-014 | Subscription limits | Upgrade mid-cycle after hitting limit | New limit applies immediately; pro-rated charge correct to 3dp | R |
| TC-015 | Subscription limits | Downgrade with usage above new tier's limit | Downgrade scheduled for next cycle; current cycle unaffected | R |
| TC-016 | RFQ state machine | Customer sends RFQ to 3 vendors | 3 vendor threads Pending; vendors notified; event workspace shows RFQ |
S |
| TC-017 | RFQ state machine | Vendor quotes → customer accepts | Accepted thread Accepted; siblings auto-Rejected; draft booking created |
S |
| TC-018 | RFQ state machine | Vendor tries to quote an Expired RFQ thread |
409 state-machine error; no quote stored | R |
| TC-019 | RFQ state machine | Quote passes validity date | Auto-Expired by scheduler; both parties notified; cannot be accepted |
R |
| TC-020 | RFQ state machine | Customer cancels RFQ while one thread is Negotiating |
All non-terminal threads → Cancelled; vendors notified |
R |
| TC-021 | RFQ state machine | Vendor at plan limit receives RFQ | Can read RFQ; cannot submit quote (entitlement gate, TC-012) | R |
| TC-022 | Payments/webhooks | Successful KNET deposit checkout (sandbox) | Booking Pending→Confirmed; receipt issued; customer + vendor notified |
S |
| TC-023 | Payments/webhooks | Same payment webhook delivered twice | Second delivery is a no-op (idempotency key); single confirmation, single receipt | S |
| TC-024 | Payments/webhooks | Webhook with invalid signature | 401/rejected; alert raised; no state change | R |
| TC-025 | Payments/webhooks | Payment fails / user abandons checkout | Booking stays Pending with retry CTA; no orphan transactions |
R |
| TC-026 | Payments/webhooks | Webhook arrives before redirect return (race) | Final state correct regardless of order; no double processing | P |
| TC-027 | Payments/webhooks | Admin partial refund via PSP | Refund recorded to the fils; booking → per policy; audit logged | R |
| TC-028 | KWD rounding | Quote with line items summing to 12.3455 | Half-up rounding at 3dp applied once at total; UI, DB, PSP amount identical | S |
| TC-029 | KWD rounding | Display of KWD 0.750 across web/mobile/AR locale | Always 3 decimals (0.750, ٠٫٧٥٠ per locale rules); never 0.75 |
R |
| TC-030 | KWD rounding | Subscription VAT-free total + promo (Scale) math | Stored integer fils == displayed amount == PSP charge | P |
| TC-031 | RTL | Full RFQ wizard in Arabic on web (Playwright ar project) |
Layout mirrored; steppers, chevrons, progress flow right→left; no clipped text | S |
| TC-032 | RTL | Mixed AR/EN vendor name in chat bubbles and notifications | Bidi renders correctly (no scrambled brackets/numbers) | R |
| TC-033 | RTL | Flutter app language switch AR↔EN at runtime | All screens re-render mirrored without restart artifacts | R |
| TC-034 | RTL | Quote comparison table in Arabic with 3dp prices | Columns mirrored, numerals consistent, sort still correct | P |
| TC-035 | Notifications | New quote received (push + email + in-app) | All three delivered; push deep-links to quote; in-app marks read | S |
| TC-036 | Notifications | User disables email channel in preferences | Email suppressed; push and in-app unaffected | R |
| TC-037 | Notifications | Notification templates in user's language | Vendor (AR) and customer (EN) each get their own language for the same event | R |
| TC-038 | Reviews | Review attempt without a Completed booking |
Blocked; UI explains verified-review rule | R |
| TC-039 | Booking lifecycle | Auto-complete N days after event date | Booking → Completed; review prompt sent |
R |
| TC-040 | Messaging | Send 10 MB+1 file and .exe file in chat | Both rejected with clear errors; 10 MB PDF succeeds | R |
| TC-041 | Search | Arabic typo search ("تصوير" misspelled) returns photographers | Meilisearch typo tolerance returns expected vendors; featured ordering respected | R |
| TC-042 | Security | IDOR probe: customer A requests customer B's event/quote/booking by ID | 403/404 on all; verified across all resource types | P |
Non-functional testing¶
Load (k6 against staging, production-like sizing)
| Target | Threshold |
|---|---|
| API p95 latency (browse/search/RFQ endpoints) | < 300 ms at 200 concurrent users |
| Search (Meilisearch-backed) p95 | < 150 ms |
| Checkout/webhook processing p95 | < 500 ms; zero lost webhooks at 20/s burst |
| Sustained load | 200 concurrent users, 30 min, error rate < 0.1% |
| Launch spike | 5× baseline for 10 min with graceful degradation (queues drain < 5 min after) |
| Push fan-out | 10k notifications queued and delivered < 2 min via Horizon |
Security
- OWASP ZAP baseline scan in CI weekly; full active scan pre-release on staging.
- Dependency audit (
composer audit,npm audit, Dependabot) — Critical/High block release. - Static analysis: Larastan level 8+, ESLint security rules, Dart analyzer.
- Manual checks pre-launch: authz matrix per role (TC-042 expanded), OTP/rate-limit abuse, file-upload polyglot tests, webhook signature enforcement, secrets scan.
- PII review: exports/logs never contain phone, license documents, or payment tokens.
Other NFR passes: accessibility smoke (focus order in RTL, contrast, TalkBack/VoiceOver on core flows), Lighthouse ≥ 90 performance/SEO on public category×city pages, offline/poor-network behavior on mobile (airplane-mode mid-checkout must not corrupt state).
Release checklist¶
- All S + R suites green on the release candidate; P pass executed and signed off
- Zero open Critical/High bugs; Mediums triaged with owner + date
- Migration dry-run against a production-size snapshot; rollback script tested
- PSP sandbox regression (TC-022–TC-027) plus one real production micro-transaction and refund
- Bilingual review of all new user-facing strings (native Arabic reviewer)
- RTL visual audit of changed screens (designer sign-off)
- Store builds: version bump, changelog AR/EN, phased rollout configured (10% day 1)
- Monitoring dashboards + alerts updated for new endpoints; Sentry release tagged
- Feature flags default states confirmed; kill switches documented
- Support/ops briefed on changes (Operations Handbook updated if SOPs changed)
- Go/no-go recorded with attendees and decision
Bug severity & SLA matrix¶
| Severity | Definition | Examples | Fix SLA | Release gate |
|---|---|---|---|---|
| Critical (S1) | Money wrong, data loss, security breach, platform down, nobody can pay/login | Double charge; webhook confirms wrong booking; OTP bypass; KWD rounding error on invoices | Hotfix immediately, workaround < 4 h | Blocks release & triggers incident process |
| High (S2) | Core flow broken for a user segment, no workaround | Vendors on iOS can't submit quotes; Arabic checkout button unreachable | Fix < 2 business days | Blocks release |
| Medium (S3) | Flow impaired but workaround exists; cosmetic issue on a core screen | Comparison table mis-sorts; notification arrives without deep link | Next sprint | Ship with noted exception |
| Low (S4) | Minor cosmetic, rare edge case, internal tooling | Misaligned icon in admin; typo in English FAQ | Backlog, batched | Never blocks |
Severity is set by QA, contestable by the tech lead; anything touching payments, entitlements, or auth is at minimum S2. Every S1/S2 requires a post-fix regression test and a one-paragraph root-cause note in the bug ticket.