forked from SeleniumHQ/selenium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDefaultWait{T}.cs
220 lines (197 loc) · 9.16 KB
/
DefaultWait{T}.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// <copyright file="DefaultWait{T}.cs" company="Selenium Committers">
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading;
#nullable enable
namespace OpenQA.Selenium.Support.UI
{
/// <summary>
/// An implementation of the <see cref="IWait<T>"/> interface that may have its timeout and polling interval
/// configured on the fly.
/// </summary>
/// <typeparam name="T">The type of object on which the wait it to be applied.</typeparam>
public class DefaultWait<T> : IWait<T>
{
private readonly T input;
private readonly IClock clock;
private readonly List<Type> ignoredExceptions = new List<Type>();
/// <summary>
/// Initializes a new instance of the <see cref="DefaultWait<T>"/> class.
/// </summary>
/// <param name="input">The input value to pass to the evaluated conditions.</param>
public DefaultWait(T input)
: this(input, SystemClock.Instance)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DefaultWait<T>"/> class.
/// </summary>
/// <param name="input">The input value to pass to the evaluated conditions.</param>
/// <param name="clock">The clock to use when measuring the timeout.</param>
/// <exception cref="ArgumentNullException">If <paramref name="clock"/> or <paramref name="input"/> are <see langword="null"/>.</exception>
public DefaultWait(T input, IClock clock)
{
this.input = input ?? throw new ArgumentNullException(nameof(input), "input cannot be null"); ;
this.clock = clock ?? throw new ArgumentNullException(nameof(clock), "clock cannot be null"); ;
}
/// <summary>
/// Gets or sets how long to wait for the evaluated condition to be true. The default timeout is 500 milliseconds.
/// </summary>
public TimeSpan Timeout { get; set; } = DefaultSleepTimeout;
/// <summary>
/// Gets or sets how often the condition should be evaluated. The default timeout is 500 milliseconds.
/// </summary>
public TimeSpan PollingInterval { get; set; } = DefaultSleepTimeout;
/// <summary>
/// Gets or sets the message to be displayed when time expires.
/// </summary>
public string Message { get; set; } = string.Empty;
private static TimeSpan DefaultSleepTimeout => TimeSpan.FromMilliseconds(500);
/// <summary>
/// Configures this instance to ignore specific types of exceptions while waiting for a condition.
/// Any exceptions not whitelisted will be allowed to propagate, terminating the wait.
/// </summary>
/// <param name="exceptionTypes">The types of exceptions to ignore.</param>
public void IgnoreExceptionTypes(params Type[] exceptionTypes)
{
if (exceptionTypes == null)
{
throw new ArgumentNullException(nameof(exceptionTypes), "exceptionTypes cannot be null");
}
foreach (Type exceptionType in exceptionTypes)
{
if (!typeof(Exception).IsAssignableFrom(exceptionType))
{
throw new ArgumentException("All types to be ignored must derive from System.Exception", nameof(exceptionTypes));
}
}
this.ignoredExceptions.AddRange(exceptionTypes);
}
/// <summary>
/// Repeatedly applies this instance's input value to the given function until one of the following
/// occurs:
/// <para>
/// <list type="bullet">
/// <item>the function returns neither null nor false</item>
/// <item>the function throws an exception that is not in the list of ignored exception types</item>
/// <item>the timeout expires</item>
/// </list>
/// </para>
/// </summary>
/// <typeparam name="TResult">The delegate's expected return type.</typeparam>
/// <param name="condition">A delegate taking an object of type T as its parameter, and returning a TResult.</param>
/// <returns>The delegate's return value.</returns>
[return: NotNull]
public virtual TResult Until<TResult>(Func<T, TResult?> condition)
{
return Until(condition, CancellationToken.None);
}
/// <summary>
/// Repeatedly applies this instance's input value to the given function until one of the following
/// occurs:
/// <para>
/// <list type="bullet">
/// <item>the function returns neither null nor false</item>
/// <item>the function throws an exception that is not in the list of ignored exception types</item>
/// <item>the timeout expires</item>
/// </list>
/// </para>
/// </summary>
/// <typeparam name="TResult">The delegate's expected return type.</typeparam>
/// <param name="condition">A delegate taking an object of type T as its parameter, and returning a TResult.</param>
/// <param name="token">A cancellation token that can be used to cancel the wait.</param>
/// <returns>The delegate's return value.</returns>
[return: NotNull]
public virtual TResult Until<TResult>(Func<T, TResult?> condition, CancellationToken token)
{
if (condition == null)
{
throw new ArgumentNullException(nameof(condition), "condition cannot be null");
}
var resultType = typeof(TResult);
if ((resultType.IsValueType && resultType != typeof(bool)) || !typeof(object).IsAssignableFrom(resultType))
{
throw new ArgumentException($"Can only wait on an object or boolean response, tried to use type: {resultType}", nameof(condition));
}
Exception? lastException = null;
var endTime = this.clock.LaterBy(this.Timeout);
while (true)
{
token.ThrowIfCancellationRequested();
try
{
var result = condition(this.input);
if (resultType == typeof(bool))
{
if (result is true)
{
return result;
}
}
else
{
if (result != null)
{
return result;
}
}
}
catch (Exception ex)
{
if (!this.IsIgnoredException(ex))
{
throw;
}
lastException = ex;
}
// Check the timeout after evaluating the function to ensure conditions
// with a zero timeout can succeed.
if (!this.clock.IsNowBefore(endTime))
{
string timeoutMessage = string.Format(CultureInfo.InvariantCulture, "Timed out after {0} seconds", this.Timeout.TotalSeconds);
if (!string.IsNullOrEmpty(this.Message))
{
timeoutMessage += ": " + this.Message;
}
this.ThrowTimeoutException(timeoutMessage, lastException);
}
Thread.Sleep(this.PollingInterval);
}
}
/// <summary>
/// Throws a <see cref="WebDriverTimeoutException"/> with the given message.
/// </summary>
/// <param name="exceptionMessage">The message of the exception.</param>
/// <param name="lastException">The last exception thrown by the condition.</param>
/// <remarks>This method may be overridden to throw an exception that is
/// idiomatic for a particular test infrastructure.</remarks>
protected virtual void ThrowTimeoutException(string exceptionMessage, Exception? lastException)
{
throw new WebDriverTimeoutException(exceptionMessage, lastException);
}
private bool IsIgnoredException(Exception exception)
{
return this.ignoredExceptions.Any(type => type.IsAssignableFrom(exception.GetType()));
}
}
}