Skip to content

Commit 2084352

Browse files
authored
Create test for hybrid flow sample (#800)
* Create test for hybrid flow sample
1 parent b898513 commit 2084352

File tree

6 files changed

+210
-3
lines changed

6 files changed

+210
-3
lines changed
-1018 Bytes
Binary file not shown.

UiTests/AnyOrgOrPersonalUiTest/AnyOrgOrPersonalTest.cs

-1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,5 @@ public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds
134134
playwright.Dispose();
135135
}
136136
}
137-
138137
}
139138
}
+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Common;
5+
using Microsoft.Identity.Lab.Api;
6+
using Microsoft.Playwright;
7+
using System;
8+
using System.Collections.Generic;
9+
using System.IO;
10+
using System.Runtime.Versioning;
11+
using System.Text;
12+
using System.Threading.Tasks;
13+
using Xunit;
14+
using Xunit.Abstractions;
15+
using Process = System.Diagnostics.Process;
16+
using TC = Common.TestConstants;
17+
18+
namespace HybridFlowUiTest
19+
{
20+
public class HybridFlowTest : IClassFixture<InstallPlaywrightBrowserFixture>
21+
{
22+
private const string SignOutPageUriPath = @"/MicrosoftIdentity/Account/SignedOut";
23+
private const uint ClientPort = 44321;
24+
private const string TraceFileClassName = "OpenIDConnect-HybridFlow";
25+
private const uint NumProcessRetries = 3;
26+
private const string SampleSlnFileName = "2-5-HybridFlow.sln";
27+
private const string SampleExeFileName = "2-5-HybridFlow.exe";
28+
private readonly LocatorAssertionsToBeVisibleOptions _assertVisibleOptions = new() { Timeout = 25000 };
29+
private readonly string _sampleAppPath = "2-WebApp-graph-user" + Path.DirectorySeparatorChar + "2-5-HybridFlow" + Path.DirectorySeparatorChar.ToString();
30+
private readonly string _testAppsettingsPath = "UiTests" + Path.DirectorySeparatorChar + "HybridFlowUiTest" + Path.DirectorySeparatorChar.ToString() + TC.AppSetttingsDotJson;
31+
private readonly string _testAssemblyLocation = typeof(HybridFlowTest).Assembly.Location;
32+
private readonly ITestOutputHelper _output;
33+
34+
public HybridFlowTest(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 = true });
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.BuildSampleUsingTestAppsettings(_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(
69+
_testAssemblyLocation,
70+
_sampleAppPath,
71+
Path.DirectorySeparatorChar.ToString() + SampleExeFileName,
72+
clientEnvVars
73+
);
74+
75+
bool areProcessesRunning = UiTestHelpers.StartAndVerifyProcessesAreRunning([clientProcessOptions], out processes, NumProcessRetries);
76+
77+
if (!areProcessesRunning)
78+
{
79+
_output.WriteLine($"Process not started after {NumProcessRetries} attempts.");
80+
StringBuilder runningProcesses = new StringBuilder();
81+
foreach (var process in processes)
82+
{
83+
#pragma warning disable CA1305 // Specify IFormatProvider
84+
runningProcesses.AppendLine($"Is {process.Key} running: {UiTestHelpers.ProcessIsAlive(process.Value)}");
85+
#pragma warning restore CA1305 // Specify IFormatProvider
86+
}
87+
Assert.Fail(TC.WebAppCrashedString + " " + runningProcesses.ToString());
88+
}
89+
90+
LabResponse labResponse = await LabUserHelper.GetSpecificUserAsync(TC.MsidLab4User);
91+
92+
// Initial sign in
93+
_output.WriteLine("Starting web app sign-in flow.");
94+
string email = labResponse.User.Upn;
95+
await UiTestHelpers.NavigateToWebApp(uriWithPort, page);
96+
await page.GetByRole(AriaRole.Link, new() { Name = "Sign in" }).ClickAsync();
97+
await UiTestHelpers.FirstLogin_MicrosoftIdFlow_ValidEmailPassword(page, email, labResponse.User.GetOrFetchPassword(), _output);
98+
await Assertions.Expect(page.GetByText("SPA Authorization Code")).ToBeVisibleAsync(_assertVisibleOptions);
99+
await Assertions.Expect(page.GetByText(email)).ToBeVisibleAsync(_assertVisibleOptions);
100+
_output.WriteLine("Web app sign-in flow successful.");
101+
102+
// Sign out
103+
_output.WriteLine("Starting web app sign-out flow.");
104+
await page.GetByRole(AriaRole.Link, new() { Name = "Sign out" }).ClickAsync();
105+
await UiTestHelpers.PerformSignOut_MicrosoftIdFlow(page, email, TC.LocalhostUrl + ClientPort + SignOutPageUriPath, _output);
106+
_output.WriteLine("Web app sign out successful.");
107+
}
108+
catch (Exception ex)
109+
{
110+
// Adding guid in case of multiple test runs. This will allow screenshots to be matched to their appropriate test runs.
111+
var guid = Guid.NewGuid().ToString();
112+
try
113+
{
114+
if (page != null)
115+
{
116+
await page.ScreenshotAsync(new PageScreenshotOptions() { Path = $"ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds_TodoAppFunctionsCorrectlyScreenshotFail{guid}.png", FullPage = true });
117+
}
118+
}
119+
catch
120+
{
121+
_output.WriteLine("No Screenshot.");
122+
}
123+
124+
string runningProcesses = UiTestHelpers.GetRunningProcessAsString(processes);
125+
Assert.Fail($"the UI automation failed: {ex} output: {ex.Message}.\n{runningProcesses}\nTest run: {guid}");
126+
}
127+
finally
128+
{
129+
// Make sure all processes and their children are stopped.
130+
UiTestHelpers.EndProcesses(processes);
131+
132+
// Stop tracing and export it into a zip archive.
133+
string path = UiTestHelpers.GetTracePath(_testAssemblyLocation, TraceFileName);
134+
await context.Tracing.StopAsync(new() { Path = path });
135+
_output.WriteLine($"Trace data for {TraceFileName} recorded to {path}.");
136+
137+
// Close the browser and stop Playwright.
138+
await browser.CloseAsync();
139+
playwright.Dispose();
140+
}
141+
}
142+
}
143+
}
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>
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"AzureAd": {
3+
"Instance": "https://login.microsoftonline.com/",
4+
"Domain": "msidlab4.onmicrosoft.com",
5+
"TenantId": "f645ad92-e38d-4d1a-b510-d1b09a74a8ca",
6+
"ClientId": "9a192b78-6580-4f8a-aace-f36ffea4f7be",
7+
"CallbackPath": "/signin-oidc",
8+
"SignedOutCallbackPath": "/signout-callback-oidc",
9+
"ClientCertificates": [
10+
{
11+
"SourceType": "KeyVault",
12+
"KeyVaultUrl": "https://webappsapistests.vault.azure.net",
13+
"KeyVaultCertificateName": "Self-Signed-5-5-22"
14+
}
15+
]
16+
},
17+
"Logging": {
18+
"LogLevel": {
19+
"Default": "Warning"
20+
}
21+
},
22+
"AllowedHosts": "*",
23+
"DownstreamApi": {
24+
"BaseUrl": "https://graph.microsoft.com/v1.0",
25+
"Scopes": "user.read contacts.read"
26+
},
27+
"SpaRedirectUri": "https://localhost:44321/"
28+
}

UiTests/UiTests.sln

+8-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
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}"
15+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "B2CUiTest", "B2CUiTest\B2CUiTest.csproj", "{BF7D9973-9B92-4BED-ADE2-09087DDA9C85}"
1616
EndProject
17-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphUserTokenCache", "GraphUserTokenCache\GraphUserTokenCache.csproj", "{B083D288-AB6E-4849-9AC2-E1DA1F727483}"
17+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphUserTokenCache", "GraphUserTokenCache\GraphUserTokenCache.csproj", "{B083D288-AB6E-4849-9AC2-E1DA1F727483}"
18+
EndProject
19+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HybridFlowUiTest", "HybridFlowUiTest\HybridFlowUiTest.csproj", "{344CD55E-14C7-4999-A040-6C049F0070CB}"
1820
EndProject
1921
Global
2022
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -38,6 +40,10 @@ Global
3840
{B083D288-AB6E-4849-9AC2-E1DA1F727483}.Debug|Any CPU.Build.0 = Debug|Any CPU
3941
{B083D288-AB6E-4849-9AC2-E1DA1F727483}.Release|Any CPU.ActiveCfg = Release|Any CPU
4042
{B083D288-AB6E-4849-9AC2-E1DA1F727483}.Release|Any CPU.Build.0 = Release|Any CPU
43+
{344CD55E-14C7-4999-A040-6C049F0070CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44+
{344CD55E-14C7-4999-A040-6C049F0070CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
45+
{344CD55E-14C7-4999-A040-6C049F0070CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
46+
{344CD55E-14C7-4999-A040-6C049F0070CB}.Release|Any CPU.Build.0 = Release|Any CPU
4147
EndGlobalSection
4248
GlobalSection(SolutionProperties) = preSolution
4349
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)