-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OSOE-935: Upgrade to xUnit 3 and handling test cancellations gracefully #432
base: dev
Are you sure you want to change the base?
Conversation
# Conflicts: # Lombiq.Tests.UI/CompatibilitySuppressions.xml
|
||
// While there may be some common code between local and remote tests, they cannot be the same, and the common code must | ||
// be in a project independent of the web app (which is not the case here for the sake of simplicity): | ||
// - In local tests, you have a much lower-level access to the app: E.g., you can access the Orchard Core log, use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean "higher-level"?
// - In local tests, you have a much lower-level access to the app: E.g., you can access the Orchard Core log, use | |
// - In local tests, you have much higher-level access to the app: E.g., you can access the Orchard Core log, use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did mean lower level: the app runs in the same process, so you can communicate with its container and types directly, whereas in prod, all you have is the public UI.
@@ -48,6 +48,8 @@ public static Task TestWorkflowsAsync(this UITestContext context) => | |||
By.XPath("//div[@class = 'jtk-endpoint jtk-endpoint-anchor jtk-draggable jtk-droppable']"), // #spell-check-ignore-line | |||
By.XPath(taskXPath)); | |||
|
|||
context.WaitElementToNotChange(By.ClassName("jtk-connector")); // #spell-check-ignore-line |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change is necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This waits for the connector to settle after the drag and drop. The test was flaky without this, since the connection might not be registered and thus saved before it.
public ITest XunitTest { get; } | ||
public string Name => XunitTest?.DisplayName; | ||
public ITest XunitTest => TestContext.Current.Test; | ||
public string Name => XunitTest?.TestDisplayName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can TestContext.Current.Test
null
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not, actually.
public ITest XunitTest { get; } | ||
public string Name => XunitTest?.DisplayName; | ||
public ITest XunitTest => TestContext.Current.Test; | ||
public string Name => XunitTest?.TestDisplayName; | ||
public Func<UITestContext, Task> TestAsync { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -369,7 +369,7 @@ protected virtual async Task ExecuteTestAsync( | |||
Func<UITestContext, Task<Uri>> setupOperation, | |||
Func<OrchardCoreUITestExecutorConfiguration, Task> changeConfigurationAsync) | |||
{ | |||
var testManifest = new UITestManifest(_testOutputHelper) { TestAsync = testAsync }; | |||
var testManifest = new UITestManifest { TestAsync = testAsync }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment for UITestManifest.TestAsync
.
Lombiq.Tests.UI/RemoteUITestBase.cs
Outdated
@@ -61,7 +61,7 @@ async Task BaseUriVisitingTest(UITestContext context) | |||
await testAsync(context); | |||
} | |||
|
|||
var testManifest = new UITestManifest(_testOutputHelper) { TestAsync = BaseUriVisitingTest }; | |||
var testManifest = new UITestManifest { TestAsync = BaseUriVisitingTest }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment for UITestManifest.TestAsync
.
@@ -61,6 +61,12 @@ public UITestExecutionSession( | |||
|
|||
public async Task<bool> ExecuteAsync(int retryCount, string dumpRootPath) | |||
{ | |||
_configuration.TestCancellationToken.Register(() => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure, but I think System.Threading.CancellationTokenRegistration
returned by CancellationToken.Register
needs to be disposed.
Is this really necessary?
Testing _configuration.TestCancellationToken.IsCancellationRequested
also wellcome before starting the whole ceremony.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It indeed needs to be disposed, so I added that.
I don't really understand the rest of your comment: xUnit 3 supports cancellations. If we want the tests to clean up after themselves when such a cancellation happens, hooking into it is necessary. Running ShutdownAsync()
will do the clean-up. Thus after this, a cancelled test won't leave ChromeDriver processes or browser windows open.
if (timeoutTask.IsCompleted) | ||
{ | ||
// If the EnterInteractiveModeAsync() extension method has been used, then timeout should be ignored to | ||
// make the debugging experience smoother. Note that EnterInteractiveModeAsync() should never be used in | ||
// committed tests. | ||
if (!ShortcutsUITestContextExtensions.InteractiveModeHasBeenUsed) | ||
{ | ||
throw new TimeoutException($"The time allotted for the test ({timeout}) was exceeded."); | ||
} | ||
|
||
await testTask; | ||
} | ||
|
||
// Since the timeout task is not yet completed but the Task.WhenAny has finished, the test task is done in | ||
// some way. So it's safe to await it here. It's also necessary to cleanly propagate any exceptions that may | ||
// have been thrown inside it. | ||
await testTask; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case when a cancellation is requested on the _configuration.TestCancellationToken
, the timeoutTask.IsCompleted
will be true
.
@@ -75,11 +60,6 @@ private static async Task ExecuteOrchardCoreTestInnerAsync( | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _configuration.TestCancellationToken.IsCancellationRequested
can also stop the loop.
This reverts commit 85972f5.
// This test checks if interactive mode works by opening it in one thread, and then clicking it away in a different | ||
// thread. Two threads are necessary because interactive mode stops test execution on its current thread, so we | ||
// wouldn't be able to end it from within a test. | ||
[Fact] | ||
public Task EnteringInteractiveModeShouldWait() => | ||
ExecuteTestAfterSetupAsync( | ||
async context => | ||
{ | ||
var currentUrl = context.Driver.Url; | ||
|
||
await Task.WhenAll( | ||
context.SwitchToInteractiveAsync(), | ||
Task.Run( | ||
async () => | ||
{ | ||
try | ||
{ | ||
ReliabilityHelper.DoWithRetriesOrFail( | ||
() => context.Driver.WindowHandles.Count > 1, | ||
TimeSpan.FromSeconds(15)); | ||
|
||
context.SwitchToLastWindow(); | ||
|
||
await context.ClickReliablyOnAsync(By.ClassName("interactive__continue")); | ||
} | ||
catch (Exception ex) | ||
{ | ||
_testOutputHelper.WriteLineTimestampedAndDebug( | ||
"Interactive mode wasn't canceled properly due to the following exception. Canceling the test. {0}", | ||
ex); | ||
|
||
// The other thread will wait indefinitely if the button wasn't clicked in the end. So, | ||
// failing the test then. | ||
TestContext.Current.CancelCurrentTest(); | ||
} | ||
}, | ||
context.Configuration.TestCancellationToken)); | ||
|
||
// Ensure that the info tab is closed and the control handed back to the last tab. | ||
context.Driver.Url.ShouldBe(currentUrl); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've spent half a day trying to make this reliable, i.e. not hang randomly (that hung the tests too), and be reliable with its interactions. I couldn't get there, it was always flaky. I don't see any point in trying harder, we don't really need this test.
OSOE-935
Fixes #430
Fixes #82
Test cancellation can be tried when running the exe of a test: run it from the CLI, wait like 10 seconds, hit Ctrl+C (only once), and you should see "Test execution was cancelled. Shutting down the test execution session." at one point (only once the tests actually started).
To be added to the release notes:
Upgraded xUnit to v3 with breaking changes
This version of the UI Testing Toolbox uses v3 of xUnit, which brings many updates. However, it's also a breaking version, requiring you to adapt your test projects, see the official guide.
Migrating UI test projects consuming the UI Testing Toolbox should be simpler, though, with you requiring to do roughly the following steps:
xunit.runner.visualstudio
package reference to latest (currently 3.0.0).<OutputType>Exe</OutputType>
to the firstPropertyGroup
in the test project's csproj). These are all the projects withxunit.runner.visualstudio
references.xunit
package references toxunit.v3
with the latest version (currently 1.0.0). If a test project lacks such a reference then add it (currently<PackageReference Include="xunit.v3" Version="1.0.0" />
). Only test projects should referencexunit
.Microsoft.NET.Test.Sdk
references in the test projects to latest (currently 17.12.0) too while we're at it, or add it if it's missing (currently<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
).ITestOutputHelper
is now in theXunit
namespace instead ofXunit.Abstractions
. Fix the namespace imports inUITestBase
classes first and then anywhere else; they'll show up as build errors.unit.v3
. You might need to do a recursive git clean to be sure to start with a clean slate.