diff --git a/src/ElectronNET.API/API/ApiBase.cs b/src/ElectronNET.API/API/ApiBase.cs index b8af9af4..96f68313 100644 --- a/src/ElectronNET.API/API/ApiBase.cs +++ b/src/ElectronNET.API/API/ApiBase.cs @@ -31,7 +31,7 @@ protected enum SocketEventNameTypes CamelCase, } - private const int InvocationTimeout = 1000; + private static readonly TimeSpan InvocationTimeout = 1000.ms(); private readonly string objectName; private readonly ConcurrentDictionary invocators; @@ -120,7 +120,7 @@ protected Task InvokeAsync(object arg = null, [CallerMemberName] string ca return this.InvokeAsyncWithTimeout(InvocationTimeout, arg, callerName); } - protected Task InvokeAsyncWithTimeout(int invocationTimeout, object arg = null, [CallerMemberName] string callerName = null) + protected Task InvokeAsyncWithTimeout(TimeSpan invocationTimeout, object arg = null, [CallerMemberName] string callerName = null) { Debug.Assert(callerName != null, nameof(callerName) + " != null"); @@ -245,7 +245,7 @@ internal class Invocator : Invocator private readonly Task tcsTask; private TaskCompletionSource tcs; - public Invocator(ApiBase apiBase, string callerName, int timeoutMs, object arg = null) + public Invocator(ApiBase apiBase, string callerName, TimeSpan timeout, object arg = null) { this.tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); this.tcsTask = this.tcs.Task; @@ -306,7 +306,7 @@ public Invocator(ApiBase apiBase, string callerName, int timeoutMs, object arg = _ = apiBase.Id >= 0 ? BridgeConnector.Socket.Emit(messageName, apiBase.Id) : BridgeConnector.Socket.Emit(messageName); } - System.Threading.Tasks.Task.Delay(timeoutMs).ContinueWith(_ => + System.Threading.Tasks.Task.Delay(timeout).ContinueWith(_ => { if (this.tcs != null) { @@ -314,7 +314,7 @@ public Invocator(ApiBase apiBase, string callerName, int timeoutMs, object arg = { if (this.tcs != null) { - var ex = new TimeoutException($"No response after {timeoutMs:D}ms trying to retrieve value {apiBase.objectName}.{callerName}()"); + var ex = new TimeoutException($"No response after {timeout:D}ms trying to retrieve value {apiBase.objectName}.{callerName}()"); this.tcs.TrySetException(ex); this.tcs = null; } diff --git a/src/ElectronNET.API/API/WebContents.cs b/src/ElectronNET.API/API/WebContents.cs index 2ff58856..7657b413 100644 --- a/src/ElectronNET.API/API/WebContents.cs +++ b/src/ElectronNET.API/API/WebContents.cs @@ -1,6 +1,7 @@ -using ElectronNET.API.Entities; using System; using System.Threading.Tasks; +using ElectronNET.API.Entities; +using ElectronNET.Common; // ReSharper disable InconsistentNaming @@ -173,7 +174,7 @@ public bool IsDevToolsFocused() /// Get system printers. /// /// printers - public Task GetPrintersAsync() => this.InvokeAsyncWithTimeout(5_000); + public Task GetPrintersAsync() => this.InvokeAsyncWithTimeout(8.seconds()); /// /// Prints window's web page. @@ -388,7 +389,7 @@ public void SetAudioMuted(bool muted) /// Returns string - The user agent for this web page. /// /// - public Task GetUserAgentAsync() => InvokeAsync(); + public Task GetUserAgentAsync() => InvokeAsyncWithTimeout(3.seconds()); /// /// Overrides the user agent for this web page. diff --git a/src/ElectronNET.API/Common/TimeSpanExtensions.cs b/src/ElectronNET.API/Common/TimeSpanExtensions.cs new file mode 100644 index 00000000..b4883a0a --- /dev/null +++ b/src/ElectronNET.API/Common/TimeSpanExtensions.cs @@ -0,0 +1,74 @@ +// +// Copyright © Emby LLC. All rights reserved. +// + +namespace ElectronNET.Common +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// The TimeSpanExtensions class. + /// + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "OK")] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "OK")] + [SuppressMessage("ReSharper", "StyleCop.SA1300", Justification = "OK")] + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "OK")] + internal static class TimeSpanExtensions + { + public static TimeSpan ms(this int value) + { + return TimeSpan.FromMilliseconds(value); + } + + public static TimeSpan ms(this long value) + { + return TimeSpan.FromMilliseconds(value); + } + + public static TimeSpan seconds(this int value) + { + return TimeSpan.FromSeconds(value); + } + + public static TimeSpan minutes(this int value) + { + return TimeSpan.FromMinutes(value); + } + + public static TimeSpan hours(this int value) + { + return TimeSpan.FromHours(value); + } + + public static TimeSpan days(this int value) + { + return TimeSpan.FromDays(value); + } + + public static TimeSpan ms(this double value) + { + return TimeSpan.FromMilliseconds(value); + } + + public static TimeSpan seconds(this double value) + { + return TimeSpan.FromSeconds(value); + } + + public static TimeSpan minutes(this double value) + { + return TimeSpan.FromMinutes(value); + } + + public static TimeSpan hours(this double value) + { + return TimeSpan.FromHours(value); + } + + public static TimeSpan days(this double value) + { + return TimeSpan.FromDays(value); + } + } +} diff --git a/src/ElectronNET.API/ElectronNET.API.csproj b/src/ElectronNET.API/ElectronNET.API.csproj index 3a500110..b5d97f77 100644 --- a/src/ElectronNET.API/ElectronNET.API.csproj +++ b/src/ElectronNET.API/ElectronNET.API.csproj @@ -35,5 +35,6 @@ + diff --git a/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs b/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs index 0cf21c54..61cae062 100644 --- a/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs +++ b/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs @@ -76,7 +76,7 @@ private async Task StartInternal(string startCmd, string args, string directoriy { try { - await Task.Delay(10).ConfigureAwait(false); + await Task.Delay(10.ms()).ConfigureAwait(false); Console.Error.WriteLine("[StartInternal]: startCmd: {0}", startCmd); Console.Error.WriteLine("[StartInternal]: args: {0}", args); @@ -85,7 +85,7 @@ private async Task StartInternal(string startCmd, string args, string directoriy this.process.ProcessExited += this.Process_Exited; this.process.Run(startCmd, args, directoriy); - await Task.Delay(500).ConfigureAwait(false); + await Task.Delay(500.ms()).ConfigureAwait(false); Console.Error.WriteLine("[StartInternal]: after run:"); diff --git a/src/ElectronNET.IntegrationTests/Common/IntegrationFactAttribute.cs b/src/ElectronNET.IntegrationTests/Common/IntegrationFactAttribute.cs new file mode 100644 index 00000000..6142bdfd --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Common/IntegrationFactAttribute.cs @@ -0,0 +1,101 @@ +namespace ElectronNET.IntegrationTests.Common +{ + using System.Runtime.InteropServices; + using Xunit.Sdk; + + /// + /// Custom fact attribute with a default timeout of 20 seconds, allowing tests to be skipped on specific environments. + /// + /// + [AttributeUsage(AttributeTargets.Method)] + [XunitTestCaseDiscoverer("Xunit.Sdk.SkippableFactDiscoverer", "Xunit.SkippableFact")] + internal sealed class IntegrationFactAttribute : FactAttribute + { + private static readonly bool IsOnWsl; + + private static readonly bool IsOnCI; + + static IntegrationFactAttribute() + { + IsOnWsl = DetectWsl(); + IsOnCI = DetectCI(); + } + + /// + /// Initializes a new instance of the class. + /// + public IntegrationFactAttribute() + { + this.Timeout = 20_000; + } + + public bool SkipOnWsl { get; set; } + + public bool SkipOnCI { get; set; } + + /// + /// Marks the test so that it will not be run, and gets or sets the skip reason + /// + public override string Skip { + get + { + if (IsOnWsl && this.SkipOnWsl) + { + return "Skipping test on WSL environment."; + } + + if (IsOnCI && this.SkipOnCI) + { + return "Skipping test on CI environment."; + } + + return base.Skip; + } + set + { + base.Skip = value; + } + } + + private static bool DetectWsl() + { + try + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return false; + } + + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WSL_DISTRO_NAME")) || + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WSL_INTEROP"))) + { + return true; + } + + return false; + } + catch + { + return false; + } + } + + private static bool DetectCI() + { + try + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TF_BUILD")) || + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"))) + { + return true; + } + + return false; + } + catch + { + return false; + } + } + } +} diff --git a/src/ElectronNET.IntegrationTests/Common/IntegrationTestBase.cs b/src/ElectronNET.IntegrationTests/Common/IntegrationTestBase.cs new file mode 100644 index 00000000..5a6407fa --- /dev/null +++ b/src/ElectronNET.IntegrationTests/Common/IntegrationTestBase.cs @@ -0,0 +1,23 @@ +namespace ElectronNET.IntegrationTests.Common +{ + using ElectronNET.API; + using ElectronNET.API.Entities; + + // Base class for integration tests providing shared access to MainWindow and OS platform constants + public abstract class IntegrationTestBase + { + protected IntegrationTestBase(ElectronFixture fixture) + { + Fixture = fixture; + MainWindow = fixture.MainWindow; + } + + protected ElectronFixture Fixture { get; } + protected BrowserWindow MainWindow { get; } + + // Constants for SupportedOSPlatform attributes + public const string Windows = "Windows"; + public const string MacOS = "macOS"; + public const string Linux = "Linux"; + } +} diff --git a/src/ElectronNET.IntegrationTests/Common/SkipOnWslFactAttribute.cs b/src/ElectronNET.IntegrationTests/Common/SkipOnWslFactAttribute.cs deleted file mode 100644 index 8c1a3d02..00000000 --- a/src/ElectronNET.IntegrationTests/Common/SkipOnWslFactAttribute.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace ElectronNET.IntegrationTests.Common -{ - using System.Runtime.InteropServices; - - [AttributeUsage(AttributeTargets.Method)] - internal sealed class SkipOnWslFactAttribute : FactAttribute - { - private static readonly bool IsOnWsl; - - static SkipOnWslFactAttribute() - { - IsOnWsl = DetectWsl(); - } - - /// - /// Initializes a new instance of the class. - /// - public SkipOnWslFactAttribute() - { - if (IsOnWsl) - { - this.Skip = "Skipping test on WSL environment."; - } - } - - private static bool DetectWsl() - { - try - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return false; - } - - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WSL_DISTRO_NAME")) || - !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WSL_INTEROP"))) - { - return true; - } - - return false; - } - catch - { - return false; - } - } - } -} diff --git a/src/ElectronNET.IntegrationTests/ElectronFixture.cs b/src/ElectronNET.IntegrationTests/ElectronFixture.cs index cfaf8df2..59be6c95 100644 --- a/src/ElectronNET.IntegrationTests/ElectronFixture.cs +++ b/src/ElectronNET.IntegrationTests/ElectronFixture.cs @@ -4,6 +4,7 @@ namespace ElectronNET.IntegrationTests using System.Reflection; using ElectronNET.API; using ElectronNET.API.Entities; + using ElectronNET.Common; // Shared fixture that starts Electron runtime once [SuppressMessage("ReSharper", "MethodHasAsyncOverload")] @@ -26,7 +27,7 @@ public async Task InitializeAsync() await runtimeController.Start(); Console.Error.WriteLine("[ElectronFixture] Waiting for Ready..."); - await Task.WhenAny(runtimeController.WaitReadyTask, Task.Delay(TimeSpan.FromSeconds(10))); + await Task.WhenAny(runtimeController.WaitReadyTask, Task.Delay(10.seconds())); if (!runtimeController.WaitReadyTask.IsCompleted) { diff --git a/src/ElectronNET.IntegrationTests/Tests/AppTests.cs b/src/ElectronNET.IntegrationTests/Tests/AppTests.cs index 1a15efdb..6d962f25 100644 --- a/src/ElectronNET.IntegrationTests/Tests/AppTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/AppTests.cs @@ -6,19 +6,16 @@ namespace ElectronNET.IntegrationTests.Tests using System.IO; using System.Runtime.Versioning; using System.Threading.Tasks; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class AppTests + public class AppTests : IntegrationTestBase { - // ReSharper disable once NotAccessedField.Local - private readonly ElectronFixture fx; - - public AppTests(ElectronFixture fx) + public AppTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_get_app_path() { var path = await Electron.App.GetAppPathAsync(); @@ -26,7 +23,7 @@ public async Task Can_get_app_path() Directory.Exists(path).Should().BeTrue(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_get_version_and_locale() { var version = await Electron.App.GetVersionAsync(); @@ -35,7 +32,7 @@ public async Task Can_get_version_and_locale() locale.Should().NotBeNullOrWhiteSpace(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_get_special_paths() { var userData = await Electron.App.GetPathAsync(PathName.UserData); @@ -47,7 +44,7 @@ public async Task Can_get_special_paths() Directory.Exists(temp).Should().BeTrue(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_get_app_metrics() { var metrics = await Electron.App.GetAppMetricsAsync(); @@ -55,23 +52,23 @@ public async Task Can_get_app_metrics() metrics.Length.Should().BeGreaterThan(0); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_get_gpu_feature_status() { var status = await Electron.App.GetGpuFeatureStatusAsync(); status.Should().NotBeNull(); } - [SkippableFact(Timeout = 20000)] - [SupportedOSPlatform("macOS")] - [SupportedOSPlatform("Windows")] + [IntegrationFact] + [SupportedOSPlatform(MacOS)] + [SupportedOSPlatform(Windows)] public async Task Can_get_login_item_settings() { var settings = await Electron.App.GetLoginItemSettingsAsync(); settings.Should().NotBeNull(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task CommandLine_append_and_query_switch() { var switchName = "integration-switch"; @@ -80,9 +77,9 @@ public async Task CommandLine_append_and_query_switch() (await Electron.App.CommandLine.GetSwitchValueAsync(switchName)).Should().Be("value123"); } - [SkippableFact(Timeout = 20000)] - [SupportedOSPlatform("macOS")] - [SupportedOSPlatform("Windows")] + [IntegrationFact] + [SupportedOSPlatform(MacOS)] + [SupportedOSPlatform(Windows)] public async Task Accessibility_support_toggle() { Electron.App.SetAccessibilitySupportEnabled(true); @@ -91,7 +88,7 @@ public async Task Accessibility_support_toggle() Electron.App.SetAccessibilitySupportEnabled(false); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task UserAgentFallback_roundtrip() { var original = await Electron.App.UserAgentFallbackAsync; @@ -101,9 +98,9 @@ public async Task UserAgentFallback_roundtrip() Electron.App.UserAgentFallback = original; // restore } - [SkippableFact(Timeout = 20000)] - [SupportedOSPlatform("Linux")] - [SupportedOSPlatform("macOS")] + [IntegrationFact] + [SupportedOSPlatform(Linux)] + [SupportedOSPlatform(MacOS)] public async Task BadgeCount_set_and_reset_where_supported() { await Electron.App.SetBadgeCountAsync(2); @@ -113,14 +110,14 @@ public async Task BadgeCount_set_and_reset_where_supported() await Electron.App.SetBadgeCountAsync(0); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task App_metrics_have_cpu_info() { var metrics = await Electron.App.GetAppMetricsAsync(); metrics[0].Cpu.Should().NotBeNull(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task App_gpu_feature_status_has_some_fields() { var status = await Electron.App.GetGpuFeatureStatusAsync(); diff --git a/src/ElectronNET.IntegrationTests/Tests/AutoUpdaterTests.cs b/src/ElectronNET.IntegrationTests/Tests/AutoUpdaterTests.cs index 091b5614..65279a89 100644 --- a/src/ElectronNET.IntegrationTests/Tests/AutoUpdaterTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/AutoUpdaterTests.cs @@ -2,18 +2,17 @@ { using API; using System.Threading.Tasks; + using ElectronNET.Common; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class AutoUpdaterTests + public class AutoUpdaterTests : IntegrationTestBase { - private readonly ElectronFixture fx; - - public AutoUpdaterTests(ElectronFixture fx) + public AutoUpdaterTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task AutoDownload_check() { Electron.AutoUpdater.AutoDownload = false; @@ -24,7 +23,7 @@ public async Task AutoDownload_check() test2.Should().BeTrue(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task AutoInstallOnAppQuit_check() { Electron.AutoUpdater.AutoInstallOnAppQuit = false; @@ -35,7 +34,7 @@ public async Task AutoInstallOnAppQuit_check() test2.Should().BeTrue(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task AllowPrerelease_check() { Electron.AutoUpdater.AllowPrerelease = false; @@ -46,7 +45,7 @@ public async Task AllowPrerelease_check() test2.Should().BeTrue(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task FullChangelog_check() { Electron.AutoUpdater.FullChangelog = false; @@ -57,7 +56,7 @@ public async Task FullChangelog_check() test2.Should().BeTrue(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task AllowDowngrade_check() { Electron.AutoUpdater.AllowDowngrade = false; @@ -68,14 +67,14 @@ public async Task AllowDowngrade_check() test2.Should().BeTrue(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task UpdateConfigPath_check() { var test1 = Electron.AutoUpdater.UpdateConfigPath; test1.Should().Be(string.Empty); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task CurrentVersionAsync_check() { var semver = await Electron.AutoUpdater.CurrentVersionAsync; @@ -83,18 +82,18 @@ public async Task CurrentVersionAsync_check() semver.Major.Should().BeGreaterThan(0); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task ChannelAsync_check() { var test = await Electron.AutoUpdater.ChannelAsync; test.Should().Be(string.Empty); Electron.AutoUpdater.SetChannel = "beta"; - await Task.Delay(500); + await Task.Delay(500.ms()); test = await Electron.AutoUpdater.ChannelAsync; test.Should().Be("beta"); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task RequestHeadersAsync_check() { var headers = new Dictionary @@ -104,28 +103,28 @@ public async Task RequestHeadersAsync_check() var test = await Electron.AutoUpdater.RequestHeadersAsync; test.Should().BeNull(); Electron.AutoUpdater.RequestHeaders = headers; - await Task.Delay(500); + await Task.Delay(500.ms()); test = await Electron.AutoUpdater.RequestHeadersAsync; test.Should().NotBeNull(); test.Count.Should().Be(1); test["key1"].Should().Be("value1"); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task CheckForUpdatesAsync_check() { var test = await Electron.AutoUpdater.CheckForUpdatesAsync(); test.Should().BeNull(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task CheckForUpdatesAndNotifyAsync_check() { var test = await Electron.AutoUpdater.CheckForUpdatesAsync(); test.Should().BeNull(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task GetFeedURLAsync_check() { var test = await Electron.AutoUpdater.GetFeedURLAsync(); diff --git a/src/ElectronNET.IntegrationTests/Tests/BrowserViewTests.cs b/src/ElectronNET.IntegrationTests/Tests/BrowserViewTests.cs index 8405da75..6fa25c0b 100644 --- a/src/ElectronNET.IntegrationTests/Tests/BrowserViewTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/BrowserViewTests.cs @@ -2,22 +2,20 @@ namespace ElectronNET.IntegrationTests.Tests { using ElectronNET.API; using ElectronNET.API.Entities; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class BrowserViewTests + public class BrowserViewTests : IntegrationTestBase { - private readonly ElectronFixture fx; - - public BrowserViewTests(ElectronFixture fx) + public BrowserViewTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Create_browser_view_and_adjust_bounds() { var view = await Electron.WindowManager.CreateBrowserViewAsync(new BrowserViewConstructorOptions()); - this.fx.MainWindow.SetBrowserView(view); + this.MainWindow.SetBrowserView(view); view.Bounds = new Rectangle { X = 0, Y = 0, Width = 300, Height = 200 }; // Access bounds again (synchronous property fetch) var current = view.Bounds; diff --git a/src/ElectronNET.IntegrationTests/Tests/BrowserWindowTests.cs b/src/ElectronNET.IntegrationTests/Tests/BrowserWindowTests.cs index 0d86d232..c22b8908 100644 --- a/src/ElectronNET.IntegrationTests/Tests/BrowserWindowTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/BrowserWindowTests.cs @@ -1,144 +1,163 @@ namespace ElectronNET.IntegrationTests.Tests { - using System.Runtime.InteropServices; using System.Runtime.Versioning; using ElectronNET.API; using ElectronNET.API.Entities; + using ElectronNET.Common; using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class BrowserWindowTests + public class BrowserWindowTests : IntegrationTestBase { - private readonly ElectronFixture fx; - - public BrowserWindowTests(ElectronFixture fx) + public BrowserWindowTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_set_and_get_title() { const string title = "Integration Test Title"; - this.fx.MainWindow.SetTitle(title); - await Task.Delay(500); - var roundTrip = await this.fx.MainWindow.GetTitleAsync(); + this.MainWindow.SetTitle(title); + await Task.Delay(500.ms()); + var roundTrip = await this.MainWindow.GetTitleAsync(); roundTrip.Should().Be(title); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_resize_and_get_size() { - this.fx.MainWindow.SetSize(643, 482); - await Task.Delay(500); - var size = await this.fx.MainWindow.GetSizeAsync(); + this.MainWindow.SetSize(643, 482); + await Task.Delay(500.ms()); + var size = await this.MainWindow.GetSizeAsync(); size.Should().HaveCount(2); size[0].Should().Be(643); size[1].Should().Be(482); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_set_progress_bar_and_clear() { - this.fx.MainWindow.SetProgressBar(0.5); + this.MainWindow.SetProgressBar(0.5); // No direct getter; rely on absence of error. Try changing again. - this.fx.MainWindow.SetProgressBar(-1); // clears - await Task.Delay(50); + this.MainWindow.SetProgressBar(-1); // clears + await Task.Delay(50.ms()); } - [SkipOnWslFact(Timeout = 20000)] + [IntegrationFact(SkipOnWsl = true)] public async Task Can_set_and_get_position() { - this.fx.MainWindow.SetPosition(134, 246); - await Task.Delay(500); - var pos = await this.fx.MainWindow.GetPositionAsync(); + this.MainWindow.SetPosition(134, 246); + await Task.Delay(500.ms()); + var pos = await this.MainWindow.GetPositionAsync(); pos.Should().BeEquivalentTo([134, 246]); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_set_and_get_bounds() { var bounds = new Rectangle { X = 10, Y = 20, Width = 400, Height = 300 }; - this.fx.MainWindow.SetBounds(bounds); - await Task.Delay(500); - var round = await this.fx.MainWindow.GetBoundsAsync(); + this.MainWindow.SetBounds(bounds); + await Task.Delay(500.ms()); + var round = await this.MainWindow.GetBoundsAsync(); round.Should().BeEquivalentTo(bounds); round.Width.Should().Be(400); round.Height.Should().Be(300); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_set_and_get_content_bounds() { var bounds = new Rectangle { X = 0, Y = 0, Width = 300, Height = 200 }; - this.fx.MainWindow.SetContentBounds(bounds); - await Task.Delay(500); - var round = await this.fx.MainWindow.GetContentBoundsAsync(); + this.MainWindow.SetContentBounds(bounds); + await Task.Delay(500.ms()); + var round = await this.MainWindow.GetContentBoundsAsync(); round.Width.Should().BeGreaterThan(0); round.Height.Should().BeGreaterThan(0); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Show_hide_visibility_roundtrip() { - this.fx.MainWindow.Show(); - await Task.Delay(500); - (await this.fx.MainWindow.IsVisibleAsync()).Should().BeTrue(); - this.fx.MainWindow.Hide(); - await Task.Delay(500); - (await this.fx.MainWindow.IsVisibleAsync()).Should().BeFalse(); + BrowserWindow window = null; + + try + { + window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true }, "about:blank"); + + await Task.Delay(100.ms()); + + window.Show(); + + await Task.Delay(500.ms()); + (await window.IsVisibleAsync()).Should().BeTrue(); + + window.Hide(); + await Task.Delay(500.ms()); + + (await window.IsVisibleAsync()).Should().BeFalse(); + } + finally + { + window?.Destroy(); + } } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task AlwaysOnTop_toggle_and_query() { - this.fx.MainWindow.SetAlwaysOnTop(true); - await Task.Delay(500); - (await this.fx.MainWindow.IsAlwaysOnTopAsync()).Should().BeTrue(); - this.fx.MainWindow.SetAlwaysOnTop(false); - await Task.Delay(500); - (await this.fx.MainWindow.IsAlwaysOnTopAsync()).Should().BeFalse(); + this.MainWindow.SetAlwaysOnTop(true); + await Task.Delay(500.ms()); + (await this.MainWindow.IsAlwaysOnTopAsync()).Should().BeTrue(); + this.MainWindow.SetAlwaysOnTop(false); + await Task.Delay(500.ms()); + (await this.MainWindow.IsAlwaysOnTopAsync()).Should().BeFalse(); } - [SkippableFact(Timeout = 20000)] - [SupportedOSPlatform("Linux")] - [SupportedOSPlatform("Windows")] + [IntegrationFact] + [SupportedOSPlatform(Linux)] + [SupportedOSPlatform(Windows)] public async Task MenuBar_auto_hide_and_visibility() { - this.fx.MainWindow.SetAutoHideMenuBar(true); - await Task.Delay(500); - (await this.fx.MainWindow.IsMenuBarAutoHideAsync()).Should().BeTrue(); - this.fx.MainWindow.SetMenuBarVisibility(false); - await Task.Delay(500); - (await this.fx.MainWindow.IsMenuBarVisibleAsync()).Should().BeFalse(); - this.fx.MainWindow.SetMenuBarVisibility(true); - await Task.Delay(500); - (await this.fx.MainWindow.IsMenuBarVisibleAsync()).Should().BeTrue(); + this.MainWindow.SetAutoHideMenuBar(true); + await Task.Delay(500.ms()); + (await this.MainWindow.IsMenuBarAutoHideAsync()).Should().BeTrue(); + this.MainWindow.SetMenuBarVisibility(false); + await Task.Delay(500.ms()); + (await this.MainWindow.IsMenuBarVisibleAsync()).Should().BeFalse(); + this.MainWindow.SetMenuBarVisibility(true); + await Task.Delay(500.ms()); + (await this.MainWindow.IsMenuBarVisibleAsync()).Should().BeTrue(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task ReadyToShow_event_fires_after_content_ready() { - var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false }, "about:blank"); - var tcs = new TaskCompletionSource(); - window.OnReadyToShow += () => tcs.TrySetResult(); + BrowserWindow window = null; - // Trigger a navigation and wait for DOM ready so the renderer paints, which emits ready-to-show - var domReadyTcs = new TaskCompletionSource(); - window.WebContents.OnDomReady += () => domReadyTcs.TrySetResult(); - await Task.Delay(500); - await window.WebContents.LoadURLAsync("about:blank"); - await domReadyTcs.Task; + try + { + window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false }, "about:blank"); + var tcs = new TaskCompletionSource(); + window.OnReadyToShow += () => tcs.TrySetResult(); - var completed = await Task.WhenAny(tcs.Task, Task.Delay(3000)); - completed.Should().Be(tcs.Task); + // Trigger a navigation and wait for DOM ready so the renderer paints, which emits ready-to-show + var domReadyTcs = new TaskCompletionSource(); + window.WebContents.OnDomReady += () => domReadyTcs.TrySetResult(); + await Task.Delay(500.ms()); + await window.WebContents.LoadURLAsync("about:blank"); + await domReadyTcs.Task; - // Typical usage is to show once ready - window.Show(); + var completed = await Task.WhenAny(tcs.Task, Task.Delay(3.seconds())); + completed.Should().Be(tcs.Task); + } + finally + { + window?.Destroy(); + } } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task PageTitleUpdated_event_fires_on_title_change() { var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true }, "about:blank"); @@ -148,90 +167,90 @@ public async Task PageTitleUpdated_event_fires_on_title_change() // Navigate and wait for DOM ready, then change the document.title to trigger the event var domReadyTcs = new TaskCompletionSource(); window.WebContents.OnDomReady += () => domReadyTcs.TrySetResult(); - await Task.Delay(500); + await Task.Delay(500.ms()); await window.WebContents.LoadURLAsync("about:blank"); await domReadyTcs.Task; await window.WebContents.ExecuteJavaScriptAsync("document.title='NewTitle';"); // Wait for event up to a short timeout - var completed2 = await Task.WhenAny(tcs.Task, Task.Delay(3000)); + var completed2 = await Task.WhenAny(tcs.Task, Task.Delay(3.seconds())); completed2.Should().Be(tcs.Task); (await tcs.Task).Should().Be("NewTitle"); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Resize_event_fires_on_size_change() { var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false }, "about:blank"); var resized = false; window.OnResize += () => resized = true; - await Task.Delay(500); + await Task.Delay(500.ms()); window.SetSize(500, 400); - await Task.Delay(300); + await Task.Delay(300.ms()); resized.Should().BeTrue(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Progress_bar_and_always_on_top_toggle() { - var win = this.fx.MainWindow; + var win = this.MainWindow; win.SetProgressBar(0.5); - await Task.Delay(50); + await Task.Delay(50.ms()); win.SetProgressBar(0.8, new ProgressBarOptions()); - await Task.Delay(50); + await Task.Delay(50.ms()); win.SetAlwaysOnTop(true); - await Task.Delay(500); + await Task.Delay(500.ms()); (await win.IsAlwaysOnTopAsync()).Should().BeTrue(); win.SetAlwaysOnTop(false); - await Task.Delay(500); + await Task.Delay(500.ms()); (await win.IsAlwaysOnTopAsync()).Should().BeFalse(); } - [SkippableFact(Timeout = 20000)] - [SupportedOSPlatform("Linux")] - [SupportedOSPlatform("Windows")] + [IntegrationFact] + [SupportedOSPlatform(Linux)] + [SupportedOSPlatform(Windows)] public async Task Menu_bar_visibility_and_auto_hide() { - var win = this.fx.MainWindow; + var win = this.MainWindow; win.SetAutoHideMenuBar(true); - await Task.Delay(500); + await Task.Delay(500.ms()); (await win.IsMenuBarAutoHideAsync()).Should().BeTrue(); win.SetMenuBarVisibility(true); - await Task.Delay(500); + await Task.Delay(500.ms()); (await win.IsMenuBarVisibleAsync()).Should().BeTrue(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Parent_child_relationship_roundtrip() { var child = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false, Width = 300, Height = 200 }, "about:blank"); - this.fx.MainWindow.SetParentWindow(null); // ensure top-level - child.SetParentWindow(this.fx.MainWindow); - await Task.Delay(500); + this.MainWindow.SetParentWindow(null); // ensure top-level + child.SetParentWindow(this.MainWindow); + await Task.Delay(500.ms()); var parent = await child.GetParentWindowAsync(); - parent.Id.Should().Be(this.fx.MainWindow.Id); - var kids = await this.fx.MainWindow.GetChildWindowsAsync(); + parent.Id.Should().Be(this.MainWindow.Id); + var kids = await this.MainWindow.GetChildWindowsAsync(); kids.Select(k => k.Id).Should().Contain(child.Id); child.Destroy(); } - [SkippableFact(Timeout = 20000)] - [SupportedOSPlatform("macOS")] + [IntegrationFact] + [SupportedOSPlatform(MacOS)] public async Task Represented_filename_and_edited_flags() { - var win = this.fx.MainWindow; + var win = this.MainWindow; var temp = Path.Combine(Path.GetTempPath(), "electronnet_test.txt"); File.WriteAllText(temp, "test"); win.SetRepresentedFilename(temp); - await Task.Delay(500); + await Task.Delay(500.ms()); var represented = await win.GetRepresentedFilenameAsync(); represented.Should().Be(temp); win.SetDocumentEdited(true); - await Task.Delay(500); + await Task.Delay(500.ms()); var edited = await win.IsDocumentEditedAsync(); edited.Should().BeTrue(); diff --git a/src/ElectronNET.IntegrationTests/Tests/ClipboardTests.cs b/src/ElectronNET.IntegrationTests/Tests/ClipboardTests.cs index 17ff0dd6..4ef2a991 100644 --- a/src/ElectronNET.IntegrationTests/Tests/ClipboardTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/ClipboardTests.cs @@ -2,19 +2,16 @@ namespace ElectronNET.IntegrationTests.Tests { using System.Runtime.Versioning; using ElectronNET.API; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class ClipboardTests + public class ClipboardTests : IntegrationTestBase { - // ReSharper disable once NotAccessedField.Local - private readonly ElectronFixture fx; - - public ClipboardTests(ElectronFixture fx) + public ClipboardTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Clipboard_text_roundtrip() { var text = $"Hello Electron {Guid.NewGuid()}"; @@ -23,7 +20,7 @@ public async Task Clipboard_text_roundtrip() read.Should().Be(text); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Available_formats_contains_text_after_write() { var text = "FormatsTest"; @@ -32,9 +29,9 @@ public async Task Available_formats_contains_text_after_write() formats.Should().Contain(f => f.Contains("text") || f.Contains("TEXT") || f.Contains("plain")); } - [SkippableFact(Timeout = 20000)] - [SupportedOSPlatform("macOS")] - [SupportedOSPlatform("Windows")] + [IntegrationFact] + [SupportedOSPlatform(MacOS)] + [SupportedOSPlatform(Windows)] public async Task Bookmark_write_and_read() { var url = "https://electron-test.com"; diff --git a/src/ElectronNET.IntegrationTests/Tests/CookiesTests.cs b/src/ElectronNET.IntegrationTests/Tests/CookiesTests.cs index 1cba009b..1bb6cc92 100644 --- a/src/ElectronNET.IntegrationTests/Tests/CookiesTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/CookiesTests.cs @@ -1,26 +1,26 @@ namespace ElectronNET.IntegrationTests.Tests { + using ElectronNET.Common; + using ElectronNET.IntegrationTests.Common; + [Collection("ElectronCollection")] - public class CookiesTests + public class CookiesTests : IntegrationTestBase { - private readonly ElectronFixture fx; - - public CookiesTests(ElectronFixture fx) + public CookiesTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [Fact(Skip = "Cookie set/get requires navigation to domain; skipping until test harness serves page")] + [IntegrationFact(Skip = "Cookie set/get requires navigation to domain; skipping until test harness serves page")] public async Task Cookie_set_get_remove_sequence() { - var session = this.fx.MainWindow.WebContents.Session; + var session = this.MainWindow.WebContents.Session; var changed = false; session.Cookies.OnChanged += (cookie, cause, removed) => changed = true; // Navigate to example.com so cookie domain matches - await this.fx.MainWindow.WebContents.LoadURLAsync("https://example.com"); + await this.MainWindow.WebContents.LoadURLAsync("https://example.com"); // Set via renderer for now - await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("document.cookie='integration_cookie=1;path=/';"); - await Task.Delay(500); + await this.MainWindow.WebContents.ExecuteJavaScriptAsync("document.cookie='integration_cookie=1;path=/';"); + await Task.Delay(500.ms()); changed.Should().BeTrue(); } } diff --git a/src/ElectronNET.IntegrationTests/Tests/GlobalShortcutTests.cs b/src/ElectronNET.IntegrationTests/Tests/GlobalShortcutTests.cs index 625618f3..942e6311 100644 --- a/src/ElectronNET.IntegrationTests/Tests/GlobalShortcutTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/GlobalShortcutTests.cs @@ -2,11 +2,16 @@ namespace ElectronNET.IntegrationTests.Tests { using System.Runtime.InteropServices; using ElectronNET.API; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class GlobalShortcutTests + public class GlobalShortcutTests : IntegrationTestBase { - [Fact(Timeout = 20000)] + public GlobalShortcutTests(ElectronFixture fx) : base(fx) + { + } + + [IntegrationFact] public async Task Can_register_and_unregister() { var accel = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Cmd+Alt+G" : "Ctrl+Alt+G"; diff --git a/src/ElectronNET.IntegrationTests/Tests/HostHookTests.cs b/src/ElectronNET.IntegrationTests/Tests/HostHookTests.cs index 254f7aa4..05b15495 100644 --- a/src/ElectronNET.IntegrationTests/Tests/HostHookTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/HostHookTests.cs @@ -1,11 +1,16 @@ namespace ElectronNET.IntegrationTests.Tests { using ElectronNET.API; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class HostHookTests + public class HostHookTests : IntegrationTestBase { - [Fact(Skip = "Requires HostHook setup; skipping")] + public HostHookTests(ElectronFixture fx) : base(fx) + { + } + + [IntegrationFact(Skip = "Requires HostHook setup; skipping")] public async Task HostHook_call_returns_value() { var result = await Electron.HostHook.CallAsync("create-excel-file", "."); diff --git a/src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs b/src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs index 60f1d62a..6136fa0a 100644 --- a/src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs @@ -1,18 +1,17 @@ namespace ElectronNET.IntegrationTests.Tests { using ElectronNET.API; + using ElectronNET.Common; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class IpcMainTests + public class IpcMainTests : IntegrationTestBase { - private readonly ElectronFixture fx; - - public IpcMainTests(ElectronFixture fx) + public IpcMainTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Ipc_On_receives_message_from_renderer() { object received = null; @@ -24,7 +23,7 @@ await Electron.IpcMain.On("ipc-on-test", obj => tcs.TrySetResult(obj as string); }); - await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("require('electron').ipcRenderer.send('ipc-on-test','payload123')"); + await this.MainWindow.WebContents.ExecuteJavaScriptAsync("require('electron').ipcRenderer.send('ipc-on-test','payload123')"); var result = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5)); @@ -33,28 +32,28 @@ await Electron.IpcMain.On("ipc-on-test", obj => result.Should().Be("payload123"); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Ipc_Once_only_fires_once() { var count = 0; Electron.IpcMain.Once("ipc-once-test", _ => count++); - await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("const {ipcRenderer}=require('electron'); ipcRenderer.send('ipc-once-test','a'); ipcRenderer.send('ipc-once-test','b');"); - await Task.Delay(500); + await this.MainWindow.WebContents.ExecuteJavaScriptAsync("const {ipcRenderer}=require('electron'); ipcRenderer.send('ipc-once-test','a'); ipcRenderer.send('ipc-once-test','b');"); + await Task.Delay(500.ms()); count.Should().Be(1); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Ipc_RemoveAllListeners_stops_receiving() { var fired = false; await Electron.IpcMain.On("ipc-remove-test", _ => fired = true); Electron.IpcMain.RemoveAllListeners("ipc-remove-test"); - await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("require('electron').ipcRenderer.send('ipc-remove-test','x')"); - await Task.Delay(400); + await this.MainWindow.WebContents.ExecuteJavaScriptAsync("require('electron').ipcRenderer.send('ipc-remove-test','x')"); + await Task.Delay(400.ms()); fired.Should().BeFalse(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Ipc_OnSync_returns_value() { object received = null; @@ -64,7 +63,7 @@ public async Task Ipc_OnSync_returns_value() received = obj; return "pong"; }); - var ret = await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("require('electron').ipcRenderer.sendSync('ipc-sync-test','ping')"); + var ret = await this.MainWindow.WebContents.ExecuteJavaScriptAsync("require('electron').ipcRenderer.sendSync('ipc-sync-test','ping')"); received.Should().BeOfType(); received.Should().Be("ping"); @@ -72,23 +71,23 @@ public async Task Ipc_OnSync_returns_value() ret.Should().Be("pong"); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Ipc_Send_from_main_reaches_renderer() { // Listener: store raw arg; if Electron packs differently we will normalize later - await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync(@"(function(){ const {ipcRenderer}=require('electron'); ipcRenderer.once('main-to-render',(e,arg)=>{ globalThis.__mainToRender = arg;}); return 'ready'; })();"); - Electron.IpcMain.Send(this.fx.MainWindow, "main-to-render", "hello-msg"); + await this.MainWindow.WebContents.ExecuteJavaScriptAsync(@"(function(){ const {ipcRenderer}=require('electron'); ipcRenderer.once('main-to-render',(e,arg)=>{ globalThis.__mainToRender = arg;}); return 'ready'; })();"); + Electron.IpcMain.Send(this.MainWindow, "main-to-render", "hello-msg"); string value = ""; for (int i = 0; i < 20; i++) { - var jsVal = await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("globalThis.__mainToRender === undefined ? '' : (typeof globalThis.__mainToRender === 'string' ? globalThis.__mainToRender : JSON.stringify(globalThis.__mainToRender))"); + var jsVal = await this.MainWindow.WebContents.ExecuteJavaScriptAsync("globalThis.__mainToRender === undefined ? '' : (typeof globalThis.__mainToRender === 'string' ? globalThis.__mainToRender : JSON.stringify(globalThis.__mainToRender))"); value = jsVal?.ToString() ?? ""; if (!string.IsNullOrEmpty(value)) { break; } - await Task.Delay(100); + await Task.Delay(100.ms()); } // Normalize possible JSON array ["hello-msg"] case diff --git a/src/ElectronNET.IntegrationTests/Tests/MenuTests.cs b/src/ElectronNET.IntegrationTests/Tests/MenuTests.cs index 85a9d847..7c9a900d 100644 --- a/src/ElectronNET.IntegrationTests/Tests/MenuTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/MenuTests.cs @@ -2,18 +2,17 @@ namespace ElectronNET.IntegrationTests.Tests { using ElectronNET.API; using ElectronNET.API.Entities; + using ElectronNET.Common; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class MenuTests + public class MenuTests : IntegrationTestBase { - private readonly ElectronFixture fx; - - public MenuTests(ElectronFixture fx) + public MenuTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task ApplicationMenu_click_invokes_handler() { var clicked = false; @@ -30,29 +29,29 @@ public async Task ApplicationMenu_click_invokes_handler() }; Electron.Menu.SetApplicationMenu(items); var targetId = items[0].Submenu[0].Id; - await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync($"require('electron').ipcRenderer.send('integration-click-application-menu','{targetId}')"); + await this.MainWindow.WebContents.ExecuteJavaScriptAsync($"require('electron').ipcRenderer.send('integration-click-application-menu','{targetId}')"); for (int i = 0; i < 20 && !clicked; i++) { - await Task.Delay(100); + await Task.Delay(100.ms()); } clicked.Should().BeTrue(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task ContextMenu_popup_registers_items() { - var win = this.fx.MainWindow; + var win = this.MainWindow; var ctxClicked = false; var ctxItems = new[] { new MenuItem { Label = "Ctx", Click = () => ctxClicked = true } }; Electron.Menu.SetContextMenu(win, ctxItems); var ctxId = ctxItems[0].Id; // simulate popup then click Electron.Menu.ContextMenuPopup(win); - await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync($"require('electron').ipcRenderer.send('integration-click-context-menu',{win.Id},'{ctxId}')"); + await this.MainWindow.WebContents.ExecuteJavaScriptAsync($"require('electron').ipcRenderer.send('integration-click-context-menu',{win.Id},'{ctxId}')"); for (int i = 0; i < 20 && !ctxClicked; i++) { - await Task.Delay(100); + await Task.Delay(100.ms()); } ctxClicked.Should().BeTrue(); diff --git a/src/ElectronNET.IntegrationTests/Tests/MultiEventRegistrationTests.cs b/src/ElectronNET.IntegrationTests/Tests/MultiEventRegistrationTests.cs index e00c8509..3d4c421b 100644 --- a/src/ElectronNET.IntegrationTests/Tests/MultiEventRegistrationTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/MultiEventRegistrationTests.cs @@ -1,13 +1,12 @@ namespace ElectronNET.IntegrationTests.Tests { + using ElectronNET.IntegrationTests.Common; + [Collection("ElectronCollection")] - public class MultiEventRegistrationTests + public class MultiEventRegistrationTests : IntegrationTestBase { - private readonly ElectronFixture fx; - - public MultiEventRegistrationTests(ElectronFixture fx) + public MultiEventRegistrationTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } private static async Task WaitAllOrTimeout(TimeSpan timeout, params Task[] tasks) @@ -17,10 +16,10 @@ private static async Task WaitAllOrTimeout(TimeSpan timeout, params Task[] return ReferenceEquals(completed, all) && all.IsCompletedSuccessfully; } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task BrowserWindow_OnResize_multiple_handlers_called() { - var win = this.fx.MainWindow; + var win = this.MainWindow; var h1 = new TaskCompletionSource(); var h2 = new TaskCompletionSource(); var h3 = new TaskCompletionSource(); @@ -41,10 +40,10 @@ public async Task BrowserWindow_OnResize_multiple_handlers_called() } } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task WebContents_OnDomReady_multiple_handlers_called() { - var wc = this.fx.MainWindow.WebContents; + var wc = this.MainWindow.WebContents; var r1 = new TaskCompletionSource(); var r2 = new TaskCompletionSource(); diff --git a/src/ElectronNET.IntegrationTests/Tests/NativeImageTests.cs b/src/ElectronNET.IntegrationTests/Tests/NativeImageTests.cs index fd8cac8a..e7a09b0c 100644 --- a/src/ElectronNET.IntegrationTests/Tests/NativeImageTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/NativeImageTests.cs @@ -1,15 +1,20 @@ +using System.Drawing; +using ElectronNET.API.Entities; +using ElectronNET.IntegrationTests.Common; using System.Runtime.Versioning; using RectangleEntity = ElectronNET.API.Entities.Rectangle; namespace ElectronNET.IntegrationTests.Tests { - using System.Drawing; - using ElectronNET.API.Entities; - - [SupportedOSPlatform("Windows")] - public class NativeImageTests + [Collection("ElectronCollection")] + [SupportedOSPlatform(Windows)] + public class NativeImageTests : IntegrationTestBase { - [SkippableFact(Timeout = 20000)] + public NativeImageTests(ElectronFixture fx) : base(fx) + { + } + + [IntegrationFact] public async Task Create_from_bitmap_and_to_png() { using var bmp = new Bitmap(10, 10); @@ -27,7 +32,7 @@ public async Task Create_from_bitmap_and_to_png() png!.Length.Should().BeGreaterThan(0); } - [SkippableFact(Timeout = 20000)] + [IntegrationFact] public async Task Create_from_buffer_and_to_data_url() { // Prepare PNG bytes @@ -46,7 +51,7 @@ public async Task Create_from_buffer_and_to_data_url() dataUrl!.StartsWith("data:image/", StringComparison.Ordinal).Should().BeTrue(); } - [SkippableFact(Timeout = 20000)] + [IntegrationFact] public async Task Resize_and_crop_produce_expected_sizes() { using var bmp = new Bitmap(12, 10); @@ -66,7 +71,7 @@ public async Task Resize_and_crop_produce_expected_sizes() csize.Height.Should().Be(3); } - [SkippableFact(Timeout = 20000)] + [IntegrationFact] public async Task Add_representation_for_scale_factor() { using var bmp = new Bitmap(5, 5); diff --git a/src/ElectronNET.IntegrationTests/Tests/NativeThemeTests.cs b/src/ElectronNET.IntegrationTests/Tests/NativeThemeTests.cs index 4128a223..9e9bb615 100644 --- a/src/ElectronNET.IntegrationTests/Tests/NativeThemeTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/NativeThemeTests.cs @@ -3,29 +3,35 @@ namespace ElectronNET.IntegrationTests.Tests using System.Runtime.Versioning; using ElectronNET.API; using ElectronNET.API.Entities; + using ElectronNET.Common; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class NativeThemeTests + public class NativeThemeTests : IntegrationTestBase { - [Fact(Timeout = 20000)] + public NativeThemeTests(ElectronFixture fx) : base(fx) + { + } + + [IntegrationFact] public async Task ThemeSource_roundtrip() { // Capture initial _ = await Electron.NativeTheme.ShouldUseDarkColorsAsync(); // Force light - await Task.Delay(50); + await Task.Delay(50.ms()); Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Light); - await Task.Delay(500); + await Task.Delay(500.ms()); var useDarkAfterLight = await Electron.NativeTheme.ShouldUseDarkColorsAsync(); var themeSourceLight = await Electron.NativeTheme.GetThemeSourceAsync(); // Force dark Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Dark); - await Task.Delay(500); + await Task.Delay(500.ms()); var useDarkAfterDark = await Electron.NativeTheme.ShouldUseDarkColorsAsync(); var themeSourceDark = await Electron.NativeTheme.GetThemeSourceAsync(); // Restore system Electron.NativeTheme.SetThemeSource(ThemeSourceMode.System); - await Task.Delay(500); + await Task.Delay(500.ms()); var themeSourceSystem = await Electron.NativeTheme.GetThemeSourceAsync(); // Assertions are tolerant (platform dependent) useDarkAfterLight.Should().BeFalse("forcing Light should result in light colors"); @@ -35,34 +41,34 @@ public async Task ThemeSource_roundtrip() themeSourceSystem.Should().Be(ThemeSourceMode.System); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Updated_event_fires_on_change() { var fired = false; Electron.NativeTheme.Updated += () => fired = true; Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Dark); - await Task.Delay(400); + await Task.Delay(400.ms()); Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Light); for (int i = 0; i < 10 && !fired; i++) { - await Task.Delay(100); + await Task.Delay(100.ms()); } fired.Should().BeTrue(); } - [SkippableFact(Timeout = 20000)] - [SupportedOSPlatform("macOS")] - [SupportedOSPlatform("Windows")] + [IntegrationFact] + [SupportedOSPlatform(MacOS)] + [SupportedOSPlatform(Windows)] public async Task Should_use_high_contrast_colors_check() { var metrics = await Electron.NativeTheme.ShouldUseHighContrastColorsAsync(); metrics.Should().Be(false); } - [SkippableFact(Timeout = 20000)] - [SupportedOSPlatform("macOS")] - [SupportedOSPlatform("Windows")] + [IntegrationFact] + [SupportedOSPlatform(MacOS)] + [SupportedOSPlatform(Windows)] public async Task Should_use_inverted_colors_check() { var metrics = await Electron.NativeTheme.ShouldUseInvertedColorSchemeAsync(); diff --git a/src/ElectronNET.IntegrationTests/Tests/NotificationTests.cs b/src/ElectronNET.IntegrationTests/Tests/NotificationTests.cs index a974bdc2..9f8772f4 100644 --- a/src/ElectronNET.IntegrationTests/Tests/NotificationTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/NotificationTests.cs @@ -3,11 +3,17 @@ namespace ElectronNET.IntegrationTests.Tests using System.Runtime.InteropServices; using ElectronNET.API; using ElectronNET.API.Entities; + using ElectronNET.Common; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class NotificationTests + public class NotificationTests : IntegrationTestBase { - [SkippableFact(Timeout = 20000)] + public NotificationTests(ElectronFixture fx) : base(fx) + { + } + + [IntegrationFact] public async Task Notification_create_check() { Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Linux), "Always returns false. Might need full-blown desktop environment"); @@ -17,16 +23,16 @@ public async Task Notification_create_check() var options = new NotificationOptions("Notification Title", "Notification test 123"); options.OnShow = () => tcs.SetResult(); - await Task.Delay(500); + await Task.Delay(500.ms()); Electron.Notification.Show(options); - await Task.WhenAny(tcs.Task, Task.Delay(5_000)); + await Task.WhenAny(tcs.Task, Task.Delay(5.seconds())); tcs.Task.IsCompletedSuccessfully.Should().BeTrue(); } - [SkippableFact(Timeout = 20000)] + [IntegrationFact] public async Task Notification_is_supported_check() { Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Linux), "Always returns false. Might need full-blown desktop environment"); diff --git a/src/ElectronNET.IntegrationTests/Tests/ProcessTests.cs b/src/ElectronNET.IntegrationTests/Tests/ProcessTests.cs index 3fe27248..f5a9f485 100644 --- a/src/ElectronNET.IntegrationTests/Tests/ProcessTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/ProcessTests.cs @@ -1,11 +1,16 @@ namespace ElectronNET.IntegrationTests.Tests { using ElectronNET.API; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class ProcessTests + public class ProcessTests : IntegrationTestBase { - [Fact(Timeout = 20000)] + public ProcessTests(ElectronFixture fx) : base(fx) + { + } + + [IntegrationFact] public async Task Process_info_is_accessible() { // Use renderer to fetch process info and round-trip @@ -14,7 +19,7 @@ public async Task Process_info_is_accessible() result.Should().Be("ok"); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Process_properties_are_populated() { var execPath = await Electron.Process.ExecPathAsync; diff --git a/src/ElectronNET.IntegrationTests/Tests/ScreenTests.cs b/src/ElectronNET.IntegrationTests/Tests/ScreenTests.cs index 2ecf9ecb..1ccf1f8b 100644 --- a/src/ElectronNET.IntegrationTests/Tests/ScreenTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/ScreenTests.cs @@ -6,17 +6,13 @@ namespace ElectronNET.IntegrationTests.Tests using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class ScreenTests + public class ScreenTests : IntegrationTestBase { - // ReSharper disable once NotAccessedField.Local - private readonly ElectronFixture fx; - - public ScreenTests(ElectronFixture fx) + public ScreenTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [SkipOnWslFact(Timeout = 20000)] + [IntegrationFact(SkipOnWsl = true)] public async Task Primary_display_has_positive_dimensions() { var display = await Electron.Screen.GetPrimaryDisplayAsync(); @@ -24,7 +20,7 @@ public async Task Primary_display_has_positive_dimensions() display.Size.Height.Should().BeGreaterThan(0); } - [SkipOnWslFact(Timeout = 20000)] + [IntegrationFact(SkipOnWsl = true)] public async Task GetAllDisplays_returns_at_least_one() { var displays = await Electron.Screen.GetAllDisplaysAsync(); @@ -32,17 +28,15 @@ public async Task GetAllDisplays_returns_at_least_one() displays.Length.Should().BeGreaterThan(0); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task GetCursorScreenPoint_check() { var point = await Electron.Screen.GetCursorScreenPointAsync(); point.Should().NotBeNull(); - point.X.Should().BeGreaterThanOrEqualTo(0); - point.Y.Should().BeGreaterThanOrEqualTo(0); } - [SkippableFact(Timeout = 20000)] - [SupportedOSPlatform("macOS")] + [IntegrationFact] + [SupportedOSPlatform(MacOS)] public async Task GetMenuBarWorkArea_check() { var area = await Electron.Screen.GetMenuBarWorkAreaAsync(); @@ -53,7 +47,7 @@ public async Task GetMenuBarWorkArea_check() area.Width.Should().BeGreaterThan(0); } - [SkipOnWslFact(Timeout = 20000)] + [IntegrationFact(SkipOnWsl = true)] public async Task GetDisplayNearestPoint_check() { var point = new Point @@ -67,7 +61,7 @@ public async Task GetDisplayNearestPoint_check() display.Size.Height.Should().BeGreaterThan(0); } - [SkipOnWslFact(Timeout = 20000)] + [IntegrationFact(SkipOnWsl = true)] public async Task GetDisplayMatching_check() { var rectangle = new Rectangle diff --git a/src/ElectronNET.IntegrationTests/Tests/SessionTests.cs b/src/ElectronNET.IntegrationTests/Tests/SessionTests.cs index 50243a97..53dad7bb 100644 --- a/src/ElectronNET.IntegrationTests/Tests/SessionTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/SessionTests.cs @@ -1,21 +1,19 @@ namespace ElectronNET.IntegrationTests.Tests { using ElectronNET.API.Entities; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class SessionTests + public class SessionTests : IntegrationTestBase { - private readonly ElectronFixture fx; - - public SessionTests(ElectronFixture fx) + public SessionTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Session_preloads_roundtrip() { - var session = this.fx.MainWindow.WebContents.Session; + var session = this.MainWindow.WebContents.Session; _ = await session.GetPreloadsAsync(); // Use a dummy path; API should store value session.SetPreloads(new[] { "/tmp/preload_dummy.js" }); @@ -23,10 +21,10 @@ public async Task Session_preloads_roundtrip() preloadsAfter.Should().Contain("/tmp/preload_dummy.js"); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Session_proxy_set_and_resolve() { - var session = this.fx.MainWindow.WebContents.Session; + var session = this.MainWindow.WebContents.Session; // Provide all ctor args (pacScript empty to ignore, proxyRules direct, bypass empty) await session.SetProxyAsync(new ProxyConfig("", "direct://", "")); var proxy = await session.ResolveProxyAsync("https://example.com"); @@ -34,10 +32,10 @@ public async Task Session_proxy_set_and_resolve() } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Session_clear_cache_and_storage_completes() { - var session = this.fx.MainWindow.WebContents.Session; + var session = this.MainWindow.WebContents.Session; await session.ClearCacheAsync(); await session.ClearStorageDataAsync(); await session.ClearHostResolverCacheAsync(); @@ -46,10 +44,10 @@ public async Task Session_clear_cache_and_storage_completes() ua.Should().NotBeNullOrWhiteSpace(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Session_preloads_set_multiple_and_clear() { - var session = this.fx.MainWindow.WebContents.Session; + var session = this.MainWindow.WebContents.Session; session.SetPreloads(new[] { "/tmp/a.js", "/tmp/b.js" }); var after = await session.GetPreloadsAsync(); after.Should().Contain("/tmp/a.js").And.Contain("/tmp/b.js"); @@ -59,40 +57,40 @@ public async Task Session_preloads_set_multiple_and_clear() empty.Should().NotContain("/tmp/a.js"); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Clear_auth_cache_overloads() { - var session = this.fx.MainWindow.WebContents.Session; + var session = this.MainWindow.WebContents.Session; await session.ClearAuthCacheAsync(); await session.ClearAuthCacheAsync(new RemovePassword("password") { Origin = "https://example.com", Username = "user", Password = "pw", Realm = "realm", Scheme = Scheme.basic }); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Clear_storage_with_options() { - var session = this.fx.MainWindow.WebContents.Session; + var session = this.MainWindow.WebContents.Session; await session.ClearStorageDataAsync(new ClearStorageDataOptions { Storages = new[] { "cookies" }, Quotas = new[] { "temporary" } }); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Enable_disable_network_emulation() { - var session = this.fx.MainWindow.WebContents.Session; + var session = this.MainWindow.WebContents.Session; session.EnableNetworkEmulation(new EnableNetworkEmulationOptions { Offline = false, Latency = 10, DownloadThroughput = 50000, UploadThroughput = 20000 }); session.DisableNetworkEmulation(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Flush_storage_data_does_not_throw() { - var session = this.fx.MainWindow.WebContents.Session; + var session = this.MainWindow.WebContents.Session; session.FlushStorageData(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Set_user_agent_affects_new_navigation() { - var session = this.fx.MainWindow.WebContents.Session; + var session = this.MainWindow.WebContents.Session; // Set UA and verify via session API (navigator.userAgent on existing WebContents may not reflect the override) session.SetUserAgent("IntegrationAgent/1.0"); var ua = await session.GetUserAgent(); diff --git a/src/ElectronNET.IntegrationTests/Tests/ShellTests.cs b/src/ElectronNET.IntegrationTests/Tests/ShellTests.cs index ff185db1..3a71ad8e 100644 --- a/src/ElectronNET.IntegrationTests/Tests/ShellTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/ShellTests.cs @@ -1,11 +1,16 @@ namespace ElectronNET.IntegrationTests.Tests { using ElectronNET.API; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class ShellTests + public class ShellTests : IntegrationTestBase { - [Fact(Skip = "This can keep the test process hanging until the e-mail window is closed")] + public ShellTests(ElectronFixture fx) : base(fx) + { + } + + [IntegrationFact(Skip = "This can keep the test process hanging until the e-mail window is closed")] public async Task OpenExternal_invalid_scheme_returns_error_or_empty() { var error = await Electron.Shell.OpenExternalAsync("mailto:test@example.com"); diff --git a/src/ElectronNET.IntegrationTests/Tests/ThumbarButtonTests.cs b/src/ElectronNET.IntegrationTests/Tests/ThumbarButtonTests.cs index 15253bd5..fbbd6981 100644 --- a/src/ElectronNET.IntegrationTests/Tests/ThumbarButtonTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/ThumbarButtonTests.cs @@ -2,28 +2,26 @@ namespace ElectronNET.IntegrationTests.Tests { using System.Runtime.Versioning; using ElectronNET.API.Entities; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class ThumbarButtonTests + public class ThumbarButtonTests : IntegrationTestBase { - private readonly ElectronFixture fx; - - public ThumbarButtonTests(ElectronFixture fx) + public ThumbarButtonTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [SkippableFact(Timeout = 20000)] - [SupportedOSPlatform("Windows")] + [IntegrationFact] + [SupportedOSPlatform(Windows)] public async Task SetThumbarButtons_returns_success() { var btn = new ThumbarButton("icon.png") { Tooltip = "Test" }; - var success = await this.fx.MainWindow.SetThumbarButtonsAsync(new[] { btn }); + var success = await this.MainWindow.SetThumbarButtonsAsync(new[] { btn }); success.Should().BeTrue(); } - [SkippableFact(Timeout = 20000)] - [SupportedOSPlatform("Windows")] + [IntegrationFact] + [SupportedOSPlatform(Windows)] public async Task Thumbar_button_click_invokes_callback() { var icon = Path.Combine(Directory.GetCurrentDirectory(), "ElectronNET.WebApp", "wwwroot", "icon.png"); @@ -34,7 +32,7 @@ public async Task Thumbar_button_click_invokes_callback() var tcs = new TaskCompletionSource(); var btn = new ThumbarButton(icon) { Tooltip = "Test", Flags = new[] { ThumbarButtonFlag.enabled }, Click = () => tcs.TrySetResult(true) }; - var ok = await this.fx.MainWindow.SetThumbarButtonsAsync(new[] { btn }); + var ok = await this.MainWindow.SetThumbarButtonsAsync(new[] { btn }); ok.Should().BeTrue(); } } diff --git a/src/ElectronNET.IntegrationTests/Tests/TrayTests.cs b/src/ElectronNET.IntegrationTests/Tests/TrayTests.cs index f5b07ff1..324534e2 100644 --- a/src/ElectronNET.IntegrationTests/Tests/TrayTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/TrayTests.cs @@ -1,19 +1,16 @@ namespace ElectronNET.IntegrationTests.Tests { using ElectronNET.API; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class TrayTests + public class TrayTests : IntegrationTestBase { - // ReSharper disable once NotAccessedField.Local - private readonly ElectronFixture fx; - - public TrayTests(ElectronFixture fx) + public TrayTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_create_tray_and_destroy() { //await Electron.Tray.Show("assets/icon.png"); diff --git a/src/ElectronNET.IntegrationTests/Tests/WebContentsTests.cs b/src/ElectronNET.IntegrationTests/Tests/WebContentsTests.cs index 6c727054..7afae016 100644 --- a/src/ElectronNET.IntegrationTests/Tests/WebContentsTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/WebContentsTests.cs @@ -2,56 +2,56 @@ namespace ElectronNET.IntegrationTests.Tests { + using ElectronNET.API; using ElectronNET.API.Entities; + using ElectronNET.Common; + using ElectronNET.IntegrationTests.Common; [Collection("ElectronCollection")] - public class WebContentsTests + public class WebContentsTests : IntegrationTestBase { - private readonly ElectronFixture fx; - - public WebContentsTests(ElectronFixture fx) + public WebContentsTests(ElectronFixture fx) : base(fx) { - this.fx = fx; } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_get_url_after_navigation() { - var wc = this.fx.MainWindow.WebContents; + var wc = this.MainWindow.WebContents; await wc.LoadURLAsync("https://example.com"); var url = await wc.GetUrl(); url.Should().Contain("example.com"); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task ExecuteJavaScript_returns_title() { - var wc = this.fx.MainWindow.WebContents; + var wc = this.MainWindow.WebContents; await wc.LoadURLAsync("https://example.com"); var title = await wc.ExecuteJavaScriptAsync("document.title"); title.Should().NotBeNull(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task DomReady_event_fires() { - var wc = this.fx.MainWindow.WebContents; + var wc = this.MainWindow.WebContents; var fired = false; wc.OnDomReady += () => fired = true; await wc.LoadURLAsync("https://example.com"); - await Task.Delay(500); + await Task.Delay(500.ms()); fired.Should().BeTrue(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_print_to_pdf() { var html = "data:text/html,

PDF Test

Electron.NET

"; - await this.fx.MainWindow.WebContents.LoadURLAsync(html); + await this.MainWindow.WebContents.LoadURLAsync(html); var tmp = Path.Combine(Path.GetTempPath(), $"electronnet_pdf_{Guid.NewGuid():N}.pdf"); try { - var ok = await this.fx.MainWindow.WebContents.PrintToPDFAsync(tmp); + var ok = await this.MainWindow.WebContents.PrintToPDFAsync(tmp); ok.Should().BeTrue(); File.Exists(tmp).Should().BeTrue(); new FileInfo(tmp).Length.Should().BeGreaterThan(0); @@ -65,92 +65,130 @@ public async Task Can_print_to_pdf() } } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task Can_basic_print() { var html = "data:text/html,

Print Test

"; - await this.fx.MainWindow.WebContents.LoadURLAsync(html); - var ok = await this.fx.MainWindow.WebContents.PrintAsync(new PrintOptions { Silent = true, PrintBackground = true }); + await this.MainWindow.WebContents.LoadURLAsync(html); + var ok = await this.MainWindow.WebContents.PrintAsync(new PrintOptions { Silent = true, PrintBackground = true }); ok.Should().BeTrue(); } - [SkippableFact(Timeout = 20000)] + [IntegrationFact] public async Task GetPrintersAsync_check() { - Skip.If(Environment.GetEnvironmentVariable("GITHUB_RUN_ID") != null, "Skipping printer test in CI environment."); - var info = await fx.MainWindow.WebContents.GetPrintersAsync(); + var info = await this.MainWindow.WebContents.GetPrintersAsync(); info.Should().NotBeNull(); } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task GetSetZoomFactor_check() { - await fx.MainWindow.WebContents.GetZoomFactorAsync(); - var ok = await fx.MainWindow.WebContents.GetZoomFactorAsync(); + await this.MainWindow.WebContents.GetZoomFactorAsync(); + var ok = await this.MainWindow.WebContents.GetZoomFactorAsync(); ok.Should().BeGreaterThan(0.0); - fx.MainWindow.WebContents.SetZoomFactor(2.0); - await Task.Delay(500); - ok = await fx.MainWindow.WebContents.GetZoomFactorAsync(); + this.MainWindow.WebContents.SetZoomFactor(2.0); + await Task.Delay(500.ms()); + ok = await this.MainWindow.WebContents.GetZoomFactorAsync(); ok.Should().Be(2.0); } - [SkippableFact(Timeout = 20000)] + [IntegrationFact] public async Task GetSetZoomLevel_check() { - Skip.If(Environment.GetEnvironmentVariable("GITHUB_RUN_ID") != null && RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Skipping test on Windows CI."); - await fx.MainWindow.WebContents.GetZoomLevelAsync(); - var ok = await fx.MainWindow.WebContents.GetZoomLevelAsync(); - ok.Should().Be(0); - fx.MainWindow.WebContents.SetZoomLevel(2); - await Task.Delay(500); - ok = await fx.MainWindow.WebContents.GetZoomLevelAsync(); - ok.Should().Be(2); + BrowserWindow window = null; + + try + { + window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true }, "about:blank"); + + await Task.Delay(100.ms()); + + window.WebContents.SetZoomLevel(0); + await Task.Delay(500.ms()); + + var ok = await window.WebContents.GetZoomLevelAsync(); + ok.Should().Be(0); + + window.WebContents.SetZoomLevel(2); + await Task.Delay(500.ms()); + + ok = await window.WebContents.GetZoomLevelAsync(); + ok.Should().Be(2); + } + finally + { + window?.Destroy(); + } } - [SkippableFact(Timeout = 20000)] + [IntegrationFact] public async Task DevTools_check() { - Skip.If(Environment.GetEnvironmentVariable("GITHUB_RUN_ID") != null && RuntimeInformation.IsOSPlatform(OSPlatform.OSX), "Skipping test on macOS CI."); - fx.MainWindow.WebContents.IsDevToolsOpened().Should().BeFalse(); - fx.MainWindow.WebContents.OpenDevTools(); - await Task.Delay(500); - fx.MainWindow.WebContents.IsDevToolsOpened().Should().BeTrue(); - fx.MainWindow.WebContents.CloseDevTools(); - await Task.Delay(500); - fx.MainWindow.WebContents.IsDevToolsOpened().Should().BeFalse(); - fx.MainWindow.WebContents.ToggleDevTools(); - await Task.Delay(500); - fx.MainWindow.WebContents.IsDevToolsOpened().Should().BeTrue(); + BrowserWindow window = null; + + try + { + window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true }, "about:blank"); + + await Task.Delay(3.seconds()); + + window.WebContents.IsDevToolsOpened().Should().BeFalse(); + window.WebContents.OpenDevTools(); + await Task.Delay(5.seconds()); + + window.WebContents.IsDevToolsOpened().Should().BeTrue(); + window.WebContents.CloseDevTools(); + await Task.Delay(2.seconds()); + + window.WebContents.IsDevToolsOpened().Should().BeFalse(); + } + finally + { + window?.Destroy(); + } } - [Fact(Timeout = 20000)] + [IntegrationFact] public async Task GetSetAudioMuted_check() { - fx.MainWindow.WebContents.SetAudioMuted(true); - await Task.Delay(500); - var ok = await fx.MainWindow.WebContents.IsAudioMutedAsync(); + this.MainWindow.WebContents.SetAudioMuted(true); + await Task.Delay(500.ms()); + var ok = await this.MainWindow.WebContents.IsAudioMutedAsync(); ok.Should().BeTrue(); - fx.MainWindow.WebContents.SetAudioMuted(false); - await Task.Delay(500); - ok = await fx.MainWindow.WebContents.IsAudioMutedAsync(); + this.MainWindow.WebContents.SetAudioMuted(false); + await Task.Delay(500.ms()); + ok = await this.MainWindow.WebContents.IsAudioMutedAsync(); ok.Should().BeFalse(); // Assuming no audio is playing, IsCurrentlyAudibleAsync should return false // there is no way to play audio in this test - ok = await fx.MainWindow.WebContents.IsCurrentlyAudibleAsync(); + ok = await this.MainWindow.WebContents.IsCurrentlyAudibleAsync(); ok.Should().BeFalse(); } - [SkippableFact(Timeout = 20000)] + [IntegrationFact] public async Task GetSetUserAgent_check() { - Skip.If(Environment.GetEnvironmentVariable("GITHUB_RUN_ID") != null && RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Skipping test on Windows CI."); - var ok = await fx.MainWindow.WebContents.GetUserAgentAsync(); - ok.Should().NotBeNullOrEmpty(); - fx.MainWindow.WebContents.SetUserAgent("MyUserAgent/1.0"); - await Task.Delay(1000); - ok = await fx.MainWindow.WebContents.GetUserAgentAsync(); - ok.Should().Be("MyUserAgent/1.0"); + BrowserWindow window = null; + + try + { + window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true }, "about:blank"); + + await Task.Delay(3.seconds()); + + window.WebContents.SetUserAgent("MyUserAgent/1.0"); + + await Task.Delay(1.seconds()); + + var ok = await window.WebContents.GetUserAgentAsync(); + ok.Should().Be("MyUserAgent/1.0"); + } + finally + { + window?.Destroy(); + } } } diff --git a/src/ElectronNET.sln.DotSettings b/src/ElectronNET.sln.DotSettings index 1eab21f2..fd98f554 100644 --- a/src/ElectronNET.sln.DotSettings +++ b/src/ElectronNET.sln.DotSettings @@ -1,6 +1,8 @@  + DO_NOT_SHOW False False + CI True True True