This lab contains instructions to create automated functional UI tests. The instructions are based on the following documentation:
- Complete lab: Pipeline as code with K8s and Terraform
-
Open the mywebapp.sln (TRAIN/azdotraining1/app) in Visual Studio 2022.
-
Add a new project to the solution with the following settings:
- Project Type: xUnit Test Project (C#)
- Name: FunctionalTests
- Framework .NET 6.0 (Long Term Support)
-
In the FunctionalTests project, remove the sample test (UnitTest1.cs) if it exists
-
Open the shortcut menu for the FunctionalTests project and choose Manage NuGet Packages. Add the following packages to your project:
- MSTest.TestFramework
- MSTest.TestAdapter
- Selenium.Support
- Selenium.WebDriver
- Selenium.WebDriver.ChromeDriver Version: (109.0.5414.7400)
- Selenium.WebDriver.Extensions
- System.Configuration.ConfigurationManager
-
Ensure the Selenium Chrome driver executable is copied to the output during publish. To do this: double click on the FunctionalTests and the FunctionalTests.csproj will be opened:
- Add the following PropertyGroup:
<PropertyGroup> <PublishChromeDriver>true</PublishChromeDriver> </PropertyGroup>
- Add the following PropertyGroup:
-
In the FunctionalTests project, create the functionalTests.runsettings file. And replace the content with the following content:
functionalTests.runsettings (expand to view code)
<?xml version="1.0" encoding="utf-8" ?> <RunSettings> <TestRunParameters> <Parameter name="siteUrl" value="http://localhost:39394" /> </TestRunParameters> </RunSettings>
-
In the functionalTests.runsettings file make sure that the port of the siteUrl is the same as the webapp is using. This port can be found in the launSettings.json (mywebapp\Properties\launchSettings.json) of the mywebapp project.
-
Configure Visual Studio to use the .runsettings file you just created. In Visual Studio go to Test-->Configure Run Settings-->Select Solution Wide runsettings File. In the explorer window select the functionalTests.runsettings file located in the folder FunctionalTests.
-
In the FunctionalTests project add functional test classes for all pages. Add the following classes to it:
HomePage.cs (expand to view code)
using FunctionalTests; using OpenQA.Selenium; public class HomePage : BasePage { public HomePage(IWebDriver driver, string baseUrl) : base(driver, baseUrl) { } public string Title { get; set; } public void GoToPage() { _driver.Navigate().GoToUrl($"{_baseUrl}"); } }
PrivacyPage.cs (expand to view code)
using FunctionalTests; using OpenQA.Selenium; public class PrivacyPage : BasePage { public PrivacyPage(IWebDriver driver, string baseUrl) : base(driver, baseUrl) { } public void GoToPage() { _driver.Navigate().GoToUrl($"{_baseUrl}/Privacy"); } }
BasePage.cs (expand to view code)
using OpenQA.Selenium; namespace FunctionalTests { public abstract class BasePage { protected readonly IWebDriver _driver; protected readonly string _baseUrl; protected BasePage(IWebDriver driver, string baseUrl) { _driver = driver; _baseUrl = baseUrl; } public HomePage GoToHomePage() { var home = _driver.FindElement(By.LinkText("Home")); home.Click(); return new HomePage(_driver, _baseUrl); } public PrivacyPage GoToPrivacyPage() { var about = _driver.FindElement(By.LinkText("Privacy")); about.Click(); return new PrivacyPage(_driver, _baseUrl); } } }
-
In the FunctionalTests project, create the following test class for the functional UI tests:
UITests.cs (expand to view code)
using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Support.Extensions; using System.Drawing; using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert; namespace aspnet_core_dotnet_core.FunctionalTests { [TestClass] public class UITests { private static TestContext _testContext; private IWebDriver _driver; private string _siteUrl; [ClassInitialize] public static void Initialize(TestContext testContext) { _testContext = testContext; } [TestInitialize()] public void MyTestInitialize() { if (_testContext.Properties["siteUrl"] != null) { _siteUrl = _testContext.Properties["siteUrl"].ToString(); } // Chrome var options = new ChromeOptions(); options.AddArguments("headless"); _driver = new ChromeDriver(Directory.GetCurrentDirectory(), options); // Driver settings _driver.Manage().Window.Size = new Size(1920, 1080); _driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(20); _driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20); } [TestMethod] [TestCategory("UI")] public void Test() { try { var homePage = new HomePage(_driver, _siteUrl); homePage.GoToPage(); SaveAsImage(_driver.TakeScreenshot(), "Home.png"); var privacyPage = new PrivacyPage(_driver, _siteUrl); privacyPage.GoToPrivacyPage(); SaveAsImage(_driver.TakeScreenshot(), "Privacy.png"); var containerDiv = _driver.FindElement(By.ClassName("pb-3")); var header = containerDiv.FindElement(By.TagName("h1")); Assert.AreEqual("Privacy Policy", header.Text); } catch (NoSuchElementException) { SaveAsImage(_driver.TakeScreenshot(), "Error.png"); throw; } } [TestCleanup()] public void MyTestCleanup() { _driver.Close(); _driver.Quit(); } private void SaveAsImage(Screenshot screenshot, string name) { var timestamp = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss.fff"); var fileName = $"{timestamp} {name}"; screenshot.SaveAsFile(fileName, ScreenshotImageFormat.Png); } } }
-
Start your website without debugging. This will ensure the website will be running in the local IIS Express instance, on the url specified in the .runsettings file.
-
Run the UI tests you just created in the Test Explorer and make sure that it succeeds.
-
When the tests are completed go in the File explorer to build folder of the FunctionalTests project (C://TRAIN/azdotraining1/app/FunctionalTests/bin/Debug/netcoreapp6.0). Here you will find some screenshots that were made during the test run, which can be used as Test evidence.
-
Don't commit and push all your changes, because this will automatically start the App pipeline which we first need to modify to include the automated testing.
-
Go to the Pipelines page on your Azure DevOps environment, select the app pipeline, and then select Edit.
-
Locate the Variables for this pipeline.
-
The automated Selenium tests will run against the public ip address of our deployed Pods. To assign the right ip address to the pipeline, we will use variables. The ip addressess can be found in the PowerShell window you used in lab 1. Add the following two variables:
- Name: testip Value: http://<public ip address of the test environment>
- Name: prodip Value: http://<public ip address of the production environment>
-
The next thing we need to do is adding build steps for the Selenium project. Make sure that you add the following code in the Build stage above the Build_containers job. Please be aware that the identation of the code is correct.
- job: Build_functional_tests pool: vmImage: 'windows-latest' steps: - task: DotNetCoreCLI@2 displayName: 'Restore' inputs: command: 'restore' projects: '**/FunctionalTests.csproj' feedsToUse: 'select' - task: DotNetCoreCLI@2 displayName: 'Publish' inputs: command: 'publish' publishWebProjects: false projects: '**/FunctionalTests.csproj' arguments: '--configuration Release -o $(build.artifactstagingdirectory)/SeleniumTests' zipAfterPublish: false modifyOutputPath: false - task: PublishPipelineArtifact@1 displayName: 'Publish Pipeline Artifact' inputs: targetPath: '$(Build.ArtifactStagingDirectory)' artifact: 'functionaltests' publishLocation: 'pipeline' -
The next step is to add the automated Selenium tests to the Test deployment pipeline by adding the following code in stage Release_Test below the block - deployment: Deploy_containers. Make sure that the identation is correct.
- deployment: Run_functional_tests dependsOn: "Deploy_containers" environment: test pool: vmImage: 'windows-latest' strategy: runOnce: deploy: steps: - task: VSTest@2 displayName: 'Run UI Tests' inputs: testSelector: 'testAssemblies' testAssemblyVer2: | **\*FunctionalTests.dll !**\*TestAdapter.dll !**\obj\** searchFolder: '$(Pipeline.Workspace)/functionaltests/SeleniumTests' overrideTestrunParameters: '-siteUrl "$(testip)"' -
The last step is to add the automated Selenium tests to the Production deployment pipeline by adding the following code in stage Release_Prod below the block - deployment: Deploy_containers. Make sure that the identation is correct.
- deployment: Run_functional_tests dependsOn: "Deploy_containers" environment: prod pool: vmImage: 'windows-latest' strategy: runOnce: deploy: steps: - task: VSTest@2 displayName: Run UI Tests inputs: testSelector: 'testAssemblies' testAssemblyVer2: | **\*FunctionalTests.dll !**\*TestAdapter.dll !**\obj\** searchFolder: '$(Pipeline.Workspace)/functionaltests/SeleniumTests' overrideTestrunParameters: '-siteUrl "$(prodip)"' -
Save your pipeline but don't run it yet. Commit the code changes you made in Visual Studio and push those. This will trigger the app pipeline. The Selenium will be automatically executed on the Test and Production environment. After the pipeline is completed you can find the test results in the tab Tests.
- Introduce a failing test and verify that the deployment stops with the failed test.
- Upload the Test screenshots to Azure DevOps. https://stackoverflow.com/questions/52823650/selenium-screenshots-in-vsts-azure-devops
Return to the Lab index and continue with the next lab