Sitemap

Manual Testing

19 min readSep 17, 2025

--

Press enter or click to view image in full size

If you were following me, it is likely that you are a software developer and not interested in reading about QA 😅. But don't hurry to leave the page. You will see some concepts and practices that you will apply in your everyday work to improve your testing process.

Today, you’ll see how to level up in manual testing, with practical techniques, tips for keeping your testing environment clean, smart use of the DevTools panel, and usage of Postman.

There are also other articles on this topic you might find interesting:

If you are ready, we can start!

Manual Testing and Its Types

Manual testing is the process of verifying software functionality by a human.

Before writing unit tests or committing code, developers manually check their features to catch obvious bugs, verify logic, or ensure that the code runs without crashing. This is also called developer testing.

Press enter or click to view image in full size

Then a pull request is created. Naturally, other developers will review it 😬. Based on their feedback, the developer may make some changes and commit the updated code. Of course, it needs to be retested.

Instead of testing every detail, the developer focuses on quickly verifying that the core functionality and happy path are working as expected. The approach is known as smoke testing.

Press enter or click to view image in full size

The final stage before approving the code would be testing it by QA.

The tester simulates end-user behavior to ensure the application works as intended, identifying bugs, usability issues, and unexpected behavior.

Press enter or click to view image in full size

This is also called alpha testing. It happens at an early stage, after initial development but before public release, and is conducted by QA or developers.

By the way, there’s also beta testing, which is more stable since it comes after alpha testing has addressed the most critical bugs. The goal here is to gather feedback from the real users and uncover remaining minor issues.

As you can see, manual testing plays a vital role throughout the software development lifecycle 😅.

The Foundation of Good QA

Before diving into tools and environments, it’s important to understand the core testing concepts that guide how we approach manual testing. These concepts shape how tests are designed, what we focus on, and how deep we go when verifying software behavior.

White Box Testing vs Black Box Testing

These two terms describe different perspectives in testing:

  • Black Box Testing means the tester has no knowledge of the internal code

The QA often had to study functionality, web pages by using a browser, and guess what could be valid input data.

  • White Box Testing is when the tester knows the internal code, logic, and structure.
Press enter or click to view image in full size

The developers often have an advantage in terms of testing, since they see the code, specific algorithms, and conditions 😏.

Therefore, we know what to test, which validation is only present on the frontend, creating security holes in the backend, etc.

  • There is also a concept of Grey Box Testing. It is when the tester has partial knowledge of the system’s internal structure or code, but still performs testing from the external interface — like a user would.

Think of it as a “best of both worlds” approach. The testers can design better test cases than in black box testing, while still maintaining some of the user’s perspective.

Press enter or click to view image in full size

Test Case vs. Checklist

When the feature is finished, you can consider writing test cases or a checklist. Usually, developers don’t do that, because we have plenty of other work 🙃. However, sometimes it is worth considering for complex features, because:

  • they act as a documentation of how the system should behave
  • test cases ensure that testing can be repeated the same way every time
  • they help to catch regression bugs
  • they can be across similar features

A test case is a detailed, structured document that includes: title, preconditions, steps to execute, expected result, etc.

Test cases can be both positive (how the system reacts to valid input) and negative (how the system reacts to invalid data) to verify how the system behaves against requirements and beyond.

A checklist is a lightweight list of test ideas or tasks, often with just a line or two of description per item.

Testing Techniques

There are various techniques to design effective test cases, reduce redundant effort, and catch bugs with less guesswork.

Here are some of the most practical ones:

— error guessing

You think about where the application is most likely to fail.

Examples: entering an email in an invalid format, emojis, or special characters

This is often less scalable, and its effectiveness depends on a person’s experience.

— boundary value analysis

Bugs often live near the boundaries of valid input ranges. So instead of only testing average values, you test the edges.

Example: if a valid range is 1–100, then you test (0, 1, 2) and (99, 100, 101)

It works because developers often make mistakes at the boundaries (off-by-one errors, inclusive/exclusive logic, etc)

— state transition testing

Useful for applications that behave differently based on the current state.

You test transitions between states, ensuring allowed paths work and invalid ones are blocked.

This is where a flowchart becomes helpful — for instance, to visualize and track all transitions in a cancel order wizard.

Press enter or click to view image in full size

— decision table testing

Decision Table Testing is a black-box test design technique used to model and test complex business logic or rules.

It helps you test different combinations of conditions (inputs) and their corresponding actions (outputs).

For example, a user can log in if:

  • they enter a valid username
  • they enter a valid password
  • the account is not locked

Those conditions can be represented by the following decision table:

Press enter or click to view image in full size

In the table, you have:

  • condition — input or situation to test
  • rule — a column that defines 1 combination of possible inputs
  • result — expected output or behavior

Each row is a test case. You verify the output (Allow Login) based on different combinations of inputs.

— pairwise testing

Pairwise testing is a combinatorial test design approach.

Imagine you have different types of users (admin, employee), who can log in to your system with different methods (password, SSO) using different applications (mobile, desktop).

This leads to 8 test cases: 2 (User Type) × 2 (Login Method) × 2 (Device), and the total grows exponentially as more parameters are added.

Instead of testing all possible combinations of input values, pairwise testing ensures that every possible pair of input values is tested at least once.

This covers all value pairs: (admin, password), (admin, SSO), (admin, desktop), (admin, mobile), (password, mobile), etc.

So every pair of values across parameters is tested, without testing every full combination.

Effective Testing

While QA teams often have the luxury of testing in a stable, pre-deployed environment, developers typically need to build and maintain a reliable, local setup.

Here are some common advice you apply in your life:

— use components that can run locally

Choose dependencies that support local execution to avoid relying on cloud infrastructure during development.

Example: favor RabbitMQ over Azure Service Bus to simulate message queues locally and work offline.

If you cannot emulate the service locally, think twice before using it.

— dockerize your services

Installing and configuring services manually can be time-consuming, error-prone, and inconsistent across machines. Docker can help you here.

Encapsulate dependencies like databases, queues, and caches for consistent and repeatable environments.

— mobile testing with Ngrok

Whether you’re testing mobile apps on real devices, handling incoming webhooks, or integrating with external services, your local server must be reachable from the Internet.

Ngrok lets you expose your local URL (localhost) via a secure public URL (ngrok.com).

— temporary webhook services

When working with third-party integrations, it’s common to receive webhooks — HTTP callbacks triggered by events.

That’s where temporary webhook services come in. They act as intermediaries, receiving and displaying webhook payloads, which is handy if you want to analyze them.

— SMTP testing

It is often a challenge to test emails. Avoid spamming real inboxes or relying on paid live email servers during development.

Tools like Papercut-SMTP, Mailpit capture outgoing emails locally, allowing you to inspect email content, attachments, and headers without ever sending a real message.

— temporary mail tools
Use throwaway email addresses to test sign-up, login, and verification flows.

Temp-Mail can help you register and receive a real email with minimal friction.

DevTools

Sometimes, code you’ve written simply refuses to work — and it’s not immediately clear why. To quickly identify the issue and resolve it efficiently, you need to master DevTools, one of the most powerful tools available to web developers.

Of course, we won’t get into details, but just overview the most useful features 😅.

To open developer tools in most browsers (Chrome, Edge, Firefox), you have a few options:

  • use keyboard shortcuts F12 or Ctrl+Shift+I
  • use Ctrl+Shift+C to open “Elements” tab
  • use Ctrl+Shift+J to open “Console” tab
  • right-click on an element on a webpage and select “Inspect” to open “Elements” tab on the concrete element

Don’t be lazy, open the Console and experiment with it as you go 😁.

Elements Tab

The Elements tab is the first — and often the most essential — tab in DevTools. It allows developers to inspect the HTML structure of a page in real time.

You can view the DOM tree, see how elements are nested, and edit HTML or CSS on the fly. This makes it easy to test changes, debug layout issues, or explore how a web page is built without modifying the source code directly.

To quickly locate a specific element on the page, you can click the cursor icon in the top-left corner of the DevTools window, then hover over and click the desired element directly on the page.

Press enter or click to view image in full size

Another way is to hover the mouse over the desired element, right-click on it on a webpage, and select “Inspect”.

You can temporarily hide the element using the H key and show it again later, or press Delete to remove it instantly.

When debugging scripts that manipulate the DOM, you can set a breakpoint on an element. Right-click on it, choose “Break on”, and then select when the browser should stop the script. It will pause execution and highlight the exact line of code that triggered the modification.

You can view, temporarily disable any breakpoint from the DOM Breakpoints sub-tab.

The Styles sub-tab shows all CSS rules applied to the selected element, including the CSS-file name and line number. You can quickly toggle styles using the checkbox, edit values by clicking them, or add new properties by clicking next to an existing one.

At the top of the CSS rules list, there’s a search box for quickly finding properties — for example, type “color” or “black” to see what sets the element’s text color.

Get iamprovidence’s stories in your inbox

Join Medium for free to get updates from this writer.

Next to it are buttons to add new classes or pseudo-classes, which is useful for inspecting styles that apply on hover or when the element is active.

Below the CSS rules list is the box model diagram of the selected element, where you can adjust styles like padding, border thickness, and margin.

Console Tab

The main purpose of the Console tab is to interact with the browser by running JavaScript commands and viewing their results.

You can test any code snippets by entering them and pressing Enter.

[1, 2, 3].length

It’s also useful for manipulating the DOM. For example, accessing <footer>, run document.getElementsByTagName('footer'). If the footer is already selected in the Elements tab, just use $0.

The Console tab also displays diagnostic messages from your code, such as those output using console.log(...):

  • console.log(1, 2, 3, { a, b, age: 30 }) — used to print general-purpose messages. You can log multiple values at once. It is also useful to log variables as object, so you don’t need to guess which value is which
  • console.warn(...) — displays a warning message in the console, usually highlighted in yellow
  • console.error(...) — displays an error message in red, often with a stack trace
  • console.table([...]) — displays tabular data — especially useful for arrays or objects
Press enter or click to view image in full size

Using the controls at the top of the Console tab, you can customize its settings and behavior.

Press enter or click to view image in full size

🚫 Click the “Clear Console” button (crossed circle) to remove all messages from the console.

🔍 Use the filter input box to show only messages that match specific keywords.

⚠️ The “All Levels” dropdown lets you choose which types of messages to display: (all, info, warnings, errors).

🧾 In Console settings, you can find “Preserve log” options. When checked, this option keeps your console logs even after you reload the page or navigate to a different route.

Network Tab

The Network tab shows a list of all resources loaded by the page, along with detailed diagnostic information for each one.

Press enter or click to view image in full size

Each column represents information about a resource. The default columns are:

  • Name: the resource’s URL
  • Status: the HTTP response code
  • Type: the resource type (HTML page, styles, image, etc)
  • Initiator: what caused a resource to be requested
  • Size: resource amount transferred over the network
  • Time: how long the request took

Clicking on a resource with the left mouse button opens a panel displaying detailed information about that resource, its headers in request and response, payload, etc.

Press enter or click to view image in full size

Now, let’s take a look at the controls located at the top of the tab.

Press enter or click to view image in full size

📹 The first button starts/stops recording logging network activity.

🚫 The second button clears the current list of network requests.

🔍 The next button displays filter sections. You can narrow down requests by type ( All, Doc, CSS, JS, etc). There is also a filter bar to search requests by name.

🧾 Preserve log, when enabled, prevents the network log from being cleared when you navigate to a new page or refresh the page.

🧹 Disable Cache. Check this option to simulate an experience from a user who visits the page for the first time. It’s commonly used by QA engineers to avoid cache-related bugs and maintain a clean, predictable testing environment.

By the way, if DevTools is open, you can right-click the refresh button and choose:

  • 🔁 Normal Reload — reloads the page, using cached files when available
  • 🔄 Hard Reload — reloads the page, ignoring the cache, but doesn’t clear it
  • ♻️ Empty Cache and Hard Reload — clears the cache first, then refreshes the page
Press enter or click to view image in full size

🌐 Next buttons help to emulate slow internet connections (e.g., 3G, 4G) or disable the network completely to test offline behavior.

🔄 Import/Export (HAR)

The next two buttons allow you to export or import network activity as a HAR (HTTP Archive) file.

HAR files are useful for sharing network logs during troubleshooting or bug reporting.

📸 For the final, let’s discuss snapshots. In the settings menu, you can enable automatic screenshots during initial page load.

This helps visualize what the user sees and in what order, especially useful when combined with slow network throttling.

Application Tab

In the Application tab, you can inspect and manage various types of web storage for each origin (website), including:

  • Local Storage — per URL origin (protocol + domain + port), shared across tabs, and is persisted even after the browser is closed
  • Session Storage — scoped to a specific tab; persists only for the duration of the page session (i.e., until the tab is closed)
  • Cookies — per URL domain (can also be shared across different subdomains). They are assigned by the server or client and are automatically sent by the browser with each HTTP request
Press enter or click to view image in full size

This panel allows you to view, edit, and add values directly in each storage type. This is helpful for testing login state, feature flags, user preferences, or settings, without changing actual app code.

While values in local storage are just name-value records, the cookies require some attention.

The Cookies table contains the following fields:

  • Name. The cookie’s name
  • Value. The cookie’s value
  • Domain. The hosts that are allowed to receive the cookie
  • Expires / Max-Age. The cookie’s expiration date or maximum age (
    like 2025–10–18). If the value is Session a cookie will exist only in a single browsing session
  • HttpOnly. If true, this field indicates that the cookie should only be used over HTTP, and JavaScript modification is not allowed. It is also helpful against XSS attacks
  • Secure. If true, this field indicates that the cookie can only be sent to the server over a secure, HTTPS connection
  • SameSite attribute controls when cookies are sent along with requests. It helps protect against CSRF attacks. It can have one of the following values:
    — None — cookies are sent with all requests, even when users perform actions on another site (e.g., submitting a form to your site from someone else's applications)
    — Strict — cookies are only sent when the user performs actions on the same site, and not sent at all if the request is coming from another website — even if they clicked a link
    — Lax(default) — cookies are mostly not sent with actions initiated by a third-party website. However, they are sent if the request is the result of a user clicking a link, typing the URL (GET requests)

🥷 While testing, it is often popular to use Incognito mode to simulate a fresh user visit, since it has a separate, temporary cache and storage.

Device Mode

The last feature I want to mention is Device Mode, activated via the device icon in the top toolbar of DevTools:

Once clicked, the browser enters a special mobile emulation mode, where you can set a custom screen width and height or select from predefined devices like iPhones, Android phones, etc.

Press enter or click to view image in full size

You can also improve your debugging process by testing on different browsers, since they use different rendering engines. It will help you to find rendering bugs, JavaScript compatibility problems, and CSS inconsistencies.

You can also explore other DevTools advanced tabs like Memory, Profiler, Lighthouse, which I find useless 😅. If you too, right-click the tab you want to hide, then “Move to bottom”. It’ll disappear from the main tabs panel 🙃.

Postman

There are many tools for testing APIs. One of the most popular ones is Postman.

Postman makes it easy to create and send various types of HTTP requests, such as GET, POST, PUT, DELETE, and more. We can configure request parameters, headers, and body content as needed. Use different variables per environment, write API tests, and share are settings with others.

Let’s review some of its basic features.

The Postman desktop application can be downloaded from the official website, or you can also use the web version if you prefer.

First, let’s create our own workspace. A workspace in Postman is a dedicated environment where you can organize your API requests.

Once your workspace is set up, you can start organizing your work using collections, folders, and requests. Here’s what each of them means:

  • a collection is a group of requests bundled together. It helps you organize related API calls — for example, all endpoints for API№1 — in one place, all API№2 in a second place
  • a folder is used to further organize requests within a collection. You can group related requests — like all user endpoints, all order requests, etc
  • a request is an individual API call, such as a GET or POST to a specific URL

When you add a new request, you enter a builder interface. Here’s a breakdown of the main elements you’ll see:

Press enter or click to view image in full size

(1) — Request Tabs: Each open request appears as a tab at the top, allowing you to work with multiple API calls at the same time, just like browser tabs
(2) — Method: This dropdown lets you choose the HTTP method (e.g., GET, POST, PUT, DELETE) for your request
(3) — URL: Here you enter the endpoint (address) of the API you want to call
(4) — Send: Click this to send the request to the server and get a response
(5) — Request Parameters: Below the URL, you’ll find tabs like Params, Headers, Body, and Authorization, where you can define all the request details, such as:

  • Params: query string parameters
  • Headers: custom headers like Content-Type or Authorization
  • Body: payload for methods like POST or PUT
  • Authorization: authentication credentials

(6) — Response Section: After sending a request, this section shows the server’s response

Some configurations, like Authorization better at the collection level.
So you don’t have to set it on each request.

The API key or Bearer token schema is quite popular. The simplest way to retrieve the credential would be to copy the value from the Authorization header of your browser’s requests😅.

Press enter or click to view image in full size

When you send a request, it often includes parameters.

In a typical GET request, these parameters appear in the URL. Postman allows you to temporarily disable specific parameters, as well as add descriptions to them — so you don’t forget what each one is for. This is especially useful for maintaining clarity when working with many or complex parameters.

Press enter or click to view image in full size

For POST and PUT requests, you typically send data in the request body, often as JSON. In Postman, you need to specify the body type to ensure the data is sent correctly.

Press enter or click to view image in full size

In the Scripts tab, you can write tests to validate the response body, request, and other details. These tests run automatically whenever you send the request or execute the entire collection.

pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});

Test results will be visible at the bottom of the Postman window.

Press enter or click to view image in full size

Now, imagine you have 100 requests and the URL to your server has changed. This means you need to update all of them 😫.

Fortunately, we have variables. They let you reuse and update values like URLs or tokens so you don’t have to type them every time.

Variables can be added either for a specific collection (other collections won’t see them) or globally for the entire environment.

To add variables to a collection, go to its settings (just click on the collection) and open the Variables.

Press enter or click to view image in full size

Here you have Initial and Current value columns:

  • when working in a team, changes made to the Initial Value are visible to everyone, so when you share the collection, all team members see this value
  • if you want to change a variable’s value and test something without affecting others, you can change the Current Value locally — only you will see this change

To use a variable, start typing an opening curly brace { and the system will suggest options, including your variables. All variables are wrapped in double curly braces like this: {{variableName}}.

Press enter or click to view image in full size

Variables can be defined and grouped per environment.

Create different environments with the same variables and try switching them. Your variables will update automatically — for example, changing the base URL from localhost:2702 in your development environment to https://www.test.com in production—so your requests target the correct server without manual edits.

Press enter or click to view image in full size

To share collections, requests, and environments with others, you can export and import them. However, in a team, it is more convenient to publish the collection online using Postman’s collaboration features.

In the Home tab, you can find the Create Team option, which allows you to set up a workspace for collaboration and invite team members to work together on collections, environments, and tests.

Press enter or click to view image in full size

Lastly, I want to show you how to create API documentation in Postman.

Open your collection from the sidebar, and select “View complete documentation”.

Press enter or click to view image in full size

Here, you can add a title, description, and notes for each request.

Then click “Publish” (top right) to generate a shareable public or private link.

Postman will automatically generate clean, readable documentation based on your requests, parameters, and responses.

Which is handy if you want to share it with somebody who wants to integrate with your system 😉.

Summary

You see (at least a little) how challenging life can be for a QA 😅.

Now you can tell yourself how hard it is to deliver quality content — you can’t just click a few buttons and hope it works. Keep your environment clean and isolated. Master techniques like pairwise testing, learn about HTTP flow, local storage, cookies, the DevTools panel, and using Postman — every detail matters.

And don’t completely give up on unit tests 😅.

Press enter or click to view image in full size

💬 Let me know, have you picked up some useful tips and will apply them in your daily testing routine?

👏 Give this article a clap if you liked it

👇 Do not forget to check out other articles

☕️ Support me if you want

✅ Follow to receive more

😉 See U

--

--

iamprovidence
iamprovidence

Written by iamprovidence

👨🏼‍💻 Full Stack Dev writing about software architecture, patterns and other programming stuff https://www.buymeacoffee.com/iamprovidence