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