//Adroll script

In this article we tackle all of the common challenges encountered when trying to do continuous integration with Azure DevOps aka VSTS aka TFS. Some critical topics that are tackled here are:

  • How to work with Azure DevOps environment variables
  • How to create a build pipeline

By the way, if you aren’t aware, TFS, VSTS and Azure DevOps are all technically the same solution. Over several years, Microsoft did a lot of rebranding that created the confusion between all of these products. Hence it went from TFS to VSTS to Azure DevOps relatively quickly. Causing lots of confusion. The latest version is called Azure DevOps. I hope that it stays with this name for the foreseeable future 🙏

Automation pipeline in Azure DevOps with .NET Framework

In this section, we will cover automated testing pipelines in Azure Pipelines.

Working Azure DevOps pipeline with automated tests in .yml

pool:
name: Hosted VS2017
demands:
msbuild
visualstudio
vstest
steps:
script: set
displayName: print all variables
task: NuGetToolInstaller@0
displayName: 'Use NuGet 4.4.1'
inputs:
versionSpec: 4.4.1
task: NuGetCommand@2
displayName: 'NuGet restore'
inputs:
restoreSolution: '**\*.sln'
task: VSBuild@1
displayName: 'Build solution'
inputs:
solution: '**\*.sln'
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactstagingdirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"'
logProjectEvents: true
task: PowerShell@2
displayName: 'Set Sauce Environment Variables'
inputs:
targetType: filePath
filePath: ./setEnvironmentVariables.ps1
arguments: '$env:SAUCE_USER $env:SAUCE_KEY'
# Using powershell ##vso command to set an environment variable in the system
powershell: |
Write-Host "Sauce Username stored in ADO variables is=>$($env:SAUCE_USER)";
Write-Host "Sauce Access Key stored in ADO variables is=>$($env:SAUCE_KEY)";
Write-Host "Sauce Username stored in Env variables is=>$($env:SAUCE_USERNAME)";
Write-Host "Sauce Access Key stored in Env variables is=>$($env:SAUCE_ACCESS_KEY)";
Write-Host "Sauce Build Repository URI stored in Env variables is=>$($env:BUILD_REPOSITORY_URI)";
#checking to make sure that env variables were set between yml tasks
powershell: |
Write-Host "Sauce Username stored in Env Variables variables is=>$($env:SAUCE_USERNAME)";
Write-Host "Sauce Access Key stored in ADO variables is=>$($env:SAUCE_ACCESS_KEY)";
displayName: display env variables bw posh tasks
task: VSTest@2
displayName: 'Run Best Practices Framework'
inputs:
searchFolder: '$(System.DefaultWorkingDirectory)'
testSelector: 'testAssemblies'
testAssemblyVer2: |
**\*Selenium*.dll
!**\SauceExamples\packages\*.dll
!**\packages\*.dll
testFiltercriteria: 'TestCategory=BestPractices'
runInParallel: true
codeCoverageEnabled: true
testRunTitle: 'NUnit Automation Framework'
rerunFailedTests: true
rerunFailedThreshold: 10
rerunMaxAttempts: 2
failOnMinTestsNotRun: true
view raw azurePipelines.yml hosted with ❤ by GitHub
Example Azure DevOps pipeline using .NET Framework with automated UI tests

Azure Pipeline config with Java, Selenium, Sauce Labs

# Maven
# Build your Java project and run tests with Apache Maven.
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/java
trigger:
main
pr:
main
pool:
vmImage: 'ubuntu-latest'
#Set the environment variables for the pipeline.
# We create a variable sauce_user and assign it a value of $(SAUCE_USERNAME), which comes from the ADO
variables:
name: sauce_user
value: $(SAUCE_USERNAME)
name: sauce_key
value: $(SAUCE_ACCESS_KEY)
steps:
bash: echo $SAUCE_USER
bash: echo $SAUCE_KEY
#This will build and run the tests in the Maven project
task: Maven@3
inputs:
mavenPomFile: 'pom.xml'
mavenOptions: '-Xmx3072m'
javaHomeOption: 'JDKVersion'
jdkVersionOption: '1.8'
jdkArchitectureOption: 'x64'
publishJUnitResults: true
testResultsFiles: '**/surefire-reports/TEST-*.xml'
goals: 'package'

Staged test execution in Azure DevOps

What if you want to run different suites in your Azure pipeline so that you have a gated release. For example, you should be trying to run unit tests first, followed by integration tests, followed by acceptance tests. In that case, you need the ability to filter.

By default, Azure DevOps will attempt to run all of your tests in your solution. Regardless of whether you are using NUnit or MsTest or something weird like XUnit (although I never tried with this and wouldn’t)

When you add a VS Test task to your pipeline, this default configuration will run all your tests

YAML file to run tests

pool:
name: Hosted VS2017
demands:
msbuild
visualstudio
vstest
steps:
script: set
displayName: print all variables
task: NuGetToolInstaller@0
displayName: 'Use NuGet 4.4.1'
inputs:
versionSpec: 4.4.1
task: NuGetCommand@2
displayName: 'NuGet restore'
inputs:
restoreSolution: '**\*.sln'
task: VSBuild@1
displayName: 'Build solution'
inputs:
solution: '**\*.sln'
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactstagingdirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"'
logProjectEvents: true
task: PowerShell@2
displayName: 'Set Sauce Environment Variables'
inputs:
targetType: filePath
filePath: ./setEnvironmentVariables.ps1
arguments: '$env:SAUCE_USER $env:SAUCE_KEY'
# Using powershell ##vso command to set an environment variable in the system
powershell: |
Write-Host "Sauce Username stored in ADO variables is=>$($env:SAUCE_USER)";
Write-Host "Sauce Access Key stored in ADO variables is=>$($env:SAUCE_KEY)";
Write-Host "Sauce Username stored in Env variables is=>$($env:SAUCE_USERNAME)";
Write-Host "Sauce Access Key stored in Env variables is=>$($env:SAUCE_ACCESS_KEY)";
Write-Host "Sauce Build Repository URI stored in Env variables is=>$($env:BUILD_REPOSITORY_URI)";
#checking to make sure that env variables were set between yml tasks
powershell: |
Write-Host "Sauce Username stored in Env Variables variables is=>$($env:SAUCE_USERNAME)";
Write-Host "Sauce Access Key stored in ADO variables is=>$($env:SAUCE_ACCESS_KEY)";
displayName: display env variables bw posh tasks
task: VSTest@2
displayName: 'Run Best Practices Framework'
inputs:
searchFolder: '$(System.DefaultWorkingDirectory)'
testSelector: 'testAssemblies'
testAssemblyVer2: |
**\*Selenium*.dll
!**\SauceExamples\packages\*.dll
!**\packages\*.dll
testFiltercriteria: 'TestCategory=BestPractices'
runInParallel: true
codeCoverageEnabled: true
testRunTitle: 'NUnit Automation Framework'
rerunFailedTests: true
rerunFailedThreshold: 10
rerunMaxAttempts: 2
failOnMinTestsNotRun: true
view raw azurePipelines.yml hosted with ❤ by GitHub

Important points about YAML file

I couldn’t get searchFolder to filter out directories

However, using testAssemblyVer2 worked really well

Filtering tests using Azure DevOps

In order to be able to have a staged test execution in our continuous integration pipeline, we need to be able to filter our tests. I have 100s of tests in this repo but I would only like to run the tests that have [Category(“BestPractices”)] from Nunit.

Furthermore, I would like to exclude all of the performance tests, even though they are also categorized as “BestPractices”. As a result, the configuration of our Azure DevOps task looks like this.

test filtering in azure devops

And here is what the same exact thing looks like in a .yml

steps:
- task: VSTest@2
  displayName: 'Run Best Practices Tests without performance'
  inputs:
    testFiltercriteria: 'TestCategory=BestPractices&TestCategory!=Performance'
    runInParallel: true
    testRunTitle: 'Best Practices Tests'

More on the filtering capabilities in Azure DevOps aka VSTS aka TFS

Sauce Labs with Azure DevOps

Sauce Labs is an extremely popular service for performing 360 degree testing in the cloud. Let’s talk about how to set it up to work with Azure DevOps.

Using .NET, Sauce Labs, and Azure DevOps

1. Setup your username and access key in ADO

If this is your first time working with Azure DevOps, be sure to read how to Create your first pipeline from MS

Furthermore, you should also understand how to use environment variables in Azure DevOps.

Assuming that you:

  • Have an account
  • Created a new Pipeline
  • Configured it to read your test code from your repository

Now we need to add some environment variables to Azure DevOps.

Open your Pipeline and click Variables.

Adding Variables to your pipeline
Paste the values of your sauce username and access key

My recommendation is that you name the variables to something similar that I have above. Do not name your ADO variables the same as your Environment variables as that will cause you issues when you are trying to read them. So don’t name your ADO variable SAUCE_USER_NAME for example

2. The source code should reference environment variables like this

// testFile.cs
var sauceUserName = Environment.GetEnvironmentVariable("SAUCE_USERNAME");
var sauceAccessKey = Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY");

Make sure that you do NOT set the EnvironmentVariableTarget.User as your 2nd parameter as Azure DevOps will not be able to read that variable.

3. Read values from Azure variables in your .yml

I created these two variables in ADO

sauceAccessKey
sauceUsername

Use the ADO variable values and pass them to your environment variable keys like this

- task: DotNetCoreCLI@2
  displayName: 'Run tests'
  env:
    SAUCE_USERNAME: $(sauceUsername) #this will store the value from 'sauceUsername' into SAUCE_USERNAME
    SAUCE_ACCESS_KEY: $(sauceKey)

Here’s an example of displaying an environment variable

- bash: echo $(SAUCE_USERNAME) # will output our username stored in SAUCE_USERNAME env variable

Limitations

It’s not possible to view the videos and images of your Sauce Labs tests in Azure DevOps. Instead, we must navigate to saucelabs.com to see that information.

Azure DevOps will display that pass/fail information however.

Using NodeJS, SauceLabs, and Azure DevOps

  1. Go to Pipelines and create a new pipeline
  2. Link the new pipeline to your repository. You will likely need to provide permissions to Azure Pipelines in your repository management system.
  3. Create a YML file that looks like the code below
# Node.js
# Build a general Node.js project with npm.
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript

trigger:
- main

pool:
  vmImage: ubuntu-latest

# multiple pipelines can re-use variables that are stored in a variable group
variables:
- group: sauce-labs-variables

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '14.x'
  displayName: 'Install Node.js'
  
# Below we navigate to the working directory, install node packages, run tests on Sauce.
# Make sure to set SAUCE_USERNAME and SAUCE_ACCESS_KEY variables
- script: |
    cd ./webdriverio/webdriver/examples/w3c/
    npm install
    npm run test.saucelabs.us -- --logLevel "debug" #it's really wise to enable a high level of logging for CI
  env: 
    SAUCE_USERNAME: $(sauceUsername) #this will read the value from 'sauceUsername' in ADO and will store it into SAUCE_USERNAME env variable
    SAUCE_ACCESS_KEY: $(sauceAccessKey)
  displayName: 'install and run WebdriverIO tests in Sauce Labs'

Ensure that you have your environment variables stored in Azure DevOps.

Learn more by following Azure DevOps docs on JavaScript and Node.js.


UI Automation CI pipeline using YAML with Sauce Labs

pool:
name: Hosted VS2017
demands:
msbuild
visualstudio
vstest
steps:
script: set
displayName: print all variables
task: NuGetToolInstaller@0
displayName: 'Use NuGet 4.4.1'
inputs:
versionSpec: 4.4.1
task: NuGetCommand@2
displayName: 'NuGet restore'
inputs:
restoreSolution: '**\*.sln'
task: VSBuild@1
displayName: 'Build solution'
inputs:
solution: '**\*.sln'
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactstagingdirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"'
logProjectEvents: true
task: PowerShell@2
displayName: 'Set Sauce Environment Variables'
inputs:
targetType: filePath
filePath: ./setEnvironmentVariables.ps1
arguments: '$env:SAUCE_USER $env:SAUCE_KEY'
# Using powershell ##vso command to set an environment variable in the system
powershell: |
Write-Host "Sauce Username stored in ADO variables is=>$($env:SAUCE_USER)";
Write-Host "Sauce Access Key stored in ADO variables is=>$($env:SAUCE_KEY)";
Write-Host "Sauce Username stored in Env variables is=>$($env:SAUCE_USERNAME)";
Write-Host "Sauce Access Key stored in Env variables is=>$($env:SAUCE_ACCESS_KEY)";
Write-Host "Sauce Build Repository URI stored in Env variables is=>$($env:BUILD_REPOSITORY_URI)";
#checking to make sure that env variables were set between yml tasks
powershell: |
Write-Host "Sauce Username stored in Env Variables variables is=>$($env:SAUCE_USERNAME)";
Write-Host "Sauce Access Key stored in ADO variables is=>$($env:SAUCE_ACCESS_KEY)";
displayName: display env variables bw posh tasks
task: VSTest@2
displayName: 'Run Best Practices Framework'
inputs:
searchFolder: '$(System.DefaultWorkingDirectory)'
testSelector: 'testAssemblies'
testAssemblyVer2: |
**\*Selenium*.dll
!**\SauceExamples\packages\*.dll
!**\packages\*.dll
testFiltercriteria: 'TestCategory=BestPractices'
runInParallel: true
codeCoverageEnabled: true
testRunTitle: 'NUnit Automation Framework'
rerunFailedTests: true
rerunFailedThreshold: 10
rerunMaxAttempts: 2
failOnMinTestsNotRun: true
view raw azurePipelines.yml hosted with ❤ by GitHub
Working example of an Azure DevOps pipeline that runs automated UI tests in Sauce Labs.

How to set Sauce Labs environment variables in Azure DevOps with .NET Core?

These are instructions if you are working with .NET Core

1. Create environment variables in Azure DevOps and assign them secret values

Adding Variables to your pipeline

Here’s an example

My recommendation is that you name the variables to something similar that I have above. Do not name your ADO variables the same as your Environment variables as that will cause you issues when you are trying to read them. So don’t name your ADO variable SAUCE_USER_NAME for example

2. Pass values from environment variables to variables in the source code

For this to work, you need to be reading values into environment variables exactly like this:

// testFile.cs
var sauceUserName = Environment.GetEnvironmentVariable("SAUCE_USERNAME");
var sauceAccessKey = Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY");

Make sure that you do NOT set the EnvironmentVariableTarget.User as your 2nd parameter as Azure DevOps will not be able to read that variable.

3. Configure .yml to read values from Azure pipelines and set them into system environment variables

Your YAML needs this piece of code to be able to set environment variables of the Azure DevOps box

#need to use the vso tasks so that the env variables persist trhough tasks in ADO
- powershell: |
   Write-Host "Our Sauce Username in ADO is=> $($env:SAUCE_USER)";
   Write-Host "Our Sauce Access Key in ADO is=> $($env:SAUCE_KEY)";
   Write-Host ("##vso[task.setvariable variable=SAUCE_USERNAME]$($env:SAUCE_USER)")
   Write-Host ("##vso[task.setvariable variable=SAUCE_ACCESS_KEY]$($env:SAUCE_KEY)")

Learn more about VSTS aka Azure DevOps logging commands. Check out this repo with a fully working example.

Extra resources

An excellent blog about environment variables in ADO

Set Sauce Labs environment variables in Azure DevOps with .NET Framework?

Using task.setvariable

One way to set environment variables in Azure DevOps is to ##vso[task.setvariable

In the .yml, specify code that looks like this

- powershell: |
   Write-Host "Our Sauce Username in ADO is=> $($env:SAUCE_USER)";
   Write-Host "Our Sauce Access Key in ADO is=> $($env:SAUCE_KEY)";
   Write-Host ("##vso[task.setvariable variable=SAUCE_USERNAME]$($env:SAUCE_USER)")
   Write-Host ("##vso[task.setvariable variable=SAUCE_ACCESS_KEY]$($env:SAUCE_KEY)")

The last 2 lines are the important ones. They state that we should set environment variable SAUCE_USERNAME with the value of $($env:SAUCE_USER). SAUCE_USER is a variable that was defined in Azure DevOps.

Using a Powershell script

This is a more complicated approach than above.

  1. First you need to create some environment variables in your Azure DevOps UI that you want to use for values. This is an example of a variable that I would like to set on the test agent for my automation scripts. For example sauce.userName. I will use the value of this variable(sauce.userName) and have a Powershell script set it in my System Environment Variables of the test agent when my automation is running. That way, the value of this variable isn’t exposed to the public.

2. Next, you will want to create a Powershell script that you attach to your solution. Here’s my solution layout.

.ps1 solution structure
Powershell script placed at the root of the solution

Don’t forget to make sure to copy your Powershell script to output directory on build in your .csproj

Here is what you want in your Powershell script.

Param(
[string]$sauceUserName,
[string]$sauceAccessKey,
[string]$rdcVodQaNativeAppApiKey,
[string]$rdcSauceDemoIosRdcApiKey
)
Write-Output "sauce.userName value from ADO was passed as a Argument in the ADO Task called $env:SAUCE_USERNAME " +
"to sauceUserName variable in the Posh. This is the value found=>$sauceUserName"
Write-Output "sauce.accessKey that was passed in from Azure DevOps=>$sauceAccessKey"
Write-Output "sauce.rdc.VodQaNativeAppApiKey stored in Azure DevOps=>$rdcVodQaNativeAppApiKey"
Write-Output "sauce.rdc.SauceDemoIosRdcApiKey stored in Azure DevOps=>$rdcSauceDemoIosRdcApiKey"

[Environment]::SetEnvironmentVariable("SAUCE_USERNAME", "$sauceUserName", "User")
[Environment]::SetEnvironmentVariable("SAUCE_ACCESS_KEY", "$sauceAccessKey", "User")
[Environment]::SetEnvironmentVariable("VODQC_RDC_API_KEY", "$rdcVodQaNativeAppApiKey", "User")
[Environment]::SetEnvironmentVariable("SAUCE_DEMO_IOS_RDC_API_KEY", "$rdcSauceDemoIosRdcApiKey", "User")

3. Create a Powershell task in your Pipeline

The Powershell script will read the values that are passed in from the Azure DevOps and then run those values through SetEnvironmentVariable() in the .ps1

When this task runs, I’m having it output the values to the Azure DevOps logs so that I can make sure the right values are being read. Here’s an example

ADO variables being read in the Posh script

How to read environment variables in Azure DevOps?

If you are reading an environment variable in your code like this

var sauceAccessKey = Environment.GetEnvironmentVariable("SAUCE_ACCESS_KEY");

Make sure that you do NOT set the EnvironmentVariableTarget.User as your 2nd parameter as Azure DevOps will not be able to read that variable.

How to create environment variables to share across pipelines

If you would like to reuse environment variables across multiple pipelines, then you need to use variable groups. It’s important to know that if use “group”, then you now need to list your other variables in a key/value pairs.

variables:
- group: sauce-labs-variables
- name: solution
  value: '**/*.sln'
- name: buildPlatform
  value: 'Any CPU'
- name: buildConfiguration
  value: 'Release'

How to package Nuget using Azure DevOps?

Phenomenal resource that will walk you through the process.

This is a resource that helps you to understand how to work with Nuget in .NET Standard.

Set up a Service connection to Nuget in your project settings

You do need to have an MSDN account and then you can create a Nuget.org account.

How to publish the Nuget package

  1. Download Nuget.exe
  2. Add a nuget.config file
  3. Run command nuget restore
  4. Run command .\nuget.exe push -Source "Simple.Sauce" -ApiKey az "C:\Source\SauceLabs\simple_sauce\dotnet\Simple.Sauce\bin\Debug\Simple.Sauce.0.1.1-debug.nupkg"

Why DevOps?

value of dev ops

value of dev-ops

Adding continuous integration

  1. Select Builds > Edit > Triggers. Under Continuous integration, select on the name of your repository.
    1. Toggle on the checkbox labeled Enable continuous integration.
    2. You can select CI for both merges and Pull Requests – https://prnt.sc/kb0g6n

How to create scheduled builds

  1. Picking up from the section above, click the Add button under Scheduled section
  2. Update the time and the day of when you want to run your builds
  3. Save & queue

How to set up your test .dll paths

Docs from MS on file matching patterns

The hardest time I had was how to configure my paths for my automation .DLL files. It’s not intuitive to know what is the working directory of your code base.

Here are some examples of what’s worked for me:

Executable Paths

"C:\Source\Github\dot-net-sauce\SauceExamples\Web.Tests\bin\Debug\Web.Tests.dll"'**\$(BuildConfiguration)\**\Web.Tests.dll'
"C:\Source\Github\dot-net-sauce\SauceExamples\SeleniumNunit\bin\Debug\SeleniumNunit.dll"'**\$(BuildConfiguration)\**\SeleniumNunit.dll'
Column A shows the local path. Column B shows how the path looks in Azure DevOps.

How to pass parameters to test code from a build or release pipeline?

A: Use a runsettings file to pass values as parameters to your test code. For example, in a release that contains several stages, you can pass the appropriate app URL to each the test tasks in each one. The runsettings file and matching parameters must be specified in the Visual Studio Test task.

Source

How to add a status badge to your Github repo

The instructions on this are really good from Microsoft and you can follow them here in the Get the status badge section

All about YAML

0
Share
Tweet
Share