How to Automate Test Plans and Suites With Azure DevOps Pipelines

Automate Test Plans and Suites With Pipelines
Image by Freepik

Managing Azure DevOps Test Plans manually becomes painful fast — especially when every sprint requires:

  • A new Test Plan
  • New Test Suites for each User Story
  • Linking existing Test Cases
  • Keeping everything aligned with iteration paths

The good news: you can automate all of it using Azure DevOps REST APIs, PowerShell, and a simple YAML pipeline.

This guide shows you exactly how to:

  • Create Test Plans automatically
  • Create Test Suites per User Story
  • Link Test Cases
  • Run everything inside an Azure DevOps Pipeline

Let’s make your sprint setup fully hands‑free.

🧭 1. Why Automate Test Plans and Suites?

Automation solves the biggest pain points in sprint‑based QA:

Benefits

  • Zero manual setup — no more clicking through Test Plans UI
  • Guaranteed consistency — naming, structure, and traceability stay clean
  • Instant sprint readiness — Test Plans are created the moment a sprint starts
  • Better DevOps alignment — pipelines become the source of truth
  • Scalable — works for 5 stories or 500
Element Best Practice Why
Test Plan One per sprint Clean historical record + sprint alignment
Test Suite One per User Story Perfect traceability
Test Cases Linked to User Story + Suite Enables reporting + automation
Pipeline One automation pipeline Runs every sprint or on demand

⚙️ 3. Azure DevOps REST APIs You Will Use

Azure DevOps exposes the following endpoints for test management:

Purpose Endpoint
Create Test Plan POST /_apis/test/plans
Create Test Suite POST /_apis/test/plans/{planId}/suites
Add Test Case to Suite POST /_apis/test/plans/{planId}/suites/{suiteId}/testcases/{testCaseId}
Query Work Items (WIQL) POST /_apis/wit/wiql
Get Work Item Details GET /_apis/wit/workitems/{id}

All examples below use API version 7.1-preview (current as of 2025).

This script:

  • Creates a Test Plan for the sprint
  • Queries all active User Stories in that sprint
  • Creates a Static Test Suite for each story
  • Finds all linked Test Cases
  • Adds them to the suite

It includes improved error handling, logging, and naming conventions.

PowerShell Script (2025 Updated Version)

# ==========================================
# Azure DevOps Test Plan Automation Script
# 2025 Updated Version
# ==========================================

param(
    [string]$OrgUrl = "https://dev.azure.com/YOURORG",
    [string]$Project = "YOURPROJECT",
    [string]$SprintName = "Sprint 18"
)

# Use System.AccessToken inside Azure DevOps Pipeline
$Token = $env:SYSTEM_ACCESSTOKEN
if (-not $Token) {
    throw "SYSTEM_ACCESSTOKEN is not available. Enable 'Allow scripts to access OAuth token' in the pipeline."
}

$Headers = @{
    Authorization = "Bearer $Token"
    "Content-Type" = "application/json"
}

$IterationPath = "$Project\$SprintName"
$AreaPath = $Project

Write-Host "🔧 Starting automation for $SprintName..."

# ---------------------------------------------------------
# 1. Create Test Plan
# ---------------------------------------------------------
$PlanBody = @{
    name      = "$SprintName - Test Plan"
    area      = @{ path = $AreaPath }
    iteration = $IterationPath
} | ConvertTo-Json -Depth 10

$PlanUri = "$OrgUrl/$Project/_apis/test/plans?api-version=7.1-preview.1"
$Plan = Invoke-RestMethod -Uri $PlanUri -Method Post -Headers $Headers -Body $PlanBody

$PlanId = $Plan.id
Write-Host "📘 Created Test Plan: $PlanId"

# ---------------------------------------------------------
# 2. Query User Stories in Sprint
# ---------------------------------------------------------
$Wiql = @"
SELECT [System.Id], [System.Title]
FROM WorkItems
WHERE [System.WorkItemType] = 'User Story'
AND [System.State] <> 'Closed'
AND [System.IterationPath] = '$IterationPath'
"@

$QueryBody = @{ query = $Wiql } | ConvertTo-Json
$QueryUri = "$OrgUrl/$Project/_apis/wit/wiql?api-version=7.1-preview.2"

$UserStories = Invoke-RestMethod -Uri $QueryUri -Method Post -Headers $Headers -Body $QueryBody

if ($UserStories.workItems.Count -eq 0) {
    Write-Host "⚠️ No active User Stories found for $SprintName."
    exit 0
}

# ---------------------------------------------------------
# 3. Create Test Suites + Link Test Cases
# ---------------------------------------------------------
foreach ($Story in $UserStories.workItems) {

    $StoryId = $Story.id
    $StoryDetails = Invoke-RestMethod -Uri "$OrgUrl/$Project/_apis/wit/workitems/$StoryId?api-version=7.1-preview.3" -Headers $Headers
    $StoryTitle = $StoryDetails.fields.'System.Title'

    Write-Host "🧩 Processing User Story $StoryId - $StoryTitle"

    # Create Static Suite
    $SuiteBody = @{
        suiteType = "StaticTestSuite"
        name      = "US$StoryId - $StoryTitle"
    } | ConvertTo-Json -Depth 10

    $SuiteUri = "$OrgUrl/$Project/_apis/test/plans/$PlanId/suites?api-version=7.1-preview.3"
    $Suite = Invoke-RestMethod -Uri $SuiteUri -Method Post -Headers $Headers -Body $SuiteBody

    $SuiteId = $Suite.id
    Write-Host "📁 Created Suite $SuiteId for User Story $StoryId"

    # Get linked Test Cases
    $Relations = $StoryDetails.relations `
        | Where-Object { $_.rel -eq "System.LinkTypes.Hierarchy-Forward" -and $_.url -match "/workItems/" }

    $TestCaseIds = @()
    foreach ($Rel in $Relations) {
        if ($Rel.url -match "/workItems/(\d+)") {
            $TestCaseIds += $Matches[1]
        }
    }

    if ($TestCaseIds.Count -eq 0) {
        Write-Host "⚠️ No test cases linked to User Story $StoryId"
        continue
    }

    # Add Test Cases to Suite
    foreach ($TcId in $TestCaseIds) {
        $AddUri = "$OrgUrl/$Project/_apis/test/plans/$PlanId/suites/$SuiteId/testcases/$TcId?api-version=7.1-preview.3"
        Invoke-RestMethod -Uri $AddUri -Method Post -Headers $Headers
        Write-Host "🔗 Linked Test Case $TcId → Suite $SuiteId"
    }
}

Write-Host "🎉 Automation complete for $SprintName!"

🚀 5. YAML Pipeline to Run the Automation

This pipeline:

  • Runs the PowerShell script
  • Passes sprint name dynamically
  • Uses System.AccessToken
  • Can be scheduled or triggered manually

azure-pipelines.yml

trigger: none

parameters:
  - name: sprintName
    type: string
    default: "Sprint 18"

pool:
  vmImage: 'windows-latest'

steps:
  - checkout: self

  - task: PowerShell@2
    displayName: "Automate Test Plan + Suites"
    inputs:
      targetType: inline
      script: |
        Write-Host "Running automation for: $"

        ./automation/Create-TestPlan.ps1 `
          -OrgUrl "https://dev.azure.com/YOURORG" `
          -Project "YOURPROJECT" `
          -SprintName "$"

    env:
      SYSTEM_ACCESSTOKEN: $(System.AccessToken)

🧠 6. Best Practices for Automated Test Plan Management

Area Best Practice
Naming Use US1234 - Title for suites
Traceability Always link Test Cases to User Stories
Regression Keep a separate long‑lived Regression Plan
Automation Run this pipeline at sprint creation
Governance Lock Test Plans after sprint ends

🏁 Conclusion

Automating your Azure DevOps Test Plans removes repetitive manual work and ensures your sprint structure is always:

  • Clean
  • Consistent
  • Traceable
  • Audit‑ready
  • Automation‑friendly

With PowerShell + Azure DevOps REST APIs + YAML pipelines, you can generate an entire sprint’s test structure in seconds — and focus your time on actual testing, not setup.

Written on November 3, 2025

Leave a Comment

🗨️ Reader Comments

Average Rating: Loading...