1+ // Copyright (c) Microsoft Corporation. All rights reserved.
2+ // Licensed under the MIT License.
3+
4+ using Azure . Identity ;
5+ using Common ;
6+ using Microsoft . Identity . Lab . Api ;
7+ using Microsoft . Playwright ;
8+ using System ;
9+ using System . Collections . Generic ;
10+ using System . Diagnostics ;
11+ using System . IO ;
12+ using System . Runtime . Versioning ;
13+ using System . Threading . Tasks ;
14+ using Xunit ;
15+ using Xunit . Abstractions ;
16+ using TC = Common . TestConstants ;
17+
18+ namespace B2CUiTest
19+ {
20+ public class B2CUiTest : IClassFixture < InstallPlaywrightBrowserFixture >
21+ {
22+ private const string KeyvaultEmailName = "IdWeb-B2C-user" ;
23+ private const string KeyvaultPasswordName = "IdWeb-B2C-password" ;
24+ private const string KeyvaultClientSecretName = "IdWeb-B2C-Client-ClientSecret" ;
25+ private const string NameOfUser = "unknown" ;
26+ private const uint ProcessStartupRetryNum = 3 ;
27+ private const string SampleSolutionFileName = "4-2-B2C-Secured-API.sln" ;
28+ private const uint TodoListClientPort = 5000 ;
29+ private const uint TodoListServicePort = 44332 ;
30+ private const string TraceClassName = "B2C-Login" ;
31+
32+ private readonly LocatorAssertionsToBeVisibleOptions _assertVisibleOptions = new ( ) { Timeout = 25000 } ;
33+ private readonly string _sampleClientAppPath ;
34+ private readonly string _samplePath = Path . Join ( "4-WebApp-your-API" , "4-2-B2C" ) ;
35+ private readonly string _sampleServiceAppPath ;
36+ private readonly Uri _keyvaultUri = new ( "https://webappsapistests.vault.azure.net" ) ;
37+ private readonly ITestOutputHelper _output ;
38+ private readonly string _testAssemblyLocation = typeof ( B2CUiTest ) . Assembly . Location ;
39+
40+ public B2CUiTest ( ITestOutputHelper output )
41+ {
42+ _output = output ;
43+ _sampleClientAppPath = Path . Join ( _samplePath , TC . s_todoListClientPath ) ;
44+ _sampleServiceAppPath = Path . Join ( _samplePath , TC . s_todoListServicePath ) ;
45+ }
46+
47+ [ Fact ]
48+ [ SupportedOSPlatform ( "windows" ) ]
49+ public async Task B2C_ValidCreds_LoginLogout ( )
50+ {
51+ // Web app and api environmental variable setup.
52+ Dictionary < string , Process > ? processes = null ;
53+ DefaultAzureCredential azureCred = new ( ) ;
54+ string clientSecret = await UiTestHelpers . GetValueFromKeyvaultWitDefaultCreds ( _keyvaultUri , KeyvaultClientSecretName , azureCred ) ;
55+ var serviceEnvVars = new Dictionary < string , string >
56+ {
57+ { "ASPNETCORE_ENVIRONMENT" , "Development" } ,
58+ { TC . KestrelEndpointEnvVar , TC . HttpStarColon + TodoListServicePort }
59+ } ;
60+ var clientEnvVars = new Dictionary < string , string >
61+ {
62+ { "ASPNETCORE_ENVIRONMENT" , "Development" } ,
63+ { "AzureAdB2C__ClientSecret" , clientSecret } ,
64+ { TC . KestrelEndpointEnvVar , TC . HttpsStarColon + TodoListClientPort }
65+ } ;
66+
67+ // Get email and password from keyvault.
68+ string email = await UiTestHelpers . GetValueFromKeyvaultWitDefaultCreds ( _keyvaultUri , KeyvaultEmailName , azureCred ) ;
69+ string password = await UiTestHelpers . GetValueFromKeyvaultWitDefaultCreds ( _keyvaultUri , KeyvaultPasswordName , azureCred ) ;
70+
71+ // Playwright setup. To see browser UI, set 'Headless = false'.
72+ const string TraceFileName = TraceClassName + "_TodoAppFunctionsCorrectly" ;
73+ using IPlaywright playwright = await Playwright . CreateAsync ( ) ;
74+ IBrowser browser = await playwright . Chromium . LaunchAsync ( new ( ) { Headless = true } ) ;
75+ IBrowserContext context = await browser . NewContextAsync ( new BrowserNewContextOptions { IgnoreHTTPSErrors = true } ) ;
76+ await context . Tracing . StartAsync ( new ( ) { Screenshots = true , Snapshots = true , Sources = true } ) ;
77+
78+ try
79+ {
80+ UiTestHelpers . BuildSampleUsingSampleAppsettings ( _testAssemblyLocation , _samplePath , SampleSolutionFileName ) ;
81+
82+ // Start the web app and api processes.
83+ // The delay before starting client prevents transient devbox issue where the client fails to load the first time after rebuilding.
84+ var clientProcessOptions = new ProcessStartOptions ( _testAssemblyLocation , _sampleClientAppPath , TC . s_todoListClientExe , clientEnvVars ) ; // probs need to add client specific path
85+ var serviceProcessOptions = new ProcessStartOptions ( _testAssemblyLocation , _sampleServiceAppPath , TC . s_todoListServiceExe , serviceEnvVars ) ;
86+
87+ UiTestHelpers . StartAndVerifyProcessesAreRunning ( [ serviceProcessOptions , clientProcessOptions ] , out processes , ProcessStartupRetryNum ) ;
88+
89+ // Navigate to web app the retry logic ensures the web app has time to start up to establish a connection.
90+ IPage page = await context . NewPageAsync ( ) ;
91+ uint InitialConnectionRetryCount = 5 ;
92+ while ( InitialConnectionRetryCount > 0 )
93+ {
94+ try
95+ {
96+ await page . GotoAsync ( TC . LocalhostUrl + TodoListClientPort ) ;
97+ break ;
98+ }
99+ catch ( PlaywrightException )
100+ {
101+ await Task . Delay ( 1000 ) ;
102+ InitialConnectionRetryCount -- ;
103+ if ( InitialConnectionRetryCount == 0 ) { throw ; }
104+ }
105+ }
106+ LabResponse labResponse = await LabUserHelper . GetB2CLocalAccountAsync ( ) ;
107+
108+ // Initial sign in
109+ _output . WriteLine ( "Starting web app sign-in flow." ) ;
110+ ILocator emailEntryBox = page . GetByPlaceholder ( "Email Address" ) ;
111+ await emailEntryBox . FillAsync ( email ) ;
112+ await emailEntryBox . PressAsync ( "Tab" ) ;
113+ await page . GetByPlaceholder ( "Password" ) . FillAsync ( password ) ;
114+ await page . GetByRole ( AriaRole . Button , new ( ) { Name = "Sign in" } ) . ClickAsync ( ) ;
115+ await Assertions . Expect ( page . GetByText ( "TodoList" ) ) . ToBeVisibleAsync ( _assertVisibleOptions ) ;
116+ await Assertions . Expect ( page . GetByText ( NameOfUser ) ) . ToBeVisibleAsync ( _assertVisibleOptions ) ;
117+ _output . WriteLine ( "Web app sign-in flow successful." ) ;
118+
119+ // Sign out
120+ _output . WriteLine ( "Starting web app sign-out flow." ) ;
121+ await page . GetByRole ( AriaRole . Link , new ( ) { Name = "Sign out" } ) . ClickAsync ( ) ;
122+ _output . WriteLine ( "Signing out ..." ) ;
123+ await Assertions . Expect ( page . GetByText ( "You have successfully signed out." ) ) . ToBeVisibleAsync ( _assertVisibleOptions ) ;
124+ await Assertions . Expect ( page . GetByText ( NameOfUser ) ) . ToBeHiddenAsync ( ) ;
125+ _output . WriteLine ( "Web app sign out successful." ) ;
126+ }
127+ catch ( Exception ex )
128+ {
129+ Assert . Fail ( $ "the UI automation failed: { ex } output: { ex . Message } .") ;
130+ }
131+ finally
132+ {
133+ // End all processes.
134+ UiTestHelpers . EndProcesses ( processes ) ;
135+
136+ // Stop tracing and export it into a zip archive.
137+ string path = UiTestHelpers . GetTracePath ( _testAssemblyLocation , TraceFileName ) ;
138+ await context . Tracing . StopAsync ( new ( ) { Path = path } ) ;
139+ _output . WriteLine ( $ "Trace data for { TraceFileName } recorded to { path } .") ;
140+
141+ // Close the browser and stop Playwright.
142+ await browser . CloseAsync ( ) ;
143+ playwright . Dispose ( ) ;
144+ }
145+ }
146+ }
147+ }
0 commit comments