All articles

Exploratory Testing Is Skilled Work, Not Random Clicking

A scripted test can only find a bug you already thought of. You pictured a way the feature could break, wrote a check for it, and that check guards that one case from then on. That is worth having. It is also a ceiling. A suite that comes back all green is telling you the failures you predicted did not happen. The bugs you never pictured stay invisible to it.

None of that is a dramatic claim. It is normal product work. The login test passed, but the user got trapped after resetting the password. The checkout test passed, but the total was stale after the cart changed. The mobile flow looked fine in the happy path, but on a real device the button felt frozen long enough for a user to tap twice.

Exploratory testing is how you find those. It is skilled investigation: you use the product, follow what you notice, and keep asking the questions a script was never told to ask. Not random clicking. A session with a mission, notes, and evidence at the end.

What does this actually prove? Where can this break? What happens with real data?

I care about it because it catches the messy bugs that never fit neatly into a pre-written test case. It is also where QA earns a place early in the conversation, because a good session turns a vague worry into evidence the team can act on.

What I mean by exploratory testing

The formal definition I still like is the simple one from Satisfice: learning, test design, and test execution happen at the same time.

That last part is what people skip. You are not throwing structure away. You are designing the next test from what the last one just taught you.

If the payment button stays disabled for four seconds, I do not keep marching through the original checklist. I ask why it feels frozen. I try a slower network. I tap once, then wait. I tap twice. I background the app during the payment call. I check whether the order was created once or twice.

The session changes because the product taught me something.

That is different from ad-hoc testing. Ad-hoc testing has no memory. Exploratory testing has a mission, notes, evidence, and a clear story at the end:

  • what I tested
  • what I found
  • what I did not reach
  • what still worries me
  • what needs to become a scripted check, bug report, or product question

If I cannot explain that after the session, I was just clicking.

Start with a charter, not a giant script

Before I explore, I do not write a giant script. I write a charter. A charter is the mission for the session. It keeps the work focused without deciding every step in advance.

For a sample food-delivery app I will call Vendly (a made-up app I use for examples), I might write one sentence: explore Vendly guest checkout, with a slow mobile connection, one cart item, one expired card, and one declined card, to discover whether payment failures are clear, recoverable, and safe from duplicate orders.

That is enough structure to begin. It tells me the target, the risk, and the kind of evidence I need. From there a session has a shape: the charter points you, you explore and the product feeds back into what you try next, and you close with notes and a debrief.

Chartermission + riskExploretime-boxedProductteaches yousomethingadjust the next testNotes +debrief

I also write down the setup before I touch the product:

Setup itemValue
BuildWeb preview build 482
DevicePixel 7a, Chrome mobile viewport
NetworkSlow 3G throttle
Account stateGuest user
Test dataAckee Bowl in cart, expired card, declined card
Main riskA user cannot recover from payment failure or gets charged twice

This looks small, but it saves you later. When a developer asks what build, what device, or what data you used, you should not have to reconstruct it from memory.

The screen I would actually test

Here is the kind of screen I want in front of me for the session. Not a perfect design mock. A realistic enough product surface with totals, state, actions, error messages, and places where timing or data can go wrong.

The reason I like a real-looking screen is that it forces better questions. A paragraph about “payment failure handling” is easy to nod at. A screen makes you notice the specific claims it puts in front of the user:

  • The cart is still saved.
  • The button can be pressed again.
  • The total is $18.25.
  • The payment failed, so no order should exist yet.
  • The message is clear enough for a real user to recover.

Each of those is a claim I can go check. A block of prose about error handling gives me nothing to point at.

How to run a session-based exploratory test

I would time-box this to 60 or 90 minutes. I want enough time to follow a lead, but not so much time that the session becomes a wandering afternoon.

The first pass is simple:

  1. Add an item to the cart.
  2. Continue as a guest.
  3. Enter delivery details.
  4. Try the expired card.
  5. Try the declined card.
  6. Retry with a valid card.
  7. Check the confirmation screen, email, order history, or API response if I have access.

Then I start varying the things that can expose risk:

VariationWhy I would try it
Slow networkPayment flows often feel broken before they technically fail.
Double tap PayThis is where duplicate orders and duplicate charges can show up.
Background the app mid-paymentMobile users do this constantly. The product still has to recover.
Edit the cart after a failed paymentTotals and payment state can desync.
Expired sessionCheckout is high risk when authentication or guest state expires mid-flow.
Sold-out item during checkoutThe UI may still show the item even when the system cannot fulfill it.

This is where the session earns its keep. The goal is plain: keep asking what would make you uncomfortable shipping this.

Session notes that are readable

The short example should not look like code because it is not code. It is a working record of the session. I want it readable by a developer, product manager, or another tester who needs to understand what happened.

Started checkout on build 482 with slow 3G and an expired card.Cart showed Ackee Bowl, total $18.25. Baseline looked ready.
Submitted expired card.Clear card declined message. Cart stayed saved. Good recovery signal, so I tried the declined card next.
Submitted declined card.Pay button stayed disabled for about 4 seconds with no spinner. Filed a usability bug because it looks frozen.
Backgrounded the app during a slow payment call.Returned to checkout and saw "pending", then received a confirmation email. Checked whether an order was created.
Refreshed order state after the pending result.The screen said payment failed, but the backend showed an order ID. Filed high-risk bug. The UI and system disagree.
Changed quantity after the failed payment.Total on the screen stayed at $18.25 even after the cart changed. Filed data bug. The UI looked fine, but the total was wrong.

That log gives the team something to use. It shows the action, the observation, the decision, and the evidence trail.

This is not hypothetical for me. On a large retail platform I tested, an exploratory pass on the basket is where I caught a stale-total problem that the scripted checks were happy with. The flow was a promotion that grew with cart subtotal, the kind that says “spend a bit more and save more.” I added an eligible item to push the cart over a threshold, and the discount tier updated, but the “spend X more to reach the next tier” number did not move with it. The total on the screen was now wrong against the cart that had actually changed. The happy-path tests passed because a discount applied. What no script had asked was whether the next-tier number stayed correct at the exact boundary where the cart crossed it. I would never have gone looking for that on the script. I found it because I kept poking at the cart after it changed, which is exactly what a session is for.

If I found the high-risk order mismatch, I would not leave it buried in the session notes. I would turn it into a bug report with:

  • title: Vendly creates an order after a failed guest checkout payment
  • build and environment: build 482, Pixel 7a, slow 3G
  • steps to reproduce
  • expected result: failed payment creates no order
  • actual result: UI says payment failed, backend shows order ID
  • evidence: screenshot, network request, order ID, timestamp
  • impact: guest users may believe payment failed while an order exists

This is where the bug report template helps. Exploratory testing can find the issue, but the report has to make it fixable.

What to capture in a session sheet

I keep the session sheet light. If the template is too heavy, people stop using it. But it must capture enough to make the work trustworthy:

  • The charter and risk I am chasing.
  • The setup: build, environment, device, browser, data, account state.
  • Coverage: the areas I reached and the areas I did not.
  • Timestamped actions and observations as the session runs.
  • Bugs filed, with their ticket IDs.
  • Questions that blocked certainty, product or technical.
  • Follow-up: what should be automated, retested, or explored next.

The fillable version of exactly this sheet is at the end of the article.

My personal rule is simple: the notes should be good enough that another tester could continue the investigation tomorrow.

They do not need to be pretty. They need to be honest.

Where to point your curiosity

If you stare at a feature and think “where do I even start”, use lenses. A lens is just a way to stop yourself from testing only the obvious path.

For checkout, I would use these:

LensQuestionVendly example
DataWhat values can change under me?Quantity, tax, delivery fee, sold-out item, promo code.
TimeWhat happens before, during, and after the action?Double tap Pay, timeout, delayed confirmation email.
StateWhat state is the user or system in?Guest user, expired session, cart already changed.
InterfaceWhat systems does this depend on?Payment provider, email service, order service.
PlatformWhere could the experience differ?Real Android device, mobile browser, slow network.
RecoveryCan the user get out safely?Retry payment, edit card, return to cart, cancel order.

I especially like data, time, and recovery. Those are where I find the bugs that hurt. A happy-path automated test proves the flow works once, under perfect conditions, and a real user is never under perfect conditions. Human curiosity is still one of the strongest testing tools we have, and this is where it pays off.

What should become automation

Exploratory testing and automation work together. A session often tells you what automation is worth writing.

After this session, I would not automate every note. I would choose the checks that give repeatable release confidence:

FindingAutomate it?Why
Expired card shows a clear error and keeps cart savedYesStable, important recovery behavior.
Failed payment must not create an orderYes, if the test can verify the order state through API or test toolingThis protects money and trust.
Button felt frozen for 4 secondsMaybe not as a functional testBetter as a UX issue, performance budget, or visual review.
Total stayed stale after cart editYesData accuracy bug that can return.
”This message feels confusing”NoKeep it as human review unless the copy is explicitly required.

Not every test deserves to be automated. Automate what gives the team repeatable confidence, not what looks impressive in a dashboard.

Where AI helps and where it does not

AI can help with exploratory testing, but I would not hand it the judgment.

I would use an agent to:

  • summarize a PRD into risk areas
  • draft starting charters
  • generate test data combinations
  • inspect network calls or logs while I explore
  • turn my notes into a clean bug report
  • suggest follow-up automation candidates

I would not expect it to fully replace the session. The human part is noticing that the screen feels frozen, the recovery path is confusing, the error message does not match what happened, or the flow would make a real user lose trust.

That is the same line I draw in AI-assisted QA more broadly. Let the tool do the mechanical work. Keep the risk sense, visual judgment, and release decision with the human. If you want the broader argument, I wrote about that in QA Is the Control Layer for AI-Assisted Development.

A practical session template

This is the session sheet I keep. Copy it into your wiki, your tracker, or a plain text file and reuse it every session, one filled-in copy per charter. Keeping the sheet identical run to run is what makes a stack of sessions comparable later, instead of a pile of notes in different shapes.

FieldFill it in
CharterExplore ___ with ___ to discover ___.
Build and environmentBuild, branch, device, browser, network, account state.
Main riskWhat would make this unsafe or embarrassing to ship?
DataWhat records, cards, users, permissions, or edge values did you use?
NotesTimestamp, action, observation, decision.
Bugs filedTicket ID, title, severity, evidence.
QuestionsAnything unclear that blocks confidence.
Follow-upRetest, automate, ask product, improve test setup.

At the end, do a ten-minute debrief. You can do it with a teammate or by yourself before you close the session. Answer these plainly:

  • What did I learn?
  • What did I prove?
  • What still worries me?
  • What evidence would make us comfortable shipping?
  • What should become a repeatable check?

A good session ends with the team holding better evidence than it started with. Charters keep it focused, notes make it trustworthy, and curiosity is what finds the bug no script was told to look for. Pick one risky feature, write a one-sentence charter, time-box an hour, and keep notes as you go. That is enough to start.

Found it useful? Share it.
Julia Pottinger

Written by

Julia Pottinger

Hi, I'm Julia. I've been in QA for over a decade. I spend my days testing software and my own time building apps and games, and I write here to share what I learn, the practical, honest lessons you can actually use.

Comments 0

Share your thoughts, ask questions, or add to the conversation.

Be kind and constructive. Stay on topic. No spam or self-promotion.
Loading comments…