How can I clone a Test Plan via the Azure DevOps Services .NET SDK?

135 views Asked by At

The issue is that one specific Test Plan on an Azure DevOps Server (Version 2020.1.2) needs to be cloned regularly. Right now, this replication encompasses multiple steps (renaming the plan, changing the iteration paths of the plan and underlying suites, modifying the queries of the suites, ...). All these steps are currently done manually. As this practice leaves a lot of room for human error it has now become a requirement to do the replication programmatically.

Azure DevOps offers a REST API and a .NET SDK for this exact purpose. I am using the NuGet package Microsoft.TeamFoundationServer.Client (Version 16.205.1). I used the following code to create a clone:

public async Task<TestPlan> CloneTestPlanAsync(int id, CancellationToken cancellationToken = default)
{
    var vssConnection = new VssConnection("{PROJECT_COLLECTION_URI}", {VssCredentials});
    TestPlanHttpClient client = await vssConnection.GetClientAsync<TestPlanHttpClient>(cancellationToken);
    TestPlan sourceTestPlan = await client.GetTestPlanByIdAsync(
        project: _projectName,
        planId: id,
        cancellationToken: cancellationToken);

    var cloneRequestBody = new CloneTestPlanParams()
    {
        sourceTestPlan = new SourceTestPlanInfo()
        {
            id = id
        },
        destinationTestPlan = new DestinationTestPlanCloneParams()
        {
            Name = $"{sourceTestPlan.Name} Clone"
        },
        cloneOptions = new Microsoft.TeamFoundation.TestManagement.WebApi.CloneOptions()
        {
            CopyAllSuites = true,
            CopyAncestorHierarchy = true
        }
    };
    CloneTestPlanOperationInformation cloneInfo = await client.CloneTestPlanAsync(
        cloneRequestBody: cloneRequestBody,
        project: _projectName,
        cancellationToken: cancellationToken);

    return await client.GetTestPlanByIdAsync(
        project: _projectName,
        planId: cloneInfo.destinationTestPlan.Id,
        cancellationToken: cancellationToken);
}

Unfortunately, the result is not as expected. Only the test plan itself is cloned without the suites. Also, the method CloneTestPlanAsync from the imported library does not seem to work properly. It throws a Newtonsoft.Json.JsonSerializationException with the message Required property 'opId' not found in JSON. Path 'cloneOperationResponse'. However, the clone is still created! The exception seems to occur during creation of the return value (CloneTestPlanOperationInformation).

Trying the same operation with the REST API reinforces my assumption:

POST https://{instance}/{collection}/{project}/_apis/testplan/Plans/CloneOperation?api-version=6.0-preview.2

The response always contains the following object:

"cloneOperationResponse": {
    "state": "queued",
    "links": {
        "_self": {
            "href": "http://{instance}/{collection}/{project}/_apis/testplan/Plans/CloneOperation/0"
        }
    }
}

No matter what I send with the request body, the response points to the clone operation "0", which does not exist.

I am aware that the method I used is - as of the time of writing - marked as preview and therefore I should not expect everything to work perfectly. I still want to ask: How can I clone a test plan along with its test suites using the above mentioned NuGet package?

1

There are 1 answers

3
Alvin Zhao - MSFT On BEST ANSWER

Please add the suiteIds in the sourceTestPlan.

Kindly take reference to the following sample body and screenshots to help understand.

{
    "cloneOptions": {
        "cloneRequirements": false,
        "copyAllSuites": true,
        "copyAncestorHierarchy": true
    },
    "destinationTestPlan": {
        "areaPath": "TheProjectName",
        "iteration": "TheProjectName",
        "project": "TheProjectName",
        "name": "Test 1 Cloned by API"
    },
    "sourceTestPlan": {
        "id": 5,
        "suiteIds": []
    }
}