Back to Blog
QAObservabilityProduction ReadinessSDET

Your Green CI Is A Lie: QA Needs Production Metrics

Most QA teams are celebrating green CI pipelines while their users suffer. The truth is, your test environments lie, and the only reliable source of truth for quality lives in production metrics. Stop optimizing for passing tests and start validating actual user experience.

October 15, 2026
8 min read
RS
Raju Shanigarapu

Most QA teams operate with a dangerous delusion: that a green CI pipeline means a healthy product. This tunnel vision, focused solely on pre-release test environments, disconnects quality assurance from the ultimate arbiter of quality: the user experience in production. We obsess over passing unit, integration, and even end-to-end tests, only to be blindsided by incidents that could have been predicted, or at least understood faster, by anyone looking at the right production dashboards.

Your "Passed" Tests Don't Reflect Reality

The fundamental flaw in traditional QA lies in its scope. We meticulously craft tests that run against environments that are, by definition, not production. They have different data volumes, network latencies, user loads, and often different third-party service versions or configurations. A UI test written with Playwright 1.4x might pass flawlessly against a staging environment with ten concurrent users and fresh data, but that same user flow might grind to a halt or throw 500 errors in production under peak load with years of accumulated data.

This isn't a failure of the tests themselves, but a failure of where we draw the line for "quality." A test that passes in a hermetic environment proves only that the code can work under specific, controlled conditions. It says nothing about how it will perform when faced with the chaotic reality of live traffic, unexpected data shapes, or downstream service degradation. Your CI is green, but your users are hitting a wall.

The Metrics That Matter to QA Aren't Just Latency

When I speak of production metrics, I'm not just talking about infrastructure-level CPU or memory usage. Those are for SREs. For QA, the critical metrics are directly tied to user experience and business outcomes. We need to look beyond generic APM dashboards and identify the specific indicators that validate the functionality we’ve tested.

Consider these:

  • Business Transaction Success Rates: Not just HTTP 200s, but the successful completion of a checkout, a user registration, a file upload. If your e-commerce platform shows a 99% success rate for GET /products, but only 80% for POST /checkout, that's a QA problem, regardless of how many Playwright tests pass on staging.
  • Error Rates (Specific): Beyond generic 5xx counts, QA needs to track specific error types. Are there more NullPointerException errors in the logs after a specific release? Are external API calls failing more frequently? These point to specific code paths or integration points that our tests likely missed or couldn't adequately simulate.
  • Queue Depths & Processing Times: If your asynchronous processing service is backing up, or messages are taking minutes instead of seconds to process, it indicates a bottleneck that impacts user experience, even if the eventual outcome is "success."
  • Cache Hit/Miss Ratios: A sudden drop in cache hits means more load on your database and potentially slower response times. This is a performance regression that QA should be able to identify and flag.

Tools like Datadog, Prometheus, Grafana, and the ELK stack aren't just for developers debugging P1 incidents. They are essential QA tools, providing the ground truth that your test reports can't.

From Test Report To Production Dashboard: A New Workflow

Integrating production metrics into the QA workflow isn't about making every QA engineer an SRE. It's about empowering them to extend their quality gate past deployment. The new workflow looks like this: when a significant feature is deployed, the QA engineer responsible for its testing doesn't just check the CI pipeline; they immediately pivot to the production dashboards.

Did the deployment cause a spike in 5xx errors for the /api/v1/new-feature endpoint? Did the average latency for GET /user-profile suddenly jump from 50ms to 200ms? These are direct signals that the release introduced a regression, even if all automated tests passed with flying colors. The best QA teams correlate test results with real-world impact.

Here's a simplified Python example demonstrating how a post-deployment check might query a Prometheus instance to monitor the error rate of a specific service. This can be integrated into a GitHub Actions workflow or any CI/CD pipeline as a mandatory health check after deployment.

import requests
import os
import time
from typing import Dict, Any

PROMETHEUS_URL = os.getenv("PROMETHEUS_URL", "http://localhost:9090")
SERVICE_JOB_NAME = os.getenv("SERVICE_JOB_NAME", "my_microservice")
TIME_WINDOW = "5m" # Time window for rate calculation, e.g., '5m', '1h'
LOOKBACK_DURATION_SECONDS = 300 # How far back to query for latest data point (5 minutes)

def query_prometheus(query: str, end_time: int, time_range_seconds: int) -> Dict[str, Any]:
    """Queries Prometheus for a given metric over a time range."""
    start_time = end_time - time_range_seconds
    params = {
        'query': query,
        'start': start_time,
        'end': end_time,
        'step': f'{time_range_seconds // 60}s' # Adjust step for resolution
    }
    try:
        response = requests.get(f"{PROMETHEUS_URL}/api/v1/query_range", params=params)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Error querying Prometheus: {e}")
        return {}

def get_latest_metric_value(result: Dict[str, Any]) -> float:
    """Extracts the latest value from a Prometheus query result."""
    if result and result.get("data", {}).get("result"):
        # Iterate through series and get the latest value
        # For simplicity, sum values from all series if multiple are returned
        latest_value = 0.0
        for series in result["data"]["result"]:
            if series.get("values"):
                latest_value += float(series["values"][-1][1]) # [timestamp, value]
        return latest_value
    return 0.0

def check_service_error_rate(service_job: str, threshold: float = 0.005) -> bool:
    """Checks if the 5xx error rate for a service exceeds a given threshold."""
    current_time = int(time.time())

    # Prometheus query to calculate 5xx error rate over a time window
    # rate(http_requests_total{job="my_microservice", code=~"5.."}[5m]) for 5xx errors
    # rate(http_requests_total{job="my_microservice"}[5m]) for all requests
    error_query = f'sum(rate(http_requests_total{{job="{service_job}", code=~"5.."}}[{TIME_WINDOW}]))'
    total_query = f'sum(rate(http_requests_total{{job="{service_job}"}}[{TIME_WINDOW}]))'

    error_data = query_prometheus(error_query, current_time, LOOKBACK_DURATION_SECONDS)
    total_data = query_prometheus(total_query, current_time, LOOKBACK_DURATION_SECONDS)

    error_count = get_latest_metric_value(error_data)
    total_count = get_latest_metric_value(total_data)

    if total_count == 0:
        print(f"No requests recorded for service '{service_job}' in the last {TIME_WINDOW}. Cannot calculate error rate.")
        return True # Consider as healthy if no traffic, or implement specific logic

    current_error_rate = error_count / total_count
    print(f"Service '{service_job}' 5xx error rate: {current_error_rate:.4f} (Threshold: {threshold:.4f})")

    return current_error_rate < threshold

if __name__ == "__main__":
    print(f"Executing post-deployment health check for service: {SERVICE_JOB_NAME}")
    if check_service_error_rate(SERVICE_JOB_NAME, threshold=0.001): # Lower threshold for critical services
        print(f"SUCCESS: Service '{SERVICE_JOB_NAME}' 5xx error rate is within acceptable limits.")
    else:
        print(f"FAILURE: Service '{SERVICE_JOB_NAME}' 5xx error rate is too high! INVESTIGATE IMMEDIATELY.")
        # In a real CI/CD pipeline, this would trigger an alert or fail the deployment.
        # exit(1)

Observability Isn't Just For Developers: It's Your New Test Data

The concept of "test data" needs an urgent update. It's not just the carefully curated JSON payloads you use in your API tests or the specific user accounts you provision for UI flows. Real test data is the live stream of events, logs, traces, and metrics flowing from your production system.

When a QA engineer can dive into a distributed trace in Jaeger or OpenTelemetry to see why a transaction failed across multiple services, they gain insights that no amount of local debugging could provide. When they can correlate an increase in log errors in Splunk or Sumo Logic with a spike in a Grafana dashboard, they're not just finding a bug; they're understanding systemic vulnerabilities. This isn't just about reacting to incidents; it's about proactively validating the efficacy of your testing strategy. If a critical bug makes it to production, and your tests didn't catch it, the production metrics will tell you where and why your tests are insufficient.

Where This Breaks Down

Adopting a production-metric-driven QA approach isn't a silver bullet. It requires a mature observability stack, which many teams still lack. Instrumenting code for meaningful metrics, logs, and traces takes effort, and the cost of robust monitoring infrastructure can be significant. There's also the challenge of data overload: without intelligent dashboards, alerts, and training, QA engineers can drown in a sea of irrelevant information, leading to alert fatigue. This approach also doesn't replace traditional testing; it augments it. You still need unit tests, integration tests, and some level of E2E coverage. It's about shifting focus from proving something works in a controlled environment to validating it works for users, continuously.

The Architect's Role: Building a Feedback Loop, Not Just Tests

As a QA architect, your role isn't just to design test frameworks or define test strategies. It's to design the feedback loops that genuinely inform quality. This means:

  1. Defining SLOs/SLIs with Product and Dev: Work with product owners and development leads to establish measurable Service Level Objectives (SLOs) and Service Level Indicators (SLIs) that directly reflect user experience and business value. These become the ultimate "pass/fail" criteria for quality.
  2. Instrumenting for Quality: Advocate for robust instrumentation that provides the metrics QA needs. This might mean custom metrics for specific business transactions or ensuring proper tagging for easier filtering in monitoring tools.
  3. Integrating Observability into CI/CD: Build checks into your deployment pipelines that query production metrics post-release. Use tools like GitHub Actions to trigger these checks and halt deployments if SLOs are violated.
  4. Training and Empowerment: Equip your QA engineers with the skills to navigate dashboards, interpret metrics, and even write simple queries against Prometheus or your APM tool. They need access and training, not just credentials.

The goal is to shift from a world where QA hands off a "tested" product, hoping it works, to one where QA actively monitors, validates, and helps maintain a high-quality user experience directly in production. This proactive, data-driven approach is the only way to truly guarantee quality in complex distributed systems.

This week, pick one critical user flow your team tests, identify the corresponding production metrics (e.g., its success rate, average latency, or specific error codes), and start monitoring them. Make it a part of your release checklist.

Want to build systems that work this way?

I work with QA engineers and engineering teams on automation architecture, framework audits, and AI-powered quality systems.

Get posts like this in your inbox

No fluff. Sharp takes on QA, AI, and engineering — once a week.

Sent with MailerLite. See the privacy policy.