Skip to content

Commit 43d6947

Browse files
authored
Feature/add async conditional waiting +semver: feature
* Implement asynchronous functions for Conditional wait - add WaitForAsync that returns Task<bool> - add WaitForTrueAsync that returns Task which may throw when awaited if the condition is not satisfied after the timeout * Update nuget package versions and Chrome version for Azure pipelines test execution
1 parent d83678d commit 43d6947

File tree

7 files changed

+239
-37
lines changed

7 files changed

+239
-37
lines changed

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Aquality.Selenium.Core.csproj

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@
4343
</None>
4444
</ItemGroup>
4545
<ItemGroup>
46-
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.5" />
47-
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.5" />
48-
<PackageReference Include="NLog" Version="4.7.2" />
46+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.8" />
47+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.8" />
48+
<PackageReference Include="NLog" Version="4.7.5" />
4949
<PackageReference Include="Selenium.Support" Version="3.141.0" />
5050
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
5151
</ItemGroup>

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Aquality.Selenium.Core.xml

+52-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Waitings/ConditionalWait.cs

+75-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Diagnostics;
99
using System.Linq;
1010
using System.Threading;
11+
using System.Threading.Tasks;
1112

1213
namespace Aquality.Selenium.Core.Waitings
1314
{
@@ -79,6 +80,44 @@ public bool WaitFor(Func<bool> condition, TimeSpan? timeout = null, TimeSpan? po
7980
}, new List<Type> { typeof(TimeoutException) });
8081
}
8182

83+
/// <summary>
84+
/// Wait for some condition asynchronously within timeout.
85+
/// </summary>
86+
/// <param name="condition">Predicate for waiting</param>
87+
/// <param name="timeout">Condition timeout. Default value is <see cref="ITimeoutConfiguration.Condition"/></param>
88+
/// <param name="pollingInterval">Condition check interval. Default value is <see cref="ITimeoutConfiguration.PollingInterval"/></param>
89+
/// <param name="exceptionsToIgnore">Possible exceptions that have to be ignored. </param>
90+
/// <returns>A task that returns true if condition satisfied and false otherwise.</returns>
91+
public Task<bool> WaitForAsync(Func<bool> condition, TimeSpan? timeout = null, TimeSpan? pollingInterval = null, IList<Type> exceptionsToIgnore = null)
92+
{
93+
if (condition == null)
94+
{
95+
throw new ArgumentNullException(nameof(condition), "condition cannot be null");
96+
}
97+
var waitTimeout = ResolveConditionTimeout(timeout);
98+
var checkInterval = ResolvePollingInterval(pollingInterval);
99+
return WaitForAsyncCore(condition, exceptionsToIgnore, waitTimeout, checkInterval);
100+
}
101+
102+
/// <summary>
103+
/// Wait for some condition asynchronously within timeout.
104+
/// </summary>
105+
/// <param name="condition">Predicate for waiting</param>
106+
/// <param name="timeout">Condition timeout. Default value is <see cref="ITimeoutConfiguration.Condition"/></param>
107+
/// <param name="pollingInterval">Condition check interval. Default value is <see cref="ITimeoutConfiguration.PollingInterval"/></param>
108+
/// <param name="message">Part of error message in case of Timeout exception</param>
109+
/// <param name="exceptionsToIgnore">Possible exceptions that have to be ignored. </param>
110+
/// <exception cref="TimeoutException">Throws when timeout exceeded and condition not satisfied, if only the task is awaited.</exception>
111+
/// <returns>A task that throws a <see cref="TimeoutException"/> if condition is not satisfied after the timeout.</returns>
112+
public async Task WaitForTrueAsync(Func<bool> condition, TimeSpan? timeout = null, TimeSpan? pollingInterval = null, string message = null, IList<Type> exceptionsToIgnore = null)
113+
{
114+
var waitTimeout = ResolveConditionTimeout(timeout);
115+
if (!await WaitForAsync(condition, waitTimeout, pollingInterval, exceptionsToIgnore))
116+
{
117+
throw GetTimeoutException(waitTimeout, message);
118+
}
119+
}
120+
82121
/// <summary>
83122
/// Wait for some condition within timeout.
84123
/// </summary>
@@ -107,19 +146,24 @@ public void WaitForTrue(Func<bool> condition, TimeSpan? timeout = null, TimeSpan
107146

108147
if (stopwatch.Elapsed > waitTimeout)
109148
{
110-
var exceptionMessage = $"Timed out after {waitTimeout.TotalSeconds} seconds";
111-
if (!string.IsNullOrEmpty(message))
112-
{
113-
exceptionMessage += $": {message}";
114-
}
115-
116-
throw new TimeoutException(exceptionMessage);
149+
throw GetTimeoutException(waitTimeout, message);
117150
}
118151

119152
Thread.Sleep(checkInterval);
120153
}
121154
}
122155

156+
private TimeoutException GetTimeoutException(TimeSpan waitTimeout, string message)
157+
{
158+
var exceptionMessage = $"Timed out after {waitTimeout.TotalSeconds} seconds";
159+
if (!string.IsNullOrEmpty(message))
160+
{
161+
exceptionMessage += $": {message}";
162+
}
163+
164+
return new TimeoutException(exceptionMessage);
165+
}
166+
123167
private bool IsConditionSatisfied(Func<bool> condition, IList<Type> exceptionsToIgnore)
124168
{
125169
try
@@ -146,5 +190,29 @@ private TimeSpan ResolvePollingInterval(TimeSpan? pollingInterval)
146190
{
147191
return pollingInterval ?? timeoutConfiguration.PollingInterval;
148192
}
193+
194+
private async Task<bool> WaitForAsyncCore(Func<bool> condition, IList<Type> exceptionsToIgnore, TimeSpan waitTimeout, TimeSpan checkInterval)
195+
{
196+
using (CancellationTokenSource cts = new CancellationTokenSource())
197+
{
198+
var token = cts.Token;
199+
var waitTask = Task.Run(async () =>
200+
{
201+
while (!IsConditionSatisfied(condition, exceptionsToIgnore ?? new List<Type>()))
202+
{
203+
await Task.Delay(checkInterval);
204+
}
205+
},
206+
token);
207+
var finishedTask = await Task.WhenAny(waitTask, Task.Delay(waitTimeout, token));
208+
cts.Cancel();
209+
var result = finishedTask == waitTask;
210+
if (result && waitTask.Exception != null)
211+
{
212+
throw waitTask.Exception.InnerExceptions.Count == 1 ? waitTask.Exception.InnerException : waitTask.Exception;
213+
}
214+
return result;
215+
}
216+
}
149217
}
150218
}

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Waitings/IConditionalWait.cs

+34-11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using OpenQA.Selenium;
33
using System;
44
using System.Collections.Generic;
5+
using System.Threading.Tasks;
56

67
namespace Aquality.Selenium.Core.Waitings
78
{
@@ -10,16 +11,6 @@ namespace Aquality.Selenium.Core.Waitings
1011
/// </summary>
1112
public interface IConditionalWait
1213
{
13-
/// <summary>
14-
/// Wait for some condition within timeout.
15-
/// </summary>
16-
/// <param name="condition">Predicate for waiting</param>
17-
/// <param name="timeout">Condition timeout. Default value is <see cref="ITimeoutConfiguration.Condition"/></param>
18-
/// <param name="pollingInterval">Condition check interval. Default value is <see cref="ITimeoutConfiguration.PollingInterval"/></param>
19-
/// <param name="exceptionsToIgnore">Possible exceptions that have to be ignored. </param>
20-
/// <returns>True if condition satisfied and false otherwise.</returns>
21-
bool WaitFor(Func<bool> condition, TimeSpan? timeout = null, TimeSpan? pollingInterval = null, IList<Type> exceptionsToIgnore = null);
22-
2314
/// <summary>
2415
/// Wait for some object from condition with timeout using Selenium WebDriver.
2516
/// </summary>
@@ -33,6 +24,26 @@ public interface IConditionalWait
3324
/// <exception cref="WebDriverTimeoutException">Throws when timeout exceeded and condition not satisfied.</exception>
3425
T WaitFor<T>(Func<IWebDriver, T> condition, TimeSpan? timeout = null, TimeSpan? pollingInterval = null, string message = null, IList<Type> exceptionsToIgnore = null);
3526

27+
/// <summary>
28+
/// Wait for some condition within timeout.
29+
/// </summary>
30+
/// <param name="condition">Predicate for waiting</param>
31+
/// <param name="timeout">Condition timeout. Default value is <see cref="ITimeoutConfiguration.Condition"/></param>
32+
/// <param name="pollingInterval">Condition check interval. Default value is <see cref="ITimeoutConfiguration.PollingInterval"/></param>
33+
/// <param name="exceptionsToIgnore">Possible exceptions that have to be ignored. </param>
34+
/// <returns>True if condition satisfied and false otherwise.</returns>
35+
bool WaitFor(Func<bool> condition, TimeSpan? timeout = null, TimeSpan? pollingInterval = null, IList<Type> exceptionsToIgnore = null);
36+
37+
/// <summary>
38+
/// Wait for some condition asynchronously within timeout.
39+
/// </summary>
40+
/// <param name="condition">Predicate for waiting</param>
41+
/// <param name="timeout">Condition timeout. Default value is <see cref="ITimeoutConfiguration.Condition"/></param>
42+
/// <param name="pollingInterval">Condition check interval. Default value is <see cref="ITimeoutConfiguration.PollingInterval"/></param>
43+
/// <param name="exceptionsToIgnore">Possible exceptions that have to be ignored. </param>
44+
/// <returns>A task that returns true if condition satisfied and false otherwise.</returns>
45+
Task<bool> WaitForAsync(Func<bool> condition, TimeSpan? timeout = null, TimeSpan? pollingInterval = null, IList<Type> exceptionsToIgnore = null);
46+
3647
/// <summary>
3748
/// Wait for some condition within timeout.
3849
/// </summary>
@@ -43,5 +54,17 @@ public interface IConditionalWait
4354
/// <param name="exceptionsToIgnore">Possible exceptions that have to be ignored. </param>
4455
/// <exception cref="TimeoutException">Throws when timeout exceeded and condition not satisfied.</exception>
4556
void WaitForTrue(Func<bool> condition, TimeSpan? timeout = null, TimeSpan? pollingInterval = null, string message = null, IList<Type> exceptionsToIgnore = null);
57+
58+
/// <summary>
59+
/// Wait for some condition asynchronously within timeout.
60+
/// </summary>
61+
/// <param name="condition">Predicate for waiting</param>
62+
/// <param name="timeout">Condition timeout. Default value is <see cref="ITimeoutConfiguration.Condition"/></param>
63+
/// <param name="pollingInterval">Condition check interval. Default value is <see cref="ITimeoutConfiguration.PollingInterval"/></param>
64+
/// <param name="message">Part of error message in case of Timeout exception</param>
65+
/// <param name="exceptionsToIgnore">Possible exceptions that have to be ignored. </param>
66+
/// <exception cref="TimeoutException">Throws when timeout exceeded and condition not satisfied, if only the task is awaited.</exception>
67+
/// <returns>A task that throws a <see cref="TimeoutException"/> if condition is not satisfied after the timeout.</returns>
68+
Task WaitForTrueAsync(Func<bool> condition, TimeSpan? timeout = null, TimeSpan? pollingInterval = null, string message = null, IList<Type> exceptionsToIgnore = null);
4669
}
47-
}
70+
}

0 commit comments

Comments
 (0)