11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4- using System ;
5- using System . IO ;
64using System . Runtime . InteropServices ;
7- using System . Threading . Tasks ;
85using Microsoft . AspNetCore . BrowserTesting ;
6+ using Microsoft . Playwright ;
97using Templates . Test . Helpers ;
10- using Xunit ;
118
129namespace BlazorTemplates . Tests ;
1310
@@ -18,14 +15,19 @@ public abstract class BlazorTemplateTest : BrowserTestBase
1815 public BlazorTemplateTest ( ProjectFactoryFixture projectFactory )
1916 {
2017 ProjectFactory = projectFactory ;
21- Microsoft . Playwright . Program . Main ( new [ ] { "install" } ) ;
18+ Microsoft . Playwright . Program . Main ( [ "install" ] ) ;
2219 }
2320
2421 public ProjectFactoryFixture ProjectFactory { get ; set ; }
2522
2623 public abstract string ProjectType { get ; }
2724
28- protected async Task < Project > CreateBuildPublishAsync ( string auth = null , string [ ] args = null , string targetFramework = null , bool serverProject = false , bool onlyCreate = false )
25+ protected async Task < Project > CreateBuildPublishAsync (
26+ string auth = null ,
27+ string [ ] args = null ,
28+ string targetFramework = null ,
29+ Func < Project , Project > getTargetProject = null ,
30+ bool onlyCreate = false )
2931 {
3032 // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278
3133 Environment . SetEnvironmentVariable ( "EnableDefaultScopedCssItems" , "true" ) ;
@@ -38,21 +40,17 @@ protected async Task<Project> CreateBuildPublishAsync(string auth = null, string
3840
3941 await project . RunDotNetNewAsync ( ProjectType , auth : auth , args : args ) ;
4042
43+ project = getTargetProject ? . Invoke ( project ) ?? project ;
44+
4145 if ( ! onlyCreate )
4246 {
43- var targetProject = project ;
44- if ( serverProject )
45- {
46- targetProject = GetSubProject ( project , "Server" , $ "{ project . ProjectName } .Server") ;
47- }
48-
49- await targetProject . RunDotNetPublishAsync ( noRestore : false ) ;
47+ await project . RunDotNetPublishAsync ( noRestore : false ) ;
5048
5149 // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release
5250 // The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build
5351 // later, while the opposite is not true.
5452
55- await targetProject . RunDotNetBuildAsync ( ) ;
53+ await project . RunDotNetBuildAsync ( ) ;
5654 }
5755
5856 return project ;
@@ -83,6 +81,138 @@ public static bool TryValidateBrowserRequired(BrowserKind browserKind, bool isRe
8381 return isRequired ;
8482 }
8583
84+ protected async Task TestBasicInteractionInNewPageAsync (
85+ BrowserKind browserKind ,
86+ string listeningUri ,
87+ string appName ,
88+ BlazorTemplatePages pagesToExclude = BlazorTemplatePages . None ,
89+ bool usesAuth = false )
90+ {
91+ if ( ! BrowserManager . IsAvailable ( browserKind ) )
92+ {
93+ EnsureBrowserAvailable ( browserKind ) ;
94+ return ;
95+ }
96+
97+ await using var browser = await BrowserManager . GetBrowserInstance ( browserKind , BrowserContextInfo ) ;
98+ var page = await browser . NewPageAsync ( ) ;
99+
100+ Output . WriteLine ( $ "Opening browser at { listeningUri } ...") ;
101+ await page . GotoAsync ( listeningUri , new ( ) { WaitUntil = WaitUntilState . NetworkIdle } ) ;
102+
103+ await TestBasicInteractionAsync ( page , appName , pagesToExclude , usesAuth ) ;
104+
105+ await page . CloseAsync ( ) ;
106+ }
107+
108+ protected async Task TestBasicInteractionAsync (
109+ IPage page ,
110+ string appName ,
111+ BlazorTemplatePages pagesToExclude = BlazorTemplatePages . None ,
112+ bool usesAuth = false )
113+ {
114+ await page . WaitForSelectorAsync ( "nav" ) ;
115+
116+ if ( ! pagesToExclude . HasFlag ( BlazorTemplatePages . Home ) )
117+ {
118+ // Initially displays the home page
119+ await page . WaitForSelectorAsync ( "h1 >> text=Hello, world!" ) ;
120+
121+ Assert . Equal ( "Home" , ( await page . TitleAsync ( ) ) . Trim ( ) ) ;
122+ }
123+
124+ if ( ! pagesToExclude . HasFlag ( BlazorTemplatePages . Counter ) )
125+ {
126+ // Can navigate to the counter page
127+ await Task . WhenAll (
128+ page . WaitForNavigationAsync ( new ( ) { UrlString = "**/counter" } ) ,
129+ page . WaitForSelectorAsync ( "h1 >> text=Counter" ) ,
130+ page . WaitForSelectorAsync ( "p >> text=Current count: 0" ) ,
131+ page . ClickAsync ( "a[href=counter]" ) ) ;
132+
133+ // Clicking the counter button works
134+ await IncrementCounterAsync ( page ) ;
135+ }
136+
137+ if ( usesAuth )
138+ {
139+ await Task . WhenAll (
140+ page . WaitForNavigationAsync ( new ( ) { UrlString = "**/Identity/Account/Login**" , WaitUntil = WaitUntilState . NetworkIdle } ) ,
141+ page . ClickAsync ( "text=Log in" ) ) ;
142+
143+ await Task . WhenAll (
144+ page . WaitForSelectorAsync ( "[name=\" Input.Email\" ]" ) ,
145+ page . WaitForNavigationAsync ( new ( ) { UrlString = "**/Identity/Account/Register**" , WaitUntil = WaitUntilState . NetworkIdle } ) ,
146+ page . ClickAsync ( "text=Register as a new user" ) ) ;
147+
148+ var userName = $ "{ Guid . NewGuid ( ) } @example.com";
149+ var password = "[PLACEHOLDER]-1a" ;
150+
151+ await page . TypeAsync ( "[name=\" Input.Email\" ]" , userName ) ;
152+ await page . TypeAsync ( "[name=\" Input.Password\" ]" , password ) ;
153+ await page . TypeAsync ( "[name=\" Input.ConfirmPassword\" ]" , password ) ;
154+
155+ // We will be redirected to the RegisterConfirmation
156+ await Task . WhenAll (
157+ page . WaitForNavigationAsync ( new ( ) { UrlString = "**/Identity/Account/RegisterConfirmation**" , WaitUntil = WaitUntilState . NetworkIdle } ) ,
158+ page . ClickAsync ( "#registerSubmit" ) ) ;
159+
160+ // We will be redirected to the ConfirmEmail
161+ await Task . WhenAll (
162+ page . WaitForNavigationAsync ( new ( ) { UrlString = "**/Identity/Account/ConfirmEmail**" , WaitUntil = WaitUntilState . NetworkIdle } ) ,
163+ page . ClickAsync ( "text=Click here to confirm your account" ) ) ;
164+
165+ // Now we can login
166+ await page . ClickAsync ( "text=Login" ) ;
167+ await page . WaitForSelectorAsync ( "[name=\" Input.Email\" ]" ) ;
168+ await page . TypeAsync ( "[name=\" Input.Email\" ]" , userName ) ;
169+ await page . TypeAsync ( "[name=\" Input.Password\" ]" , password ) ;
170+ await page . ClickAsync ( "#login-submit" ) ;
171+
172+ // Need to navigate to fetch page
173+ await page . GotoAsync ( new Uri ( page . Url ) . GetLeftPart ( UriPartial . Authority ) ) ;
174+ Assert . Equal ( appName . Trim ( ) , ( await page . TitleAsync ( ) ) . Trim ( ) ) ;
175+ }
176+
177+ if ( ! pagesToExclude . HasFlag ( BlazorTemplatePages . Weather ) )
178+ {
179+ await page . ClickAsync ( "a[href=weather]" ) ;
180+ await page . WaitForSelectorAsync ( "h1 >> text=Weather" ) ;
181+
182+ // Asynchronously loads and displays the table of weather forecasts
183+ await page . WaitForSelectorAsync ( "table>tbody>tr" ) ;
184+ Assert . Equal ( 5 , await page . Locator ( "p+table>tbody>tr" ) . CountAsync ( ) ) ;
185+ }
186+
187+ static async Task IncrementCounterAsync ( IPage page )
188+ {
189+ // Allow multiple click attempts because some interactive render modes
190+ // won't be immediately available
191+ const int MaxIncrementAttempts = 5 ;
192+ const float IncrementTimeoutMilliseconds = 3000f ;
193+ for ( var i = 0 ; i < MaxIncrementAttempts ; i ++ )
194+ {
195+ await page . ClickAsync ( "p+button >> text=Click me" ) ;
196+ try
197+ {
198+ await page . WaitForSelectorAsync ( "p >> text=Current count: 1" , new ( )
199+ {
200+ Timeout = IncrementTimeoutMilliseconds ,
201+ } ) ;
202+
203+ // The counter successfully incremented, so we're done
204+ return ;
205+ }
206+ catch ( TimeoutException )
207+ {
208+ // The counter did not increment; try again
209+ }
210+ }
211+
212+ Assert . Fail ( $ "The counter did not increment after { MaxIncrementAttempts } attempts") ;
213+ }
214+ }
215+
86216 protected void EnsureBrowserAvailable ( BrowserKind browserKind )
87217 {
88218 Assert . False (
@@ -92,4 +222,14 @@ protected void EnsureBrowserAvailable(BrowserKind browserKind)
92222 out var errorMessage ) ,
93223 errorMessage ) ;
94224 }
225+
226+ [ Flags ]
227+ protected enum BlazorTemplatePages
228+ {
229+ None = 0 ,
230+ Home = 1 ,
231+ Counter = 2 ,
232+ Weather = 4 ,
233+ All = ~ 0 ,
234+ }
95235}
0 commit comments