Skip to content

Commit fdac0a3

Browse files
committed
Created B2C UI test
1 parent 6cfd554 commit fdac0a3

File tree

10 files changed

+195
-4
lines changed

10 files changed

+195
-4
lines changed

4-WebApp-your-API/4-2-B2C/Client/appsettings.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"SignedOutCallbackPath": "/signout/B2C_1_susi_reset_v2",
77
"SignUpSignInPolicyId": "B2C_1_susi_reset_v2",
88
"EditProfilePolicyId": "B2C_1_edit_profile_v2", // Optional profile editing policy
9-
"ClientSecret": "X330F3#92!z614M4"
109
//"CallbackPath": "/signin/B2C_1_sign_up_in" // defaults to /signin-oidc
1110
},
1211
"TodoList": {

UiTests/B2CUiTest/B2CUiTest.cs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.IO;
8+
using System.Runtime.Versioning;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Azure.Identity;
12+
using Common;
13+
using Microsoft.Identity.Lab.Api;
14+
using Microsoft.Playwright;
15+
using Xunit;
16+
using Xunit.Abstractions;
17+
using TC = Common.TestConstants;
18+
19+
namespace B2CUiTest
20+
{
21+
public class B2CUiTest : IClassFixture<InstallPlaywrightBrowserFixture>
22+
{
23+
private const string KeyvaultEmailName = "IdWeb-B2C-user";
24+
private const string KeyvaultPasswordName = "IdWeb-B2C-password";
25+
private const string KeyvaultClientSecretName = "IdWeb-B2C-Client-ClientSecret";
26+
private const string NameOfUser = "unknown";
27+
private const uint TodoListClientPort = 5000;
28+
private const uint TodoListServicePort = 44332;
29+
private const string TraceClassName = "B2C-Login";
30+
private readonly LocatorAssertionsToBeVisibleOptions _assertVisibleOptions = new() { Timeout = 25000 };
31+
private readonly string _sampleAppPath = Path.Join("4-WebApp-your-API", "4-2-B2C");
32+
private readonly Uri _keyvaultUri = new("https://webappsapistests.vault.azure.net");
33+
private readonly ITestOutputHelper _output;
34+
private readonly string _testAssemblyPath = typeof(B2CUiTest).Assembly.Location;
35+
36+
public B2CUiTest(ITestOutputHelper output)
37+
{
38+
_output = output;
39+
}
40+
41+
[Fact]
42+
[SupportedOSPlatform("windows")]
43+
public async Task B2C_ValidCreds_LoginLogout()
44+
{
45+
// Web app and api environmental variable setup.
46+
DefaultAzureCredential azureCred = new();
47+
string clientSecret = await UiTestHelpers.GetValueFromKeyvaultWitDefaultCreds(_keyvaultUri, KeyvaultClientSecretName, azureCred);
48+
var serviceEnvVars = new Dictionary<string, string>
49+
{
50+
{"ASPNETCORE_ENVIRONMENT", "Development" },
51+
{TC.KestrelEndpointEnvVar, TC.HttpStarColon + TodoListServicePort}
52+
};
53+
var clientEnvVars = new Dictionary<string, string>
54+
{
55+
{"ASPNETCORE_ENVIRONMENT", "Development"},
56+
{"AzureAdB2C__ClientSecret", clientSecret},
57+
{TC.KestrelEndpointEnvVar, TC.HttpsStarColon + TodoListClientPort}
58+
};
59+
60+
// Get email and password from keyvault.
61+
string email = await UiTestHelpers.GetValueFromKeyvaultWitDefaultCreds(_keyvaultUri, KeyvaultEmailName, azureCred);
62+
string password = await UiTestHelpers.GetValueFromKeyvaultWitDefaultCreds(_keyvaultUri, KeyvaultPasswordName, azureCred);
63+
64+
// Playwright setup. To see browser UI, set 'Headless = false'.
65+
const string TraceFileName = TraceClassName + "_TodoAppFunctionsCorrectly";
66+
using IPlaywright playwright = await Playwright.CreateAsync();
67+
IBrowser browser = await playwright.Chromium.LaunchAsync(new() { Headless = false });
68+
IBrowserContext context = await browser.NewContextAsync(new BrowserNewContextOptions { IgnoreHTTPSErrors = true });
69+
await context.Tracing.StartAsync(new() { Screenshots = true, Snapshots = true, Sources = true });
70+
71+
Process? serviceProcess = null;
72+
Process? clientProcess = null;
73+
74+
try
75+
{
76+
// Start the web app and api processes.
77+
// The delay before starting client prevents transient devbox issue where the client fails to load the first time after rebuilding.
78+
serviceProcess = UiTestHelpers.StartProcessLocally(_testAssemblyPath, _sampleAppPath + TC.s_todoListServicePath, TC.s_todoListServiceExe, serviceEnvVars);
79+
await Task.Delay(3000);
80+
clientProcess = UiTestHelpers.StartProcessLocally(_testAssemblyPath, _sampleAppPath + TC.s_todoListClientPath, TC.s_todoListClientExe, clientEnvVars);
81+
82+
if (!UiTestHelpers.ProcessesAreAlive(new List<Process>() { clientProcess, serviceProcess }))
83+
{
84+
Assert.Fail(TC.WebAppCrashedString);
85+
}
86+
87+
// Navigate to web app the retry logic ensures the web app has time to start up to establish a connection.
88+
IPage page = await context.NewPageAsync();
89+
uint InitialConnectionRetryCount = 5;
90+
while (InitialConnectionRetryCount > 0)
91+
{
92+
try
93+
{
94+
await page.GotoAsync(TC.LocalhostUrl + TodoListClientPort);
95+
break;
96+
}
97+
catch (PlaywrightException ex)
98+
{
99+
await Task.Delay(1000);
100+
InitialConnectionRetryCount--;
101+
if (InitialConnectionRetryCount == 0) { throw ex; }
102+
}
103+
}
104+
LabResponse labResponse = await LabUserHelper.GetB2CLocalAccountAsync();
105+
106+
// Initial sign in
107+
_output.WriteLine("Starting web app sign-in flow.");
108+
ILocator emailEntryBox = page.GetByPlaceholder("Email Address");
109+
await emailEntryBox.FillAsync(email);
110+
await emailEntryBox.PressAsync("Tab");
111+
await page.GetByPlaceholder("Password").FillAsync(password);
112+
await page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
113+
await Assertions.Expect(page.GetByText("TodoList")).ToBeVisibleAsync(_assertVisibleOptions);
114+
await Assertions.Expect(page.GetByText(NameOfUser)).ToBeVisibleAsync(_assertVisibleOptions);
115+
_output.WriteLine("Web app sign-in flow successful.");
116+
117+
// Sign out
118+
_output.WriteLine("Starting web app sign-out flow.");
119+
await page.GetByRole(AriaRole.Link, new() { Name = "Sign out" }).ClickAsync();
120+
_output.WriteLine("Signing out ...");
121+
await Assertions.Expect(page.GetByText("You have successfully signed out.")).ToBeVisibleAsync(_assertVisibleOptions);
122+
await Assertions.Expect(page.GetByText(NameOfUser)).ToBeHiddenAsync();
123+
_output.WriteLine("Web app sign out successful.");
124+
}
125+
catch (Exception ex)
126+
{
127+
Assert.Fail($"the UI automation failed: {ex} output: {ex.Message}.");
128+
}
129+
finally
130+
{
131+
// Add the following to make sure all processes and their children are stopped.
132+
Queue<Process> processes = new Queue<Process>();
133+
if (serviceProcess != null) { processes.Enqueue(serviceProcess); }
134+
if (clientProcess != null) { processes.Enqueue(clientProcess); }
135+
UiTestHelpers.KillProcessTrees(processes);
136+
137+
// Stop tracing and export it into a zip archive.
138+
string path = UiTestHelpers.GetTracePath(_testAssemblyPath, TraceFileName);
139+
await context.Tracing.StopAsync(new() { Path = path });
140+
_output.WriteLine($"Trace data for {TraceFileName} recorded to {path}.");
141+
142+
// Close the browser and stop Playwright.
143+
await browser.CloseAsync();
144+
playwright.Dispose();
145+
}
146+
}
147+
}
148+
}

UiTests/B2CUiTest/B2CUiTest.csproj

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net8.0</TargetFrameworks>
5+
<!--<TargetFrameworks Condition="'$(TargetNet9)'== 'True'">$(TargetFrameworks); net9.0</TargetFrameworks>-->
6+
<IsPackable>false</IsPackable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(MicrosoftAspNetCoreMvcTestingVersion)" />
11+
<PackageReference Include="Microsoft.Identity.Lab.Api" Version="$(MicrosoftIdentityLabApiVersion)" />
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
13+
<PackageReference Include="Microsoft.Playwright" Version="$(MicrosoftPlaywrightVersion)" />
14+
<PackageReference Include="System.Management" Version="$(SystemManagementVersion)" />
15+
<PackageReference Include="System.Text.Json" Version="$(SystemTextJsonVersion)" />
16+
<PackageReference Include="xunit" Version="$(XunitVersion)" />
17+
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioVersion)">
18+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
19+
<PrivateAssets>all</PrivateAssets>
20+
</PackageReference>
21+
<PackageReference Include="coverlet.collector" Version="$(CoverletCollectorVersion)">
22+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
23+
<PrivateAssets>all</PrivateAssets>
24+
</PackageReference>
25+
</ItemGroup>
26+
27+
<ItemGroup>
28+
<ProjectReference Include="..\Common\Common.csproj" />
29+
<ProjectReference Include="..\..\4-WebApp-your-API\4-2-B2C\TodoListService\TodoListService.csproj" />
30+
<ProjectReference Include="..\..\4-WebApp-your-API\4-2-B2C\Client\TodoListClient.csproj" />
31+
</ItemGroup>
32+
33+
</Project>

UiTests/Common/TestConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ public static class TestConstants
2121
public const string HttpsStarColon = "https://*:";
2222
public const string WebAppCrashedString = $"The web app process has exited prematurely.";
2323
public const string OIDCUser = "[email protected]";
24+
2425
public static readonly string s_oidcWebAppExe = Path.DirectorySeparatorChar.ToString() + "WebApp-OpenIDConnect-DotNet.exe";
2526
public static readonly string s_oidcWebAppPath = Path.DirectorySeparatorChar.ToString() + "WebApp-OpenIDConnect";
27+
public static readonly string s_todoListClientExe = Path.DirectorySeparatorChar.ToString() + "TodoListClient.exe";
28+
public static readonly string s_todoListClientPath = Path.DirectorySeparatorChar.ToString() + "Client";
29+
public static readonly string s_todoListServiceExe = Path.DirectorySeparatorChar.ToString() + "TodoListService.exe";
30+
public static readonly string s_todoListServicePath = Path.DirectorySeparatorChar.ToString() + "TodoListService";
2631
}
2732
}

UiTests/Common/UiTestHelpers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ public static void InstallPlaywrightBrowser()
350350
/// <param name="keyvaultSecretName">The name of the secret</param>
351351
/// <returns>The value of the secret from key vault</returns>
352352
/// <exception cref="ArgumentNullException">Throws if no secret name is provided</exception>
353-
internal static async Task<string> GetValueFromKeyvaultWitDefaultCreds(Uri keyvaultUri, string keyvaultSecretName, TokenCredential creds)
353+
public static async Task<string> GetValueFromKeyvaultWitDefaultCreds(Uri keyvaultUri, string keyvaultSecretName, TokenCredential creds)
354354
{
355355
if (string.IsNullOrEmpty(keyvaultSecretName))
356356
{

UiTests/UiTests/UiTests.sln renamed to UiTests/UiTests.sln

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
44
VisualStudioVersion = 17.11.35303.130
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnyOrgOrPersonalUiTest", "AnyOrgOrPersonalUiTest.csproj", "{2B42751A-8650-4DE4-9B46-B01C21825EB1}"
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnyOrgOrPersonalUiTest", "AnyOrgOrPersonalUiTest\AnyOrgOrPersonalUiTest.csproj", "{2B42751A-8650-4DE4-9B46-B01C21825EB1}"
77
EndProject
8-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "..\Common\Common.csproj", "{3074B729-52E8-408E-8BBC-815FE9217385}"
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "Common\Common.csproj", "{3074B729-52E8-408E-8BBC-815FE9217385}"
99
EndProject
1010
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6A8B8F57-DBC6-43E2-84E7-16D24E54157B}"
1111
ProjectSection(SolutionItems) = preProject
1212
..\Directory.Build.props = ..\Directory.Build.props
1313
EndProjectSection
1414
EndProject
15+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "B2CUiTest", "B2CUiTest\B2CUiTest.csproj", "{BF7D9973-9B92-4BED-ADE2-09087DDA9C85}"
16+
EndProject
1517
Global
1618
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1719
Debug|Any CPU = Debug|Any CPU
@@ -26,6 +28,10 @@ Global
2628
{3074B729-52E8-408E-8BBC-815FE9217385}.Debug|Any CPU.Build.0 = Debug|Any CPU
2729
{3074B729-52E8-408E-8BBC-815FE9217385}.Release|Any CPU.ActiveCfg = Release|Any CPU
2830
{3074B729-52E8-408E-8BBC-815FE9217385}.Release|Any CPU.Build.0 = Release|Any CPU
31+
{BF7D9973-9B92-4BED-ADE2-09087DDA9C85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32+
{BF7D9973-9B92-4BED-ADE2-09087DDA9C85}.Debug|Any CPU.Build.0 = Debug|Any CPU
33+
{BF7D9973-9B92-4BED-ADE2-09087DDA9C85}.Release|Any CPU.ActiveCfg = Release|Any CPU
34+
{BF7D9973-9B92-4BED-ADE2-09087DDA9C85}.Release|Any CPU.Build.0 = Release|Any CPU
2935
EndGlobalSection
3036
GlobalSection(SolutionProperties) = preSolution
3137
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)