Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create test for hybrid flow sample #800

Merged
merged 3 commits into from
Oct 31, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified 2-WebApp-graph-user/2-5-HybridFlow/appsettings.json
Binary file not shown.
1 change: 0 additions & 1 deletion UiTests/AnyOrgOrPersonalUiTest/AnyOrgOrPersonalTest.cs
Original file line number Diff line number Diff line change
@@ -134,6 +134,5 @@ public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds
playwright.Dispose();
}
}

}
}
143 changes: 143 additions & 0 deletions UiTests/HybridFlowUiTest/HybridFlowTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Common;
using Microsoft.Identity.Lab.Api;
using Microsoft.Playwright;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
using Process = System.Diagnostics.Process;
using TC = Common.TestConstants;

namespace HybridFlowUiTest
{
public class HybridFlowTest : IClassFixture<InstallPlaywrightBrowserFixture>
{
private const string SignOutPageUriPath = @"/MicrosoftIdentity/Account/SignedOut";
private const uint ClientPort = 44321;
private const string TraceFileClassName = "OpenIDConnect-HybridFlow";
private const uint NumProcessRetries = 3;
private const string SampleSlnFileName = "2-5-HybridFlow.sln";
private const string SampleExeFileName = "2-5-HybridFlow.exe";
private readonly LocatorAssertionsToBeVisibleOptions _assertVisibleOptions = new() { Timeout = 25000 };
private readonly string _sampleAppPath = "2-WebApp-graph-user" + Path.DirectorySeparatorChar + "2-5-HybridFlow" + Path.DirectorySeparatorChar.ToString();
private readonly string _testAppsettingsPath = "UiTests" + Path.DirectorySeparatorChar + "HybridFlowUiTest" + Path.DirectorySeparatorChar.ToString() + TC.AppSetttingsDotJson;
private readonly string _testAssemblyLocation = typeof(HybridFlowTest).Assembly.Location;
private readonly ITestOutputHelper _output;

public HybridFlowTest(ITestOutputHelper output)
{
_output = output;
}

[Fact]
[SupportedOSPlatform("windows")]
public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds_LoginLogout()
{
// Setup web app and api environmental variables.
var clientEnvVars = new Dictionary<string, string>
{
{"ASPNETCORE_ENVIRONMENT", "Development"},
{TC.KestrelEndpointEnvVar, TC.HttpsStarColon + ClientPort}
};

Dictionary<string, Process>? processes = null;

// Arrange Playwright setup, to see the browser UI set Headless = false.
const string TraceFileName = TraceFileClassName + "_LoginLogout";
using IPlaywright playwright = await Playwright.CreateAsync();
IBrowser browser = await playwright.Chromium.LaunchAsync(new() { Headless = true });
IBrowserContext context = await browser.NewContextAsync(new BrowserNewContextOptions { IgnoreHTTPSErrors = true });
await context.Tracing.StartAsync(new() { Screenshots = true, Snapshots = true, Sources = true });
IPage page = await context.NewPageAsync();
string uriWithPort = TC.LocalhostUrl + ClientPort;

try
{
// Build the sample app with correct appsettings file.
UiTestHelpers.BuildSampleUsingTestAppsettings(_testAssemblyLocation, _sampleAppPath, _testAppsettingsPath, SampleSlnFileName);

// Start the web app and api processes.
// The delay before starting client prevents transient devbox issue where the client fails to load the first time after rebuilding
var clientProcessOptions = new ProcessStartOptions(
_testAssemblyLocation,
_sampleAppPath,
Path.DirectorySeparatorChar.ToString() + SampleExeFileName,
clientEnvVars
);

bool areProcessesRunning = UiTestHelpers.StartAndVerifyProcessesAreRunning([clientProcessOptions], out processes, NumProcessRetries);

if (!areProcessesRunning)
{
_output.WriteLine($"Process not started after {NumProcessRetries} attempts.");
StringBuilder runningProcesses = new StringBuilder();
foreach (var process in processes)
{
#pragma warning disable CA1305 // Specify IFormatProvider
runningProcesses.AppendLine($"Is {process.Key} running: {UiTestHelpers.ProcessIsAlive(process.Value)}");
#pragma warning restore CA1305 // Specify IFormatProvider
}
Assert.Fail(TC.WebAppCrashedString + " " + runningProcesses.ToString());
}

LabResponse labResponse = await LabUserHelper.GetSpecificUserAsync(TC.MsidLab4User);

// Initial sign in
_output.WriteLine("Starting web app sign-in flow.");
string email = labResponse.User.Upn;
await UiTestHelpers.NavigateToWebApp(uriWithPort, page);
await page.GetByRole(AriaRole.Link, new() { Name = "Sign in" }).ClickAsync();
await UiTestHelpers.FirstLogin_MicrosoftIdFlow_ValidEmailPassword(page, email, labResponse.User.GetOrFetchPassword(), _output);
await Assertions.Expect(page.GetByText("SPA Authorization Code")).ToBeVisibleAsync(_assertVisibleOptions);
await Assertions.Expect(page.GetByText(email)).ToBeVisibleAsync(_assertVisibleOptions);
_output.WriteLine("Web app sign-in flow successful.");

// Sign out
_output.WriteLine("Starting web app sign-out flow.");
await page.GetByRole(AriaRole.Link, new() { Name = "Sign out" }).ClickAsync();
await UiTestHelpers.PerformSignOut_MicrosoftIdFlow(page, email, TC.LocalhostUrl + ClientPort + SignOutPageUriPath, _output);
_output.WriteLine("Web app sign out successful.");
}
catch (Exception ex)
{
// Adding guid in case of multiple test runs. This will allow screenshots to be matched to their appropriate test runs.
var guid = Guid.NewGuid().ToString();
try
{
if (page != null)
{
await page.ScreenshotAsync(new PageScreenshotOptions() { Path = $"ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds_TodoAppFunctionsCorrectlyScreenshotFail{guid}.png", FullPage = true });
}
}
catch
{
_output.WriteLine("No Screenshot.");
}

string runningProcesses = UiTestHelpers.GetRunningProcessAsString(processes);
Assert.Fail($"the UI automation failed: {ex} output: {ex.Message}.\n{runningProcesses}\nTest run: {guid}");
}
finally
{
// Make sure all processes and their children are stopped.
UiTestHelpers.EndProcesses(processes);

// Stop tracing and export it into a zip archive.
string path = UiTestHelpers.GetTracePath(_testAssemblyLocation, TraceFileName);
await context.Tracing.StopAsync(new() { Path = path });
_output.WriteLine($"Trace data for {TraceFileName} recorded to {path}.");

// Close the browser and stop Playwright.
await browser.CloseAsync();
playwright.Dispose();
}
}
}
}
31 changes: 31 additions & 0 deletions UiTests/HybridFlowUiTest/HybridFlowUiTest.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(MicrosoftAspNetCoreMvcTestingVersion)" />
<PackageReference Include="Microsoft.Identity.Lab.Api" Version="$(MicrosoftIdentityLabApiVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
<PackageReference Include="Microsoft.Playwright" Version="$(MicrosoftPlaywrightVersion)" />
<PackageReference Include="System.Management" Version="$(SystemManagementVersion)" />
<PackageReference Include="System.Text.Json" Version="$(SystemTextJsonVersion)" />
<PackageReference Include="xunit" Version="$(XunitVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioVersion)">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="$(CoverletCollectorVersion)">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
</ItemGroup>

</Project>
28 changes: 28 additions & 0 deletions UiTests/HybridFlowUiTest/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "msidlab4.onmicrosoft.com",
"TenantId": "f645ad92-e38d-4d1a-b510-d1b09a74a8ca",
"ClientId": "9a192b78-6580-4f8a-aace-f36ffea4f7be",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath": "/signout-callback-oidc",
"ClientCertificates": [
{
"SourceType": "KeyVault",
"KeyVaultUrl": "https://webappsapistests.vault.azure.net",
"KeyVaultCertificateName": "Self-Signed-5-5-22"
}
]
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"DownstreamApi": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read contacts.read"
},
"SpaRedirectUri": "https://localhost:44321/"
}
10 changes: 8 additions & 2 deletions UiTests/UiTests.sln
Original file line number Diff line number Diff line change
@@ -12,9 +12,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.props = Directory.Build.props
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "B2CUiTest", "B2CUiTest\B2CUiTest.csproj", "{BF7D9973-9B92-4BED-ADE2-09087DDA9C85}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "B2CUiTest", "B2CUiTest\B2CUiTest.csproj", "{BF7D9973-9B92-4BED-ADE2-09087DDA9C85}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphUserTokenCache", "GraphUserTokenCache\GraphUserTokenCache.csproj", "{B083D288-AB6E-4849-9AC2-E1DA1F727483}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphUserTokenCache", "GraphUserTokenCache\GraphUserTokenCache.csproj", "{B083D288-AB6E-4849-9AC2-E1DA1F727483}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HybridFlowUiTest", "HybridFlowUiTest\HybridFlowUiTest.csproj", "{344CD55E-14C7-4999-A040-6C049F0070CB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -38,6 +40,10 @@ Global
{B083D288-AB6E-4849-9AC2-E1DA1F727483}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B083D288-AB6E-4849-9AC2-E1DA1F727483}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B083D288-AB6E-4849-9AC2-E1DA1F727483}.Release|Any CPU.Build.0 = Release|Any CPU
{344CD55E-14C7-4999-A040-6C049F0070CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{344CD55E-14C7-4999-A040-6C049F0070CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{344CD55E-14C7-4999-A040-6C049F0070CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{344CD55E-14C7-4999-A040-6C049F0070CB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Loading