Performance Test Plan: Drop — Fintech Payment App
E2EPerformance Test Plan: Drop — Fintech Payment App
Project: Drop — Remittance + QR Payments Version: 1.0 Date: 2026-02-23 Author: John (AI Director) Status: Approved Reviewers: Alem Bašić (CEO)
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | 2026-02-23 | John | Initial |
1. E2EPerformance Testing Objectives
Objectives:
- Validate that Drop meets the
5performancemostSLAscriticaldefinedDropin the NFR document under normal operating conditions (200 concurrent users on SQLite MVP; 5,000+ on PostgreSQL Phase 1) - Determine the SQLite concurrent user
journeyslimitworkbeforeend-to-endmigratingontostagingPostgreSQL (target: migrate at 200 concurrent) CatchIdentifyintegrationbottlenecksfailures(bcryptbetweenhashing,Next.jsDBfrontend,queries,APIrateroutes,limiter)SQLitebeforeDB,productionand mock BaaS that unit/integration tests cannot detectreleaseProvideEstablishconfidenceabeforeperformanceevery production deployment via post-staging deploy gateServe as living documentation of critical user flowsbaseline forCEOregressionUATcomparison in future releases
WhatReference E2ENFRs: tests are NOT for:
Complete feature coverage (handled by unit/integration testsdocs/BUSINESS-REQUIREMENTS/non-functional-requirements.md—40+SectionVitest tests)Testing every error state2 (too expensive to maintain; covered by input-chaos.spec.ts for validation)Performance benchmarking (handled by api-benchmarks.test.ts)Visual pixel-perfection (Figma source of truth: mockups/figma-make-export/)
2. CriticalPerformance UserRequirements JourneysReference
Journey 1: User Registration — 3-Step Onboarding
|
Steps:
Assertions:
201 Created on registration (no password hash in response)JWT httpOnly cookie set after PIN setupDashboard visible with user's nameAudit log entry created
Journey 2: User Login + Dashboard Access
|
Steps:
Assertions:
200 on login; JWT cookie set; SameSite=Strict; httpOnlyGET /api/auth/me returns user object (no password hash)Balance shown is from mock BaaS (not stored in Drop DB)
Journey 3: Remittance — NOK to RSD
|
Steps:
Log in as KYC-approved consumerNavigate to "Send penger" (Send Money)Select recipient; enter amount = 1,000 NOK; select currency = RSDReview: fee shown as 5 NOK (0.5%); exchange rate shownConfirm and submitTransaction created with status = completedNavigate to Transaction History — transaction visible
Assertions:
201 Created; transaction in DB with fee = 5 NOKTransaction history shows amount, fee, currency, statusNo balance change in Drop DB (pass-through — balance read from mock AISP)
Journey 4: QR Payment — Scan + Pay
|
Steps:
Log in as KYC-approved consumerNavigate to "Scan QR"Enter valid merchantId (pre-seeded merchant)Enter amount = 200 NOKReview: merchant_fee = 2 NOK (1%)Confirm paymentTransaction created; visible in history
Assertions:
201 Created; merchant_fee = 2 NOK; amount = 200 NOKTransaction visible in consumer historyMock PISP payment recorded
Journey 5: Merchant Registration + QR Generation
|
Steps:
Log in as user (not yet a merchant)Navigate to "Bli Merchant" (Become Merchant)Enter business_name and bank_accountSubmit — merchant created with unique QR code valueNavigate to merchant dashboard — QR code visible
Assertions:
201 Created; merchant has unique qr_code_valueGET /api/merchants/me returns merchant details + QR codeQR code is unique (no two merchants share a code)
All Journeys Summary
3. Browser & Device Matrix
Desktop Browsers
| Notes | ||||||
|---|---|---|---|---|---|---|
GET /api/health |
< |
Health |
||||
POST /api/auth/login (bcrypt) |
< 0.1% | bcrypt 12 rounds; NFR-P03 | ||||
POST /api/auth/register (bcrypt) |
< 700ms | < 900ms | < 1,000ms | < 1,200ms | < 0.1% | Same bcrypt cost |
POST /api/transactions/remittance |
< 200ms | < 350ms | < 500ms | < 800ms | < 0.1% | NFR-P02 |
POST /api/transactions/qr-payment |
< 200ms | < 350ms | < 500ms | < 800ms | < 0.1% | NFR-P02 |
GET /api/rates |
< 20ms | < 50ms | < 100ms | < 200ms | < 0.1% | Cached / fast |
GET /api/transactions |
< 50ms | < 100ms | < 200ms | < 400ms | < 0.1% | NFR-P02 |
| DB SELECT query | — | — | < |
— | 0% | NFR-P04 |
| DB INSERT query | — | — | < 20ms | — | 0% | NFR-P04 |
| 50 concurrent rate limit checks | — | — | — | — | 0% | < 2,000ms total (NFR-P06) |
| Page load (First Contentful Paint) | — | — | < 3,000ms | — | — | NFR-P01 (4G, cold cache) |
| Core Web Vitals LCP | — | — | < 2,500ms | — | — | NFR-P05 |
3. Test Types
Mobile3.1 BrowsersLoad Testing — Normal Load Simulation
Objective: Confirm Drop meets SLAs under expected normal load (Norwegian remittance corridor peak traffic) Normal load definition: 200 concurrent users (SQLite MVP limit) / ~20 requests/second Duration: 10 minutes (after 2-minute ramp-up) Pass criteria: All SLA targets in Section 2 met with ≤ 0.1% error rate
3.2 Stress Testing — Beyond Normal Capacity
Objective: Find SQLite concurrent user limit; understand failure behavior to plan PostgreSQL migration Starting point: 50 users, increasing by 25 users every 2 minutes Stop condition: Error rate > 5% or P99 > 3,000ms or service crashes Pass criteria: System fails gracefully with meaningful error messages (429 rate limit, not 500); data integrity maintained; no double-spend under stress
3.3 Spike Testing — Sudden Traffic Surges
Objective: Simulate scenarios like marketing campaigns or media coverage causing sudden user influx Baseline: 20 users Spike to: 100 users (5× baseline) Spike duration: 2 minutes Pass criteria: System recovers to baseline performance within 5 minutes after spike ends; no data corruption
3.4 Endurance / Soak Testing — Sustained Load
Objective: Identify SQLite file lock issues, connection pool exhaustion, memory leaks under sustained load Load level: 50 users (25% of 200 concurrent limit) Duration: 2 hours Metrics to watch: SQLite write queue depth, memory usage trend, response time trend Pass criteria: No upward trend in response time over soak period; no SQLite database locked errors
3.5 Scalability Testing — Phase 1 PostgreSQL Migration
Objective: Verify PostgreSQL migration enables proportional scaling for Phase 1 target (5,000+ users) Test: Step load from 200 to 1,000 concurrent users on PostgreSQL staging Pass criteria: P95 stays under 500ms as concurrent users scale; no P99 explosion Note: PostgreSQL not yet deployed — Phase 1 task. This plan establishes the baseline for comparison.
4. Load Profiles
| Ramp-Down | Think Time | Notes | ||||
|---|---|---|---|---|---|---|
| 10s | 1min | 10s | 1s | Per-deploy quick check | ||
| Load (normal SQLite) | 200 | 2min | 10min | 1min | 2s | NFR-S01 MVP target |
| Stress (find limit) | Up to 500 | 2min steps | Until fail | — | 2s | SQLite vs PostgreSQL |
| Spike (traffic burst) | 100 (instant) | 0s | 2min | 0s | 1s | Marketing/media event |
| Soak (stability) | 50 | 1min | 2h | 2min | 3s | Memory leak detection |
Think time simulation: Realistic user think time of 2–5s (Norwegian users reviewing remittance details before confirming)
5. Test Environment
NOTE: Performance tests run against a representative staging environment. SQLite MVP limits are tested on Fly.io staging (1× shared CPU, 256MB RAM). Phase 1 PostgreSQL tests require production-equivalent environment.
| Component | MVP Staging (Fly.io) | Phase 1 Target |
|---|---|---|
| App instances | 1× shared-cpu-1x (256MB) | 2× dedicated-cpu-2x |
| Database | SQLite on persistent volume | PostgreSQL (managed Fly.io) |
| Region | Stockholm (arn) | Stockholm (arn) |
| CDN | None (API only) | None (API) / Vercel (landing) |
Load generator:
- Tool: Vitest benchmarks (
api-benchmarks.test.ts) for micro-benchmarks - Load generator for integration: k6 scripts in
infrastructure/performance/ - Load generator location: Local or CI runner (same region as Fly.io Stockholm)
6. Test Data Requirements
| Data Type | Volume | Generation Method |
|---|---|---|
| Users | 500 | npm run db:seed:perf |
| Recipients (remittance targets) | 200 | Seed script |
| Merchants | 50 | Seed script |
| Transactions (historical) | 10,000 | Bulk insert seed |
| Exchange rates | 6 corridors (fixed) | Always seeded |
Database size at test time: ~50MB SQLite file
Data preparation: npm run db:seed:perf (estimated time: ~2 minutes)
7. Tools & Infrastructure
| Tool | Version | Purpose | Config |
|---|---|---|---|
| Vitest (bench) | 2.x | Micro-benchmarks: bcrypt, DB queries, rate limiter | src/drop-app/__tests__/api-benchmarks.test.ts |
| k6 | Latest | infrastructure/performance/k6-scripts/ |
|
| SQLite EXPLAIN QUERY PLAN | DB |
Via flyctl ssh console |
Script location: src/drop-app/__tests__/api-benchmarks.test.ts (Vitest benchmarks)
8. Key Metrics to Capture
Response Time
| Metric | Description | Tool |
|---|---|---|
| P50 (median) | Half of requests faster than this | Vitest bench / k6 |
| P90 | 90% of requests faster than this | k6 |
| P95 | 95% of requests faster than this | k6 (NFR gate) |
| P99 | 99% of requests faster than this | k6 |
| Max | Worst single request | k6 |
Screen Resolutions (configured in playwright.config.ts)Throughput
| Requests/second | Total API throughput at peak load |
| Transactions/second | Successful remittance/QR transactions per second |
Error Metrics
| Metric | |
|---|---|
| HTTP error rate (5xx) | < 0.1% |
| SQLite database locked errors | 0% (rate limit blocks excess concurrency) |
| Connection timeout rate | < 0.1% |
Resource Utilization (Fly.io Metrics)
| Resource | Warning | Critical |
|---|---|---|
| App CPU | > 70% | > 90% |
| App Memory | > 80% (200MB / 256MB) | > 95% |
| SQLite write queue | > 50ms wait | > 200ms wait |
Database Query Performance (NFR-P04)
| Metric | Target |
|---|---|
| SELECT query P95 | < 10ms |
| INSERT query P95 | < 20ms |
| Slow queries (> 100ms) | 0 per minute under normal load |
9. SLA Targets Per Endpoint (from api-benchmarks.test.ts)
| Endpoint | Method | P95 SLA | Error Rate SLA | Notes |
|---|---|---|---|---|
/api/auth/login |
bcrypt |
|||
/api/auth/register |
bcrypt |
|||
/api/transactions/remittance |
NFR-P02 | |||
/api/transactions/qr-payment |
Matrix in CI: Chromium + WebKit (mobile viewports 375px and 428px) on every deployment.
4. Test Data Setup & Teardown
Setup Strategy
| |||||
/api/rates |
|
< 0.1% | Cached | ||
|
100ms | < 0.1% | Always fast | ||
|
Seed command: npm run db:seed (populates staging SQLite with synthetic test data)
Teardown Strategy
NFR-P04 |
||||
20ms |
0% | NFR-P04 | ||
Rule: Tests must not leave state that affects other tests. Each test creates its own isolated data.
Test Accounts
0% |
|||
| |||
|
Credentials stored in: Vaultwarden (vault.basicconsulting.no) → "Drop E2E Test Accounts"
5. Authentication Handling in Tests
Strategy: API-based auth — authenticate via POST /api/auth/login in beforeAll, save storage state, reuse for all tests in the suite. Only use UI login when testing the login flow itself.
// playwright.config.ts approach:
// 1. beforeAll: POST /api/auth/login → save JWT cookie to storage state
// 2. Each test that needs auth: restore storage state (< 1ms overhead)
// 3. Tests for login UI: always fresh (no saved state)
Session reuse:
Session state saved per role (consumer, merchant) after first authReused for all tests requiring that roleJWT expires in 7 days — within test run duration
6. Test Environment Requirements
| |
| |
Critical: E2E tests must NOT run against a production environment. Drop handles financial data — all E2E tests use mock BaaS exclusively.
7. Flaky Test Management Strategy
Definition: A test that fails inconsistently for the same code.
Prevention:
Use explicit waits:waitForResponse('/api/...')notpage.waitForTimeout(1000)Isolate test data — unique emails per test run (e2e-fresh-{Date.now()}@test.alai.no)Usedata-testidselectors — not CSS classes or text (can change with Norwegian/English toggle)Disable animations in test env viaprefers-reduced-motion
Detection:
Any test failing > 5% of runs without code changes = flakyPlaywright HTML report shows flaky tests in CI artifacts
Response:
Tag flaky test withtest.fixme()annotation in PlaywrightCreate Mission Control task with P2 priorityFix within 3 days or remove the testMonthly flaky test review during sprint retrospective
8. Visual Regression Testing
Approach: Playwright screenshot comparisons on critical screens.
Baseline: Stored in src/drop-app/__tests__/e2e/snapshots/ (committed to repo)
Source of Truth: Figma Make export: mockups/figma-make-export/src/components/
Review process: Screenshot diffs require Builder + Validator sign-off before merge. Figma is the source of truth — any diff vs Figma is a regression.
9. Performance Assertions Within E2E
10. CIBaseline IntegrationEstablishment (api-benchmarks.test.ts Results)
Trigger:Baseline established: Post-stagingPhase deployment0 (GitHub Actions workflow: .github/workflows/e2e.yml)
Parallelization:
3 Playwright projects run in parallel: user-flows, full-flows, input-chaos2 workers per projectMVP onCIlocal dev (total:SQLite6 parallel Playwright workers)Estimated total time: < 10 minutes
CI configuration:
# .github/workflows/e2e.yml
# Key settings:
# - Projects: user-flows, full-flows, input-chaos
# - Retries: 1 (flaky detection; not masking failures)
# - Timeout per test: 30,000ms
# - Timeout total: 600,000ms (10 min)
# - Base URL: https://drop-staging.fly.dev/
On failure:
Collect screenshots, videos, Playwright traces as CI artifactsRetain artifacts for 7 daysAlert Slack #drop-e2e-failures on alai-talk.slack.comBlock production deployment until fixed
11. Test Report Format & Artifacts
Report format: Playwright HTML report + JUnit XML for CI
Report location: GitHub Actions CI artifacts (7-day retention)
Artifacts collected on failure:in-memory)
| Test | |||
|---|---|---|---|
| api-benchmarks.test.ts | |||
| api-benchmarks.test.ts | |||
| api-benchmarks.test.ts | |||
| api-benchmarks.test.ts | |||
|
api-benchmarks.test.ts | ||
| SHA-256 hash (reject baseline) | ~1ms | 2026-02-23 | auth.test.ts |
Regression threshold: Alert if any metric degrades > 15% vs baseline
11. Test Execution Schedule
| Run Type | Trigger | Environment | Frequency |
|---|---|---|---|
| Benchmark smoke | Every CI run | Local / CI runner | Per commit |
| api-benchmarks.test.ts full | Every PR | CI | Per PR |
| k6 load test | Release candidate | Fly.io staging | Per release |
| k6 stress test | Before Phase 1 PostgreSQL migration | Fly.io staging | Quarterly |
| k6 soak test | Before Phase 1 launch | Fly.io staging | Pre-launch |
12. MaintenanceResults StrategyAnalysis Template
PageTest ObjectRun Pattern:ID: {RUN_ID}
Date: {DATE}
Tester: Builder Agent + Validator Agent
Scenario: Load (normal) / Stress / Spike / Soak
Build / Version: v{VERSION}
| Endpoint | P50 (ms) | P90 (ms) | P95 (ms) | P99 (ms) | Error % | RPS | Status vs SLA |
|---|---|---|---|---|---|---|---|
/api/auth/login |
— | — | — | — | — | — | Pass / Fail |
/api/transactions/remittance |
— | — | — | — | — | — | Pass / Fail |
| DB SELECT | — | — | — | — | — | — | Pass / Fail |
Summary:
AllPeakpageconcurrentinteractionsusers:wrapped in lightweight Page Object classes{PEAK_USERS}SelectorsPeakcentralizedthroughput:in{PEAK_RPS}req/stests/e2e/pages/Location:Testduration: {DURATION} minsrc/drop-app/__tests__/e2e/pages/- Total requests: {TOTAL_REQUESTS}
- Total errors: {TOTAL_ERRORS}
SelectorNotable strategy (Drop-specific):
data-testidattributes — preferred (e.g.,data-testid="remittance-amount")ARIA roles + accessible name (e.g.,getByRole('button', { name: 'Send penger' }))Text content — acceptable for stable Norwegian/English textCSS class names — avoid (Tailwind classes can change)XPath — never
Review cadence:findings:
E2ESQLitetestsconcurrentupdateduser limit reached at: {LIMIT} users- Recommend PostgreSQL migration at: {TRIGGER} concurrent users (per NFR-S02)
Recommendation: Pass / Fail / Conditional pass with PostgreSQL migration required for Phase 1
13. Bottleneck Identification Process
Drop-specific bottleneck investigation order:
1. Check bcrypt timing → bcrypt > 1,000ms P95 = config issue (rounds too high OR long password pre-check missing)
2. Check SQLite write queue → "database is locked" errors = concurrent write saturation → trigger PostgreSQL migration
3. Check rate limiter DB table → rate_limit_requests table query slow = add index or migrate to Redis
4. Check transaction lock → SELECT FOR UPDATE timeout = deadlock in samedouble-spend PRprevention
as5. featureCheck changesmock BaaS response → mock service timeout = CI/staging environment issue
6. Check Fly.io metrics → CPU > 90% = scale up instance; Memory > 200MB = Node.js leak check
14. Remediation Tracking
| Issue | Found In | Severity | Root Cause | Fix | Fixed In | Verified |
|---|---|---|---|---|---|---|
| In-memory rate limiter reset on restart | Phase 0 load test | High | In-memory Map → DB-backed | Phase 0.5 security hardening | v0.5.0 | db.test.ts |
| Long password bcrypt DoS ( |
Phase |
High | No |
1,000 |
v0.5.0 | validation.test.ts |
Related Documents
- Test Strategy
TestNon-FunctionalPlanTest Case TemplatePerformance Test PlanRequirements- Testing Guide
- Test Inventory
Approval
| Role | Name | Date | Signature |
|---|---|---|---|
| Author | John (AI Director) | 2026-02-23 | Approved (AI) |
| QA Lead | Validator Agent | 2026-02-23 | Approved (AI) |
| AI Director (John) | John | 2026-02-23 | Approved |
| CEO (Alem) | Alem Bašić | TBD |