Skip to content

Commit 3753b78

Browse files
authored
OIDC Ui Test Automation (#791)
added UI Test for sample app 1-3
1 parent a9dd76a commit 3753b78

File tree

10 files changed

+920
-3
lines changed

10 files changed

+920
-3
lines changed

1-WebApp-OIDC/1-3-AnyOrgOrPersonal/appsettings.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{
1+
{
22
"AzureAd": {
33
"Instance": "https://login.microsoftonline.com/",
44
"Domain": "[Enter the domain of your tenant, e.g. contoso.onmicrosoft.com]",
@@ -15,4 +15,4 @@
1515
}
1616
},
1717
"AllowedHosts": "*"
18-
}
18+
}

3-WebApp-multi-APIs/appsettings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@
2020
},
2121
"AllowedHosts": "*",
2222
"GraphApiUrl": "https://graph.microsoft.com"
23-
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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.IO;
7+
using System.Linq;
8+
using System.Runtime.Versioning;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
using Common;
12+
using Microsoft.Identity.Lab.Api;
13+
using Microsoft.Playwright;
14+
using Xunit;
15+
using Xunit.Abstractions;
16+
using Process = System.Diagnostics.Process;
17+
using TC = Common.TestConstants;
18+
19+
namespace MultipleApiUiTest
20+
{
21+
public class AnyOrgOrPersonalTest : IClassFixture<InstallPlaywrightBrowserFixture>
22+
{
23+
private const string SignOutPageUriPath = @"/MicrosoftIdentity/Account/SignedOut";
24+
private const uint ClientPort = 44321;
25+
private const string TraceFileClassName = "OpenIDConnect";
26+
private const uint NumProcessRetries = 3;
27+
private const string SampleSlnFileName = "1-3-AnyOrgOrPersonal.sln";
28+
private readonly LocatorAssertionsToBeVisibleOptions _assertVisibleOptions = new() { Timeout = 25000 };
29+
private readonly string _sampleAppPath = "1-WebApp-OIDC" + Path.DirectorySeparatorChar + "1-3-AnyOrgOrPersonal" + Path.DirectorySeparatorChar.ToString();
30+
private readonly string _testAppsettingsPath = "UiTests" + Path.DirectorySeparatorChar + "AnyOrgOrPersonalUiTest" + Path.DirectorySeparatorChar.ToString() + TC.AppSetttingsDotJson;
31+
private readonly string _testAssemblyLocation = typeof(AnyOrgOrPersonalTest).Assembly.Location;
32+
private readonly ITestOutputHelper _output;
33+
34+
public AnyOrgOrPersonalTest(ITestOutputHelper output)
35+
{
36+
_output = output;
37+
}
38+
39+
[Fact]
40+
[SupportedOSPlatform("windows")]
41+
public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds_LoginLogout()
42+
{
43+
// Setup web app and api environmental variables.
44+
var clientEnvVars = new Dictionary<string, string>
45+
{
46+
{"ASPNETCORE_ENVIRONMENT", "Development"},
47+
{TC.KestrelEndpointEnvVar, TC.HttpsStarColon + ClientPort}
48+
};
49+
50+
Dictionary<string, Process>? processes = null;
51+
52+
// Arrange Playwright setup, to see the browser UI set Headless = false.
53+
const string TraceFileName = TraceFileClassName + "_LoginLogout";
54+
using IPlaywright playwright = await Playwright.CreateAsync();
55+
IBrowser browser = await playwright.Chromium.LaunchAsync(new() { Headless = false });
56+
IBrowserContext context = await browser.NewContextAsync(new BrowserNewContextOptions { IgnoreHTTPSErrors = true });
57+
await context.Tracing.StartAsync(new() { Screenshots = true, Snapshots = true, Sources = true });
58+
IPage page = await context.NewPageAsync();
59+
string uriWithPort = TC.LocalhostUrl + ClientPort;
60+
61+
try
62+
{
63+
// Build the sample app with correct appsettings file.
64+
UiTestHelpers.BuildSampleWithTestAppsettings(_testAssemblyLocation, _sampleAppPath, _testAppsettingsPath, SampleSlnFileName);
65+
66+
// Start the web app and api processes.
67+
// The delay before starting client prevents transient devbox issue where the client fails to load the first time after rebuilding
68+
var clientProcessOptions = new ProcessStartOptions(_testAssemblyLocation, _sampleAppPath, TC.s_oidcWebAppExe, clientEnvVars);
69+
70+
bool areProcessesRunning = UiTestHelpers.StartAndVerifyProcessesAreRunning([clientProcessOptions], out processes, NumProcessRetries);
71+
72+
if (!areProcessesRunning)
73+
{
74+
_output.WriteLine($"Process not started after {NumProcessRetries} attempts.");
75+
StringBuilder runningProcesses = new StringBuilder();
76+
foreach (var process in processes)
77+
{
78+
#pragma warning disable CA1305 // Specify IFormatProvider
79+
runningProcesses.AppendLine($"Is {process.Key} running: {UiTestHelpers.ProcessIsAlive(process.Value)}");
80+
#pragma warning restore CA1305 // Specify IFormatProvider
81+
}
82+
Assert.Fail(TC.WebAppCrashedString + " " + runningProcesses.ToString());
83+
}
84+
85+
LabResponse labResponse = await LabUserHelper.GetSpecificUserAsync(TC.OIDCUser);
86+
87+
// Initial sign in
88+
_output.WriteLine("Starting web app sign-in flow.");
89+
string email = labResponse.User.Upn;
90+
await UiTestHelpers.NavigateToWebApp(uriWithPort, page);
91+
await UiTestHelpers.EnterEmailAsync(page, email, _output);
92+
await UiTestHelpers.EnterPasswordAsync(page, labResponse.User.GetOrFetchPassword(), _output);
93+
await Assertions.Expect(page.GetByText("Integrating Azure AD V2")).ToBeVisibleAsync(_assertVisibleOptions);
94+
await Assertions.Expect(page.GetByText(email)).ToBeVisibleAsync(_assertVisibleOptions);
95+
_output.WriteLine("Web app sign-in flow successful.");
96+
97+
// Sign out
98+
_output.WriteLine("Starting web app sign-out flow.");
99+
await page.GetByRole(AriaRole.Link, new() { Name = "Sign out" }).ClickAsync();
100+
await UiTestHelpers.PerformSignOut_MicrosoftIdFlow(page, email, TC.LocalhostUrl + ClientPort + SignOutPageUriPath, _output);
101+
_output.WriteLine("Web app sign out successful.");
102+
}
103+
catch (Exception ex)
104+
{
105+
// Adding guid in case of multiple test runs. This will allow screenshots to be matched to their appropriate test runs.
106+
var guid = Guid.NewGuid().ToString();
107+
try
108+
{
109+
if (page != null)
110+
{
111+
await page.ScreenshotAsync(new PageScreenshotOptions() { Path = $"ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds_TodoAppFunctionsCorrectlyScreenshotFail{guid}.png", FullPage = true });
112+
}
113+
}
114+
catch
115+
{
116+
_output.WriteLine("No Screenshot.");
117+
}
118+
119+
string runningProcesses = UiTestHelpers.GetRunningProcessAsString(processes);
120+
Assert.Fail($"the UI automation failed: {ex} output: {ex.Message}.\n{runningProcesses}\nTest run: {guid}");
121+
}
122+
finally
123+
{
124+
// Make sure all processes and their children are stopped.
125+
UiTestHelpers.EndProcesses(processes);
126+
127+
// Stop tracing and export it into a zip archive.
128+
string path = UiTestHelpers.GetTracePath(_testAssemblyLocation, TraceFileName);
129+
await context.Tracing.StopAsync(new() { Path = path });
130+
_output.WriteLine($"Trace data for {TraceFileName} recorded to {path}.");
131+
132+
// Close the browser and stop Playwright.
133+
await browser.CloseAsync();
134+
playwright.Dispose();
135+
}
136+
}
137+
138+
}
139+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net8.0</TargetFrameworks>
5+
<IsPackable>false</IsPackable>
6+
<Nullable>enable</Nullable>
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+
</ItemGroup>
30+
31+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"AzureAd": {
3+
"Instance": "https://login.microsoftonline.com/",
4+
"Domain": "msidlab3.onmicrosoft.com",
5+
"TenantId": "8e44f19d-bbab-4a82-b76b-4cd0a6fbc97a",
6+
"ClientId": "d9cde0be-ad97-41e6-855e-2f85136671c1",
7+
"CallbackPath": "/signin-oidc",
8+
"SignedOutCallbackPath": "/signout-callback-oidc"
9+
},
10+
"Logging": {
11+
"LogLevel": {
12+
"Default": "Information",
13+
"Microsoft": "Warning",
14+
"Microsoft.Hosting.Lifetime": "Information"
15+
}
16+
},
17+
"AllowedHosts": "*"
18+
}

UiTests/Common/Common.csproj

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(MicrosoftAspNetCoreMvcTestingVersion)" />
12+
<PackageReference Include="Microsoft.Identity.Lab.Api" Version="$(MicrosoftIdentityLabApiVersion)" />
13+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
14+
<PackageReference Include="Microsoft.Playwright" Version="$(MicrosoftPlaywrightVersion)" />
15+
<PackageReference Include="System.Management" Version="$(SystemManagementVersion)" />
16+
<PackageReference Include="System.Text.Json" Version="$(SystemTextJsonVersion)" />
17+
<PackageReference Include="xunit" Version="$(XunitVersion)" />
18+
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioVersion)">
19+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20+
<PrivateAssets>all</PrivateAssets>
21+
</PackageReference>
22+
<PackageReference Include="coverlet.collector" Version="$(CoverletCollectorVersion)">
23+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
24+
<PrivateAssets>all</PrivateAssets>
25+
</PackageReference>
26+
</ItemGroup>
27+
28+
<ItemGroup>
29+
<Using Include="Xunit" />
30+
</ItemGroup>
31+
32+
</Project>

UiTests/Common/TestConstants.cs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
10+
namespace Common
11+
{
12+
public static class TestConstants
13+
{
14+
public const string AppSetttingsDotJson = "appsettings.json";
15+
public const string ClientFilePrefix = "client_";
16+
public const string EmailText = "Email";
17+
public const string Headless = "headless";
18+
public const string HeaderText = "Header";
19+
public const string HttpStarColon = "http://*:";
20+
public const string HttpsStarColon = "https://*:";
21+
public const string KestrelEndpointEnvVar = "Kestrel:Endpoints:Http:Url";
22+
public const string LocalhostUrl = @"https://localhost:";
23+
public const string OIDCUser = "[email protected]";
24+
public const string PasswordText = "Password";
25+
public const string ServerFilePrefix = "server_";
26+
public const string TodoTitle1 = "Testing create todo item";
27+
public const string TodoTitle2 = "Testing edit todo item";
28+
public const string WebAppCrashedString = $"The web app process has exited prematurely.";
29+
30+
public static readonly string s_oidcWebAppExe = Path.DirectorySeparatorChar.ToString() + "WebApp-OpenIDConnect-DotNet.exe";
31+
public static readonly string s_oidcWebAppPath = Path.DirectorySeparatorChar.ToString() + "WebApp-OpenIDConnect";
32+
public static readonly string s_todoListClientExe = Path.DirectorySeparatorChar.ToString() + "TodoListClient.exe";
33+
public static readonly string s_todoListClientPath = Path.DirectorySeparatorChar.ToString() + "Client";
34+
public static readonly string s_todoListServiceExe = Path.DirectorySeparatorChar.ToString() + "TodoListService.exe";
35+
public static readonly string s_todoListServicePath = Path.DirectorySeparatorChar.ToString() + "TodoListService";
36+
}
37+
}

0 commit comments

Comments
 (0)