#17: Multi-Stage Deployments With Azure DevOps
Nitya Narasimhan, Ph.D
Posted on May 18, 2022
Welcome to Week 3, Day 3
of #30DaysOfSWA!!
Published From: Microsoft Tech Community
You can also find this post on the Apps On Azure Tech Community Blog along with other articles on the topic of Web Apps
What We'll Cover
- Build SWA with Azure Functions API + Playwright Tests
- Deploy SWA to staging environment
- Automate validation of staged app with Playwright
- Wait for manual approval
- Deploy to production
- Exercise. Explore the Demo Application Source and try it out!
About the Author
This post is authored with Anthony Chu, a Product Manager at Microsoft for Azure Container Apps, Azure Static Web Apps and Azure Functions. Follow him @nthonyChu or right here on dev.to
Azure Static Web Apps recently introduced the ability to automatically configure an Azure DevOps pipeline to build and deploy your app. It's a great way to get your app up and running quickly.
For production applications, it's common to first deploy an app to staging environment(s) prior to deploying to production. In this article, we'll walk through how to configure a robust Azure DevOps pipeline that will:
- Build an app, Azure Functions API, and Playwright tests
- Deploy the app to a staging environment
- Automatically validate the staging app with the Playwright tests
- Wait for a manual approval
- Deploy your app to production
Sample application
We'll use a .NET 6 full-stack application.
- Frontend: Blazor WebAssembly
- Backend: Azure Functions
- Tests: Playwright
To follow along, import the repository into your Azure DevOps project. You can use similar steps for a Node.js app.
:::info Source Code Available
Check out the code at: @anthonychu/swa-devops-pipeline-demo.
:::
Create a static web app and deployment pipeline
Recently, Azure Static Web Apps added the ability to generate an Azure DevOps pipeline to deploy your app.
You can create a static web app and the deployment pipeline in a single step.
In the Azure portal, search for and create a new static web app.
-
During the creation process, select "Azure DevOps" as the deployment source and select the DevOps repository and branch that contains the app.
-
In the build presets, select "Blazor". The pre-populates the app and API folder locations.
When you create the app, a new pipeline YAML file will be created in the repository. It'll automatically run. It takes a few minutes to build and deploy the app.
Open the pipeline YAML file in your browser or locally in an editor to see its contents. It contains a single AzureStaticWebApp
task that automatically builds and deploys the app.
Create Azure Pipelines environments
Azure Pipelines allows you to define environments. Environments are useful for adding manual approvals to your pipeline.
We'll create two environments — Staging and Production. They'll correspond to the two Azure Static Web Apps environments we'll deploy to.
To create a Pipelines environment, select "Environments" under Pipelines.
Create one named "Staging". Because you don't need a manual approval for this stage, you don't to configure anything else.
Next, create a new environment named "Production". Because we want to require a manual approval before deploying to production, you can configure the environment to require a manual approval.
In the environment's "Approvals and checks", select "Approvals".
Add yourself as an approver and create the approval policy.
The pipeline you'll update later will reference these environments.
Protect Azure Static Web Apps environments with a password
Azure Static Web Apps provides preview environments to let you test out your app before deploying to production.
Preview environments were intially available for pull requests in GitHub. Recently, Static Web Apps introduced the ability to arbitrary define named preview environments. For instance, we can create an environment named "Staging".
Preview environments are public by default. This is great for open source projects, but sometimes we want to protect them from public access.
You can add password protection to your app's preview environments.
In the Azure portal, open your static web app.
In "Configuration", select the "General settings" tab.
-
Select "Protect staging environments only" and enter a password.
You also have the option to protect all environments. But in this app, we want the production environment to be accessible to the public.
Configure the multi-stage pipeline
Now that we've configured the Azure Pipelines environments and password protection, we can configure the pipeline.
Open the pipeline YAML file in your browser or locally in an editor. Replace its contents the contents of this file.
We'll walk through the different parts of the pipeline. It has 3 main stages: build, deploy to staging, and deploy to production.
Stage 1: build the app
trigger:
- main
pool:
vmImage: ubuntu-latest
stages:
- stage: Build
jobs:
- job: build
displayName: Build app
steps:
- task: UseDotNet@2
displayName: Install .NET SDK
inputs:
packageType: 'sdk'
version: '6.0.x'
- script: |
dotnet publish -c Release -o "$(Build.ArtifactStagingDirectory)/frontend"
displayName: Build Blazor frontend
workingDirectory: $(System.DefaultWorkingDirectory)/Client
- script: |
dotnet publish -c Release -o "$(Build.ArtifactStagingDirectory)/api"
displayName: Build Azure Functions API
workingDirectory: $(System.DefaultWorkingDirectory)/Api
- script: |
dotnet build -c Release -o "$(Build.ArtifactStagingDirectory)/tests"
displayName: Build Playwright tests
workingDirectory: $(System.DefaultWorkingDirectory)/PlaywrightTests
- task: PublishBuildArtifacts@1
displayName: Publish artifacts
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'
publishLocation: 'Container'
The pipeline triggers on any changes to the main
branch. If your app uses a different branch, you can change it.
The pipeline then builds the app, Azure Functions API, and Playwright tests. It then outputs the artifacts. The same build artifacts are deployed to all environments. This ensures that the app you tested in other environments is the same one you're deploying to production.
Stage 2: deploy to staging and run Playwright tests
- stage: deploy_staging
displayName: Deploy to staging
jobs:
- deployment: deploy
displayName: Deploy and test
environment: Staging
variables:
# Change the variable group name to match the one in the generated pipeline
- group: Azure-Static-Web-Apps-calm-coast-0df39b910-variable-group
strategy:
runOnce:
deploy:
steps:
- download: none
- checkout: none
- task: DownloadBuildArtifacts@1
displayName: Download artifacts
inputs:
buildType: current
downloadType: single
artifactName: drop
downloadPath: $(System.ArtifactsDirectory)
- task: AzureStaticWebApp@0
displayName: Deploy to staging environment
inputs:
app_location: frontend/wwwroot
api_location: api
skip_app_build: true
skip_api_build: true
verbose: true
azure_static_web_apps_api_token: $(AZURE_STATIC_WEB_APPS_API_TOKEN_CALM_COAST_0DF39B910)
deployment_environment: staging
workingDirectory: $(System.ArtifactsDirectory)/drop
- task: UseDotNet@2
displayName: Install .NET SDK
inputs:
packageType: 'sdk'
version: '6.0.x'
- script: |
chmod -R a+x $(System.ArtifactsDirectory)/drop/tests
sudo --preserve-env=PLAYWRIGHT_BROWSERS_PATH pwsh $(System.ArtifactsDirectory)/drop/tests/playwright.ps1 install --with-deps chromium
dotnet test $(System.ArtifactsDirectory)/drop/tests/PlaywrightTests.dll --logger trx
displayName: Run Playwright tests on staging app
env:
PLAYWRIGHT_BROWSERS_PATH: $(Build.SourcesDirectory)/browsers
LOGIN_PASSWORD: $(LOGIN_PASSWORD)
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs:
testRunner: VSTest
testResultsFiles: '**/*.trx'
This is the most complex stage. Note that it references the "Staging" environment we created in the Azure Pipeline. It also references the variable group that was automatically generated when the static web app was first created. Change its name to match the variable group in the generated pipeline.
This stage starts by downloading the artifacts from the build stage.
Next, it uses the AzureStaticWebApp
task to deploy the app to the staging environment.
-
skip_app_build
andskip_api_build
are set totrue
because the app and API artifacts were already built and don't need to be built again. -
deployment_environment
is set tostaging
because we want to deploy to the staging environment.
After the app is deployed, the pipeline installs the .NET SDK and runs a script that installs the required dependencies for Playwright. Playwright is a testing framework that automates running tests in a browser.
After the dependencies are installed, the script runs the Playwright tests using the dotnet test
command.
One thing to note is that we need to configure a secret variable named LOGIN_PASSWORD
. This is the password that the user will enter when they log into the staging environment. The Playwright tests will use this password to log in when it runs tests on the staging environment.
The last step in the stage publishes the test results.
Stage 3: deploy to production
- stage: deploy_production
displayName: Deploy to production
jobs:
- deployment: deploy
displayName: Deploy
environment: Production
variables:
# Change the variable group name to match the one in the generated pipeline
- group: Azure-Static-Web-Apps-calm-coast-0df39b910-variable-group
strategy:
runOnce:
deploy:
steps:
- download: none
- checkout: none
- task: DownloadBuildArtifacts@1
displayName: Download artifacts
inputs:
buildType: current
downloadType: single
artifactName: drop
downloadPath: $(System.ArtifactsDirectory)
- task: AzureStaticWebApp@0
displayName: Deploy to production environment
inputs:
app_location: frontend/wwwroot
api_location: api
skip_app_build: true
skip_api_build: true
verbose: true
azure_static_web_apps_api_token: $(AZURE_STATIC_WEB_APPS_API_TOKEN_CALM_COAST_0DF39B910)
workingDirectory: $(System.ArtifactsDirectory)/drop
This stage references the "Production" environment in the Azure Pipeline. Because we configured this environment to require approval, this will trigger a manual approval step before this stage is run.
Like the previous stage, this stage downloads the artifacts and deploys them with the AzureStaticWebApp
task. This time, no deployment_environment
is set because we want to deploy to the production environment of the static web app.
Run the multi-stage pipeline
Now that the pipeline is set up, you can run it by saving the file. If you edited it locally, don't forget to push it to your Azure DevOps repo.
After the "Deploy to staging" stage is run, you should see that the Playwright tests have been run to validate the staging environment and the results are published.
The pipeline run is paused because the "Deploy to production" stage requires an approval.
When you approve the pipeline, the pipeline will run again.
After approval, the app is deployed to production.
A closer look
Before we end this article, we want to dive a bit deeper into the Static Web Apps environments and the Playwright tests.
Azure Static Web Apps environments
To see the environments you've created, click your static web app's "Environments" tab in the Azure portal. You should see the production and staging environments.
Playwright tests
The Playwright tests are located in the PlaywrightTests
project and they're written in C#.
Playwright can be used to automate testing of web apps using real browsers. The tests in the example use Chrome (Chromium), but Playwright also supports Firefox, WebKit, and Microsoft Edge.
This is an example of a Playwright test in C#. It navigates the browser to the app's homepage, clicks on the "Fetch data" link, and confirms that the data is fetched from the backend API and it is rendered successfully.
[Test]
public async Task ShouldLoadWeather()
{
await using var browser = await Playwright.Chromium.LaunchAsync();
var page = await browser.NewPageAsync();
await GoToHomePage(page);
await page.ClickAsync("a[href='fetchdata']");
var h1 = await page.QuerySelectorAsync("div#app main h1");
var h1Text = h1 == null ? "" : await h1.TextContentAsync();
Assert.AreEqual("Weather forecast", h1Text);
var rowsSelector = "div#app main table tbody tr";
// wait for table to have rows
await page.WaitForFunctionAsync($"document.querySelectorAll('{rowsSelector}').length");
var rows = await page.QuerySelectorAllAsync(rowsSelector);
Assert.AreEqual(5, rows.Count);
}
The tests use the AZURESTATICWEBAPP_STATIC_WEB_APP_URL
environment variable to determine the URL of the app to test. In an Azure Pipeline, this variable is set by the Azure Static Web Apps task after a successful deployment. Because the tests run in the pipeline after a deployment to the staging environment, the variable contains the URL of the staging Static Web Apps environment.
Another interesting aspect of the tests is the following code for navigating to the homepage:
private async Task GoToHomePage(IPage page)
{
await page.GotoAsync($"{siteBaseUrl}/");
var homePageLocatorTask = page.Locator("text=Hello, world!").WaitForAsync();
var passwordPageLocatorTask = page.Locator("text=Password protected").WaitForAsync();
var isPasswordPage = (await Task.WhenAny(homePageLocatorTask, passwordPageLocatorTask)) == passwordPageLocatorTask;
if (isPasswordPage)
{
System.Console.WriteLine("Found password page, logging in...");
var passwordInput = await page.QuerySelectorAsync("input[type=password]");
if (passwordInput == null)
{
throw new Exception("Could not find password input");
}
await passwordInput.TypeAsync(sitePassword);
await page.ClickAsync("button");
await page.Locator("text=Hello, world!").WaitForAsync();
}
}
This code automatically logs in to the staging environment if the browser session is unauthenticated. At run-time, the sitePassword
variable is set to the value of the LOGIN_PASSWORD
secret variable.
Summary
With the addition of features like named preview environments and automatic pipeline generation, it's now possible to configure complex, robust pipelines for building and deploying to Azure Static Web Apps from Azure DevOps.
Exercise
The demo uses a .NET 6 full-stack application with a Blazor WebAssembly frontend, an Azure Functions backend, and integrated Playwright tests.
- Explore the source code here.
- Import the repository into your Azure DevOps project and follow the tutorial.
Resources
Posted on May 18, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.