Back to All Articles
Networking

Network Testing Tools for QA Engineers

Honnesh Muppala May 5, 2026 14 min read

Why QA Engineers Need Network Testing Skills

Network testing is one of the most underrated skills in a QA engineer's toolkit. Most engineers focus on functional correctness — does the app produce the right output given the right input? But a significant category of real-world bugs is entirely network-driven: APIs that time out under high latency, apps that silently fail on packet loss, authentication flows that break due to expired SSL certificates, mobile apps that crash on network switches from WiFi to cellular.

These bugs are invisible to unit tests and integration tests run on localhost. They only surface when you understand what is happening at the network layer. Here are the scenarios where network testing skills pay off:

Architecture Overview

The network stack a mobile app traverses from tap to database response:

Mobile App
(iOS / Android)
WiFi / Cellular
(network layer)
Proxy
(Charles/mitmproxy)
Internet
(DNS, routing)
Backend API
(REST/GraphQL)
Database
(SQL / NoSQL)

As a QA engineer, your tools operate at different layers of this stack. Wireshark and tcpdump operate at the raw packet level. Charles Proxy and Fiddler operate at the HTTP/HTTPS application layer. curl operates at the HTTP request level. Understanding which layer to inspect for a given class of bug is what separates effective network debuggers from engineers who spend hours guessing.

Wireshark

Wireshark is the world's most-used network protocol analyser. It captures raw packets from your network interface and displays them with full protocol dissection — you can see every TCP connection, DNS query, HTTP request, TLS handshake, and application payload.

Installation

# macOS
brew install --cask wireshark

# Ubuntu
sudo apt install wireshark
sudo usermod -aG wireshark $USER   # allow non-root capture
newgrp wireshark

# Windows: download installer from wireshark.org

Capture filters (set before capture starts)

Capture filters use BPF syntax and filter packets at the capture stage — they reduce noise and file size significantly.

# Capture only traffic on port 443 (HTTPS)
port 443

# Capture only traffic to/from a specific IP
host 52.84.12.200

# Capture only TCP traffic on ports 80 or 443
tcp port 80 or tcp port 443

# Capture traffic from a specific source IP
src host 192.168.1.100

# Capture only DNS queries (UDP port 53)
udp port 53

Display filters (applied after capture)

Display filters are far more powerful than capture filters and use Wireshark's own filter syntax. Apply them in the filter bar at the top of the Wireshark UI.

# Show only HTTP traffic
http

# Show only HTTPS TLS handshakes
tls.handshake

# Show only HTTP 5xx responses
http.response.code >= 500

# Show requests to a specific host
http.host contains "api.example.com"

# Show TCP connection resets (RST packets — often indicates connection refused)
tcp.flags.reset == 1

# Show DNS query failures (NXDOMAIN)
dns.flags.rcode == 3

# Show packets with retransmissions (packet loss indicator)
tcp.analysis.retransmission

# Show TLS certificate details
tls.handshake.type == 11

Reading a TCP handshake

Every TCP connection starts with a three-way handshake: SYN → SYN-ACK → ACK. In Wireshark, you can see this as three packets before any HTTP data flows. Use tcp.flags.syn == 1 to filter just these handshake packets and measure connection establishment time.

tcpdump

tcpdump is the CLI equivalent of Wireshark — it captures network packets and either displays them in the terminal or saves them to a .pcap file for analysis in Wireshark. It runs on any Linux server without a GUI, which makes it indispensable for capturing traffic on remote test servers and CI environments.

# Capture all traffic on the default interface and display it
sudo tcpdump

# Capture on a specific interface
sudo tcpdump -i eth0

# Capture only HTTP and HTTPS traffic
sudo tcpdump -i eth0 'port 80 or port 443'

# Capture traffic to/from a specific host
sudo tcpdump -i eth0 host api.example.com

# Show packet contents in ASCII (useful for plaintext HTTP)
sudo tcpdump -i eth0 -A port 80

# Show packet contents in hex and ASCII
sudo tcpdump -i eth0 -X port 80

# Save capture to a .pcap file for analysis in Wireshark
sudo tcpdump -i eth0 -w capture.pcap port 443

# Limit capture to 1000 packets
sudo tcpdump -i eth0 -c 1000 -w capture.pcap

# Read a saved .pcap file
sudo tcpdump -r capture.pcap

# Capture DNS traffic and show query names
sudo tcpdump -i eth0 -n 'udp port 53' | grep -i "api.example.com"

The most common pattern for remote server debugging: SSH in, start a tcpdump capture to a .pcap file, reproduce the bug, stop the capture, then scp the .pcap back to your laptop and open it in Wireshark for full visual analysis.

# On the remote server: start capture in background
sudo tcpdump -i eth0 -w /tmp/debug-capture.pcap port 8080 &

# Reproduce the issue...

# Stop the capture
sudo kill %1

# On your local machine: copy the capture file
scp honnesh@test-server.company.com:/tmp/debug-capture.pcap ./

# Open in Wireshark
wireshark debug-capture.pcap

Charles Proxy

Charles Proxy is an HTTP proxy that sits between your device and the internet, intercepting all HTTP and HTTPS traffic. It gives you a clear, structured view of every API call your app makes — request headers, request body, response status, response body, timing — without writing any code.

Charles is particularly powerful for mobile app testing because it lets you inspect HTTPS traffic (normally encrypted end-to-end) by acting as a man-in-the-middle proxy with a trusted certificate you install on the device.

Initial setup as system proxy (macOS)

# Charles automatically configures macOS system proxy when launched.
# To configure manually: System Settings → Network → WiFi → Details → Proxies
# HTTP Proxy: 127.0.0.1 Port: 8888
# HTTPS Proxy: 127.0.0.1 Port: 8888

SSL proxying — enabling HTTPS inspection

  1. In Charles: Proxy menu → SSL Proxying Settings → Add → Host: *, Port: 443
  2. Install Charles Root Certificate: Help menu → SSL Proxying → Install Charles Root Certificate
  3. Trust the certificate: macOS Keychain → find "Charles Proxy CA" → Get Info → Always Trust

iOS device setup

  1. Connect iPhone to the same WiFi network as your Mac
  2. In iPhone WiFi settings → tap your network → Configure Proxy → Manual
  3. Server: your Mac's IP address, Port: 8888
  4. Open Safari on iPhone and visit chls.pro/ssl
  5. Download and install the Charles certificate profile
  6. Settings → General → VPN & Device Management → trust the Charles certificate
  7. Settings → General → About → Certificate Trust Settings → enable full trust for Charles

Android device setup

  1. WiFi settings → long-press your network → Modify Network → Manual proxy
  2. Proxy hostname: your Mac's IP, Port: 8888
  3. In Charles: Help → SSL Proxying → Save Charles Root Certificate
  4. Transfer the .pem file to the device via ADB or email
  5. Settings → Security → Install from storage → select the certificate
  6. Note: Android 7+ apps must explicitly trust user-installed CAs via network_security_config.xml for HTTPS inspection to work in debug builds

Key Charles features for QA

From Experience at Virtusa: Charles Proxy was an essential part of my daily toolkit when testing Android and Fire OS applications. One of the most valuable techniques was using Charles's Rewrite rules to inject error responses from third-party services — for example, making the payment gateway return a 503 instead of a 200, then observing how the app's checkout flow handled it. This revealed several cases where the app displayed a blank screen instead of an error message, which were filed as critical defects. Without Charles, reproducing these server-side error conditions would have required backend changes or mocking infrastructure. With Charles, it was a 30-second Rewrite rule configuration.

curl Deep Dive

curl is a command-line tool for making HTTP requests. It is available on every OS, requires no setup, and lets you test any API endpoint with complete control over every aspect of the request.

# Basic GET request
curl https://api.example.com/products

# GET with authentication header
curl -H "Authorization: Bearer eyJhbGci..." \
     https://api.example.com/user/profile

# POST with JSON body
curl -X POST https://api.example.com/auth/login \
     -H "Content-Type: application/json" \
     -d '{"email":"test@example.com","password":"Pass123!"}'

# PUT to update a resource
curl -X PUT https://api.example.com/products/42 \
     -H "Authorization: Bearer TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"name":"Updated Product","price":29.99}'

# DELETE a resource
curl -X DELETE https://api.example.com/products/42 \
     -H "Authorization: Bearer TOKEN"

# Show response headers only (-I HEAD request)
curl -I https://api.example.com/health

# Show full request and response headers (-v verbose)
curl -v https://api.example.com/health 2>&1 | head -50

# Follow redirects automatically
curl -L https://short.url/abc123

# Save response body to a file
curl -o response.json https://api.example.com/products

# Ignore SSL certificate errors (use only for internal/test servers)
curl -k https://self-signed.internal.example.com/api

# Send a file as multipart form upload
curl -X POST https://api.example.com/upload \
     -H "Authorization: Bearer TOKEN" \
     -F "file=@/path/to/test-image.jpg" \
     -F "description=Test upload"

Timing breakdown with -w

The -w flag lets you print detailed timing metrics after a request. This is invaluable for diagnosing where latency is coming from in an API call.

curl -w "\n\nDNS lookup:       %{time_namelookup}s\n\
TCP connect:      %{time_connect}s\n\
TLS handshake:    %{time_appconnect}s\n\
Time to 1st byte: %{time_starttransfer}s\n\
Total time:       %{time_total}s\n\
Response size:    %{size_download} bytes\n\
HTTP status:      %{http_code}\n" \
-o /dev/null -s https://api.example.com/products

This breakdown immediately tells you if the bottleneck is DNS (slow DNS resolver), TCP (network latency), TLS (certificate validation or cipher negotiation), or the server itself (TTFB). A TTFB of 400ms with a total time of 410ms means the network is fine and the server is processing slowly. A TTFB of 400ms with a DNS lookup of 380ms means your DNS resolver is the problem.

Postman for API and Network Testing

Postman goes beyond simple API testing — it provides a full environment for automating API test suites, managing authentication flows, and running collections against multiple environments.

Pre-request scripts for dynamic authentication

// Pre-request Script: auto-refresh auth token before each request
pm.sendRequest({
    url: pm.environment.get('base_url') + '/auth/refresh',
    method: 'POST',
    header: { 'Content-Type': 'application/json' },
    body: {
        mode: 'raw',
        raw: JSON.stringify({
            refresh_token: pm.environment.get('refresh_token')
        })
    }
}, function (err, response) {
    if (!err && response.code === 200) {
        const body = response.json();
        pm.environment.set('access_token', body.access_token);
    }
});

Test assertions in Postman

// Tests tab — run after the response is received
pm.test("Status is 200", () => {
    pm.response.to.have.status(200);
});

pm.test("Response time under 500ms", () => {
    pm.expect(pm.response.responseTime).to.be.below(500);
});

pm.test("Product has required fields", () => {
    const body = pm.response.json();
    pm.expect(body).to.have.property('id');
    pm.expect(body).to.have.property('name');
    pm.expect(body.price).to.be.a('number').and.above(0);
});

pm.test("Authorization header was sent", () => {
    pm.expect(pm.request.headers.get('Authorization')).to.include('Bearer');
});

Running collections via CLI (Newman)

# Install Newman
npm install -g newman

# Run a Postman collection against staging
newman run collection.json \
    -e environments/staging.json \
    --reporters cli,html \
    --reporter-html-export newman-report.html

# Run in CI with JUnit output for test reporting
newman run collection.json \
    -e environments/staging.json \
    --reporters junit \
    --reporter-junit-export results.xml

Network Simulation

Testing your app only on a fast, stable WiFi connection is not realistic. Real users operate on 3G, congested 4G, switching networks, and flaky public WiFi. Network simulation tools let you reproduce these conditions in a controlled way.

Linux: tc (traffic control)

# Add 200ms latency to all outgoing traffic on eth0
sudo tc qdisc add dev eth0 root netem delay 200ms

# Add latency with variability (200ms ± 50ms jitter)
sudo tc qdisc add dev eth0 root netem delay 200ms 50ms

# Simulate 5% packet loss
sudo tc qdisc add dev eth0 root netem loss 5%

# Limit bandwidth to 1Mbit/s (slow 3G simulation)
sudo tc qdisc add dev eth0 root tbf rate 1mbit burst 32kbit latency 400ms

# Combine: 100ms delay + 2% packet loss (poor LTE)
sudo tc qdisc add dev eth0 root netem delay 100ms loss 2%

# Remove all tc rules
sudo tc qdisc del dev eth0 root

macOS: Network Link Conditioner

On macOS, install Network Link Conditioner from Xcode Additional Tools (download from Apple Developer). It adds a System Preferences pane where you can select predefined profiles (3G, LTE, DSL, Edge, 100% Loss) or create custom profiles with specific bandwidth, latency, and packet loss values.

Android emulator network throttling

# Using Android emulator console (telnet to emulator port)
telnet localhost 5554

# In the emulator console:
network speed edge     # EDGE: 118.4/118.4 kbps
network speed umts     # UMTS (3G): 14.4/5.76 Mbps
network speed lte      # LTE: 58.0/12.0 Mbps
network delay gprs     # Add GPRS delay (150-550ms)
network delay none     # Remove delay

iOS: Xcode Network Link Conditioner

On a physical iOS device connected to Xcode, go to Settings → Developer → Network Link Conditioner (enabled when the Xcode dev tools are installed on the device). Select a profile: Very Bad Network, Edge, 3G, LTE, or 100% Loss.

Common Network Errors and How to Diagnose Them

Error Likely Cause How to Diagnose Fix Direction
DNS resolution failed / NXDOMAIN Wrong hostname, DNS misconfiguration dig api.example.com, check DNS response Fix hostname / DNS record
SSL certificate error Expired cert, hostname mismatch, untrusted CA Charles Proxy SSL view, openssl s_client Renew cert / fix SANs / trust CA
Connection refused (ECONNREFUSED) Service not running, wrong port, firewall ss -tlnp | grep PORT, nmap Start service / open firewall port
Connection timed out Firewall dropping packets, host unreachable ping, traceroute, Wireshark TCP RST/no SYN-ACK Fix routing / firewall rules
HTTP 502 Bad Gateway Upstream service unavailable or crashing Check upstream service logs, Wireshark between proxy and upstream Fix upstream service
Slow API response (high TTFB) Database query, N+1 queries, missing cache curl -w timing, APM traces Optimise query / add cache
Intermittent failures Packet loss, flaky upstream, race condition tcpdump for retransmissions, Wireshark tcp.analysis.retransmission Retry logic / fix network path

Mobile-Specific Network Testing

ADB logcat for network errors on Android

# View all logs filtered to network errors
adb logcat | grep -i "network\|socket\|timeout\|connect"

# Filter to your app's package specifically
adb logcat --pid=$(adb shell pidof com.example.myapp)

# Save logs during a test session
adb logcat -v threadtime > session-$(date +%Y%m%d_%H%M%S).log &

# Clear existing logcat buffer before starting
adb logcat -c

# Show only errors
adb logcat *:E

openssl for certificate inspection

# Check what certificate a server is presenting
openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null \
    | openssl x509 -noout -text | grep -A2 "Subject:\|Not After\|SANs"

# Check certificate expiry date
echo | openssl s_client -connect api.example.com:443 2>/dev/null \
    | openssl x509 -noout -dates

# Test TLS version and cipher support
openssl s_client -connect api.example.com:443 -tls1_2
openssl s_client -connect api.example.com:443 -tls1_3
From Experience at Viasat: Network testing was at the centre of my work at Viasat because satellite communication introduces latency and packet loss characteristics that are fundamentally different from terrestrial networks. A typical geostationary satellite link adds 600ms round-trip latency — something that would be unacceptable in other contexts is just the physics of the medium. Testing applications over satellite required specific network simulation setups using tc netem on our Linux test servers to replicate the actual link conditions, including variable packet loss during weather events and the burst-loss patterns characteristic of satellite modems. This is where understanding network simulation at the Linux level — rather than relying on GUI tools — was essential. We wrote shell scripts that applied different tc profiles during test runs and automatically restored clean network conditions afterwards, ensuring test results were reproducible across sessions.

Tool Comparison

Tool Layer GUI HTTPS Inspection Mobile Support Best For
Wireshark Packet (L2-L7) Yes With key export Via pcap from device Deep protocol analysis, TCP debugging
tcpdump Packet (L2-L7) No (CLI) With key export Via ADB on Android Headless capture on servers, CI
Charles Proxy HTTP/HTTPS (L7) Yes Yes (MITM cert) Excellent (iOS & Android) Mobile app API inspection, throttling
Fiddler HTTP/HTTPS (L7) Yes Yes (MITM cert) Good Windows-primary, .NET apps
mitmproxy HTTP/HTTPS (L7) Web UI + CLI Yes (MITM cert) Yes Scripted traffic manipulation, CI
curl HTTP/HTTPS (L7) No (CLI) Yes (native) N/A (desktop tool) Quick API tests, CI scripts, timing

End-to-End Debug Workflow: Diagnosing a Failing Mobile API Call

Here is the structured workflow I use when a mobile app API call fails and the cause is not immediately obvious. This covers the majority of network-related defects you will encounter in practice.

1
Reproduce the issue with Charles Proxy running

Configure Charles as the device proxy and enable SSL proxying for the failing domain. Reproduce the exact steps that trigger the failure. Charles will capture the exact request and response — or show you that no request was made at all (a client-side failure before the network call).

2
Check the request in Charles

Verify: Was the correct URL called? Were the right headers sent (especially Authorization)? Was the request body correctly formatted? Is the Content-Type header set? Many failures are simply missing or malformed headers that look like server errors.

3
Replay the request with curl

Copy the request from Charles (right-click → Copy as curl command). Paste it into your terminal and run it. Does the same failure occur from curl? If yes, it is a server-side issue. If curl succeeds but the app fails, the issue is in how the app constructs or handles the request.

# Charles generates this for you - example output:
curl -X POST 'https://api.example.com/auth/login' \
  -H 'Authorization: Bearer TOKEN' \
  -H 'Content-Type: application/json' \
  --data-raw '{"email":"test@example.com","password":"Pass123"}'
4
Check DNS if there is no response at all

If Charles shows the request was never sent, or there is a DNS error: dig api.example.com from your Mac, then verify the device's DNS is resolving the same IP. On different network environments (VPN, corporate WiFi) the same hostname can resolve to different addresses.

5
Check for SSL certificate issues

If Charles shows an SSL error or the request is not being intercepted: openssl s_client -connect api.example.com:443 to inspect the certificate. Check expiry, hostname match, and CA chain. On Android, also check whether the app's network_security_config.xml trusts user-installed certificates.

6
Simulate degraded network and retry

If the failure only happens intermittently: use Charles throttling to simulate slow network (e.g., 3G / 400ms latency / 10% packet loss) and try to reproduce consistently. Many timeout bugs and retry-logic bugs only appear under these conditions.

7
Review ADB logcat for the client-side perspective

Run adb logcat | grep -i "network\|exception\|error\|timeout" simultaneously with the Charles capture. The app logs often reveal exactly which component failed and why — whether it is the HTTP client library throwing a timeout, an SSL handshake exception, or a JSON parsing error on the response body.

8
Document with full evidence before filing the bug

Save the Charles session (File → Save Session), the curl command that reproduces it, the curl output with -v verbose headers, and the relevant ADB logcat section. A bug report with this level of network evidence is actionable immediately — developers do not need to reproduce the environment themselves.


Back to Blog
From Experience — Viasat: Working with ARINC 429 data buses and IFC hardware at Viasat, Wireshark became an essential daily tool. During a Boeing integration test, we observed unexpected burst rate timing that didn't match the Interface Control Document. Capturing ARINC 429 frames in Wireshark and comparing against the ICD revealed a 12ms timing offset — within tolerance on its own, but enough to cause cumulative drift on long-haul flights. That finding prevented an on-wing integration failure. Linux CLI diagnostics alongside Wireshark are indispensable for this level of root cause analysis.