Skip to content

Commit 6799bdb

Browse files
authored
chore: use manual TimeoutAttribute instead of CancelAfter attribute (#3126)
1 parent 034aa3c commit 6799bdb

File tree

7 files changed

+90
-40
lines changed

7 files changed

+90
-40
lines changed

src/Playwright.Tests/Attributes/PlaywrightTestAttribute.cs

Lines changed: 82 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* SOFTWARE.
2323
*/
2424

25+
using System.Reflection;
2526
using NUnit.Framework.Interfaces;
2627
using NUnit.Framework.Internal;
2728
using NUnit.Framework.Internal.Commands;
@@ -36,14 +37,24 @@ namespace Microsoft.Playwright.Tests;
3637
/// Enables decorating test facts with information about the corresponding test in the upstream repository.
3738
/// </summary>
3839
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
39-
public class PlaywrightTestAttribute : TestAttribute, IApplyToContext, IApplyToTest, IWrapSetUpTearDown
40+
public class PlaywrightTestAttribute : TestAttribute, IWrapSetUpTearDown
4041
{
41-
private readonly CancelAfterAttribute _cancelAfterAttribute = new(TestConstants.DefaultTestTimeout);
42+
private readonly int? _timeout;
4243

4344
public PlaywrightTestAttribute()
4445
{
4546
}
4647

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+
4758
/// <summary>
4859
/// Creates a new instance of the attribute.
4960
/// </summary>
@@ -60,11 +71,6 @@ public PlaywrightTestAttribute(string fileName, string nameOfTest)
6071
/// </summary>
6172
public string FileName { get; }
6273

63-
/// <summary>
64-
/// Returns the trimmed file name.
65-
/// </summary>
66-
public string TrimmedName => FileName.Substring(0, FileName.IndexOf('.'));
67-
6874
/// <summary>
6975
/// The name of the test, the decorated code is based on.
7076
/// </summary>
@@ -75,29 +81,14 @@ public PlaywrightTestAttribute(string fileName, string nameOfTest)
7581
/// </summary>
7682
public string Describe { get; }
7783

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-
}
9484
/// <summary>
9585
/// Wraps the current test command in a <see cref="UnobservedTaskExceptionCommand"/>.
9686
/// </summary>
9787
/// <param name="command">the test command</param>
9888
/// <returns>the wrapped test command</returns>
9989
public TestCommand Wrap(TestCommand command)
10090
{
91+
command = new TimeoutCommand(command, _timeout ?? TestConstants.DefaultTestTimeout);
10192
if (Environment.GetEnvironmentVariable("CI") != null)
10293
{
10394
command = new RetryCommand(command, 3);
@@ -121,7 +112,7 @@ public override TestResult Execute(TestExecutionContext context)
121112
try
122113
{
123114
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)
125116
{
126117
isPassed = true;
127118
break;
@@ -205,4 +196,71 @@ private void UnobservedTaskException(object sender, UnobservedTaskExceptionEvent
205196
_unobservedTaskExceptions.Add(e.Exception);
206197
}
207198
}
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+
}
208266
}

src/Playwright.Tests/BaseTests/HttpService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ public static Task<HttpService> Register(WorkerAwareTest test)
3939
var assetDir = Path.Combine(TestUtils.FindParentDirectory("Playwright.Tests"), "assets");
4040
var http = new HttpService
4141
{
42-
Server = SimpleServer.Create(8907 + workerIndex * 2, assetDir),
43-
HttpsServer = SimpleServer.CreateHttps(8907 + workerIndex * 2 + 1, assetDir)
42+
Server = SimpleServer.Create(8907 + workerIndex * 4, assetDir),
43+
HttpsServer = SimpleServer.CreateHttps(8907 + workerIndex * 4 + 1, assetDir)
4444
};
4545
await Task.WhenAll(http.Server.StartAsync(TestContext.CurrentContext.CancellationToken), http.HttpsServer.StartAsync(TestContext.CurrentContext.CancellationToken));
4646
return http;

src/Playwright.Tests/BrowserTypeConnectTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,7 @@ public async Task ShouldRecordContextTraceWithNoDirectoryName()
451451
}
452452
}
453453

454-
[PlaywrightTest("browsertype-connect.spec.ts", "should upload large file")]
455-
[CancelAfter(TestConstants.SlowTestTimeout)]
454+
[PlaywrightTest("browsertype-connect.spec.ts", "should upload large file", TestConstants.SlowTestTimeout)]
456455
public async Task ShouldUploadLargeFile()
457456
{
458457
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);

src/Playwright.Tests/ClientCertficatesTests.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,6 @@ public void Setup()
103103
{
104104
Assert.Ignore("WebKit on macOS doesn't proxy localhost requests");
105105
}
106-
if (TestConstants.IsChromium && TestConstants.IsWindows)
107-
{
108-
// TODO: Remove after https://github.com/microsoft/playwright/issues/17252 is fixed.
109-
Assert.Ignore("Chromium on Windows doesn't proxy localhost requests");
110-
}
111106
}
112107

113108
[PlaywrightTest("", "")]

src/Playwright.Tests/PageSetInputFilesTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,7 @@ public async Task ShouldWorkForWebkitdirectory()
352352
Assert.True(fileChooser.IsMultiple);
353353
}
354354

355-
[PlaywrightTest("page-set-input-files.spec.ts", "should upload large file")]
356-
[CancelAfter(TestConstants.SlowTestTimeout)]
355+
[PlaywrightTest("page-set-input-files.spec.ts", "should upload large file", TestConstants.SlowTestTimeout)]
357356
public async Task ShouldUploadLargeFile()
358357
{
359358
await Page.GotoAsync(Server.Prefix + "/input/fileupload.html");

src/Playwright.Tests/PageWaitForNavigationTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,7 @@ public async Task ShouldWorkOnFrame()
306306
StringAssert.Contains("/frames/one-frame.html", Page.Url);
307307
}
308308

309-
[PlaywrightTest]
310-
[CancelAfter(TestConstants.SlowTestTimeout)]
309+
[PlaywrightTest(TestConstants.SlowTestTimeout)]
311310
public async Task ShouldHaveADefaultTimeout()
312311
{
313312
await Page.GotoAsync(Server.Prefix + "/frames/one-frame.html");

src/Playwright.Tests/Playwright.Tests.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2727
<PrivateAssets>all</PrivateAssets>
2828
</PackageReference>
29-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
30-
<PackageReference Include="NUnit" Version="4.2.2" />
31-
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
29+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
30+
<PackageReference Include="NUnit" Version="4.3.2" />
31+
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
3232
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.10" />
3333
</ItemGroup>
3434
<ItemGroup>

0 commit comments

Comments
 (0)