Sample test report: Acme Portal
Release report · version 1.4
This is a fictional example to show the format and depth of a report. The app, bugs, data and screenshots are not real.
01Summary
Found
Main risks
- Pressing “Pay” twice charges the customer twice.
- Anyone with the app’s public key can read every order, with addresses and phone numbers.
- A payment can be “confirmed” with a forged request — no money needed.
What is good
Sign-up, the catalogue, the cart and the product admin work reliably on both devices. No secret keys in the page code.
Every critical and high issue can be fixed with the prompts in this report. After the fixes, retests are free for 14 days.
02What we tested
Devices and browsers
| Device | System | Browser | Screen |
|---|---|---|---|
| MacBook Air M2 | macOS 15.0 | Chrome 129 | 1440 × 900 |
| iPhone 15 | iOS 18.0 | Safari | 393 × 852 |
Roles
Customer · Store owner · Admin · Signed out
Out of scope
Load testing; Nova Poshta delivery prices (no test access); the iOS and Android apps.
5 main scenarios
- Sign-up and login (e-mail, Google, password reset)
- Catalogue → cart → payment with a test card
- E-mails: confirmation, password reset, receipt
- Customer account: orders, profile
- Admin: products, orders, roles
Screens: 22 of the 25 included. Test data: a separate test account for each role, a Stripe test card.
03How we tested
Scenario testing
A test plan of 64 test cases across the 5 main scenarios — each run on two devices.
Exploratory testing
Four 60-minute sessions looking for where an ordinary customer gets stuck or does something unexpected.
Negative and boundary values
Empty fields, 1,000 characters, emoji, phone numbers with “+” and spaces, past dates, repeated clicks.
Roles and access
Every action checked under three roles and signed out: who sees what and who can change what.
Non-destructive vulnerability testing
Database access rules (Supabase RLS), keys in the code, admin and API without login, forged payments, password guessing. Only on test accounts and our own test orders.
Regression
After the fixes, a retest of every issue and of the scenarios it touches.
How we rate severity
- Critical
- Money or data lost, or the main scenario broken for everyone.
- High
- An important scenario breaks for some people; no workaround or a hard one.
- Medium
- Gets in the way, but there is a simple workaround.
- Low
- Cosmetic, wording, a rare case.
04Results by scenario
| Scenario | Test cases | Passed | Failed | Blocked | Issues |
|---|---|---|---|---|---|
| Sign-up and login | 14 | 12 | 2 | 0 | B-02, V-03 |
| Catalogue, cart, payment | 16 | 11 | 3 | 2 | B-01, B-03, V-02 |
| E-mails | 10 | 9 | 1 | 0 | B-05 |
| Customer account | 12 | 9 | 3 | 0 | B-04, B-06, V-01 |
| Admin | 12 | 10 | 1 | 1 | B-07 |
| Total | 64 | 51 | 10 | 3 |
Blocked: delivery prices (no Nova Poshta test access) and the CSV export of orders (cut off after 30 s — raised with the team as a question, not a bug).
05Bugs
Pressing “Pay” twice creates two orders and charges twice
- Scenario
- Catalogue, cart, payment
- Environment
- MacBook Air · Chrome 129 · macOS 15; also iPhone 15 · Safari
- Frequency
- 5 of 5
- Preconditions
- The customer is signed in; one $24 item in the cart.
Steps to reproduce
- Open the payment page /checkout.
- Enter the test card 4242 4242 4242 4242.
- Quickly press “Pay” twice.
Expected result
One order and one charge. The button is disabled while the payment runs.
Actual result
Two orders (#1043 and #1044) and two $24 charges in the Stripe test dashboard.
Screenshot
$24.00 · Succeeded · 14:02:11
$24.00 · Succeeded · 14:02:11
AI prompt
On the payment page, disable the “Pay” button and show a spinner from the first click until the request finishes. In the create-payment function, make the request idempotent: generate one key per cart and return the existing payment when the same key arrives again. Add a test: two clicks within 300 ms create exactly one order and one charge.
The password-reset link is “invalid” at once for customers ahead of UTC
- Scenario
- Sign-up and login
- Environment
- Both devices
- Frequency
- 5 of 5 (time zone UTC+3)
- Preconditions
- A customer account with a password, time zone Kyiv.
Steps to reproduce
- Press “Forgot password?” and enter the e-mail.
- Open the e-mail within 2 minutes.
- Press the link in the e-mail.
Expected result
The new-password form; the link works for 60 minutes.
Actual result
A page “The link is invalid or expired”. Likely cause: the expiry is compared in local time, not UTC — in Kyiv the link is 3 hours “older”.
Screenshot
The link is invalid or expired
Request a new link
AI prompt
Store and compare the password-reset token expiry in UTC (timestamptz), not in local time. Add a test: a user in UTC+3 opens the link after 2 minutes and sees the new-password form.
After payment the account says “Awaiting payment” until the page is reloaded
- Scenario
- Catalogue, cart, payment
- Environment
- Both devices
- Frequency
- 4 of 5
- Preconditions
- The customer paid an order with the test card.
Steps to reproduce
- Pay for an order.
- Wait to return to “My orders”.
- Do not reload the page for 60 seconds.
Expected result
“Paid” appears a few seconds after the payment is confirmed.
Actual result
The status stays “Awaiting payment” and “Download” is disabled. After a reload it says “Paid”.
Screenshot
Order #1045 · Awaiting payment
Download (disabled)
AI prompt
On “My orders”, subscribe to order status changes (Supabase Realtime) or check the status every 3 seconds for 60 seconds after returning from payment. As soon as the status is “paid”, update the card and enable “Download”.
The “Phone” field rejects numbers with “+” and spaces
- Scenario
- Customer account
- Environment
- Both devices
- Frequency
- 5 of 5
- Preconditions
- The customer is on the profile page.
Steps to reproduce
- Open “Profile”.
- Enter +380 67 123 45 67.
- Press “Save”.
Expected result
The number is saved as +380671234567.
Actual result
An “Invalid format” error. Only 0671234567 is accepted.
Screenshot
Phone: +380 67 123 45 67
Invalid format
AI prompt
In the “Phone” field, strip spaces, brackets and dashes before validation and accept the international format with “+”. Store the number in E.164 (+380671234567). Add tests for “+380 67 123 45 67”, “067-123-45-67” and “(067) 123 45 67”.
The “Order received” e-mail arrives in English while the interface is Ukrainian
- Scenario
- E-mails
- Environment
- Gmail and Apple Mail
- Frequency
- 5 of 5
- Preconditions
- The interface language is Ukrainian.
Steps to reproduce
- Place an order.
- Open the confirmation e-mail.
Expected result
The e-mail is in Ukrainian, like the interface.
Actual result
The subject and text are in English: “Your order has been received”.
Screenshot
Your order has been received
Order #1045 · $24.00
AI prompt
Store the language the customer chose in the interface on the profile and the order. Send the confirmation e-mail with that language’s template; if there is none, use the default language.
An empty “My orders” page shows “undefined”
- Scenario
- Customer account
- Environment
- Both devices
- Frequency
- 5 of 5
- Preconditions
- A new account without orders.
Steps to reproduce
- Sign in to a new account.
- Open “My orders”.
Expected result
The text “You have no orders yet” and a “Go to catalogue” button.
Actual result
The page says “undefined”.
Screenshot
undefined
AI prompt
On “My orders”, when the list is empty, show “You have no orders yet” and a “Go to catalogue” button instead of the variable’s value.
Admin: sorting orders by date works like sorting text
- Scenario
- Admin
- Environment
- MacBook Air · Chrome 129
- Frequency
- 5 of 5
- Preconditions
- Admin role; orders from 9.10 and 10.10.
Steps to reproduce
- Open “Orders”.
- Sort by date.
Expected result
10.10 after 9.10.
Actual result
10.10 comes before 9.10 — the dates are compared as strings.
Screenshot
10.10.2026 · #1040
9.10.2026 · #1039
AI prompt
In the admin, sort orders by the created_at field (date and time), not by the formatted date string.
06Vulnerabilities
The orders table is open to anyone with the public key
- Where
- Supabase, table orders: Row Level Security is off.
- Impact
- Every customer’s personal data leaks. This breaks data-protection law and GDPR.
How we checked
Signed in as test customer B, we called the Supabase REST API with the public key visible in the page code. We got every order, including test customer A’s: names, addresses, phone numbers. Nothing was changed.
AI prompt
Enable Row Level Security on public.orders. Add policies: a customer sees only rows where customer_id = auth.uid(); a store owner only their store’s orders; no insert, update or delete from the browser — only the create-order function creates orders. Check with two test accounts: customer B cannot see customer A’s orders.
The payment webhook does not check the signature — payments can be forged
- Where
- The payment-webhook function.
- Impact
- Anyone can get goods or access without paying.
How we checked
We sent /functions/v1/payment-webhook a test request without a Stripe signature for our own test order. The order became “Paid” without any payment.
AI prompt
In the payment-webhook function, verify the Stripe-Signature header with the webhook secret (stripe.webhooks.constructEvent) and reject requests without a valid signature with a 400. Mark an order paid only when the payment amount and currency match the order.
Login does not limit password attempts
- Where
- The login page and Supabase Auth.
- Impact
- A password can be guessed by brute force.
How we checked
On our own test account we entered 50 wrong passwords in a minute — no lock, delay or captcha.
AI prompt
Limit login attempts: after 5 failed attempts in 15 minutes for one e-mail or IP, add a delay and a captcha. Turn on brute-force protection in Supabase Auth and show a clear “Too many attempts, try again in a few minutes”.
07What works well
- Sign-up with e-mail and Google on both devices.
- The catalogue, filters and search are fast; an empty search shows a hint.
- The cart is kept between sessions and devices.
- Admin: adding and editing products, assigning roles.
- The admin is closed without login and to the Customer role.
- No secret keys in the page code — only the public Supabase key, which is fine.
08Retest and verdict
Retest on 21 October 2026 (example)
| # | Issue | Status |
|---|---|---|
| V-01 | Orders table open to everyone | Fixed ✓ |
| B-01 | Double charge | Fixed ✓ |
| V-02 | Forged payment | Fixed ✓ |
| B-02 | Password-reset link | Fixed ✓ |
| B-03 | Status after payment | Fixed ✓ |
| V-03 | Password guessing | Fixed ✓ |
| B-04 | “Phone” field | Still failing |
| B-05 | E-mail language | Not fixed yet |
| B-06 | “undefined” | Not fixed yet |
| B-07 | Date sorting | Not fixed yet |
The release decision is yours. We give you evidence within the test plan; testing does not guarantee that no bugs are left.