22
22
* SOFTWARE.
23
23
*/
24
24
25
+ using System . Reflection ;
25
26
using NUnit . Framework . Interfaces ;
26
27
using NUnit . Framework . Internal ;
27
28
using NUnit . Framework . Internal . Commands ;
@@ -36,14 +37,24 @@ namespace Microsoft.Playwright.Tests;
36
37
/// Enables decorating test facts with information about the corresponding test in the upstream repository.
37
38
/// </summary>
38
39
[ AttributeUsage ( AttributeTargets . Method , AllowMultiple = true ) ]
39
- public class PlaywrightTestAttribute : TestAttribute , IApplyToContext , IApplyToTest , IWrapSetUpTearDown
40
+ public class PlaywrightTestAttribute : TestAttribute , IWrapSetUpTearDown
40
41
{
41
- private readonly CancelAfterAttribute _cancelAfterAttribute = new ( TestConstants . DefaultTestTimeout ) ;
42
+ private readonly int ? _timeout ;
42
43
43
44
public PlaywrightTestAttribute ( )
44
45
{
45
46
}
46
47
48
+ public PlaywrightTestAttribute ( int timeout ) : this ( )
49
+ {
50
+ _timeout = timeout ;
51
+ }
52
+
53
+ public PlaywrightTestAttribute ( string fileName , string nameOfTest , int timeout ) : this ( fileName , nameOfTest )
54
+ {
55
+ _timeout = timeout ;
56
+ }
57
+
47
58
/// <summary>
48
59
/// Creates a new instance of the attribute.
49
60
/// </summary>
@@ -60,11 +71,6 @@ public PlaywrightTestAttribute(string fileName, string nameOfTest)
60
71
/// </summary>
61
72
public string FileName { get ; }
62
73
63
- /// <summary>
64
- /// Returns the trimmed file name.
65
- /// </summary>
66
- public string TrimmedName => FileName . Substring ( 0 , FileName . IndexOf ( '.' ) ) ;
67
-
68
74
/// <summary>
69
75
/// The name of the test, the decorated code is based on.
70
76
/// </summary>
@@ -75,29 +81,14 @@ public PlaywrightTestAttribute(string fileName, string nameOfTest)
75
81
/// </summary>
76
82
public string Describe { get ; }
77
83
78
- public void ApplyToContext ( TestExecutionContext context )
79
- {
80
- if ( context . TestCaseTimeout == 0 )
81
- {
82
- ( _cancelAfterAttribute as IApplyToContext ) . ApplyToContext ( context ) ;
83
- }
84
- }
85
-
86
- public new void ApplyToTest ( Test test )
87
- {
88
- base . ApplyToTest ( test ) ;
89
- if ( TestExecutionContext . CurrentContext . TestCaseTimeout == 0 )
90
- {
91
- _cancelAfterAttribute . ApplyToTest ( test ) ;
92
- }
93
- }
94
84
/// <summary>
95
85
/// Wraps the current test command in a <see cref="UnobservedTaskExceptionCommand"/>.
96
86
/// </summary>
97
87
/// <param name="command">the test command</param>
98
88
/// <returns>the wrapped test command</returns>
99
89
public TestCommand Wrap ( TestCommand command )
100
90
{
91
+ command = new TimeoutCommand ( command , _timeout ?? TestConstants . DefaultTestTimeout ) ;
101
92
if ( Environment . GetEnvironmentVariable ( "CI" ) != null )
102
93
{
103
94
command = new RetryCommand ( command , 3 ) ;
@@ -121,7 +112,7 @@ public override TestResult Execute(TestExecutionContext context)
121
112
try
122
113
{
123
114
innerCommand . Execute ( context ) ;
124
- if ( context . CurrentResult . ResultState == ResultState . Success )
115
+ if ( context . CurrentResult . ResultState == ResultState . Success || context . CurrentResult . ResultState == ResultState . Skipped || context . CurrentResult . ResultState == ResultState . Ignored )
125
116
{
126
117
isPassed = true ;
127
118
break ;
@@ -205,4 +196,71 @@ private void UnobservedTaskException(object sender, UnobservedTaskExceptionEvent
205
196
_unobservedTaskExceptions . Add ( e . Exception ) ;
206
197
}
207
198
}
199
+
200
+ public class TimeoutCommand : BeforeAndAfterTestCommand
201
+ {
202
+ private readonly int _timeout ;
203
+
204
+ internal TimeoutCommand ( TestCommand innerCommand , int timeout ) : base ( innerCommand )
205
+ {
206
+ _timeout = timeout ;
207
+ }
208
+
209
+ public override TestResult Execute ( TestExecutionContext context )
210
+ {
211
+ try
212
+ {
213
+ using ( new TestExecutionContext . IsolatedContext ( ) )
214
+ {
215
+ var testExecution = Task . Run ( ( ) => innerCommand . Execute ( TestExecutionContext . CurrentContext ) ) ;
216
+ var timedOut = Task . WaitAny ( [ testExecution ] , _timeout ) == - 1 ;
217
+
218
+ if ( timedOut )
219
+ {
220
+ context . CurrentResult . SetResult (
221
+ ResultState . Failure ,
222
+ $ "Test exceeded Timeout value of { _timeout } ms") ;
223
+ // When the timeout is reached the TearDown methods are not called. This is a best-effort
224
+ // attempt to call them and close the browser / http server.
225
+ foreach ( var tearDown in GetHackyTearDownMethods ( context ) )
226
+ {
227
+ tearDown ( ) ;
228
+ }
229
+ }
230
+ else
231
+ {
232
+ context . CurrentResult = testExecution . GetAwaiter ( ) . GetResult ( ) ;
233
+ }
234
+ }
235
+ }
236
+ catch ( Exception exception )
237
+ {
238
+ context . CurrentResult . RecordException ( exception , FailureSite . Test ) ;
239
+ }
240
+
241
+ return context . CurrentResult ;
242
+ }
243
+
244
+ private Action [ ] GetHackyTearDownMethods ( TestExecutionContext context )
245
+ {
246
+ var methods = new List < Action > ( ) ;
247
+ foreach ( var method in new string [ ] { "WorkerTeardown" , "BrowserTearDown" } )
248
+ {
249
+ var methodFun = context . CurrentTest . Method . MethodInfo . DeclaringType
250
+ . GetMethod ( method , BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) ;
251
+ if ( methodFun != null )
252
+ {
253
+ methods . Add ( ( ) =>
254
+ {
255
+ var maybeTask = methodFun . Invoke ( context . TestObject , null ) ;
256
+ if ( maybeTask is Task task )
257
+ {
258
+ task . GetAwaiter ( ) . GetResult ( ) ;
259
+ }
260
+ } ) ;
261
+ }
262
+ }
263
+ return methods . ToArray ( ) ;
264
+ }
265
+ }
208
266
}
0 commit comments