diff --git a/2-WebApp-graph-user/2-5-HybridFlow/appsettings.json b/2-WebApp-graph-user/2-5-HybridFlow/appsettings.json
index 5321a590..1a4512ff 100644
Binary files a/2-WebApp-graph-user/2-5-HybridFlow/appsettings.json and b/2-WebApp-graph-user/2-5-HybridFlow/appsettings.json differ
diff --git a/UiTests/AnyOrgOrPersonalUiTest/AnyOrgOrPersonalTest.cs b/UiTests/AnyOrgOrPersonalUiTest/AnyOrgOrPersonalTest.cs
index 563f6218..3df45d55 100644
--- a/UiTests/AnyOrgOrPersonalUiTest/AnyOrgOrPersonalTest.cs
+++ b/UiTests/AnyOrgOrPersonalUiTest/AnyOrgOrPersonalTest.cs
@@ -134,6 +134,5 @@ public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds
                 playwright.Dispose();
             }
         }
-
     }
 }
\ No newline at end of file
diff --git a/UiTests/HybridFlowUiTest/HybridFlowTest.cs b/UiTests/HybridFlowUiTest/HybridFlowTest.cs
new file mode 100644
index 00000000..bdab75bb
--- /dev/null
+++ b/UiTests/HybridFlowUiTest/HybridFlowTest.cs
@@ -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();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/UiTests/HybridFlowUiTest/HybridFlowUiTest.csproj b/UiTests/HybridFlowUiTest/HybridFlowUiTest.csproj
new file mode 100644
index 00000000..56b7a9b5
--- /dev/null
+++ b/UiTests/HybridFlowUiTest/HybridFlowUiTest.csproj
@@ -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>
diff --git a/UiTests/HybridFlowUiTest/appsettings.json b/UiTests/HybridFlowUiTest/appsettings.json
new file mode 100644
index 00000000..5b999da1
--- /dev/null
+++ b/UiTests/HybridFlowUiTest/appsettings.json
@@ -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/"
+}
diff --git a/UiTests/UiTests.sln b/UiTests/UiTests.sln
index af7897cc..b8f997db 100644
--- a/UiTests/UiTests.sln
+++ b/UiTests/UiTests.sln
@@ -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