Skip to main content
Back to Blog
Guide
2026-05-18

Test Automation Metrics and KPIs: What to Measure and How to Report

Complete guide to test automation metrics and KPIs covering pass rates, execution time, flaky test rate, code coverage, automation ROI, defect detection rate, and dashboard templates for stakeholder reporting.

Why Metrics Matter for Test Automation

Test automation without metrics is flying blind. You invest time and money building test suites but cannot answer basic questions: Are the tests catching bugs? Is the suite getting slower? Are flaky tests eroding team trust? Is the automation investment paying off?

Metrics transform test automation from a cost center into a measurable quality initiative. They help you justify automation investments to leadership, identify areas needing improvement, track progress over time, and make data-driven decisions about where to focus testing effort.

However, measuring the wrong things is worse than measuring nothing. Vanity metrics like total test count create perverse incentives. A team incentivized to write more tests writes trivial, low-value tests. This guide focuses on metrics that drive real quality improvement and provides practical templates for collecting, visualizing, and reporting them.

Core Metrics: The Essential Five

1. Test Pass Rate

The test pass rate is the percentage of tests that pass on a given run. It is the most basic indicator of test suite health. A consistently high pass rate means the application is stable or the tests are not catching bugs. A declining pass rate signals quality problems, test rot, or environmental issues.

Formula:

Pass Rate = (Passed Tests / Total Tests Executed) x 100

Target: Greater than 98% on the main branch. If your pass rate is below 95%, focus on fixing failing tests before writing new ones.

Important nuances: Track pass rate separately for different test types (unit, integration, E2E) and for different branches. A 99% pass rate on main means something different from 85% on feature branches. The main branch should always be near 100%.

// Calculate pass rate from test results
interface TestSuiteResult {
  total: number;
  passed: number;
  failed: number;
  skipped: number;
  duration: number;
  timestamp: string;
}

function calculatePassRate(result: TestSuiteResult): number {
  const executed = result.total - result.skipped;
  if (executed === 0) return 0;
  return (result.passed / executed) * 100;
}

function getPassRateTrend(results: TestSuiteResult[]): {
  current: number;
  average7d: number;
  average30d: number;
  trend: 'improving' | 'stable' | 'declining';
} {
  const current = calculatePassRate(results[0]);
  const last7 = results.slice(0, 7);
  const last30 = results.slice(0, 30);

  const avg7 =
    last7.reduce((sum, r) => sum + calculatePassRate(r), 0) / last7.length;
  const avg30 =
    last30.reduce((sum, r) => sum + calculatePassRate(r), 0) / last30.length;

  let trend: 'improving' | 'stable' | 'declining' = 'stable';
  if (avg7 - avg30 > 1) trend = 'improving';
  if (avg30 - avg7 > 1) trend = 'declining';

  return { current, average7d: avg7, average30d: avg30, trend };
}

2. Test Execution Time

Total suite execution time directly impacts developer productivity. Slow tests mean slow feedback loops, which discourage running tests locally and create long CI queues. Track total suite time, average test time, and the slowest tests.

Targets by test type:

Test TypeIndividual TestFull Suite
Unit tests< 100ms< 2 minutes
Integration tests< 2 seconds< 5 minutes
E2E tests< 30 seconds< 15 minutes
Full regression-< 30 minutes

Track and alert on trends. A 5% increase in suite execution time per week compounds quickly. After 6 months, your 10-minute suite takes 25 minutes. Set alerts for when suite time exceeds your target by 20%.

interface TestTimingReport {
  suiteTotal: number;
  averagePerTest: number;
  median: number;
  p95: number;
  p99: number;
  slowestTests: Array<{
    name: string;
    duration: number;
    file: string;
  }>;
}

function analyzeTimings(
  results: Array<{ name: string; duration: number; file: string }>
): TestTimingReport {
  const durations = results.map((r) => r.duration).sort((a, b) => a - b);
  const total = durations.reduce((sum, d) => sum + d, 0);

  return {
    suiteTotal: total,
    averagePerTest: total / results.length,
    median: durations[Math.floor(durations.length / 2)],
    p95: durations[Math.floor(durations.length * 0.95)],
    p99: durations[Math.floor(durations.length * 0.99)],
    slowestTests: results
      .sort((a, b) => b.duration - a.duration)
      .slice(0, 10),
  };
}

3. Flaky Test Rate

Flaky tests pass sometimes and fail sometimes without any code changes. They are the single biggest threat to test suite trust. When developers stop trusting test results, they stop looking at failures, and real bugs slip through.

Formula:

Flaky Rate = (Tests that both passed and failed in recent runs / Total Tests) x 100

Target: Less than 2%. If your flaky rate exceeds 5%, declare a testing quality emergency and stop writing new tests until the flaky tests are fixed.

Detection strategy: Run tests multiple times on the same commit. Any test that produces different results is flaky. Track this automatically in CI.

interface FlakyTestReport {
  totalTests: number;
  flakyTests: number;
  flakyRate: number;
  flakyDetails: Array<{
    name: string;
    passCount: number;
    failCount: number;
    lastFlake: string;
    commonError?: string;
  }>;
}

function detectFlakyTests(
  runs: Array<{
    timestamp: string;
    results: Map<string, 'passed' | 'failed' | 'skipped'>;
  }>,
  windowSize: number = 10
): FlakyTestReport {
  const recentRuns = runs.slice(0, windowSize);
  const testResults = new Map<
    string,
    { passed: number; failed: number; lastFlake: string }
  >();

  for (const run of recentRuns) {
    for (const [testName, status] of run.results) {
      if (status === 'skipped') continue;
      const existing = testResults.get(testName) || {
        passed: 0,
        failed: 0,
        lastFlake: '',
      };
      if (status === 'passed') existing.passed++;
      else existing.failed++;
      testResults.set(testName, existing);
    }
  }

  const flakyDetails = [];
  for (const [name, counts] of testResults) {
    if (counts.passed > 0 && counts.failed > 0) {
      flakyDetails.push({
        name,
        passCount: counts.passed,
        failCount: counts.failed,
        lastFlake: counts.lastFlake,
      });
    }
  }

  return {
    totalTests: testResults.size,
    flakyTests: flakyDetails.length,
    flakyRate: (flakyDetails.length / testResults.size) * 100,
    flakyDetails: flakyDetails.sort(
      (a, b) => b.failCount - a.failCount
    ),
  };
}

4. Code Coverage

Code coverage measures the percentage of production code exercised by tests. While 100% coverage does not guarantee bug-free code, low coverage guarantees untested code paths. Track line coverage, branch coverage, and function coverage.

Targets:

Coverage TypeMinimumGoodExcellent
Line coverage60%80%90%+
Branch coverage50%70%85%+
Function coverage70%85%95%+

Important: Coverage targets should vary by code area. Business logic (payment processing, access control) should have 90%+ coverage. UI rendering code can be lower. Configuration files and generated code should be excluded.

Track coverage trends, not just absolute numbers. A declining coverage trend means new code is being written without tests. Set CI gates that prevent merging code that reduces coverage below the threshold.

5. Automation Coverage

Automation coverage (or automation percentage) measures how much of your testing effort is automated versus manual. It answers the question: what percentage of your test cases have automated equivalents?

Formula:

Automation Coverage = (Automated Test Cases / Total Test Cases) x 100

Target: 70-80% overall. Some testing (exploratory, usability, ad-hoc) should remain manual. The goal is not 100% automation but rather automating everything that is repetitive and predictable.

Track automation coverage by feature area to identify gaps. If your checkout flow has 95% automation but your admin panel has 20%, that highlights where to focus next.

Advanced Metrics

6. Defect Detection Rate

Defect detection rate (or defect detection efficiency) measures how many bugs are caught by automation versus found in production. This is the ultimate measure of test suite effectiveness.

Formula:

Detection Rate = (Bugs caught by automation / Total bugs found) x 100

Tracking approach: Tag every bug in your issue tracker with how it was found: unit test, integration test, E2E test, manual testing, production monitoring, or user report. Monthly, calculate the percentage caught by each method.

Target: Automated tests should catch at least 60% of all bugs. If production bugs consistently come from areas with automated tests, the tests are not covering the right scenarios.

7. Mean Time to Recovery (MTTR)

When a test failure indicates a real bug, how long does it take to fix? MTTR measures the time from test failure detection to the fix being deployed. A short MTTR indicates effective test reporting and efficient debugging workflows.

Formula:

MTTR = Average time from failure detection to fix deployment

Target: Less than 4 hours for critical path failures. Less than 24 hours for non-critical failures.

8. Build Stability Index

The build stability index tracks how often your CI pipeline passes versus fails. A stable build gives teams confidence to deploy. An unstable build blocks deployments and wastes time investigating failures.

Formula:

Build Stability = (Successful builds / Total builds) x 100

Target: Greater than 90% on the main branch. Feature branches may be lower but should still be above 75%.

interface BuildStabilityReport {
  totalBuilds: number;
  successfulBuilds: number;
  failedBuilds: number;
  stabilityRate: number;
  failureCategories: Record<string, number>;
  averageRecoveryTime: number;
}

function analyzeBuildStability(
  builds: Array<{
    status: 'success' | 'failure';
    timestamp: string;
    failureReason?: string;
  }>
): BuildStabilityReport {
  const successful = builds.filter((b) => b.status === 'success').length;
  const failed = builds.filter((b) => b.status === 'failure');

  const categories: Record<string, number> = {};
  for (const build of failed) {
    const reason = build.failureReason || 'unknown';
    categories[reason] = (categories[reason] || 0) + 1;
  }

  return {
    totalBuilds: builds.length,
    successfulBuilds: successful,
    failedBuilds: failed.length,
    stabilityRate: (successful / builds.length) * 100,
    failureCategories: categories,
    averageRecoveryTime: 0, // Calculate from consecutive fail/pass pairs
  };
}

9. Test Automation ROI

Return on investment is the metric that matters most to leadership. Calculate the cost of building and maintaining automation against the cost savings from reduced manual testing, faster releases, and fewer production bugs.

Cost components:

Cost CategoryManual TestingAutomated Testing
Initial setupLowHigh
Per-execution costHigh (human time)Low (compute time)
MaintenanceLowMedium
Scaling costLinear (more testers)Near-zero (more runs)
Feedback speedHours to daysMinutes

ROI Formula:

ROI = ((Manual Testing Cost - Automation Cost) / Automation Cost) x 100

Manual testing cost calculation: Number of test cases x time per test x hourly rate x number of regression cycles per year.

Automation cost calculation: Initial development hours x hourly rate + annual maintenance hours x hourly rate + CI infrastructure costs.

interface AutomationROI {
  manualCostPerYear: number;
  automationCostYear1: number;
  automationCostPerYear: number;
  breakEvenMonths: number;
  year1ROI: number;
  year3ROI: number;
}

function calculateROI(params: {
  testCaseCount: number;
  minutesPerManualTest: number;
  hourlyRate: number;
  regressionCyclesPerYear: number;
  automationDevelopmentHours: number;
  annualMaintenanceHours: number;
  annualInfrastructureCost: number;
}): AutomationROI {
  const manualCostPerYear =
    (params.testCaseCount *
      params.minutesPerManualTest *
      params.regressionCyclesPerYear *
      params.hourlyRate) /
    60;

  const automationInitialCost =
    params.automationDevelopmentHours * params.hourlyRate;
  const automationAnnualCost =
    params.annualMaintenanceHours * params.hourlyRate +
    params.annualInfrastructureCost;

  const automationCostYear1 = automationInitialCost + automationAnnualCost;

  const monthlySavings = (manualCostPerYear - automationAnnualCost) / 12;
  const breakEvenMonths = Math.ceil(automationInitialCost / monthlySavings);

  const year1ROI =
    ((manualCostPerYear - automationCostYear1) / automationCostYear1) * 100;
  const year3Cost = automationCostYear1 + automationAnnualCost * 2;
  const year3ROI =
    ((manualCostPerYear * 3 - year3Cost) / year3Cost) * 100;

  return {
    manualCostPerYear,
    automationCostYear1,
    automationCostPerYear: automationAnnualCost,
    breakEvenMonths,
    year1ROI,
    year3ROI,
  };
}

10. Test Coverage by Risk Area

Not all code carries equal risk. Map your test coverage against business-critical areas and assign risk scores. High-risk, low-coverage areas need immediate attention.

Feature AreaBusiness RiskCurrent CoverageGap
Payment ProcessingCritical92%Low
User AuthenticationCritical88%Medium
Product SearchHigh75%Medium
Admin DashboardMedium45%High
Settings PageLow30%Acceptable
Marketing PagesLow15%Acceptable

Building Dashboards

Grafana Dashboard Template

Grafana is the most popular choice for test automation dashboards because it supports multiple data sources, has excellent visualization options, and is free.

Recommended panels for a test automation dashboard:

  1. Pass Rate Trend (Time series) - Shows pass rate over the last 30 days with threshold lines at 95% and 98%.

  2. Suite Execution Time (Time series) - Total execution time trend with separate lines for unit, integration, and E2E suites.

  3. Flaky Test Leaderboard (Table) - Top 10 flakiest tests with name, flaky rate, and days since first flake.

  4. Build Stability (Stat panel) - Current build stability percentage with color coding (green above 90%, yellow 80-90%, red below 80%).

  5. Test Count Breakdown (Pie chart) - Distribution of tests by type and status.

  6. Coverage Trend (Time series) - Line and branch coverage over time.

  7. Recent Failures (Table) - Last 20 test failures with test name, error message, timestamp, and link to CI run.

Allure Report Integration

Allure provides rich test reporting with trends, categories, and historical data. Integrate Allure into your CI pipeline for detailed per-run reports.

// playwright.config.ts - Allure reporter configuration
export default defineConfig({
  reporter: [
    ['html', { open: 'never' }],
    ['allure-playwright', {
      resultsDir: 'allure-results',
      detail: true,
      suiteTitle: true,
    }],
    ['json', { outputFile: 'test-results/results.json' }],
  ],
});

Custom JSON Reporter for Data Collection

Build a custom reporter that outputs structured JSON for ingestion into your metrics database.

// reporters/metrics-reporter.ts
import type {
  FullConfig,
  FullResult,
  Reporter,
  Suite,
  TestCase,
  TestResult,
} from '@playwright/test/reporter';

class MetricsReporter implements Reporter {
  private results: Array<{
    name: string;
    status: string;
    duration: number;
    retries: number;
    file: string;
    tags: string[];
  }> = [];

  onTestEnd(test: TestCase, result: TestResult) {
    this.results.push({
      name: test.title,
      status: result.status,
      duration: result.duration,
      retries: result.retry,
      file: test.location.file,
      tags: test.tags,
    });
  }

  async onEnd(result: FullResult) {
    const summary = {
      timestamp: new Date().toISOString(),
      status: result.status,
      duration: result.duration,
      total: this.results.length,
      passed: this.results.filter((r) => r.status === 'passed').length,
      failed: this.results.filter((r) => r.status === 'failed').length,
      skipped: this.results.filter((r) => r.status === 'skipped').length,
      flaky: this.results.filter(
        (r) => r.status === 'passed' && r.retries > 0
      ).length,
      tests: this.results,
    };

    // Send to metrics endpoint or write to file
    console.log(JSON.stringify(summary, null, 2));
  }
}

export default MetricsReporter;

Reporting to Stakeholders

Weekly Team Report

For engineering teams, provide a concise weekly summary with actionable items.

Template:

  • Pass Rate: 98.5% (up from 97.2% last week)
  • Suite Time: 12m 34s (down 8% from last week)
  • Flaky Tests: 3 tests identified, 2 fixed this week
  • New Tests: 15 added for checkout redesign
  • Coverage: 82.3% line coverage (up 1.2%)
  • Action Items: Fix remaining flaky test in payment flow, add tests for new admin bulk actions

Monthly Leadership Report

For non-technical leadership, focus on business impact and ROI.

Template:

  • Quality Score: 94/100 (based on weighted metrics)
  • Bugs Caught Before Release: 23 this month (up from 18)
  • Production Incidents Prevented: Estimated 4 based on severity of caught bugs
  • Release Velocity: Average 3 deploys per day (up from 2)
  • Cost Savings: Automation prevented approximately 80 hours of manual regression testing this month
  • ROI: 340% year-to-date (automation cost vs manual testing equivalent)

Quarterly Board Report

For board-level reporting, translate metrics into business language.

Focus on three numbers: time-to-market improvement (how much faster you ship features due to automated testing), quality improvement (reduction in production incidents), and cost efficiency (testing cost per release compared to industry benchmarks).

Common Anti-Patterns

Measuring Test Count

Total test count is a vanity metric. It incentivizes writing trivial tests instead of meaningful ones. A suite with 100 well-designed tests catches more bugs than a suite with 1,000 trivial assertions.

Chasing 100% Coverage

100% code coverage does not mean your software is bug-free. It means every line was executed at least once. Branches, race conditions, integration errors, and user experience issues can all exist in code with 100% coverage. Aim for meaningful coverage of critical paths rather than arbitrary coverage targets.

Ignoring Flaky Tests

Teams that accept flaky tests as normal gradually lose trust in the entire test suite. Set a zero-tolerance policy for flaky tests: quarantine them immediately and fix within one sprint.

Not Tracking Trends

A single snapshot of metrics is less useful than tracking trends over time. A 95% pass rate is great if it was 90% last month and terrible if it was 99% last month. Always compare current values against historical baselines.

Setting Up Metrics Collection

Step 1: Instrument Your Test Runner

Add JSON reporting to your test runner configuration. Most frameworks (Playwright, Jest, Vitest) support multiple reporters running simultaneously.

Step 2: Store Results in a Database

Pipe test results into a time-series database (InfluxDB, TimescaleDB) or a simple PostgreSQL table. Store one row per test run with summary metrics and one row per test result for detailed analysis.

Step 3: Build Dashboards

Connect Grafana to your database and build the dashboard panels described above. Start with the essential five metrics and add advanced metrics as your practice matures.

Step 4: Automate Alerts

Set up alerts for metric thresholds: pass rate drops below 95%, suite time exceeds 20 minutes, flaky rate exceeds 3%, or coverage drops below minimum. Route alerts to Slack or Teams for immediate visibility.

Step 5: Schedule Reports

Automate weekly team reports and monthly leadership reports. Use CI scheduled jobs to generate and distribute reports automatically.

Metrics That Drive Action

The best metrics are those that drive specific actions. If a metric does not lead to a decision or improvement, stop measuring it.

MetricAction When Threshold Breached
Pass rate < 95%Stop new feature work, fix failures
Flaky rate > 3%Quarantine flaky tests, assign fix owners
Suite time > 20minProfile slow tests, add parallelization
Coverage < 70%Require test coverage for new PRs
Build stability < 85%Investigate infrastructure issues
Detection rate < 50%Review test scenarios, add missing coverage

Start measuring today. Even basic tracking of pass rate and execution time provides more visibility than most teams have. Build from there as your automation practice matures.

# Get metrics and reporting skills for your AI agent
npx @qaskills/cli add test-reporting
npx @qaskills/cli add qa-metrics
Test Automation Metrics and KPIs: What to Measure and How to Report | QASkills.sh