Skip to main content

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 E2Eperformance plan — 3targets Playwrightfrom projectsNFR-P01..P06 (user-flows,+ full-flows, input-chaos)api-benchmarks.test.ts

1. E2EPerformance Testing Objectives

Objectives:

  1. Validate that Drop meets the 5performance mostSLAs criticaldefined Dropin the NFR document under normal operating conditions (200 concurrent users on SQLite MVP; 5,000+ on PostgreSQL Phase 1)
  2. Determine the SQLite concurrent user journeyslimit workbefore end-to-endmigrating onto stagingPostgreSQL (target: migrate at 200 concurrent)
  3. CatchIdentify integrationbottlenecks failures(bcrypt betweenhashing, Next.jsDB frontend,queries, APIrate routes,limiter) SQLitebefore DB,production and mock BaaS that unit/integration tests cannot detectrelease
  4. ProvideEstablish confidencea beforeperformance every production deployment via post-staging deploy gate
  5. Serve as living documentation of critical user flowsbaseline for CEOregression UATcomparison in future releases

WhatReference E2ENFRs: tests are NOT for:

  • Complete feature coverage (handled by unit/integration testsdocs/BUSINESS-REQUIREMENTS/non-functional-requirements.md40+Section Vitest 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/)
Performance)


2. CriticalPerformance UserRequirements JourneysReference

Journey 1: User Registration — 3-Step Onboarding

FieldEndpoint / Feature Value
PriorityCritical
Business ImpactCore user acquisition — no registration = no users
FrequencyEvery new user (one-time)
Test Filesrc/drop-app/__tests__/e2e/user-flows.spec.ts

Steps:

  1. Navigate to https://drop-staging.fly.dev/
  2. Click "Registrer deg" — registration form opens
  3. Enter valid data: name (with Unicode: Amir Hasić), email, password ≥ 8 chars, Norwegian phone (+47), DOB ≥ 18 years
  4. Submit form — OTP sent to phone, OTP input shown
  5. Enter correct 6-digit OTP
  6. Enter and confirm 4-digit PIN
  7. Redirect to dashboard — account activated

Assertions:

  • 201 Created on registration (no password hash in response)
  • JWT httpOnly cookie set after PIN setup
  • Dashboard visible with user's name
  • Audit log entry created

Journey 2: User Login + Dashboard Access

FieldP50 Value
PriorityCritical
Business ImpactCore returning user flow — no login = no revenue
FrequencyEvery user, every session
Test Filesrc/drop-app/__tests__/e2e/user-flows.spec.ts

Steps:

  1. Navigate to /login
  2. Enter valid email and password
  3. Submit
  4. Redirected to dashboard
  5. Bank balance shown (from mock AISP)
  6. Recent transactions visible

Assertions:

  • 200 on login; JWT cookie set; SameSite=Strict; httpOnly
  • GET /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

FieldP90 Value
PriorityCritical
Business ImpactCore revenue — 0.5% fee per transaction
FrequencyMultiple times per month per user
Test Filesrc/drop-app/__tests__/e2e/full-flows.spec.ts

Steps:

  1. Log in as KYC-approved consumer
  2. Navigate to "Send penger" (Send Money)
  3. Select recipient; enter amount = 1,000 NOK; select currency = RSD
  4. Review: fee shown as 5 NOK (0.5%); exchange rate shown
  5. Confirm and submit
  6. Transaction created with status = completed
  7. Navigate to Transaction History — transaction visible

Assertions:

  • 201 Created; transaction in DB with fee = 5 NOK
  • Transaction history shows amount, fee, currency, status
  • No balance change in Drop DB (pass-through — balance read from mock AISP)

Journey 4: QR Payment — Scan + Pay

FieldP95 Value
PriorityCritical
Business ImpactCore QR revenue — 1% merchant fee
FrequencyMultiple times per week per merchant
Test Filesrc/drop-app/__tests__/e2e/full-flows.spec.ts

Steps:

  1. Log in as KYC-approved consumer
  2. Navigate to "Scan QR"
  3. Enter valid merchantId (pre-seeded merchant)
  4. Enter amount = 200 NOK
  5. Review: merchant_fee = 2 NOK (1%)
  6. Confirm payment
  7. Transaction created; visible in history

Assertions:

  • 201 Created; merchant_fee = 2 NOK; amount = 200 NOK
  • Transaction visible in consumer history
  • Mock PISP payment recorded

Journey 5: Merchant Registration + QR Generation

Error
FieldP99 Value
PriorityHigh
Business ImpactMerchant onboarding — enables QR revenue
FrequencyOne-time per merchant
Test Filesrc/drop-app/__tests__/e2e/full-flows.spec.ts

Steps:

  1. Log in as user (not yet a merchant)
  2. Navigate to "Bli Merchant" (Become Merchant)
  3. Enter business_name and bank_account
  4. Submit — merchant created with unique QR code value
  5. Navigate to merchant dashboard — QR code visible

Assertions:

  • 201 Created; merchant has unique qr_code_value
  • GET /api/merchants/me returns merchant details + QR code
  • QR code is unique (no two merchants share a code)

All Journeys Summary

JourneyPriorityEst. DurationAutomatedPlaywright Project
User Registration (3-step)Critical~30sYesuser-flows
User Login + DashboardCritical~15sYesuser-flows
Remittance NOK→RSDCritical~30sYesfull-flows
QR PaymentCritical~25sYesfull-flows
Merchant RegistrationHigh~20sYesfull-flows
Input Chaos (20+ edge cases)High~60sYesinput-chaos

3. Browser & Device Matrix

Desktop Browsers

browserPlaywright requiredforusersonMac
BrowserVersionOSPriorityRate Notes
ChromeGET /api/health Latest< stable20ms macOS,< Linux (CI)50ms Critical< 100ms Primary< test200ms < in0.1% Health CIcheck — fast always
SafariPOST /api/auth/login (bcrypt) Latest< stable700ms macOS< 900ms High< 1,000ms WebKit< engine1,200ms < 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 < Norwegian10ms 0% NFR-P04
DB INSERT query< 20ms0%NFR-P04
50 concurrent rate limit checks0%< 2,000ms total (NFR-P06)
Page load (First Contentful Paint)< 3,000msNFR-P01 (4G, cold cache)
Core Web Vitals LCP< 2,500msNFR-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

BrowserScenario PlatformVirtual Users VersionRamp-Up PriorityHoldRamp-DownThink Time Notes
SafariSmoke (quick sanity) iOS510s1min10s1sPer-deploy quick check
Load (normal SQLite)2002min10min1min2sNFR-S01 MVP target
Stress (find limit)Up to 5002min stepsUntil fail2sSQLite vs PostgreSQL
Spike (traffic burst)100 (instant)0s2min0s1sMarketing/media event
Soak (stability)501min2h2min3sMemory 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.

ComponentMVP Staging (Fly.io)Phase 1 Target
App instances1× shared-cpu-1x (256MB)2× dedicated-cpu-2x
DatabaseSQLite on persistent volumePostgreSQL (managed Fly.io)
RegionStockholm (arn)Stockholm (arn)
CDNNone (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 TypeVolumeGeneration Method
Users500npm run db:seed:perf
Recipients (remittance targets)200Seed script
Merchants50Seed script
Transactions (historical)10,000Bulk insert seed
Exchange rates6 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

mobile
ToolVersionPurposeConfig
Vitest (bench)2.xMicro-benchmarks: bcrypt, DB queries, rate limitersrc/drop-app/__tests__/api-benchmarks.test.ts
k6 Latest CriticalHTTP load testing (concurrent users) Primary Drop platform — iPhone SE to iPhone Pro Maxinfrastructure/performance/k6-scripts/
ChromeFly.io Metrics Android LatestReal-time CPU/memory/request metrics HighFly.io dashboard
SQLite EXPLAIN QUERY PLAN Secondary DB platformquery analysisVia flyctl ssh console

Script location: src/drop-app/__tests__/api-benchmarks.test.ts (Vitest benchmarks)


8. Key Metrics to Capture

Response Time

MetricDescriptionTool
P50 (median)Half of requests faster than thisVitest bench / k6
P9090% of requests faster than thisk6
P9595% of requests faster than thisk6 (NFR gate)
P9999% of requests faster than thisk6
MaxWorst single requestk6

Screen Resolutions (configured in playwright.config.ts)Throughput

CategoryMetric ResolutionDescription
Requests/secondTotal API throughput at peak load
Transactions/secondSuccessful remittance/QR transactions per second

Error Metrics

Priority
Metric TestTarget
HTTP error rate (5xx)< 0.1%
SQLite database locked errors0% (rate limit blocks excess concurrency)
Connection timeout rate< 0.1%

Resource Utilization (Fly.io Metrics)

ResourceWarningCritical
App CPU> 70%> 90%
App Memory> 80% (200MB / 256MB)> 95%
SQLite write queue> 50ms wait> 200ms wait

Database Query Performance (NFR-P04)

MetricTarget
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)

targettarget
EndpointMethodP95 SLAError Rate SLA Notes
Mobile S (iPhone SE)/api/auth/login 375×667POST Critical1,000ms Drop< primary0.1% bcrypt device12 rounds
Mobile L (iPhone 14 Pro)/api/auth/register 428×926POST Critical1,000ms Drop< primary0.1% bcrypt device12 rounds
Tablet/api/transactions/remittance 768×1024POST Medium500ms Secondary< 0.1%NFR-P02
Desktop/api/transactions/qr-payment 1280×800POST Medium500ms Fallback< / admin use

Matrix in CI: Chromium + WebKit (mobile viewports 375px and 428px) on every deployment.


4. Test Data Setup & Teardown

Setup Strategy

suite suite
Data TypeMethodScope
Consumer account (KYC approved)0.1% Pre-seeded in beforeAll via APITest suiteNFR-P02
Merchant account/api/rates Pre-seeded in beforeAll via APIGET Test100ms < 0.1%Cached
Test recipients (for remittance)Pre-seeded in beforeAll/api/health TestGET 100ms< 0.1%Always fast
IsolatedDB fresh user (for registration tests)SELECT Created via API in beforeEach with unique email Individual test

Seed command: npm run db:seed (populates staging SQLite with synthetic test data)

Teardown Strategy

Cleanup ItemMethodTrigger
Fresh users created per test10ms DELETE via API0% afterEachNFR-P04
TestDB session dataINSERT Clear browser storage afterEach20ms0%NFR-P04
Transactions50 createdconcurrent duringrate testlimit Archive (not delete  audit trail) Test2,000ms suite end

Rule: Tests must not leave state that affects other tests. Each test creates its own isolated data.

Test Accounts

AccountEmailRolePurpose
Consumer (Amir)total [email protected]0% Consumer, KYC approvedStandard journeys: login, remittance, QR
Merchant (Ahmet)[email protected]MerchantMerchant dashboard, QR generation
Fresh usere2e-fresh-{timestamp}@test.alai.noNoneRegistration flow tests

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 auth
  • Reused for all tests requiring that role
  • JWT expires in 7 days — within test run duration

6. Test Environment Requirements

RequirementSpecification
EnvironmentStaging (https://drop-staging.fly.dev/)
DatabaseStaging SQLite with synthetic seed data only
External servicesMock BaaS (NEXT_PUBLIC_SERVICE_MODE=mock); mock Sumsub
StabilityNo active deployments during E2E run (Fly.io deployment check)
Data persistenceIsolated from manual testing during E2E run
Response times< 5,000ms for all endpoints (Playwright default timeout)

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/...') not page.waitForTimeout(1000)
  • Isolate test data — unique emails per test run (e2e-fresh-{Date.now()}@test.alai.no)
  • Use data-testid selectors — not CSS classes or text (can change with Norwegian/English toggle)
  • Disable animations in test env via prefers-reduced-motion

Detection:

  • Any test failing > 5% of runs without code changes = flaky
  • Playwright HTML report shows flaky tests in CI artifacts

Response:

  • Tag flaky test with test.fixme() annotation in Playwright
  • Create Mission Control task with P2 priority
  • Fix within 3 days or remove the test
  • Monthly 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/

CheckScopeTrigger
Dashboard screenshotDashboard screenPost-staging deploy
Send Money formRemittance formOn UI changes
QR Scan screenQR payment screenOn UI changes
Mobile 375px layoutAll critical screensOn responsive changes

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

AssertionMetricThresholdJourney
Page loadTime to Interactive< 3,000msDashboard, Send Money
Core Web Vitals — LCPLargest Contentful Paint< 2,500msHomepage, Dashboard
API responseRemittance endpoint< 1,000msJourney 3
API responseLogin endpoint< 1,000msJourney 2
API responseExchange rates< 200msJourney 3 (rates call)NFR-P06

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-chaos
  • 2 workers per projectMVP on CIlocal dev (total:SQLite 6 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 artifacts
  • Retain artifacts for 7 days
  • Alert Slack #drop-e2e-failures on alai-talk.slack.com
  • Block 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)

step failuretest failuretest failuretest failure
ArtifactMetric FormatBaseline Value WhenDate CollectedRecordedTest
Screenshotbcrypt hash (register) PNG~800ms On2026-02-23 api-benchmarks.test.ts
Screenbcrypt recordingverify (login) WebM video~800ms On2026-02-23 api-benchmarks.test.ts
NetworkRate tracelimit check (50 concurrent) HAR file~1,800ms On2026-02-23 api-benchmarks.test.ts
BrowserDB console logSELECT JSON~5ms On2026-02-23 api-benchmarks.test.ts
PlaywrightDB traceINSERT .zip (Playwright trace viewer)~10ms On2026-02-23api-benchmarks.test.ts
SHA-256 hash (reject baseline)~1ms2026-02-23auth.test.ts

Regression threshold: Alert if any metric degrades > 15% vs baseline


11. Test Execution Schedule

failure
Run TypeTriggerEnvironmentFrequency
Benchmark smokeEvery CI runLocal / CI runnerPer commit
api-benchmarks.test.ts fullEvery PRCIPer PR
k6 load test Release candidateFly.io stagingPer release
k6 stress testBefore Phase 1 PostgreSQL migrationFly.io stagingQuarterly
k6 soak testBefore Phase 1 launchFly.io stagingPre-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}

EndpointP50 (ms)P90 (ms)P95 (ms)P99 (ms)Error %RPSStatus vs SLA
/api/auth/loginPass / Fail
/api/transactions/remittancePass / Fail
DB SELECTPass / Fail

Summary:

  • AllPeak pageconcurrent interactionsusers: wrapped in lightweight Page Object classes{PEAK_USERS}
  • SelectorsPeak centralizedthroughput: in{PEAK_RPS} tests/e2e/pages/req/s
  • Location:Test src/drop-app/__tests__/e2e/pages/duration: {DURATION} min
  • Total requests: {TOTAL_REQUESTS}
  • Total errors: {TOTAL_ERRORS}

SelectorNotable strategy (Drop-specific):

  1. data-testid attributes — preferred (e.g., data-testid="remittance-amount")
  2. ARIA roles + accessible name (e.g., getByRole('button', { name: 'Send penger' }))
  3. Text content — acceptable for stable Norwegian/English text
  4. CSS class names — avoid (Tailwind classes can change)
  5. XPath — never

Review cadence:findings:

  • E2ESQLite testsconcurrent updateduser 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

arePR)
  • Monthly
  • deprecatedeveryUIchange
    IssueFound InSeverityRoot CauseFixFixed InVerified
    In-memory rate limiter reset on restartPhase 0 load testHighIn-memory Map → DB-backedPhase 0.5 security hardeningv0.5.0db.test.ts
    Long password bcrypt DoS (tests10KB) Phase part0 ofsecurity theaudit High No review:max removelength testsbefore forbcrypt 1,000 featureschar
  • Figma-make-exportlimit checkedin aftervalidation
  • v0.5.0 validation.test.ts


    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