The problem with “it worked on my machine”
Manual integration tests often become stories instead of evidence.
Someone runs through a workflow, sees the expected screen once, and reports that the feature works. But later, another engineer tries the same flow in a different environment and gets a different result. A dependency was down. A feature flag was different. A token had extra permissions. A cached response hid the failure. A background consumer was not running. The test did not lie intentionally — it simply did not prove enough.
That is the problem with unstructured manual testing.
Manual testing is still valuable, especially for integration workflows that cross services, authentication, environments, queues, and UI behavior. But it only becomes useful evidence when it is repeatable, observable, and scoped.
A good manual test should answer three questions:
- Can someone else repeat the same steps?
- Can we prove what happened?
- Do we know exactly which contract was validated?
If the answer is no, the test result is not evidence. It is only a memory.
Why manual integration testing is hard
Unit tests usually run in a controlled world. Manual integration tests do not.
They depend on real environments, real service versions, real data, real credentials, and real timing. That makes them closer to production, but also more fragile.
A single workflow may depend on:
- frontend configuration
- middleware routing
- authentication tokens
- tenant or project selection
- downstream APIs
- database state
- message consumers
- background jobs
- feature flags
- network conditions
- cached browser or service state
When the test passes, you need to know which version of that world you tested.
Without that context, “passed” does not mean much.
What makes a manual test trustworthy
A trustworthy manual integration test has three qualities.
First, it is repeatable. Another engineer should be able to follow the same steps and reach the same result in the same environment.
Second, it is observable. The test should capture enough evidence to prove what happened: request IDs, timestamps, sanitized payloads, response shape, logs, screenshots, or database records where appropriate.
Third, it is scoped. A test should be clear about the contract it is validating. If one manual test tries to validate everything, it becomes difficult to know what actually passed.
For example, “Risk Register works” is too broad. A better manual test might say:
- token exchange succeeds for the selected tenant
- middleware routes the request to the expected risk service
- CORS allows the frontend call
- association history returns the expected response shape
- the UI shows the Risk Register entry point only when the required context exists
Each of these validates a smaller contract. Together, they create stronger confidence.
Step 1: State the intent
Every manual integration test should start with a short intent statement.
Before running the test, write down:
- which capability is being validated
- which user or system action starts the workflow
- which service boundary is most important
- what the expected result looks like
- what evidence will prove success
This prevents the test from drifting.
For example:
Validate that the middleware proxy routes Risk Register requests to the correct downstream service for a selected well context, using the expected token-exchange flow, and returns a successful response visible to the frontend.
That intent is specific enough to guide the test. It also makes failures easier to interpret.
If the token exchange fails, the routing contract was not reached. If routing succeeds but the frontend fails, the issue is likely in response handling, UI state, or CORS. A clear intent narrows the investigation.
Step 2: Freeze the environment
Manual tests often fail later because the environment was never documented.
Before running the workflow, capture the testing context:
- environment name
- application version
- frontend build version
- middleware version
- downstream service versions
- feature flags
- relevant environment variables
- tenant, project, or well context
- browser and user role
- credential scope, without exposing secrets
- test data identifiers
- time and date of execution
This may feel slow, but it saves time later.
If a test passes in pre-prod and fails in production, environment details help explain why. If a test passes with admin credentials but fails for a normal user, credential scope becomes the clue. If a test passes after a deployment but fails before it, version capture matters.
A manual test without environment context is difficult to trust.
Step 3: Validate dependencies before the workflow
A common mistake is starting with the full user workflow before checking whether dependencies are healthy.
If the workflow fails, the team then has to determine whether the feature is broken or whether a dependency was already unavailable.
Before running the main test, perform quick dependency checks:
- Can the frontend load the expected configuration?
- Can middleware reach the downstream service?
- Is the correct tenant or project selected?
- Are required background consumers running?
- Are database connections healthy?
- Are external APIs reachable?
- Are required queues or topics active?
- Are feature flags in the expected state?
These checks do not replace the integration test. They make the result easier to interpret.
If dependency checks fail, the workflow result is not a clean feature validation. It is an environment-health issue.
Step 4: Capture request and response evidence
Manual tests should leave artifacts.
For API or service integrations, capture:
- correlation ID or trace ID
- request timestamp
- sanitized request payload
- response status code
- sanitized response payload
- downstream service called
- relevant log lines
- retry attempts, if any
- final user-visible result
This evidence is what turns a manual test into something another person can review.
Screenshots alone are usually not enough. A screenshot can prove the UI showed something, but it cannot prove which service returned the data, whether retries happened, or whether the response was correct.
For integration work, request and response evidence matters because the bug often lives between layers.
Step 5: Test one contract at a time
Manual testing becomes unreliable when one long test tries to validate too much.
A full end-to-end flow is useful, but it should not be the only test. Break the workflow into contracts.
For example:
- Auth/token contract
- Middleware route contract
- Downstream service contract
- Data-shape contract
- UI rendering contract
- Error-handling contract
- Retry or timeout contract
Testing this way helps isolate failure.
If the auth contract fails, there is no point debugging UI rendering. If the downstream service returns the wrong shape, middleware and frontend behavior may be secondary symptoms. If the API response is correct but the UI does not update, the issue is likely in client state or rendering.
Scoped tests reduce guesswork.
Common failure mode 1: cached responses hide failures
Caching can make a manual test look successful even when the live path is broken.
A browser cache, service cache, local storage, session state, or API-level cache can return data that was produced earlier. The UI appears correct, but the current integration path may not have worked.
To avoid false confidence:
- test with cold browser state
- clear local and session storage when needed
- verify timestamps in returned data
- check whether the service was actually called
- inspect correlation IDs
- test after cache expiration if relevant
If you cannot prove the live path ran, the test did not validate the live path.
Common failure mode 2: partial success with silent errors
Some workflows appear successful even when part of the integration failed.
For example, the UI may show a success message while a background save failed. A request may return partial data with warnings. Middleware may fall back to a default response. A downstream error may be swallowed and logged only at debug level.
This is why manual testing should inspect both the visible result and the system evidence.
Ask:
- Did all required requests complete?
- Were any errors logged during the “successful” flow?
- Did the database or downstream system reflect the change?
- Did the workflow produce warnings?
- Did retries hide an underlying flaky dependency?
- Did the UI show stale data?
A test is not successful just because the final screen looks acceptable.
Common failure mode 3: async steps fail after the UI moves on
Modern applications often trigger background work after the user action appears complete.
The UI may move to the next step while async processing continues in the background. If that background step fails, the user may not notice immediately.
Manual tests often miss this because the tester stops when the UI looks done.
For async workflows, keep observing after the visible action:
- check logs after completion
- verify downstream records
- confirm message processing
- watch retry behavior
- inspect final state after a delay
- refresh and verify persistence
If the system uses queues, sockets, or background workers, “the button succeeded” is not the end of the test.
Common failure mode 4: retries mask flakiness
Retries are useful, but they can make manual tests look cleaner than the system really is.
A workflow that succeeds after three retries is not the same as a workflow that succeeds immediately. Both may produce the same final UI, but they have different reliability implications.
When testing integration paths, capture retry evidence:
- how many attempts occurred
- which dependency failed first
- how long recovery took
- whether backoff behaved correctly
- whether final logs preserve the earlier failure
- whether the user experienced delay
This helps distinguish a healthy path from a flaky-but-recovered path.
A passing test with hidden retries may still point to a reliability issue worth fixing.
Common failure mode 5: wrong credentials create false confidence
Credentials matter.
A workflow tested with broad admin permissions may pass even though the intended user role would fail. A token may contain more scopes than production users have. A service account may bypass a permission check that real users hit.
To avoid this:
- document the user role
- document token scope without exposing secrets
- test with the intended role
- test negative permission cases where relevant
- verify that unauthorized paths fail clearly
Integration tests should validate the real permission model, not just the happy path with powerful credentials.
How to document a manual integration test
A useful manual test record does not need to be complicated.
A simple structure works well:
Test intent
What capability or contract is being validated?
Environment
Where was it tested? Which versions, flags, tenant/project, and role were used?
Preconditions
What data, configuration, services, and dependencies must exist before the test?
Steps
What exact actions were performed?
Expected result
What should happen if the contract works?
Actual result
What happened?
Evidence
What screenshots, request IDs, logs, payloads, timestamps, or records prove the result?
Notes and follow-ups
What was unclear, flaky, or worth automating later?
This structure makes the test replayable.
When manual tests should become automated tests
Not every manual test needs to become automated immediately. But some should.
A manual test is a good automation candidate when:
- the workflow is business-critical
- the same test is repeated before releases
- the contract crosses several services
- failures are expensive to debug
- the setup is stable enough to automate
- the test has clear expected outputs
- the manual process is time-consuming
Manual testing can be the discovery phase. Once the team understands the contract and the evidence needed, automation can preserve it.
The best automated tests often start as well-documented manual tests.
A practical field checklist
Before trusting a manual integration test, I ask:
- Did I state the exact capability being validated?
- Did I capture environment and version details?
- Did I validate dependencies before running the workflow?
- Did I use the intended user role and credential scope?
- Did I capture request and response evidence?
- Did I check logs for silent errors?
- Did I verify async/background completion?
- Did I account for caching?
- Did I record retries or timeouts?
- Could another engineer replay this test tomorrow?
If the answer is yes, the manual test becomes useful evidence.
If the answer is no, the result may still be helpful, but it should not be treated as a strong validation.
Closing
Manual integration testing is valuable when it is treated like an experiment.
An experiment has a hypothesis, controlled conditions, repeatable steps, observed results, and evidence. A good manual test should do the same.
The goal is not to make manual testing heavy. The goal is to make it honest.
“It worked on my machine” is not enough. A reliable manual integration test should show what was tested, where it was tested, which dependencies were involved, what evidence was captured, and whether someone else can replay the result.
Manual testing does not lie when it produces artifacts. Same inputs, same steps, documented outputs — that is what turns a manual test from a story into engineering evidence.