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