44
55public static class WebAppService
66{
7- public static async Task < ( Process webApp , int port ) > StartWebApp ( string projectPath , int ? port = null )
7+ public static async Task < ( Process webApp , int port ) > StartWebApp ( string projectPath , int ? port = null , TimeSpan ? startupTimeout = null )
88 {
99 port ??= GetAvailablePort ( ) ;
1010 Process process = StartProcess ( "dotnet" , $ "run --urls=http://localhost:{ port } ", projectPath ) ;
11- await Task . Delay ( 5000 ) ; // Allow app to start
11+
12+ await WaitForWebAppReadyAsync ( process , port . Value , startupTimeout ?? TimeSpan . FromSeconds ( 20 ) ) ;
1213
1314 return ( process , port . Value ) ;
1415 }
@@ -50,4 +51,48 @@ private static Process StartProcess(string fileName, string arguments, string wo
5051
5152 return process ;
5253 }
54+
55+ private static async Task WaitForWebAppReadyAsync ( Process process , int port , TimeSpan timeout )
56+ {
57+ var stopwatch = Stopwatch . StartNew ( ) ;
58+
59+ while ( stopwatch . Elapsed < timeout )
60+ {
61+ if ( process . HasExited )
62+ {
63+ throw new InvalidOperationException ( "The web application process exited before it started accepting requests." ) ;
64+ }
65+
66+ if ( await IsPortOpenAsync ( port ) )
67+ {
68+ return ;
69+ }
70+
71+ await Task . Delay ( 250 ) ;
72+ }
73+
74+ throw new TimeoutException ( $ "Timed out waiting for the web application to start listening on port { port } .") ;
75+ }
76+
77+ private static async Task < bool > IsPortOpenAsync ( int port )
78+ {
79+ try
80+ {
81+ using var client = new TcpClient ( ) ;
82+ var connectTask = client . ConnectAsync ( IPAddress . Loopback , port ) ;
83+ var completed = await Task . WhenAny ( connectTask , Task . Delay ( 500 ) ) ;
84+
85+ if ( completed == connectTask )
86+ {
87+ await connectTask ;
88+ return true ;
89+ }
90+ }
91+ catch ( SocketException )
92+ {
93+ // Port is not accepting connections yet
94+ }
95+
96+ return false ;
97+ }
5398}
0 commit comments