Trang chủ
QA Learning Hub
Dự Án Thực Hành · ShopVN E-commerce Website
Thực Hành Full Project

ShopVN — E-commerce Website

Dự án thực hành web thương mại điện tử đầy đủ từ A đến Z theo chuẩn quốc tế — Requirements, Test Plan, Playwright E2E, API, Database, Cross-browser, Performance, Security, Accessibility và Go/No-Go Report.

🛒 Project Overview
📌 Project Brief
ShopVN là website thương mại điện tử bán hàng điện tử tại Việt Nam. Sprint 5 (2 tuần) focus vào: Product Catalog, Search & Filter, Cart, Checkout (VNPay + Stripe), Order Confirmation. Team: 5 devs (3 frontend React, 2 backend Node), 1 QA (bạn), 1 BA, 1 PM.
ItemDetail
ProductShopVN v2.1 — Sprint 5 Build #142
FrontendNext.js 14 (App Router) + TypeScript + Tailwind CSS
BackendNode.js 20 + Express 5 + TypeScript
DatabasePostgreSQL 15 (primary), Redis 7 (cache + session), Elasticsearch 8 (product search)
CloudAWS (ECS, RDS Multi-AZ, ElastiCache, CloudFront CDN, S3 images)
AuthNextAuth.js — JWT + Credentials + Google OAuth
PaymentStripe (Visa/MC/Amex quốc tế), VNPay (nội địa Vietnam)
SearchElasticsearch full-text search với Vietnamese analyzer
Test Environmentstaging.shopvn.vn (AWS clone, seeded 5,000 products, 500 users)
CI/CDGitHub Actions → Vercel (frontend) + AWS CodeDeploy (backend)
Sprint GoalUser có thể tìm kiếm sản phẩm, filter, thêm vào giỏ, checkout với VNPay/Stripe và nhận order confirmation email
📋 Requirements
🏗️ Architecture
📝 Test Plan
🧪 Frontend E2E
🔌 API Tests
🗄️ DB Tests
🌐 Cross-browser
⚡ Performance
🔒 Security
♿ A11y
🚦 Go/No-Go
🏗️ System Architecture — QA Perspective
CDN / Edge
CloudFront + Vercel
Static assets cached tại edge. Next.js SSR/SSG pages. Image optimization tự động. HTTPS everywhere.
Frontend
Next.js 14 App Router
Server Components cho product listing (SEO). Client Components cho cart/checkout. Zustand state management.
API Gateway
AWS API Gateway
Rate limit: 200 req/min per IP. WAF: OWASP ruleset. JWT validation tại gateway level.
Backend
REST API (Node.js)
7 modules: Auth, Product, Search, Cart, Order, Payment, Notification. Swagger /api-docs. Versioning /api/v1/
Search
Elasticsearch 8
Vietnamese tokenizer, phonetic analysis. Faceted search (category, brand, price range). Auto-suggest.
Database
PostgreSQL + Redis
PG: users, products, orders, payments. Redis: cart (TTL 7 days), product cache (TTL 15min), session.
Payment
Stripe + VNPay
Stripe: international cards. VNPay: QR code + ATM cards. Webhook: /webhooks/stripe, /webhooks/vnpay.
Email
AWS SES
Order confirmation, shipping updates. Template: Handlebars. Bounce/complaint handling via SNS.
Monitoring
DataDog + Sentry
APM tracing, RUM (Real User Monitoring), Core Web Vitals. Sentry cho JS errors. PagerDuty on-call.
💡 QA Integration Points — Rủi ro cao nhất
1. VNPay webhook: Staging VNPay sandbox đôi khi delay 2-3 phút → timeout test cần patience.
2. Elasticsearch sync: Product update trong DB → ES index update có ~5 giây lag → search test cần wait/poll.
3. Redis cart vs DB cart: Cart trong Redis, chỉ persist to DB khi checkout → nếu Redis restart, cart mất.
4. CloudFront cache: Product image update có thể cần 24h để propagate tới edge nodes → test với cache-bypass header.
5. Inventory race condition: Flash sale scenario — nhiều users mua cùng lúc → oversell risk.
Development
localhost:3000
Docker Compose. Mock payment. 100 products seeded. Dev only.
Staging
staging.shopvn.vn
AWS clone. Stripe test + VNPay sandbox. 5,000 products. QA + Dev access.
Production
shopvn.vn
Live. Post-deploy smoke only. Readonly monitoring access.
📋 User Stories & Acceptance Criteria

Sprint 5: 4 Epics, 9 User Stories. Reviewed theo chuẩn CCCTF (Completeness, Clarity, Consistency, Testability, Feasibility).

Epic 1: Product Catalog & Search

US-501 Tìm kiếm sản phẩm theo từ khóa 5
"As a shopper, I want to search for products by keyword so that I can quickly find what I'm looking for."
  • AC-001: Search box available trên mọi page (header). Shortcut Ctrl/Cmd+K mở search.
  • AC-002: Debounce 300ms. Auto-suggest hiện sau 2 ký tự (max 8 suggestions).
  • AC-003: Full-text search trên tên sản phẩm, mô tả, thương hiệu, model number.
  • AC-004: Hỗ trợ tiếng Việt có dấu và không dấu: "iphone" tìm được "iPhone 15 Pro".
  • AC-005: Kết quả hiện trong 2 giây với ≤ 100 concurrent users.
  • AC-006: "Không tìm thấy kết quả cho '...'" → gợi ý 4 sản phẩm phổ biến.
  • AC-007: Search history lưu local (max 10), hiện khi focus search box khi field trống.
US-502 Filter và sort sản phẩm 5
  • AC-001: Filter options: Danh mục, Thương hiệu (multi-select checkbox), Khoảng giá (range slider), Đánh giá (≥ N sao), Tình trạng (Còn hàng / Tất cả).
  • AC-002: Sort by: Phổ biến nhất, Mới nhất, Giá thấp → cao, Giá cao → thấp, Đánh giá cao nhất.
  • AC-003: Filter state lưu trong URL params → shareable link và browser back button hoạt động.
  • AC-004: Filter active hiện dưới dạng chips, có nút "×" xóa từng filter. "Xóa tất cả" button.
  • AC-005: Số lượng kết quả cập nhật real-time khi thay đổi filter (không cần submit).
  • AC-006: Trên mobile (≤768px): Filter hiện trong bottom drawer, không phải sidebar.
US-503 Xem chi tiết sản phẩm 3
  • AC-001: Product detail page có: ảnh gallery (zoom khi hover), tên, giá (gạch ngang giá gốc nếu có khuyến mãi), SKU, thương hiệu, mô tả, thông số kỹ thuật, đánh giá.
  • AC-002: Variant selector (màu sắc, dung lượng) — chọn variant → giá và ảnh cập nhật.
  • AC-003: Stock indicator: "Còn X sản phẩm" nếu ≤ 5, "Còn hàng" nếu > 5, "Hết hàng" nếu 0.
  • AC-004: "Hết hàng" → nút "Thêm vào giỏ" disabled, hiện "Đăng ký thông báo".
  • AC-005: Breadcrumb navigation: Trang chủ → Danh mục → Sản phẩm.
  • AC-006: Structured data (JSON-LD) cho SEO: Product schema với price, rating, availability.

Epic 2: Shopping Cart

US-504 Quản lý giỏ hàng 5
  • AC-001: "Thêm vào giỏ" → mini cart slide-in từ phải, hiện sản phẩm vừa thêm và tổng giỏ hàng.
  • AC-002: Cart icon trên header hiện badge với số lượng items (cập nhật real-time).
  • AC-003: Tăng/giảm số lượng trong cart. Min = 1, max = stock available. Quantity 0 → xóa.
  • AC-004: Cart persist cho logged-in user (server-side). Guest cart: localStorage, sync khi đăng nhập.
  • AC-005: Giá tự động cập nhật nếu sản phẩm có promotion change. Hiện banner "Giá sản phẩm đã thay đổi".
  • AC-006: "Hết hàng" sau khi thêm vào cart → hiện cảnh báo tại cart page, không cho checkout.
  • AC-007: "Lưu để sau" (Save for later) — move item ra khỏi cart nhưng không xóa.

Epic 3: Checkout & Payment

US-505 Checkout flow — nhập thông tin giao hàng 5
  • AC-001: Checkout flow 3 steps: (1) Địa chỉ giao hàng → (2) Phương thức vận chuyển → (3) Thanh toán.
  • AC-002: Logged-in user: pre-fill địa chỉ đã lưu. Có thể chọn địa chỉ khác hoặc nhập mới.
  • AC-003: Guest checkout được phép — chỉ cần email + địa chỉ, không bắt tạo account.
  • AC-004: Địa chỉ Việt Nam: Tỉnh/Thành phố → Quận/Huyện → Phường/Xã (dropdown cascade từ API).
  • AC-005: Tính phí vận chuyển real-time dựa trên địa chỉ giao hàng và trọng lượng đơn hàng.
  • AC-006: Order summary sidebar sticky — visible khi scroll qua checkout steps.
US-506 Thanh toán Stripe (quốc tế) 8
  • AC-001: Stripe Elements embedded trong checkout page (không redirect). Hỗ trợ Visa/MC/Amex/JCB.
  • AC-002: 3D Secure authentication khi issuing bank yêu cầu.
  • AC-003: Payment thành công → order status "Confirmed", email xác nhận gửi trong 60 giây.
  • AC-004: Payment thất bại → hiện lý do cụ thể (card declined, insufficient funds, expired), không navigate khỏi checkout.
  • AC-005: Idempotency key trên mỗi payment intent — double submit không charge 2 lần.
  • AC-006: Không lưu card number trong DB của ShopVN — chỉ lưu Stripe PaymentMethod ID.
US-507 Thanh toán VNPay 5
  • AC-001: Chọn VNPay → redirect tới VNPay payment page (chuẩn VNPay integration).
  • AC-002: VNPay hỗ trợ: QR Code (VNPay QR), ATM nội địa (96 banks), VISA/MC qua VNPay.
  • AC-003: Sau khi thanh toán: VNPay redirect về /checkout/return?vnp_ResponseCode=00 (success) hoặc code khác (fail).
  • AC-004: Server verify chữ ký HMAC-SHA512 từ VNPay webhook trước khi cập nhật order.
  • AC-005: VNPay timeout (user không thanh toán trong 15 phút) → order status "Payment Expired", cart không bị clear.
US-508 Áp dụng mã giảm giá (Coupon) 3
  • AC-001: Input mã coupon tại checkout. Real-time validation khi blur hoặc Enter.
  • AC-002: Coupon types: % discount, fixed amount, free shipping, buy-X-get-Y.
  • AC-003: Invalid coupon → error "Mã không hợp lệ hoặc đã hết hạn".
  • AC-004: Coupon đã dùng (single-use) → error "Mã này đã được sử dụng".
  • AC-005: Minimum order value: Coupon chỉ valid nếu subtotal ≥ threshold. Hiện: "Cần mua thêm X để sử dụng mã này".
  • AC-006: Discount hiện trong order summary: gạch ngang subtotal + dòng "Giảm giá: -X₫".
⚠️ QA Requirements Review — Issues Found
US-506 AC-003: "email xác nhận gửi trong 60 giây" — phụ thuộc AWS SES delivery rate, không fully testable. Đề xuất split: "email được trigger trong 5 giây" (testable) và "email delivery SLA" là concern riêng của infra.
US-507 AC-005: "cart không bị clear" — conflict với logic hiện tại khi order tạo, cart được clear. Cần clarify với dev: order được tạo trước rồi chờ payment, hay chỉ tạo khi payment thành công?
🗺️ Requirements Traceability Matrix (RTM)
Req IDAcceptance CriteriaTest Case ID(s)TypeStatus
US-501AC-001: Search box + Ctrl+K shortcutTC-SRCH-001, 002E2E AutoPassed ✓
AC-002: Debounce + auto-suggestTC-SRCH-003, 004E2E + APIPassed ✓
AC-004: Tiếng Việt không dấuTC-SRCH-005E2E AutoFailed ✗ BUG-001
AC-005: Response ≤ 2s (100 users)TC-PERF-001k6 Load TestPassed ✓
US-502AC-003: Filter state trong URLTC-FILTER-003E2E AutoPassed ✓
AC-006: Mobile filter → bottom drawerTC-FILTER-006Manual (mobile viewport)Passed ✓
US-503AC-003: Stock indicator real-timeTC-PROD-003Manual + APIFailed ✗ BUG-002
AC-006: Structured data / JSON-LDTC-SEO-001Manual (DevTools)Passed ✓
US-504AC-004: Guest cart sync on loginTC-CART-004E2E AutoPassed ✓
AC-007: Save for laterTC-CART-007E2E AutoPassed ✓
US-505AC-003: Guest checkoutTC-CHK-003E2E AutoPassed ✓
AC-004: Cascade dropdown addressTC-CHK-004, 005E2E + APIPassed ✓
US-506AC-002: 3D Secure flowTC-PAY-002Manual (Stripe test)Passed ✓
AC-005: IdempotencyTC-PAY-005API AutoPassed ✓
AC-006: No card data in DBTC-SEC-008DB QueryPassed ✓
US-507AC-004: HMAC-SHA512 verifyTC-PAY-010API AutoFailed ✗ BUG-003
AC-005: Timeout — cart not clearedTC-PAY-011ManualPassed ✓
US-508AC-005: Minimum order valueTC-COUP-005E2E AutoPassed ✓
55
Total TCs
51
Executed
48
Passed
3
Failed
4
Skipped
94%
Pass Rate
📝 Test Plan — Sprint 5
MụcNội Dung
ObjectivesVerify Sprint 5 user stories theo AC. Ensure no regression trong Auth, User Profile (Sprint 4). Validate web performance đạt Core Web Vitals (LCP < 2.5s, CLS < 0.1, INP < 200ms). Confirm WCAG 2.1 AA compliance cho checkout flow.
In ScopeProduct search + filter, Product detail page, Cart management, Checkout (Stripe + VNPay), Coupon, Order confirmation email trigger, Cross-browser testing
Out of ScopeOrder tracking/shipping updates (Sprint 6), Admin dashboard, Seller portal, Return/refund flow, Native mobile app
Test TypesFunctional (Playwright E2E + manual), API (Postman/Newman), Database integrity, Cross-browser (Chrome/Safari/Firefox/Edge), Performance (k6 + Lighthouse), Security (OWASP ZAP + manual), Accessibility (axe-core + manual)
BrowsersChrome 120+ ✓, Firefox 121+ ✓, Safari 17+ ✓, Edge 120+ ✓ | Mobile: Chrome Android, Safari iOS
Entry CriteriaBuild #142 deployed to staging ✓, Swagger docs updated ✓, Dev unit test coverage ≥80% ✓, 5,000 products seeded ✓, VNPay sandbox configured ✓
Exit Criteria≥90% test cases executed, Pass rate ≥90%, 0 Critical open, 0 High security vulns, Core Web Vitals pass, WCAG AA critical issues resolved
TimelineDay 1-2: Test design + review | Day 3-7: Functional execution | Day 7-8: Cross-browser + A11y | Day 8-9: Performance + Security | Day 9-10: Bug retest + regression | Day 10: Go/No-Go report
ToolsPlaywright (E2E), Postman + Newman (API), k6 (performance), Lighthouse CI (Web Vitals), OWASP ZAP (security), axe DevTools (A11y), BrowserStack (cross-browser), Jira (bugs), Allure (reporting)
Test Data5,000 seeded products (đủ categories), 10 test user accounts (buyer/guest/admin), Stripe test cards (success/fail/3DS), VNPay sandbox credentials, 20 coupon codes (various types)
🖥️ Frontend Test Cases — Playwright E2E

Automation bằng Playwright với TypeScript. Page Object Model. Chạy trên Chrome + Firefox + Safari trong CI.

Search & Filter

TC IDTest CaseStepsExpectedPriResult
TC-SRCH-001 Search happy path Vào trang chủ → gõ "iPhone 15" vào search box → nhấn Enter Navigate tới /search?q=iPhone+15. Kết quả hiện ≥1 product có "iPhone 15" trong tên. Breadcrumb: "Kết quả cho 'iPhone 15' (N sản phẩm)" P1PASS
TC-SRCH-002 Keyboard shortcut Ctrl+K Đang ở bất kỳ trang nào → nhấn Ctrl+K (Mac: Cmd+K) Search modal/overlay mở ra, focus tự động vào search input P1PASS
TC-SRCH-005 Tìm kiếm không dấu tiếng Việt Gõ "iphone" (không dấu, chữ thường) vào search Trả về kết quả có "iPhone" trong tên. Elasticsearch Vietnamese analyzer phải handle được. P1FAIL — BUG-001
TC-SRCH-006 Auto-suggest dropdown Gõ "sam" → đợi 300ms Dropdown hiện max 8 suggestions bắt đầu bằng "Samsung". Click suggestion → navigate search results. P2PASS
TC-SRCH-007 Empty search result Gõ "xyzabc123notexist" Empty state: "Không tìm thấy kết quả cho 'xyzabc123notexist'". Hiện 4 sản phẩm gợi ý phổ biến. P2PASS
TC-FILTER-001 Filter theo khoảng giá Search "laptop" → kéo price range slider: 5 triệu – 20 triệu → kết quả cập nhật Chỉ hiển thị laptops có giá trong khoảng 5-20 triệu. URL có ?minPrice=5000000&maxPrice=20000000 P1PASS
TC-FILTER-003 Filter state trong URL Apply filter (brand=Apple, minPrice=10000000) → copy URL → mở tab mới Tab mới hiển thị đúng filter đã apply — URL params được đọc và applied đúng. P2PASS
TC-FILTER-004 Xóa filter individual Apply 3 filters → click "×" trên chip "Apple" Chỉ xóa filter "Apple", 2 filter còn lại vẫn active. Kết quả cập nhật. P2PASS

Product Detail

TC IDTest CaseStepsExpectedPriResult
TC-PROD-001 Product page — happy path Click vào sản phẩm "iPhone 15 Pro 256GB" từ search results Product detail page load. Có: ảnh gallery (≥1 ảnh), tên, giá (có hoặc không có giá gốc gạch ngang), SKU, mô tả. Breadcrumb đúng. P1PASS
TC-PROD-002 Variant selector — giá thay đổi Trên iPhone 15 Pro page: chọn variant "512GB" (giá khác "256GB") Giá update ngay tức thì, ảnh chính chuyển sang ảnh của variant 512GB, SKU thay đổi. P1PASS
TC-PROD-003 Stock indicator — sản phẩm sắp hết Set stock_quantity = 3 trong DB → reload product page Hiện "Còn 3 sản phẩm" (warning style). Nếu stock = 0: hiện "Hết hàng", button disabled. P1FAIL — BUG-002
TC-PROD-006 JSON-LD structured data DevTools → Elements → tìm <script type="application/ld+json"> JSON-LD có @type: "Product", name, image, offers.price, offers.availability, aggregateRating. Valid theo schema.org. P3PASS

Cart & Checkout

TC IDTest CaseStepsExpectedPriResult
TC-CART-001 Thêm vào giỏ — mini cart slide-in Click "Thêm vào giỏ hàng" trên product page Mini cart slide-in từ phải trong <500ms. Hiện tên sản phẩm, ảnh, giá, số lượng, tổng giỏ hàng. Badge icon header cập nhật. P1PASS
TC-CART-004 Guest cart sync khi đăng nhập Guest: thêm 2 sản phẩm vào cart → đăng nhập → xem cart Cart sau đăng nhập chứa 2 items của guest. Nếu user đã có cart từ trước: merge (không replace). P1PASS
TC-CHK-001 Checkout happy path — Stripe Cart có 1 item → Checkout → nhập địa chỉ HCM → chọn GHN Nhanh → dùng Stripe card 4242424242424242 → Place Order Trang Order Confirmation với order #SVN-XXXXXX, summary đầy đủ. Cart cleared. Email trigger log visible trong server. P1PASS
TC-CHK-003 Guest checkout Không đăng nhập → cart 1 item → Checkout → nhập email + địa chỉ → thanh toán Checkout hoàn thành không bắt tạo account. Sau thanh toán: offer tạo account với data đã nhập pre-filled. P1PASS
TC-CHK-005 3D Secure — Stripe Dùng Stripe test card 4000002500003155 (3DS required) 3DS authentication popup/redirect hiện ra. Complete challenge → order confirmed. Decline challenge → "Payment failed". P1PASS
TC-CHK-006 Payment failed — card declined Dùng Stripe card 4000000000000002 (always declines) Lỗi "Thẻ bị từ chối. Vui lòng kiểm tra lại thông tin hoặc thử thẻ khác." Ở lại trang checkout. Order KHÔNG được tạo trong DB. P1PASS
TC-CHK-008 VNPay — success flow Chọn VNPay → redirect tới sandbox → thanh toán thành công → redirect về URL: /checkout/return?vnp_ResponseCode=00&... Server verify HMAC → order confirmed → order confirmation page. P1PASS
TC-COUP-005 Coupon — minimum order chưa đạt Coupon "SAVE100K" yêu cầu tối thiểu 500k. Cart = 300k. Nhập coupon. "Cần mua thêm 200.000₫ để sử dụng mã này." Coupon chưa được apply. P2PASS
🔌 API Test Cases — Postman / Newman

Base URL: https://staging.shopvn.vn/api/v1. Runner: Newman trong GitHub Actions.

GET /products/search

TC IDScenarioRequestExpected ResponseResult
API-SRCH-001 Search cơ bản GET /products/search?q=iPhone&limit=20&offset=0 200 OK. Body: {total: N, products: [...], facets: {brands, categories, priceRange}}. Response time < 500ms. PASS
API-SRCH-002 Pagination GET /products/search?q=laptop&limit=10&offset=20 Trả về sản phẩm từ position 21-30. Có total, hasNextPage, hasPrevPage. PASS
API-SRCH-003 XSS injection trong search query GET /products/search?q=<script>alert(1)</script> 200 OK với kết quả (empty hoặc có). Response body KHÔNG chứa unescaped <script>. Query được sanitized. PASS

POST /orders + Payment flow

// Postman Test Script — TC-API-ORDER-001: Create order with Stripe const body = pm.response.json(); pm.test("201 Created", () => pm.response.to.have.status(201)); pm.test("Response time < 3000ms", () => pm.expect(pm.response.responseTime).to.be.below(3000)); pm.test("Order ID format SVN-XXXXXX", () => { pm.expect(body.orderId).to.match(/^SVN-\d{6}$/); }); pm.test("Total amount matches cart", () => { pm.expect(body.totalAmount).to.equal(pm.environment.get('expectedTotal')); }); pm.test("Payment intent created", () => { pm.expect(body.stripePaymentIntentId).to.match(/^pi_/); }); pm.test("Cart cleared", () => { pm.expect(body.cartCleared).to.be.true; }); pm.environment.set('lastOrderId', body.orderId); pm.environment.set('paymentIntentId', body.stripePaymentIntentId);
TC IDScenarioInputExpectedResult
API-PAY-005 Idempotency — double submit POST /orders 2 lần cùng idempotency key Cả 2 trả về 201 nhưng CÙNG orderId. Chỉ 1 record trong DB. Chỉ charge 1 lần. PASS
API-PAY-010 VNPay webhook — HMAC verification POST /webhooks/vnpay với vnp_SecureHash sai 200 OK (tránh VNPay retry) nhưng order NOT updated. Log "Invalid HMAC signature". FAIL — BUG-003
API-PAY-011 Cart price tamper POST /orders với body tự set "totalAmount": 1 Server recalculate từ DB prices, ignore client amount. Order tạo với giá đúng. PASS
API-COUP-001 Validate coupon trước checkout POST /coupons/validate {"code":"SAVE100K","cartTotal":300000} 422 Unprocessable Entity. {"error":"MINIMUM_NOT_MET","shortfall":200000} PASS
🗄️ Database Testing
-- TC-DB-001: Order data integrity sau checkout thành công SELECT o.id, o.status, o.total_amount, o.shipping_fee, o.discount_amount, o.coupon_code, o.stripe_payment_intent_id, o.created_at FROM orders o WHERE o.id = 'SVN-000142'; -- Expected: status='confirmed', amounts chính xác, payment_intent_id có prefix 'pi_' SELECT oi.product_id, oi.variant_id, oi.quantity, oi.unit_price, oi.product_name_snapshot FROM order_items oi WHERE oi.order_id = 'SVN-000142'; -- Expected: price snapshot đúng với giá tại thời điểm order (không bị ảnh hưởng nếu giá sau đó thay đổi) -- product_name_snapshot NOT NULL (de-normalized, bảo toàn lịch sử) -- TC-DB-002: Inventory decrement sau order confirmed SELECT stock_quantity, reserved_quantity FROM product_variants WHERE id = '<variantId>'; -- stock_quantity giảm đúng số lượng ordered -- reserved_quantity = 0 (không còn reserved sau khi confirmed) -- TC-DB-003: No card data stored SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND column_name IN ('card_number', 'cvv', 'pan'); -- Expected: 0 rows — không có column nào lưu card data -- TC-DB-004: Coupon single-use enforcement SELECT times_used, max_uses, is_active FROM coupons WHERE code = 'SAVE100K'; -- times_used tăng +1 sau mỗi order dùng coupon này -- Nếu times_used >= max_uses → coupon không thể dùng thêm -- TC-DB-005: Race condition — flash sale stock -- Setup: stock = 1 cho variant ID X UPDATE product_variants SET stock_quantity = 1 WHERE id = 'flash-item-id'; -- Sau khi 2 concurrent orders gửi: -- Expected: chỉ 1 order confirmed, 1 order failed "Hết hàng" -- stock_quantity = 0 (không phải -1) -- Fix: dùng atomic UPDATE với RETURNING, check rows affected -- TC-DB-006: Elasticsearch sync check -- Sau khi update product name trong DB: UPDATE products SET name = 'iPhone 15 Pro Max 512GB Black' WHERE id = 'prod-001'; -- Sau 10 giây, search API phải reflect tên mới -- Verify: GET /products/search?q=Pro+Max+512GB → phải tìm thấy sản phẩm này
⚠️ TC-DB-005 — Flash Sale Race Condition
Test với Apache Benchmark: ab -n 50 -c 50 -p order.json -T application/json https://staging.shopvn.vn/api/v1/orders → stock đếm âm. Tương tự BUG-004 trong Mobile Project — cần atomic UPDATE với WHERE stock_quantity > 0. → BUG-004 (Critical).
🌐 Cross-browser Testing

Platform: BrowserStack cho real devices + Playwright multi-browser CI. Focus vào checkout flow và product display.

TC IDTest CaseChromeFirefoxSafariEdgeSafari iOS
TC-XB-001 Product image gallery — zoom on hover PASSPASSFAILPASSN/A (touch)
TC-XB-002 Checkout form — address cascade dropdown PASSPASSPASSPASSPASS
TC-XB-003 Stripe payment form render PASSPASSPASSPASSPASS
TC-XB-004 Price range slider (filter) PASSPASSFAILPASSFAIL
TC-XB-005 Mini cart animation slide-in PASSPASSPASSPASSPASS
TC-XB-006 Mobile filter — bottom drawer N/AN/AN/AN/APASS
TC-XB-007 Font rendering — tiếng Việt PASSPASSPASSPASSPASS
⚠️ Safari Issues Found — BUG-005
TC-XB-001: Product image zoom không hoạt động trên Safari 17 — mousemove event handler dùng offsetX/offsetY không tương thích Safari. Fix: dùng getBoundingClientRect().
TC-XB-004: Range input slider styling không apply trên Safari (WebKit cần -webkit-appearance reset). Priority P2 — price filter vẫn hoạt động qua input text fallback.
Performance Testing

Hai loại: k6 (backend load test) + Lighthouse CI (Core Web Vitals frontend). SLA: P90 < 2s, Core Web Vitals pass.

// k6 — tests/performance/sprint5-ecommerce.js import http from 'k6/http'; import { check, sleep, group } from 'k6'; export const options = { scenarios: { browse_and_search: { executor: 'ramping-vus', stages: [ { duration: '2m', target: 100 }, // Ramp up { duration: '5m', target: 500 }, // Flash sale simulation { duration: '2m', target: 0 }, // Ramp down ], }, }, thresholds: { 'http_req_duration{scenario:browse}': ['p(90)<2000'], 'http_req_duration{scenario:search}': ['p(90)<500'], // Elasticsearch fast 'http_req_failed': ['rate<0.005'], // < 0.5% errors }, }; export default function() { group('Product Browse', () => { const res = http.get('https://staging.shopvn.vn/api/v1/products?category=dien-thoai&limit=20'); check(res, { 'status 200': r => r.status === 200 }); sleep(2); }); group('Search', () => { const queries = ['iphone', 'samsung', 'laptop dell', 'tai nghe bluetooth']; const q = queries[Math.floor(Math.random() * queries.length)]; const res = http.get(`https://staging.shopvn.vn/api/v1/products/search?q=${q}&limit=20`); check(res, { 'search 200': r => r.status === 200 }); sleep(1); }); group('Add to Cart', () => { const res = http.post( 'https://staging.shopvn.vn/api/v1/cart/items', JSON.stringify({ productId: 'prod-001', variantId: 'var-001', quantity: 1 }), { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${__ENV.TEST_TOKEN}` } } ); check(res, { 'cart 200': r => r.status === 200 || r.status === 201 }); sleep(1); }); }

Performance Results — Sprint 5

EndpointP50P90P99Error RateSLA
GET /products (browse)95ms280ms590ms0.02%✓ Pass
GET /products/search120ms390ms820ms0.05%✓ Pass
POST /cart/items55ms140ms310ms0.01%✓ Pass
POST /orders (Stripe)780ms1,890ms3,200ms0.3%✓ Pass (barely)
GET /address/provinces8ms22ms45ms0%✓ Pass (cached)

Core Web Vitals — Lighthouse CI

PageLCPINPCLSFCPStatus
Home Page1.8s ✓145ms ✓0.05 ✓0.9s✓ All Pass
Product List2.1s ✓180ms ✓0.12 ✗1.1s✗ CLS = 0.12 (threshold 0.1) — BUG-006
Product Detail2.3s ✓95ms ✓0.04 ✓1.2s✓ All Pass
Checkout1.6s ✓110ms ✓0.02 ✓0.8s✓ All Pass
⚠️ BUG-006: CLS = 0.12 trên Product List page
Product images load sau text → layout shift. Fix: thêm aspect-ratio: 1/1 hoặc width/height attributes trên img tags để browser reserve space. Priority P2.
🔒 Security Testing — OWASP Top 10
TC IDOWASPTest ScenarioExpectedResult
TC-SEC-001 A01: Access Control User A xem order của User B: GET /orders/SVN-000001 với token của User B 403 Forbidden. Order data không leak. PASS
TC-SEC-002 A01: IDOR — cart GET /cart?userId=other-user-id API ignore userId param, trả về cart của authenticated user. Không thể xem cart người khác. PASS
TC-SEC-003 A03: XSS Product review submission với <img src=x onerror=alert(1)> trong text Review được sanitized (DOMPurify hoặc server-side). Hiển thị dưới dạng text, không execute script. PASS
TC-SEC-004 A03: SQL Injection GET /products/search?q='; DROP TABLE products;-- 200 với kết quả empty hoặc 400. DB không bị modified. Parameterized queries. PASS
TC-SEC-005 A04: Insecure Design POST /orders với tự đặt giá: {"items":[{"price":1}]} Server recalculate từ DB. Order total = giá thực tế, không dùng client-submitted price. PASS
TC-SEC-006 A05: Security Headers Kiểm tra HTTP response headers Có: HSTS, X-Frame-Options:DENY, X-Content-Type-Options:nosniff, CSP. Không có: X-Powered-By, Server version. PASS
TC-SEC-007 A07: Auth Cookie: Kiểm tra session cookie attributes Cookies có: HttpOnly, Secure, SameSite=Strict. Không accessible via JavaScript. PASS
TC-SEC-008 A02: Cryptographic SELECT * FROM payments — có card data không? Chỉ có stripe_payment_method_id (pm_xxx). Không có card_number, cvv, expiry. PASS
TC-SEC-009 A10: SSRF Product import URL field: nhập http://169.254.169.254/latest/meta-data/ (AWS metadata) Request bị blocked. Không trả về AWS internal metadata. URL allowlist hoặc block internal ranges. PASS
TC-SEC-010 A09: Logging Trigger failed login 10 lần → check logs Logs có: timestamp, IP, user email (partial mask). Không có: password, full card number trong bất kỳ log nào. PASS
Accessibility Testing — WCAG 2.1 AA

Tools: axe DevTools (automated) + manual keyboard + NVDA (Windows) / VoiceOver (Mac). Focus: Checkout flow (highest barrier for users with disabilities).

TC IDTest CaseMethodExpected (WCAG)Result
TC-A11Y-001 Keyboard navigation — checkout flow Manual: Tab through toàn bộ checkout Mọi interactive element đạt được bằng Tab. Focus indicator visible (outline). Không bị "focus trap" ngoài modal. (WCAG 2.1.1) PASS
TC-A11Y-002 Form labels — checkout inputs axe DevTools + manual Mọi input có label liên kết (for/id hoặc aria-label). Error messages có aria-describedby. (WCAG 1.3.1) PASS
TC-A11Y-003 Color contrast — button và text axe DevTools Contrast ratio: normal text ≥4.5:1, large text ≥3:1. (WCAG 1.4.3) FAIL — BUG-007
TC-A11Y-004 Screen reader — product list NVDA + Chrome Mỗi product card đọc: "Tên sản phẩm, giá, số sao rating, Nút Thêm vào giỏ". Không đọc thừa decorative images. (WCAG 1.1.1) PASS
TC-A11Y-005 Error messages — form validation axe + VoiceOver Khi field invalid: error message được announce ngay (aria-live="polite" hoặc focus shift). (WCAG 3.3.1) PASS
TC-A11Y-006 Skip navigation link Tab từ đầu trang First Tab press hiện "Skip to main content" link. (WCAG 2.4.1) PASS
TC-A11Y-007 Modal — Stripe payment form Keyboard + Screen reader Khi payment modal mở: focus chuyển vào modal, Esc đóng, focus trap trong modal, focus trở về trigger khi đóng. (WCAG 2.1.2) PASS
⚠️ BUG-007: Color Contrast Fail — "Hết hàng" badge
Badge "Hết hàng" dùng màu #999999 text trên #f5f5f5 background → contrast ratio 2.85:1 (yêu cầu 4.5:1). Fix: đổi text color thành #666666 hoặc tối hơn. Ảnh hưởng: users với visual impairment không đọc được trạng thái sản phẩm.
🤖 Automation Suite — Playwright
// tests/e2e/checkout/stripe-checkout.spec.ts import { test, expect } from '@playwright/test'; import { CartPage } from '../pages/CartPage'; import { CheckoutPage } from '../pages/CheckoutPage'; import { ProductPage } from '../pages/ProductPage'; test.describe('Stripe Checkout Flow', () => { test.beforeEach(async ({ page }) => { // Login với test user từ storageState await page.goto('/'); }); test('TC-CHK-001: Complete checkout with Stripe', async ({ page }) => { const productPage = new ProductPage(page); const cartPage = new CartPage(page); const checkoutPage = new CheckoutPage(page); // Add product to cart await productPage.navigate('iphone-15-pro-256gb'); await productPage.addToCart(); await productPage.miniCart.expectVisible(); // Proceed to checkout await cartPage.navigate(); await cartPage.proceedToCheckout(); // Fill shipping address await checkoutPage.fillShippingAddress({ fullName: 'Nguyễn Văn Test', phone: '0901234567', province: 'Hồ Chí Minh', district: 'Quận 1', ward: 'Phường Bến Nghé', street: '123 Nguyễn Huệ', }); // Select shipping method await checkoutPage.selectShippingMethod('GHN Express'); await checkoutPage.continueToPayment(); // Fill Stripe card (inside iframe) const stripeFrame = page.frameLocator('[name="cardNumber"]'); await stripeFrame.locator('input[name="cardnumber"]').fill('4242424242424242'); await page.frameLocator('[name="cardExpiry"]').locator('input').fill('12/28'); await page.frameLocator('[name="cardCvc"]').locator('input').fill('123'); // Submit order await checkoutPage.placeOrder(); // Verify confirmation await expect(page).toHaveURL(/\/order-confirmation\/SVN-\d{6}/); await expect(page.getByRole('heading', { name: /Đặt hàng thành công/ })).toBeVisible(); await expect(page.getByTestId('order-id')).toHaveText(/SVN-\d{6}/); }); test('TC-CHK-006: Card declined shows error', async ({ page }) => { const checkoutPage = new CheckoutPage(page); await checkoutPage.navigateWithCartPrefilled(); await checkoutPage.fillAllFieldsExceptCard(); // Use declined card const stripeFrame = page.frameLocator('[name="cardNumber"]'); await stripeFrame.locator('input').fill('4000000000000002'); await checkoutPage.placeOrder(); await expect(page.getByTestId('payment-error')).toBeVisible(); await expect(page.getByTestId('payment-error')).toContainText('Thẻ bị từ chối'); await expect(page).toHaveURL(/checkout/); // Still on checkout page }); });
📌 Playwright Config — Multi-browser CI
// playwright.config.ts export default defineConfig({ projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, { name: 'webkit', use: { ...devices['Desktop Safari'] } }, { name: 'mobile-chrome', use: { ...devices['Pixel 7'] } }, { name: 'mobile-safari', use: { ...devices['iPhone 14'] } }, ], use: { baseURL: process.env.BASE_URL || 'https://staging.shopvn.vn', trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', }, reporter: [['allure-playwright'], ['html']], });
🐛 Bug Reports — Sprint 5

7 bugs tìm được. 1 Critical, 3 Major, 2 Minor, 1 Trivial.

BUG-001 Tìm kiếm không trả kết quả khi gõ không dấu tiếng Việt Major
Component
Elasticsearch index — Vietnamese analyzer config
Priority
P1 High — core feature, ảnh hưởng phần lớn user VN
Steps
Gõ "iphone" (không dấu) → 0 kết quả. Gõ "iPhone" (đúng hoa) → có kết quả.
Expected
Elasticsearch Vietnamese analyzer phải handle case-insensitive và diacritics (dấu). "iphone" → "iPhone 15 Pro".
Root Cause
Index mapping thiếu icu_folding token filter. Fix: thêm ICU Analysis plugin, rebuild index với lowercase + ascii_folding filters.
Severity Justification
Major: Feature hoạt động nhưng degraded — user VN thường gõ không dấu.
Status
In Progress — estimated fix 1 ngày
BUG-002 Stock indicator "Còn X sản phẩm" không hiển thị khi stock ≤ 5 Minor
Component
ProductDetail component — StockIndicator
Priority
P2 Medium — UX issue, không block purchase
Steps
1. Set DB: UPDATE product_variants SET stock_quantity = 3 WHERE id='var-001'. 2. Reload product page. 3. Quan sát stock indicator.
Actual
Hiển thị "Còn hàng" thay vì "Còn 3 sản phẩm". Condition check dùng stock < 5 thay vì stock <= 5 — off-by-one.
Status
Fixed — verified ngày 2026-06-03
BUG-003 [CRITICAL] VNPay webhook không verify HMAC-SHA512 — order được confirm với forged webhook Critical
Component
Payment Service — POST /webhooks/vnpay handler
Priority
P1 BLOCKER — Security + Financial fraud risk
Steps
1. Craft POST request tới /webhooks/vnpay với body {"vnp_ResponseCode":"00","vnp_TxnRef":"SVN-000001","vnp_SecureHash":"fakehash"}. 2. Send request. 3. Check order status trong DB.
Actual
Order SVN-000001 status được set thành "confirmed" mà không thực sự thanh toán. Code không verify vnp_SecureHash.
Impact
Attacker có thể forge VNPay callback để confirm orders miễn phí. Critical financial fraud vulnerability.
Fix Required
Implement HMAC-SHA512 verification: computedHash = HMAC_SHA512(secret, sortedParams); if (computedHash !== vnp_SecureHash) throw 'Invalid signature'
Status
🔴 BLOCKER — Hotfix Required
BUG-004 [CRITICAL] Flash sale race condition — stock_quantity có thể âm Critical
Component
Order Service — createOrder() inventory check
Priority
P1 BLOCKER — Data integrity, overselling
Steps
Set stock=1 → send 50 concurrent POST /orders với item này → check stock_quantity.
Actual
stock_quantity = -12. 13 orders confirmed cho 1 item. Revenue impact: phải refund 12 orders, ship 1.
Fix
Atomic: UPDATE product_variants SET stock_quantity = stock_quantity - $qty WHERE id = $id AND stock_quantity >= $qty RETURNING id. Kiểm tra rowCount = 1, nếu không → throw 409 Out of Stock.
Status
🔴 BLOCKER — Must Fix Before Any Flash Sale
BUG-005 Image zoom không hoạt động trên Safari 17 (mousemove event) Minor
Component
ProductImageGallery component
Priority
P3 Low — chỉ ảnh hưởng Safari desktop, feature non-critical
Root Cause
Dùng event.offsetX/offsetY — deprecated/inconsistent trên Safari. Fix: const rect = img.getBoundingClientRect(); x = event.clientX - rect.left;
Status
Open — fix planned sprint 6 (low priority)
📊 Test Metrics — Sprint 5 Summary
55
Total TCs
51
Executed
48
Passed
3
Failed
4
Skipped
94%
Pass Rate
93%
Req Coverage
7
Bugs Found
KPITargetActualStatus
Test Execution Rate≥90%92.7% (51/55)✓ Met
Pass Rate≥90%94.1% (48/51)✓ Met
Critical Bugs Open02 (BUG-003 VNPay, BUG-004 race condition)✗ NOT Met
Core Web VitalsAll PassCLS fail on Product List (BUG-006)✗ NOT Met
WCAG 2.1 AA Critical0 Critical violations1 contrast issue (BUG-007)✗ NOT Met
API Performance P90<2s tất cảAll endpoints <2s ✓✓ Met
Cross-browser — Critical pathsPass Chrome/Firefox/Safari/Edge2 Safari issues (non-blocking checkout)✓ Conditional
Security — Critical vulns01 Critical (BUG-003 VNPay HMAC)✗ NOT Met
Automation Coverage (P1 TCs)≥65%76% (19/25 P1 cases automated)✓ Met
Defect Leakage từ Sprint 4<3%0% (không tìm được Sprint 4 bugs)✓ Met

Bug Severity Distribution

SeverityFoundFixedOpenDeferred
Critical2020
Major3111
Minor2101
Total7232
🚦 Go / No-Go Release Report — Sprint 5
CriteriaRequirementActualDecision
Test Pass Rate≥90%94.1%✅ Go
Critical Bugs0 open2 open (VNPay HMAC, Race condition)🔴 No-Go
Security Vulnerabilities0 Critical1 Critical (BUG-003)🔴 No-Go
Core Web VitalsAll PassCLS = 0.12 (fail) on Product List🟡 Conditional
WCAG 2.1 AA0 critical A11y issues1 contrast issue (BUG-007, P3 impact)🟡 Conditional
API PerformanceP90 <2sAll endpoints <2s ✓✅ Go
Cross-browser CheckoutPass tất cả browsersCheckout works on all browsers. Safari image zoom fail (non-blocking)✅ Go
Regression0 regressions Sprint 40 regressions✅ Go
Vietnamese SearchSupport tiếng Việt không dấuBUG-001 — search không dấu fail🔴 No-Go
📋 QA Official Recommendation — Sprint 5

Recommendation: NO-GO

Blockers bắt buộc fix trước release:

  1. BUG-003 — VNPay HMAC verification: Security critical. Attacker có thể confirm orders miễn phí bằng forged webhook. Estimated fix: 4 giờ.
  2. BUG-004 — Race condition inventory: Có thể oversell trong flash sale hoặc khi nhiều user cùng mua sản phẩm ít hàng. Estimated fix: 6 giờ.
  3. BUG-001 — Vietnamese search: Cốt lõi của product discovery cho user VN. 70%+ users gõ không dấu. Estimated fix: 1 ngày (rebuild ES index).

Có thể release sau khi fix 3 blockers trên (với risk acceptance từ PM cho các mục sau):

  1. CLS 0.12 trên Product List → impact SEO nhưng không ảnh hưởng user flow. Monitor Core Web Vitals sau deploy.
  2. Safari image zoom → ảnh hưởng ~18% users, feature non-critical. Fix Sprint 6.
  3. WCAG contrast "Hết hàng" → fix cùng sprint 6 với accessibility audit toàn trang.

Revised release estimate: +2 ngày (fix 3 blockers + 1 ngày regression test mini).

Signed: QA Engineer — Sprint 5 | ShopVN v2.1 | Date: 2026-06-04

💡 Lessons Learned — Sprint 5
1. VNPay HMAC phải trong checklist security từ đầu: Đây là lỗi design, không phải coding error — phải được clarify trong Requirements stage. "Verify webhook signature" phải là explicit AC.
2. Vietnamese search cần spike sớm: Elasticsearch Vietnamese analyzer config là technical prerequisite — nên được verify trong Sprint 1 (foundation sprint) thay vì phát hiện lúc QA.
3. CLS phải test trong CI, không chờ QA: Lighthouse CI nên chạy trong GitHub Actions trên mỗi PR, không phải chỉ khi QA chạy manual. Developer biết CLS fail ngay khi code.
4. Race condition là pattern lặp lại: Mọi inventory decrement, coupon use, flash sale phải có explicit review về concurrency trong Design Review. Add vào PR checklist.
🏆 Senior QA Insight — Web vs Mobile Testing
E-commerce web testing có những điểm phức tạp riêng so với mobile: (1) Cross-browser: Mobile chỉ test iOS/Android, web phải test 4+ browsers mỗi với quirks riêng. (2) SEO: Web cần test structured data, meta tags, Core Web Vitals — không liên quan mobile native. (3) Payment webhook: Web dùng server-side webhook cho VNPay/Stripe — mobile SDK tự handle phần lớn. (4) CDN/Cache: Web có nhiều cache layer hơn (CDN edge, browser cache, Redis) — invalidation phức tạp hơn. (5) Accessibility: Web WCAG requirements nghiêm ngặt hơn vì screen readers dùng nhiều trên desktop web.