Skip to content

OIDC Ui Test Automation #791

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

Merged
merged 28 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
04f5a6a
added Ui test for multiple API sample
JoshLozensky Sep 25, 2024
e576909
updating for help with debugging
JoshLozensky Sep 26, 2024
03e71ce
making draft PR
JoshLozensky Sep 27, 2024
f84fb66
adding temporary appsettings.json fix
JoshLozensky Sep 27, 2024
6cfd554
small constants update
JoshLozensky Sep 30, 2024
fdac0a3
Created B2C UI test
JoshLozensky Sep 30, 2024
b836757
sort usings
JoshLozensky Oct 4, 2024
1bccda8
adding helpers
JoshLozensky Oct 4, 2024
9e15088
rearranging
JoshLozensky Oct 4, 2024
2e29cf0
removing project going in a different branch
JoshLozensky Oct 4, 2024
02d5dc0
Added building sample to test
JoshLozensky Oct 4, 2024
025947b
Adding notes
JoshLozensky Oct 4, 2024
3f14ac1
remove unneeded edits
JoshLozensky Oct 4, 2024
ea5f258
Update UiTests/Common/TestConstants.cs
JoshLozensky Oct 4, 2024
880cf53
enabled nullables
JoshLozensky Oct 4, 2024
d448a7b
Merge branch 'lozenksy/AddingUiTests' of https://github.com/Azure-Sam…
JoshLozensky Oct 4, 2024
4a02242
Update UiTests/AnyOrgOrPersonalUiTest/AnyOrgOrPersonalTest.cs
JoshLozensky Oct 7, 2024
4d48da0
Update UiTests/AnyOrgOrPersonalUiTest/AnyOrgOrPersonalTest.cs
JoshLozensky Oct 7, 2024
e8a5398
Update UiTests/Common/UiTestHelpers.cs
JoshLozensky Oct 7, 2024
7b7e203
Update UiTests/Common/UiTestHelpers.cs
JoshLozensky Oct 7, 2024
6e369fc
Addressing PR comments
JoshLozensky Oct 8, 2024
167b79e
addressed PR feedback
JoshLozensky Oct 8, 2024
0913e36
Update UiTests/Common/UiTestHelpers.cs
JoshLozensky Oct 8, 2024
1971a1a
Update UiTests/Common/UiTestHelpers.cs
JoshLozensky Oct 8, 2024
a41e865
Update UiTests/Common/UiTestHelpers.cs
JoshLozensky Oct 8, 2024
e27c8bb
Update UiTests/Common/UiTestHelpers.cs
JoshLozensky Oct 8, 2024
89854c4
added docstring
JoshLozensky Oct 8, 2024
61f33b1
Merge branch 'lozenksy/AddingUiTests' of https://github.com/Azure-Sam…
JoshLozensky Oct 8, 2024
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
4 changes: 2 additions & 2 deletions 1-WebApp-OIDC/1-3-AnyOrgOrPersonal/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "[Enter the domain of your tenant, e.g. contoso.onmicrosoft.com]",
Expand All @@ -15,4 +15,4 @@
}
},
"AllowedHosts": "*"
}
}
2 changes: 1 addition & 1 deletion 3-WebApp-multi-APIs/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@
},
"AllowedHosts": "*",
"GraphApiUrl": "https://graph.microsoft.com"
}
}
139 changes: 139 additions & 0 deletions UiTests/AnyOrgOrPersonalUiTest/AnyOrgOrPersonalTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

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

namespace MultipleApiUiTest
{
public class AnyOrgOrPersonalTest : IClassFixture<InstallPlaywrightBrowserFixture>
{
private const string SignOutPageUriPath = @"/MicrosoftIdentity/Account/SignedOut";
private const uint ClientPort = 44321;
private const string TraceFileClassName = "OpenIDConnect";
private const uint NumProcessRetries = 3;
private const string SampleSlnFileName = "1-3-AnyOrgOrPersonal.sln";
private readonly LocatorAssertionsToBeVisibleOptions _assertVisibleOptions = new() { Timeout = 25000 };
private readonly string _sampleAppPath = "1-WebApp-OIDC" + Path.DirectorySeparatorChar + "1-3-AnyOrgOrPersonal" + Path.DirectorySeparatorChar.ToString();
private readonly string _testAppsettingsPath = "UiTests" + Path.DirectorySeparatorChar + "AnyOrgOrPersonalUiTest" + Path.DirectorySeparatorChar.ToString() + TC.AppSetttingsDotJson;
private readonly string _testAssemblyLocation = typeof(AnyOrgOrPersonalTest).Assembly.Location;
private readonly ITestOutputHelper _output;

public AnyOrgOrPersonalTest(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 = false });
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.BuildSampleWithTestAppsettings(_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, TC.s_oidcWebAppExe, 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.OIDCUser);

// Initial sign in
_output.WriteLine("Starting web app sign-in flow.");
string email = labResponse.User.Upn;
await UiTestHelpers.NavigateToWebApp(uriWithPort, page);
await UiTestHelpers.EnterEmailAsync(page, email, _output);
await UiTestHelpers.EnterPasswordAsync(page, labResponse.User.GetOrFetchPassword(), _output);
await Assertions.Expect(page.GetByText("Integrating Azure AD V2")).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();
}
}

}
}
32 changes: 32 additions & 0 deletions UiTests/AnyOrgOrPersonalUiTest/AnyOrgOrPersonalUiTest.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<!--<TargetFrameworks Condition="'$(TargetNet9)'== 'True'">$(TargetFrameworks); net9.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>
18 changes: 18 additions & 0 deletions UiTests/AnyOrgOrPersonalUiTest/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "msidlab3.onmicrosoft.com",
"TenantId": "8e44f19d-bbab-4a82-b76b-4cd0a6fbc97a",
"ClientId": "d9cde0be-ad97-41e6-855e-2f85136671c1",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath": "/signout-callback-oidc"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
32 changes: 32 additions & 0 deletions UiTests/Common/Common.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</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>
<Using Include="Xunit" />
</ItemGroup>

</Project>
37 changes: 37 additions & 0 deletions UiTests/Common/TestConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Common
{
public static class TestConstants
{
public const string AppSetttingsDotJson = "appsettings.json";
public const string ClientFilePrefix = "client_";
public const string EmailText = "Email";
public const string Headless = "headless";
public const string HeaderText = "Header";
public const string HttpStarColon = "http://*:";
public const string HttpsStarColon = "https://*:";
public const string KestrelEndpointEnvVar = "Kestrel:Endpoints:Http:Url";
public const string LocalhostUrl = @"https://localhost:";
public const string OIDCUser = "[email protected]";
public const string PasswordText = "Password";
public const string ServerFilePrefix = "server_";
public const string TodoTitle1 = "Testing create todo item";
public const string TodoTitle2 = "Testing edit todo item";
public const string WebAppCrashedString = $"The web app process has exited prematurely.";

public static readonly string s_oidcWebAppExe = Path.DirectorySeparatorChar.ToString() + "WebApp-OpenIDConnect-DotNet.exe";
public static readonly string s_oidcWebAppPath = Path.DirectorySeparatorChar.ToString() + "WebApp-OpenIDConnect";
public static readonly string s_todoListClientExe = Path.DirectorySeparatorChar.ToString() + "TodoListClient.exe";
public static readonly string s_todoListClientPath = Path.DirectorySeparatorChar.ToString() + "Client";
public static readonly string s_todoListServiceExe = Path.DirectorySeparatorChar.ToString() + "TodoListService.exe";
public static readonly string s_todoListServicePath = Path.DirectorySeparatorChar.ToString() + "TodoListService";
}
}
Loading
Loading