Skip to content

Commit 1fc4060

Browse files
authored
Feature/60 action retrier [+semver: major] (#66)
* Add ActionRetrier * Add tests for ActionRetrier and ElementActionRetrier * Fix warningns, update tests * Modify pipeline * Fix tests * Update RetrierTests * Set default value for handledExceptions * Update ActionRetrier and ElementActionRetrier interfaces * Fix tests * Rename property * Check pipeline * Fix sonar code smells * Add setter to HandledExceptions property in ElementActionRetrier
1 parent fe60e68 commit 1fc4060

File tree

9 files changed

+508
-143
lines changed

9 files changed

+508
-143
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using Aquality.Selenium.Core.Configurations;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Threading;
6+
7+
namespace Aquality.Selenium.Core.Utilities
8+
{
9+
public class ActionRetrier : IActionRetrier
10+
{
11+
private readonly IRetryConfiguration retryConfiguration;
12+
13+
/// <summary>
14+
/// Instantiates the class using retry configuration.
15+
/// </summary>
16+
/// <param name="retryConfiguration">Retry configuration.</param>
17+
public ActionRetrier(IRetryConfiguration retryConfiguration)
18+
{
19+
this.retryConfiguration = retryConfiguration;
20+
}
21+
22+
/// <summary>
23+
/// Retries the action when one of the handledExceptions occures.
24+
/// </summary>
25+
/// <param name="action">Action to be applied.</param>
26+
/// <param name="handledExceptions">Exceptions to be handled.</param>
27+
public virtual void DoWithRetry(Action action, IEnumerable<Type> handledExceptions = null)
28+
{
29+
DoWithRetry(() =>
30+
{
31+
action();
32+
return true;
33+
}, handledExceptions);
34+
}
35+
36+
/// <summary>
37+
/// Retries the action when one of the handledExceptions occures.
38+
/// </summary>
39+
/// <typeparam name="T">Return type of function.</typeparam>
40+
/// <param name="function">Function to be applied.</param>
41+
/// <param name="handledExceptions">Exceptions to be handled.</param>
42+
/// <returns>Result of the function.</returns>
43+
public virtual T DoWithRetry<T>(Func<T> function, IEnumerable<Type> handledExceptions = null)
44+
{
45+
var exceptionsToHandle = handledExceptions ?? new List<Type>();
46+
var retryAttemptsLeft = retryConfiguration.Number;
47+
var actualInterval = retryConfiguration.PollingInterval;
48+
var result = default(T);
49+
50+
while (retryAttemptsLeft >= 0)
51+
{
52+
try
53+
{
54+
result = function();
55+
break;
56+
}
57+
catch (Exception exception)
58+
{
59+
if (IsExceptionHandled(exceptionsToHandle, exception) && retryAttemptsLeft != 0)
60+
{
61+
Thread.Sleep(actualInterval);
62+
retryAttemptsLeft--;
63+
}
64+
else
65+
{
66+
throw;
67+
}
68+
}
69+
}
70+
return result;
71+
}
72+
73+
/// <summary>
74+
/// Decides should the occured exception be handled (ignored during the retry) or not.
75+
/// </summary>
76+
/// <param name="handledExceptions">Exceptions to be handled.</param>
77+
/// <param name="exception">Exception to proceed.</param>
78+
/// <returns>True if the exception should be ignored, false otherwise.</returns>
79+
protected virtual bool IsExceptionHandled(IEnumerable<Type> handledExceptions, Exception exception)
80+
{
81+
return handledExceptions.Any(type => type.IsAssignableFrom(exception.GetType()));
82+
}
83+
}
84+
}

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Utilities/ElementActionRetrier.cs

Lines changed: 18 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,93 +2,57 @@
22
using OpenQA.Selenium;
33
using System;
44
using System.Collections.Generic;
5-
using System.Linq;
6-
using System.Threading;
75

86
namespace Aquality.Selenium.Core.Utilities
97
{
108
/// <summary>
119
/// Retries an action or function when <see cref="HandledExceptions"/> occures.
1210
/// </summary>
13-
public class ElementActionRetrier : IElementActionRetrier
11+
public class ElementActionRetrier : ActionRetrier, IElementActionRetrier
1412
{
15-
private readonly IRetryConfiguration retryConfiguration;
16-
1713
/// <summary>
1814
/// Instantiates the class using retry configuration.
1915
/// </summary>
2016
/// <param name="retryConfiguration">Retry configuration.</param>
21-
/// <param name="handledExceptions">Exceptions to be handled.
22-
/// If set to null, <see cref="StaleElementReferenceException"/> and <see cref="InvalidElementStateException"/> will be handled.</param>
23-
public ElementActionRetrier(IRetryConfiguration retryConfiguration, IList<Type> handledExceptions = null)
17+
public ElementActionRetrier(IRetryConfiguration retryConfiguration) : base(retryConfiguration)
2418
{
25-
this.retryConfiguration = retryConfiguration;
26-
HandledExceptions = handledExceptions ?? new List<Type> { typeof(StaleElementReferenceException), typeof(InvalidElementStateException) };
19+
HandledExceptions = new List<Type>
20+
{
21+
typeof(StaleElementReferenceException),
22+
typeof(InvalidElementStateException)
23+
};
2724
}
2825

2926
/// <summary>
3027
/// Exceptions to be ignored during action retrying.
3128
/// Set by the constructor parameter.
32-
/// If were not passed to constructor, <see cref="StaleElementReferenceException"/> and <see cref="InvalidElementStateException"/> will be handled.
33-
/// </summary>
34-
public virtual IList<Type> HandledExceptions { get; }
35-
36-
/// <summary>
37-
/// Decides should the occured exception be handled (ignored during the retry) or not.
29+
/// If were not passed to constructor, <see cref="StaleElementReferenceException"/>
30+
/// and <see cref="InvalidElementStateException"/> will be handled.
3831
/// </summary>
39-
/// <param name="exception">Exception to proceed.</param>
40-
/// <returns>True if the exception should be ignored, false otherwise.</returns>
41-
protected virtual bool IsExceptionHandled(Exception exception)
42-
{
43-
return HandledExceptions.Any(type => type.IsAssignableFrom(exception.GetType()));
44-
}
32+
public virtual IEnumerable<Type> HandledExceptions { get; set; }
4533

4634
/// <summary>
4735
/// Retries the action when the handled exception <see cref="HandledExceptions"/> occures.
4836
/// </summary>
4937
/// <param name="action">Action to be applied.</param>
50-
public virtual void DoWithRetry(Action action)
38+
/// <param name="handledExceptions">Exceptions to be handled.</param>
39+
public override void DoWithRetry(Action action, IEnumerable<Type> handledExceptions = null)
5140
{
52-
DoWithRetry(() =>
53-
{
54-
action();
55-
return true;
56-
});
41+
var exceptionsToHandle = handledExceptions ?? HandledExceptions;
42+
base.DoWithRetry(action, exceptionsToHandle);
5743
}
5844

5945
/// <summary>
6046
/// Retries the function when <see cref="HandledExceptions"/> occures.
6147
/// </summary>
6248
/// <typeparam name="T">Return type of function.</typeparam>
6349
/// <param name="function">Function to be applied.</param>
50+
/// <param name="handledExceptions">Exceptions to be handled.</param>
6451
/// <returns>Result of the function.</returns>
65-
public virtual T DoWithRetry<T>(Func<T> function)
52+
public override T DoWithRetry<T>(Func<T> function, IEnumerable<Type> handledExceptions = null)
6653
{
67-
var retryAttemptsLeft = retryConfiguration.Number;
68-
var actualInterval = retryConfiguration.PollingInterval;
69-
var result = default(T);
70-
while (retryAttemptsLeft >= 0)
71-
{
72-
try
73-
{
74-
result = function();
75-
break;
76-
}
77-
catch (Exception exception)
78-
{
79-
if (IsExceptionHandled(exception) && retryAttemptsLeft != 0)
80-
{
81-
Thread.Sleep(actualInterval);
82-
retryAttemptsLeft--;
83-
}
84-
else
85-
{
86-
throw;
87-
}
88-
}
89-
}
90-
91-
return result;
54+
var exceptionsToHandle = handledExceptions ?? HandledExceptions;
55+
return base.DoWithRetry(function, exceptionsToHandle);
9256
}
9357
}
9458
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace Aquality.Selenium.Core.Utilities
5+
{
6+
/// <summary>
7+
/// Retries an action or function when handledExceptions occurs.
8+
/// </summary>
9+
public interface IActionRetrier
10+
{
11+
/// <summary>
12+
/// Retries the action when one of the handledExceptions occures.
13+
/// </summary>
14+
/// <param name="action">Action to be applied.</param>
15+
/// <param name="handledExceptions">Exceptions to be handled.</param>
16+
void DoWithRetry(Action action, IEnumerable<Type> handledExceptions = null);
17+
18+
/// <summary>
19+
/// Retries the action when one of the handledExceptions occures.
20+
/// </summary>
21+
/// <typeparam name="T">Return type of function.</typeparam>
22+
/// <param name="function">Function to be applied.</param>
23+
/// <param name="handledExceptions">Exceptions to be handled.</param>
24+
/// <returns>Result of the function.</returns>
25+
T DoWithRetry<T>(Func<T> function, IEnumerable<Type> handledExceptions = null);
26+
}
27+
}

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Utilities/IElementActionRetrier.cs

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,15 @@
55
namespace Aquality.Selenium.Core.Utilities
66
{
77
/// <summary>
8-
/// Retries an action or function when <see cref="HandledExceptions"/> occures.
8+
/// Retries an action or function when one of <see cref="HandledExceptions"/> occures.
99
/// </summary>
10-
public interface IElementActionRetrier
10+
public interface IElementActionRetrier : IActionRetrier
1111
{
1212
/// <summary>
1313
/// Exceptions to be ignored during action retrying.
14-
/// By the default implementation, <see cref="StaleElementReferenceException"/> and <see cref="InvalidElementStateException"/> are handled.
14+
/// By the default implementation, <see cref="StaleElementReferenceException"/>
15+
/// and <see cref="InvalidElementStateException"/> are handled.
1516
/// </summary>
16-
IList<Type> HandledExceptions { get; }
17-
18-
/// <summary>
19-
/// Retries the action when the handled exception <see cref="HandledExceptions"/> occures.
20-
/// </summary>
21-
/// <param name="action">Action to be applied.</param>
22-
void DoWithRetry(Action action);
23-
24-
/// <summary>
25-
/// Retries the function when the handled exception <see cref="HandledExceptions"/> occures.
26-
/// </summary>
27-
/// <typeparam name="T">Return type of function.</typeparam>
28-
/// <param name="function">Function to be applied.</param>
29-
/// <returns>Result of the function.</returns>
30-
T DoWithRetry<T>(Func<T> function);
17+
IEnumerable<Type> HandledExceptions { get; set; }
3118
}
3219
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using Aquality.Selenium.Core.Utilities;
2+
using NUnit.Framework;
3+
using System;
4+
using System.Collections.Generic;
5+
6+
namespace Aquality.Selenium.Core.Tests.Utilities
7+
{
8+
public class ActionRetrierTests : RetrierTests
9+
{
10+
private IEnumerable<Type> HandledExceptions => new List<Type> { typeof(InvalidOperationException) };
11+
12+
private IActionRetrier ActionRetrier => new ActionRetrier(RetryConfiguration);
13+
14+
[Test]
15+
public void Retrier_ShouldWork_OnceIfMethodSucceeded()
16+
{
17+
Retrier_ShouldWork_OnceIfMethodSucceeded(() => ActionRetrier.DoWithRetry(() => Console.WriteLine(1), new List<Type>()));
18+
}
19+
20+
[Test]
21+
public void Retrier_ShouldWork_OnceIfMethodSucceeded_WithReturnValue()
22+
{
23+
Retrier_ShouldWork_OnceIfMethodSucceeded(() => ActionRetrier.DoWithRetry(() => string.Empty, new List<Type>()));
24+
}
25+
26+
[Test]
27+
public void Retrier_ShouldWait_PollingTimeBetweenMethodCalls()
28+
{
29+
var throwException = true;
30+
Retrier_ShouldWait_PollingIntervalBetweenMethodsCall(() =>
31+
ActionRetrier.DoWithRetry(() => {
32+
if (throwException)
33+
{
34+
throwException = false;
35+
throw new InvalidOperationException();
36+
}
37+
}, HandledExceptions));
38+
}
39+
40+
[Test]
41+
public void Retrier_ShouldWait_PollingTimeBetweenMethodCalls_WithReturnValue()
42+
{
43+
var throwException = true;
44+
Retrier_ShouldWait_PollingIntervalBetweenMethodsCall(() =>
45+
ActionRetrier.DoWithRetry(() => {
46+
if (throwException)
47+
{
48+
throwException = false;
49+
throw new InvalidOperationException();
50+
}
51+
return string.Empty;
52+
}, HandledExceptions));
53+
}
54+
55+
[Test]
56+
public void Retrier_ShouldThrow_UnhandledException()
57+
{
58+
Assert.Throws<InvalidOperationException>(() => ActionRetrier.DoWithRetry(() => {
59+
throw new InvalidOperationException();
60+
}, new List<Type>()));
61+
}
62+
63+
[Test]
64+
public void Retrier_ShouldThrow_UnhandledException_WithReturnValue()
65+
{
66+
Assert.Throws<InvalidOperationException>(() => ActionRetrier.DoWithRetry(() => {
67+
throw new InvalidOperationException();
68+
#pragma warning disable CS0162 // Unreachable code detected
69+
return string.Empty;
70+
#pragma warning restore CS0162 // Unreachable code detected
71+
}, new List<Type>()));
72+
}
73+
74+
[Test]
75+
public void Retrier_ShouldWork_CorrectTimes()
76+
{
77+
var actualAttempts = 0;
78+
Retrier_ShouldWork_CorrectTimes(typeof(InvalidOperationException), ref actualAttempts, () =>
79+
ActionRetrier.DoWithRetry(() => {
80+
Logger.Info($"current attempt is {actualAttempts++}");
81+
throw new InvalidOperationException();
82+
}, HandledExceptions));
83+
}
84+
85+
[Test]
86+
public void Retrier_ShouldWork_CorrectTimes_WithReturnValue()
87+
{
88+
var actualAttempts = 0;
89+
Retrier_ShouldWork_CorrectTimes(typeof(InvalidOperationException), ref actualAttempts, () =>
90+
ActionRetrier.DoWithRetry(() => {
91+
Logger.Info($"current attempt is {actualAttempts++}");
92+
throw new InvalidOperationException();
93+
#pragma warning disable CS0162 // Unreachable code detected
94+
return string.Empty;
95+
#pragma warning restore CS0162 // Unreachable code detected
96+
}, HandledExceptions));
97+
}
98+
99+
[Test]
100+
public void Retrier_Should_ReturnValue()
101+
{
102+
var returnValue = string.Empty;
103+
Assert.AreEqual(returnValue, ActionRetrier.DoWithRetry(() => returnValue, new List<Type>()), "Retrier should return value");
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)