We are using the Azure DevOps Test Plan module for our manual testing. We have a Test Plan, under which, we have Test Suite, under which we have Test Cases that are assigned to different Testers. Testers use ADO to mark a Test Case outcome as passed or fail.
We want to download the Test Case outcome/results, but I don't see that option. We have a Test Run tab under Test Plan which shows all Run outcomes but doesn't give the option to download.
I am afraid that there is no out-of-box method can directly export the test results of test runs to excel.
To meet your requirement, you can use Rest API to list all required test runs and test results. Then you can export them to Excel.
You can use the following two Rest APIs:
Get Test Runs: Runs - Query
GET https://dev.azure.com/{organization}/{project}/_apis/test/runs?minLastUpdatedDate={minLastUpdatedDate}&maxLastUpdatedDate={maxLastUpdatedDate}&state={state}&planIds={planIds}&isAutomated={isAutomated}&publishContext={publishContext}&buildIds={buildIds}&buildDefIds={buildDefIds}&branchName={branchName}&releaseIds={releaseIds}&releaseDefIds={releaseDefIds}&releaseEnvIds={releaseEnvIds}&releaseEnvDefIds={releaseEnvDefIds}&runTitle={runTitle}&$top={$top}&continuationToken={continuationToken}&api-version=7.0
Get Test Results: Results - List
GET https://dev.azure.com/{organization}/{project}/_apis/test/Runs/{runId}/results?detailsToInclude={detailsToInclude}&$skip={$skip}&$top={$top}&outcomes={outcomes}&api-version=7.0
Here is PowerShell sample:
$token = "PAT"
$url=" https://dev.azure.com/orgname/projectname/_apis/test/runs?api-version=7.0"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($token)"))
$response = Invoke-RestMethod -Uri $url -Headers #{Authorization = "Basic $token"} -Method Get -ContentType application/json
echo $response
ForEach( $testrunid in $response.value.id )
{
echo $testrunid
$url1 ="https://dev.azure.com/orgname/projectname/_apis/test/Runs/$($testrunid)/results?api-version=7.0"
$response1 = Invoke-RestMethod -Uri $url1 -Headers #{Authorization = "Basic $token"} -Method Get -ContentType application/json
ForEach( $testresult in $response1.value)
{
$outcome = $testresult.outcome
$startedDate = $testresult.startedDate
$testCase =$testresult.testCase.name
$completedDate = $testresult.completedDate
$testCaseTitle = $testresult.testCaseTitle
echo $outcome
echo $startedDate
$Output = New-Object -TypeName PSObject -Property #{
outcome = $outcome
runid =$testrunid
startedDate = $startedDate
completedDate= $completedDate
testCase = $testCase
testCaseTitle = $testCaseTitle
} | Select-Object runid, outcome,startedDate,testCase,testCaseTitle,completedDate
$Output | Export-Csv C:\testresult.csv -Append
}
}
You can customize the output excel column according to your requirements.
Result:
You could Export the test plan properties, test suite properties along with details of the test cases and test points as either an email or print to pdf by using the Export function of the test suite: doc
Make sure you have test access and permission in Azure DevOps.
Below are the steps to download the Test Run results from ADO –
Navigate to Test Plan -> Runs
Click on filters, remove all filters and run the query
Click on one result and press Ctrl + A and then Ctrl + C
Paste the content in Notepad++, then copy the content from Notepad++ and paste it Excel.
This will give you list of all Test Runs with results. You can get the latest outcome of test run, by sorting data using Run ID in descending order and then using unique rows only.
Related
I used to work with Azure DevOps on CI/CD with its release pipelines. We are a multidisciplinary team, however we do implement features on same projects simultaneously. On good old release pipelines the steps run on a FIFO style. For instance, if I trigger release X and a colleague triggers release Y right after, he would never run Production step at the same time that me, release Y would be blocked on Production step until X is finished on Production step.
It turns out that we've moved to Azure pipelines as code and we faced this issue. Releases does not work on FIFO style anymore on its steps, which would cause a "race condition" with releases.
Is there a way to emulate old Release pipeline behaviour? Thanks on advance.
Current pipeline and expected behaviour
Each release will start from stage 1. Thus we can add a PowerShell task as the first task for stage 1 to check if there are previous in-progress deployments.
In this PowerShell task, we can call this Rest API to check release stage status.
Power shell script:
# Base64-encodes the Personal Access Token (PAT) appropriately
$token = "$(pat)"
$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($connectionToken)"))
$success = $false
$count = 0
do{
try{
$stageurl2 = "https://vsrm.dev.azure.com/{org name}/{project name}/_apis/release/deployments?definitionId={release definition ID}&deploymentStatus=inProgress&api-version=6.0"
$stageinfo2 = Invoke-RestMethod -Method Get -ContentType application/json -Uri $stageurl2 -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
$inprogressdeployments = $stageinfo2.value | where {($_.deploymentStatus -eq "inProgress") -and ($_.release.name -ne $ENV:RELEASE_RELEASENAME) -and ($_.releaseEnvironment.name -ne 'stop services')} | Sort-Object -Property completedOn -Descending
#write-output $inprogressdeployments
$stageurl3 = "https://vsrm.dev.azure.com/{org name}/{project name}/_apis/release/deployments?definitionId={release definition ID}&operationStatus=QueuedForAgent&api-version=6.0"
$stageinfo3 = Invoke-RestMethod -Method Get -ContentType application/json -Uri $stageurl3 -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
$queueddeployments = $stageinfo3.value
#write-output $queueddeployments
if($inprogressdeployments) {
Write-output "Deployment In Progress, Waiting for it to finish!"
Write-output "Next attempt in 30 seconds"
Start-sleep -Seconds 30
} else {
Write-Host "No Current Deployment in Progress"
if($queueddeployments) {
write-output "Current Queued deployments"
Write-output "if 2 - Next attempt in 30 seconds"
Start-sleep -Seconds 30
}
else{
write-output "No Queued deployments, starting release"
$success = $true
}
}
}
catch{
Write-output "catch - Next attempt in 30 seconds"
write-output "1"
Start-sleep -Seconds 30
# Put the start-sleep in the catch statemtnt so we
# don't sleep if the condition is true and waste time
}
$count++
}until($count -eq 2000 -or $success)
if(-not($success)){exit}
Result:
Stage1 will continue to check until all previous versions are complete
In addition, you need purchase parallel jobs or create multiple self-hosted agent
Another solution is set exclusive lock, you could refer to this Exclusive deployment lock policy and Evaluate artifact for more details.
I am looking for a simple step in my yaml pipeline to add 1 to the run-version number every time the task is run successfully. I then want to parse this to the outside, something like version: #{version}#
I will then use replace token to pick this up and update the variable in the pipeline.
You can use epoch time, A unique timestamp to generate a unique version everytime when you run yaml
You can check counter function (https://learn.microsoft.com/en-us/azure/devops/pipelines/process/expressions?view=azure-devops#counter).
For instance I can prepend version of my product (and increment it every single run) to the name of the run by adding this:
name: $(Major).$(Minor).$(Fix)
variables:
Major: 2
Minor: 0
Fix: $[counter(variables['Major'].variables['Minor'], 0)]
steps:
- step: ...
The result of this increases by +1 every time you run your build. You can print it out in your pipeline or use it as an argument for a task.
You can consider storing the unique run version value in Variable Group. So that we can make this unique value available across multiple pipelines/jobs/steps.
1.Create one Variable Group and define the version variable.
2.Manage its security and add the ProjectName Build Service as admin role, save the changes.
3.Then we can add one PS task to update the version value.
- task: PowerShell#2
inputs:
targetType: 'inline'
script: |
$url = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/distributedtask/variablegroups/{YourVariableGroupID}?api-version=6.0-preview.2"
$linkedVariableGroup = Invoke-RestMethod -Uri $url -Headers #{Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"}
$versionNumber = [int]$linkedVariableGroup.variables.version.value
$versionNumber+=1
$linkedVariableGroup.variables.version.value=$versionNumber
$json = $linkedVariableGroup | ConvertTo-Json -Depth 100
Invoke-RestMethod -Method PUT -Uri $url -ContentType "application/json" -Headers #{Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"} -Body $json
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
Add this step at the end of your pipeline, so that this task adds 1 to the version variable every time the pipeline runs successfully. You should reply the {YourVariableGroupID} with your own VariableGroupID in above script.
If I run this powershell code locally:
$url = "refs/pull/5625/merge"
$SourceBranchFromBuild = $url.split('/')[-1]
$featureReleaseUrl = "http://$sourceBranchFromBuild.azurewebsites.net"
Write-Output $featureReleaseUrl
The output is:
http://merge.azurewebsites.net
When I run this code on a Azure Powershell:
$url = "refs/pull/5625/merge"
$SourceBranchFromBuild = $url.split('/')[-1]
Write-Host "##vso[task.setvariable variable=prSourceBranchName;]"$SourceBranchFromBuild
And then create the URL in another Azure Powershell script:
$featureReleaseUrl = "http://$env:prSourceBranchName.azurewebsites.net"
Write-Output $featureReleaseUrl
The ouput is
http:// merge.azurewebsites.net
What's causing this leading space character in the $env:prSourceBranchName?
Azure isn't adding anything - your write-host is!
Your code is doing this:
PS> $x = "xxx"
PS> write-host "aaa"$x
aaa xxx
but presumably you want
PS> $x = "xxx"
PS> write-host "aaa$x"
aaaxxx
Note where the second quote is the write-host in both examples. In the first it's before the $x variable name. In the second it's after.
In your question it's calling this (with the quote before the variable name):
Write-Host "##vso[task.setvariable variable=prSourceBranchName;]"$SourceBranchFromBuild
which will write a logging command to the log file, and Azure DevOps will process that and update the environment variable.
You're probably expecting it to write this to the log file:
##vso[task.setvariable variable=prSourceBranchName;]merge
but it's actually writing this:
##vso[task.setvariable variable=prSourceBranchName;] merge
Try switching your code to this (i.e. second quote after the variable name):
Write-Host "##vso[task.setvariable variable=prSourceBranchName;]$SourceBranchFromBuild"
and it should omit the space in front of the branch name in your url.
#PeterBoomsma Try putting $SourceBranchFromBuild inside the double quotes like this:
Write-Host "##vso[task.setvariable variable=prSourceBranchName;]$SourceBranchFromBuild"
I have the code below runs OK in Azure Functions on it's own and displays output. However the code is static, I need the URL (top line) to be able to be passed through as a parameter with an HTTP on-demand trigger.
The article here talks about Binding at runtime via imperative bindings, however it’s not 100% clear as to how to pass an HTTP based parameter e.g. https://myfunction.azurewebsites.net/api/AustereoJustPlaying?url=legacy.scahw.com.au/2classicrock_128.xspf and then work with the parameter in the PowerShell code.
# Get the initial metadata for the stream
$url = 'http://legacy.scahw.com.au/2classicrock_128.xspf'
$iwr = Invoke-RestMethod -Uri $url
# Build up the .Net web client
$HttpCompletionOption = 'ResponseContentRead'
$webClient = New-Object System.Net.Http.HttpClient
$webclient.DefaultRequestHeaders.Add('Icy-MetaData', '1')
# Get the Stream URL
$null = $iwr.InnerXml -match '<location>(?<location>.*)<\/location>'
$location = $matches.location
# Fire up the stream
$response = $webClient.GetAsync($location,$HttpCompletionOption)
$null = $webclient.DefaultRequestHeaders.Remove('Icy-MetaData')
# Pause until the stream title actually fires up
Start-Sleep -Seconds 2
# Grab the song
$iwr = Invoke-RestMethod -Uri $url
$null = $iwr.InnerXml -match '<title>(?<song>.*)<\/title>'
# Kill the stream
$webclient.Dispose()
# Output the song
$matches.song
Side note, if you get the error below on your computer…..
New-Object : Cannot find type [System.Net.Http.HttpClient]: verify that the assembly containing this type is loaded
Run this block of code, seems that you need to ‘warm’ up the system in order to find the ‘type’, running Find-Type httpClient a few times seems to wake up the system to realise Yes, it does have this type installed.
function Find-Type ([regex]$pattern)
{
[System.AppDomain]::CurrentDomain.GetAssemblies().GetTypes() |
Select-Object -ExpandProperty FullName | Select-String $pattern
}
Do {
cls
$TypeSearch = Find-Type httpClient
} until ($TypeSearch -match 'System.Net.Http.HttpClient')
The default PowerShell HTTP Trigger template shows an example of that.
Query string parameters will be made available to your script as variables in the format req_query_<parametername>, so in your example above the URL parameter would be accessible using: $req_query_url
The following function is a simple example of just returning the parameter
Out-File -Encoding Ascii -FilePath $res -inputObject "URL parameter $req_query_url"
I am looking for a bit of help, hope nobody will bash me for being an ignorant.
Not that long ago I became something of an AD admin, organisation is big so the tasks vary. I can easily complete what I require via Powershell or snap-ins in most cases.
However I have a task on my hands that exceed my "creativity". I have a list of over 10 000 users in .csv which I need to look up in on-premises AD if they exist. My two problems are:
-I am very new to scripting and getting increasingly frustrated that I can't comprehend it and make my scripts work as I need them to
-Deadline for this task and other responsibilities give me little time to read more on scripting basics and learn. As such I am in most cases forced to look for script snippets on the web and modify them a bit to meet my needs. This worked up until now as the script I have on my hands is a bit too complex for me.
Biggest problem I was facing so far is creating a forest-wide search. My organization have a single root domain and 4 child domains. When running a simple foreach loop a like the one below:
ForEach ($User in (Import-Csv c:\users\public\users.csv))
{ If (Get-ADUser $User.mail -server GLOBALCATALOGADDRESS:xxxx)
{ Write-Host "User found: $($User.mail)"
}
Else
{ Write-Host "User not found: $($User.mail)"
}
}
It searches only domain to which my computer is connected.
So I managed to find and modify a forest-wide search script and came up with following:
#Get Domain List
$objForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$DomainList = #($objForest.Domains | Select-Object Name)
$Domains = $DomainList | foreach {$_.Name}
$User = Import-CSV c:\users\public\users.csv
#Act on each domain
foreach($Domain in ($Domains))
{
Write-Host "Checking $Domain" -fore red
$ADsPath = [ADSI]"LDAP://$Domain"
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher($ADsPath)
#The filter
Foreach($mail in($User))
{
$objSearcher.Filter = "(&(objectCategory=user)(mail=$User.mail))"
$objSearcher.SearchScope = "Subtree"
$colResults = $objSearcher.FindAll()
foreach ($objResult in $colResults)
{
$objArray = $objResult.GetDirectoryEntry()
write-host $objArray.mail
}
}
}
The script seems to be good in its original form (found here: http://powershell.nicoh.me/powershell-1/active-directory/forest-wide-object-searches) and searches well with wildcard and single parameter as filter.
However I have no idea what am I missing to make it search for every email address I have in .csv and to make it return information whether or not user with such mail was found.
Script itself runs but given the time it takes and blank output it feels like it searches for only one user. I am 100% sure that at least one user from the list exists in on-prem AD.
Any suggestions are very welcome.
Thanks for your attention.
[EDIT]
Final script:
#Get Domain List and load user e-mails from file
$objForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$DomainList = #($objForest.Domains | Select-Object Name)
$Domains = $DomainList | foreach {$_.Name}
$Users = Import-CSV c:\users\public\users.csv
#Act on each domain
foreach($Domain in ($Domains))
{
Write-Host "Checking $Domain" -fore red
Foreach($mail in ($Users.mail))
{
Get-ADUser -filter {mail -eq $mail} -Server $domain -properties mail | select mail
}
}
Do yourself a favour and download AD Powershell module: http://blogs.msdn.com/b/rkramesh/archive/2012/01/17/how-to-add-active-directory-module-in-powershell-in-windows-7.aspx
You will then be able to simplify your code and run things like this, making your task much clearer:
...
foreach($Domain in ($Domains))
{
Write-Host "Checking $Domain" -fore red
Foreach($mail in ($User.mail))
{
Get-ADUser -filter {mail -eq $mail} -Server $domain -Properties mail |
select-object -ExpandProperty mail
}
}
...
More on AD PS cmdlets: http://technet.microsoft.com/en-us/library/ee617195.aspx
Use -LDAPfilter & point the -Server to GC.
Get-ADUser -Server DC01.Contoso.com:3268
-Ldapfilter "(ObjectClass=user)(mailnickname=David)"
The above command will search the GC DC01.contoso.com for all the users that their Alias/mailnickname is David.
Is is enough to contact the Domain itself instead of a DC of the domain. Thus this shoud also work
get-aduser -Filter {mailnickname -eq "David") -Server contoso.com:3268