Merge pull request #931 from softworkz/submit_update_tests

Integration Tests Everywhere!
This commit is contained in:
Florian Rappl
2025-11-16 02:42:22 +01:00
committed by GitHub
21 changed files with 472 additions and 135 deletions

209
.github/workflows/integration-tests.yml vendored Normal file
View File

@@ -0,0 +1,209 @@
name: Tests
on:
push:
branches: [ develop, main ]
pull_request:
branches: [ develop, main ]
concurrency:
group: integration-tests-${{ github.ref }}
cancel-in-progress: true
jobs:
tests:
name: Integration Tests (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
rid: linux-x64
- os: windows-2022
rid: win-x64
- os: macos-14
rid: osx-arm64
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
DOTNET_NOLOGO: 1
CI: true
ELECTRON_ENABLE_LOGGING: 1
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Restore
run: dotnet restore -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj
- name: Build
run: dotnet build --no-restore -c Release -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj
- name: Install Linux GUI dependencies
if: runner.os == 'Linux'
run: |
set -e
sudo apt-get update
# Core Electron dependencies
sudo apt-get install -y xvfb \
libgtk-3-0 libnss3 libgdk-pixbuf-2.0-0 libdrm2 libgbm1 libxss1 libxtst6 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libx11-xcb1 libasound2t64
- name: Run tests (Linux)
if: runner.os == 'Linux'
continue-on-error: true
run: |
mkdir -p test-results/Ubuntu
xvfb-run -a dotnet test src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj \
-c Release --no-build -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} \
--logger "trx;LogFileName=Ubuntu.trx" \
--logger "console;verbosity=detailed" \
--results-directory test-results
- name: Run tests (Windows)
if: runner.os == 'Windows'
continue-on-error: true
run: |
New-Item -ItemType Directory -Force -Path test-results/Windows | Out-Null
dotnet test src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj -c Release --no-build -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} --logger "trx;LogFileName=Windows.trx" --logger "console;verbosity=detailed" --results-directory test-results
- name: Run tests (macOS)
if: runner.os == 'macOS'
continue-on-error: true
run: |
mkdir -p test-results/macOS
dotnet test src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj -c Release --no-build -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} --logger "trx;LogFileName=macOS.trx" --logger "console;verbosity=detailed" --results-directory test-results
- name: Upload raw test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.os }}
path: test-results/*.trx
retention-days: 7
summary:
name: Test Results
runs-on: ubuntu-24.04
if: always()
needs: [tests]
permissions:
actions: read
contents: read
checks: write
pull-requests: write
steps:
- name: Download all test results
uses: actions/download-artifact@v4
with:
path: test-results
- name: Setup .NET (for CTRF conversion)
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Install CTRF TRX→CTRF converter (dotnet tool)
run: |
dotnet new tool-manifest
dotnet tool install DotnetCtrfJsonReporter --local
- name: Convert TRX → CTRF and clean names (keep suites; set filePath=OS)
shell: bash
run: |
set -euo pipefail
mkdir -p ctrf
shopt -s globstar nullglob
conv=0
for trx in test-results/**/*.trx; do
fname="$(basename "$trx")"
os="${fname%.trx}"
outdir="ctrf/${os}"
mkdir -p "$outdir"
out="${outdir}/ctrf-report.json"
dotnet tool run DotnetCtrfJsonReporter -p "$trx" -d "$outdir" -f "ctrf-report.json"
jq --arg os "$os" '.results.tests |= map(.filePath = $os)' "$out" > "${out}.tmp" && mv "${out}.tmp" "$out"
echo "Converted & normalized $trx -> $out"
conv=$((conv+1))
done
echo "Processed $conv TRX file(s)"
- name: Publish Test Report
if: always()
uses: ctrf-io/github-test-reporter@v1
with:
report-path: 'ctrf/**/*.json'
summary: true
pull-request: false
status-check: false
status-check-name: 'Integration Tests'
use-suite-name: true
update-comment: true
always-group-by: true
overwrite-comment: true
exit-on-fail: true
group-by: 'suite'
upload-artifact: true
fetch-previous-results: true
summary-report: false
summary-delta-report: true
github-report: true
test-report: false
test-list-report: false
failed-report: true
failed-folded-report: false
skipped-report: true
suite-folded-report: true
suite-list-report: false
file-report: true
previous-results-report: true
insights-report: true
flaky-report: true
flaky-rate-report: true
fail-rate-report: false
slowest-report: false
report-order: 'summary-delta-report,failed-report,skipped-report,suite-folded-report,file-report,previous-results-report,github-report'
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Create PR Comment
if: always()
uses: ctrf-io/github-test-reporter@v1
with:
report-path: 'ctrf/**/*.json'
summary: true
pull-request: true
use-suite-name: true
update-comment: true
always-group-by: true
overwrite-comment: true
upload-artifact: false
pull-request-report: true
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Summary
run: echo "All matrix test jobs completed."

View File

@@ -24,6 +24,8 @@
StartupManager.Initialize();
}
public static string ElectronExtraArguments { get; set; }
public static int? ElectronSocketPort { get; internal set; }
public static int? AspNetWebPort { get; internal set; }

View File

@@ -40,7 +40,7 @@
{
var isUnPacked = ElectronNetRuntime.StartupMethod.IsUnpackaged();
var electronBinaryName = ElectronNetRuntime.ElectronExecutable;
var args = Environment.CommandLine;
var args = string.Format("{0} {1}", ElectronNetRuntime.ElectronExtraArguments, Environment.CommandLine).Trim();
this.port = ElectronNetRuntime.ElectronSocketPort;
if (!this.port.HasValue)
@@ -49,10 +49,15 @@
ElectronNetRuntime.ElectronSocketPort = this.port;
}
Console.Error.WriteLine("[StartCore]: isUnPacked: {0}", isUnPacked);
Console.Error.WriteLine("[StartCore]: electronBinaryName: {0}", electronBinaryName);
Console.Error.WriteLine("[StartCore]: args: {0}", args);
this.electronProcess = new ElectronProcessActive(isUnPacked, electronBinaryName, args, this.port.Value);
this.electronProcess.Ready += this.ElectronProcess_Ready;
this.electronProcess.Stopped += this.ElectronProcess_Stopped;
Console.Error.WriteLine("[StartCore]: Before Start");
return this.electronProcess.Start();
}
@@ -82,11 +87,11 @@
private void HandleStopped()
{
if (this.socketBridge.State != LifetimeState.Stopped)
if (this.socketBridge != null && this.socketBridge.State != LifetimeState.Stopped)
{
this.socketBridge.Stop();
}
else if (this.electronProcess.State != LifetimeState.Stopped)
else if (this.electronProcess != null && this.electronProcess.State != LifetimeState.Stopped)
{
this.electronProcess.Stop();
}

View File

@@ -5,6 +5,7 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
/// <summary>
@@ -42,6 +43,11 @@
var electrondir = Path.Combine(dir.FullName, ".electron");
startCmd = Path.Combine(electrondir, "node_modules", "electron", "dist", "electron");
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
startCmd = Path.Combine(electrondir, "node_modules", "electron", "dist", "Electron.app", "Contents", "MacOS", "Electron");
}
args = $"main.js -unpackeddotnet --trace-warnings -electronforcedport={this.socketPort:D} " + this.extraArguments;
workingDir = electrondir;
}
@@ -68,22 +74,40 @@
private async Task StartInternal(string startCmd, string args, string directoriy)
{
await Task.Delay(10).ConfigureAwait(false);
this.process = new ProcessRunner("ElectronRunner");
this.process.ProcessExited += this.Process_Exited;
this.process.Run(startCmd, args, directoriy);
await Task.Delay(500).ConfigureAwait(false);
if (!this.process.IsRunning)
try
{
Task.Run(() => this.TransitionState(LifetimeState.Stopped));
await Task.Delay(10).ConfigureAwait(false);
throw new Exception("Failed to launch the Electron process.");
Console.Error.WriteLine("[StartInternal]: startCmd: {0}", startCmd);
Console.Error.WriteLine("[StartInternal]: args: {0}", args);
this.process = new ProcessRunner("ElectronRunner");
this.process.ProcessExited += this.Process_Exited;
this.process.Run(startCmd, args, directoriy);
await Task.Delay(500).ConfigureAwait(false);
Console.Error.WriteLine("[StartInternal]: after run:");
if (!this.process.IsRunning)
{
Console.Error.WriteLine("[StartInternal]: Process is not running: " + this.process.StandardError);
Console.Error.WriteLine("[StartInternal]: Process is not running: " + this.process.StandardOutput);
Task.Run(() => this.TransitionState(LifetimeState.Stopped));
throw new Exception("Failed to launch the Electron process.");
}
this.TransitionState(LifetimeState.Ready);
}
catch (Exception ex)
{
Console.Error.WriteLine("[StartInternal]: Exception: " + this.process?.StandardError);
Console.Error.WriteLine("[StartInternal]: Exception: " + this.process?.StandardOutput);
Console.Error.WriteLine("[StartInternal]: Exception: " + ex);
throw;
}
this.TransitionState(LifetimeState.Ready);
}
private void Process_Exited(object sender, EventArgs e)

View File

@@ -132,13 +132,19 @@
if (electronAssembly == null)
{
Console.WriteLine("GatherBuildInfo: Early exit");
return buildInfo;
}
if (electronAssembly.GetName().Name == "testhost" || electronAssembly.GetName().Name == "ReSharperTestRunner")
{
Console.WriteLine("GatherBuildInfo: Detected testhost");
electronAssembly = AppDomain.CurrentDomain.GetData("ElectronTestAssembly") as Assembly ?? electronAssembly;
}
else
{
Console.WriteLine("GatherBuildInfo: No testhost detected: " + electronAssembly.GetName().Name);
}
var attributes = electronAssembly.GetCustomAttributes<AssemblyMetadataAttribute>().ToList();

View File

@@ -0,0 +1,49 @@
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();
}
/// <summary>
/// Initializes a new instance of the <see cref="SkipOnWslFactAttribute" /> class.
/// </summary>
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;
}
}
}
}

View File

@@ -1,38 +1,65 @@
namespace ElectronNET.IntegrationTests
{
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using ElectronNET.API;
using ElectronNET.API.Entities;
// Shared fixture that starts Electron runtime once
[SuppressMessage("ReSharper", "MethodHasAsyncOverload")]
public class ElectronFixture : IAsyncLifetime
{
public BrowserWindow MainWindow { get; private set; } = null!;
public async Task InitializeAsync()
{
AppDomain.CurrentDomain.SetData("ElectronTestAssembly", Assembly.GetExecutingAssembly());
var runtimeController = ElectronNetRuntime.RuntimeController;
await runtimeController.Start();
await runtimeController.WaitReadyTask;
// create hidden window for tests (avoid showing UI)
this.MainWindow = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
try
{
Show = false,
Width = 800,
Height = 600,
});
Console.Error.WriteLine("[ElectronFixture] InitializeAsync: start");
AppDomain.CurrentDomain.SetData("ElectronTestAssembly", Assembly.GetExecutingAssembly());
// Clear potential cache side-effects
await this.MainWindow.WebContents.Session.ClearCacheAsync();
Console.WriteLine("[ElectronFixture] Acquire RuntimeController");
var runtimeController = ElectronNetRuntime.RuntimeController;
ElectronNetRuntime.ElectronExtraArguments = "--no-sandbox";
Console.Error.WriteLine("[ElectronFixture] Starting Electron runtime...");
await runtimeController.Start();
Console.Error.WriteLine("[ElectronFixture] Waiting for Ready...");
await Task.WhenAny(runtimeController.WaitReadyTask, Task.Delay(TimeSpan.FromSeconds(10)));
if (!runtimeController.WaitReadyTask.IsCompleted)
{
throw new TimeoutException("The Electron process did not start within 10 seconds");
}
Console.Error.WriteLine("[ElectronFixture] Runtime Ready");
// create hidden window for tests (avoid showing UI)
this.MainWindow = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
{
Show = false,
Width = 800,
Height = 600,
}, "about:blank");
await this.MainWindow.WebContents.Session.ClearCacheAsync();
}
catch (Exception ex)
{
Console.Error.WriteLine("[ElectronFixture] InitializeAsync: exception");
Console.Error.WriteLine(ex.ToString());
throw;
}
}
public async Task DisposeAsync()
{
var runtimeController = ElectronNetRuntime.RuntimeController;
Console.Error.WriteLine("[ElectronFixture] Stopping Electron runtime...");
await runtimeController.Stop();
await runtimeController.WaitStoppedTask;
Console.Error.WriteLine("[ElectronFixture] Runtime stopped");
}
}

View File

@@ -8,7 +8,7 @@
<Import Project="..\ElectronNET\build\ElectronNET.Core.props" Condition="$(ElectronNetDevMode)" />
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
@@ -16,14 +16,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.4.1" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="FluentAssertions" Version="8.8.0" />
<PackageReference Include="Xunit.SkippableFact" Version="1.5.23" />
</ItemGroup>
@@ -37,6 +37,12 @@
<ParallelizeTestCollections>false</ParallelizeTestCollections>
</PropertyGroup>
<ItemGroup>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Import Project="..\ElectronNET\build\ElectronNET.Core.targets" Condition="$(ElectronNetDevMode)" />
</Project>

View File

@@ -1,10 +1,10 @@
namespace ElectronNET.IntegrationTests.Tests
{
using System.Runtime.InteropServices;
using ElectronNET.API;
using ElectronNET.API.Entities;
using System;
using System.IO;
using System.Runtime.Versioning;
using System.Threading.Tasks;
[Collection("ElectronCollection")]
@@ -47,28 +47,6 @@ namespace ElectronNET.IntegrationTests.Tests
Directory.Exists(temp).Should().BeTrue();
}
[Fact(Timeout = 20000)]
public async Task Badge_count_roundtrip_where_supported()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var ok = await Electron.App.SetBadgeCountAsync(3);
ok.Should().BeTrue();
var count = await Electron.App.GetBadgeCountAsync();
count.Should().Be(3);
// reset
await Electron.App.SetBadgeCountAsync(0);
}
else
{
// On Windows it's usually unsupported; ensure badge query works and returns a non-negative value
await Electron.App.SetBadgeCountAsync(0); // ignore return value
var count = await Electron.App.GetBadgeCountAsync();
count.Should().BeGreaterOrEqualTo(0);
}
}
[Fact(Timeout = 20000)]
public async Task Can_get_app_metrics()
{
@@ -84,21 +62,15 @@ namespace ElectronNET.IntegrationTests.Tests
status.Should().NotBeNull();
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public async Task Can_get_login_item_settings()
{
var settings = await Electron.App.GetLoginItemSettingsAsync();
settings.Should().NotBeNull();
}
[Fact(Timeout = 20000)]
public async Task Can_set_app_logs_path()
{
var tempDir = Path.Combine(Path.GetTempPath(), "ElectronLogsTest" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempDir);
Electron.App.SetAppLogsPath(tempDir);
}
[Fact(Timeout = 20000)]
public async Task CommandLine_append_and_query_switch()
{
@@ -108,7 +80,9 @@ namespace ElectronNET.IntegrationTests.Tests
(await Electron.App.CommandLine.GetSwitchValueAsync(switchName)).Should().Be("value123");
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public async Task Accessibility_support_toggle()
{
Electron.App.SetAccessibilitySupportEnabled(true);
@@ -127,13 +101,15 @@ namespace ElectronNET.IntegrationTests.Tests
Electron.App.UserAgentFallback = original; // restore
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("Linux")]
[SupportedOSPlatform("macOS")]
public async Task BadgeCount_set_and_reset_where_supported()
{
await Electron.App.SetBadgeCountAsync(2);
var count = await Electron.App.GetBadgeCountAsync();
// Some platforms may always return0; just ensure call didn't throw and is non-negative
count.Should().BeGreaterOrEqualTo(0);
count.Should().BeGreaterThanOrEqualTo(0);
await Electron.App.SetBadgeCountAsync(0);
}
@@ -144,17 +120,6 @@ namespace ElectronNET.IntegrationTests.Tests
metrics[0].Cpu.Should().NotBeNull();
}
[Fact(Timeout = 20000)]
public async Task App_badge_count_roundtrip()
{
// Set then get (non-mac platforms treat as no-op but should return0 or set value)
var success = await Electron.App.SetBadgeCountAsync(3);
success.Should().BeTrue();
var count = await Electron.App.GetBadgeCountAsync();
// Allow fallback to0 on platforms without badge support
(count == 3 || count == 0).Should().BeTrue();
}
[Fact(Timeout = 20000)]
public async Task App_gpu_feature_status_has_some_fields()
{
@@ -164,4 +129,4 @@ namespace ElectronNET.IntegrationTests.Tests
status.VideoDecode.Should().NotBeNull();
}
}
}
}

View File

@@ -89,6 +89,7 @@
var test = await Electron.AutoUpdater.ChannelAsync;
test.Should().Be(string.Empty);
Electron.AutoUpdater.SetChannel = "beta";
await Task.Delay(500);
test = await Electron.AutoUpdater.ChannelAsync;
test.Should().Be("beta");
}

View File

@@ -1,8 +1,10 @@
namespace ElectronNET.IntegrationTests.Tests
{
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using ElectronNET.API;
using ElectronNET.API.Entities;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class BrowserWindowTests
@@ -42,7 +44,7 @@ namespace ElectronNET.IntegrationTests.Tests
await Task.Delay(50);
}
[Fact(Timeout = 20000)]
[SkipOnWslFact(Timeout = 20000)]
public async Task Can_set_and_get_position()
{
this.fx.MainWindow.SetPosition(134, 246);
@@ -91,21 +93,26 @@ namespace ElectronNET.IntegrationTests.Tests
(await this.fx.MainWindow.IsAlwaysOnTopAsync()).Should().BeFalse();
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
[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();
}
[Fact(Timeout = 20000)]
public async Task ReadyToShow_event_fires_after_content_ready()
{
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false });
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false }, "about:blank");
var tcs = new TaskCompletionSource();
window.OnReadyToShow += () => tcs.TrySetResult();
@@ -125,7 +132,7 @@ namespace ElectronNET.IntegrationTests.Tests
[Fact(Timeout = 20000)]
public async Task PageTitleUpdated_event_fires_on_title_change()
{
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true });
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = true }, "about:blank");
var tcs = new TaskCompletionSource<string>();
window.OnPageTitleUpdated += title => tcs.TrySetResult(title);
@@ -145,7 +152,7 @@ namespace ElectronNET.IntegrationTests.Tests
[Fact(Timeout = 20000)]
public async Task Resize_event_fires_on_size_change()
{
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false });
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false }, "about:blank");
var resized = false;
window.OnResize += () => resized = true;
window.SetSize(500, 400);
@@ -165,7 +172,9 @@ namespace ElectronNET.IntegrationTests.Tests
(await win.IsAlwaysOnTopAsync()).Should().BeFalse();
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("Linux")]
[SupportedOSPlatform("Windows")]
public async Task Menu_bar_visibility_and_auto_hide()
{
var win = this.fx.MainWindow;
@@ -178,7 +187,7 @@ namespace ElectronNET.IntegrationTests.Tests
[Fact(Timeout = 20000)]
public async Task Parent_child_relationship_roundtrip()
{
var child = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false, Width = 300, Height = 200 });
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);
var parent = await child.GetParentWindowAsync();
@@ -188,36 +197,28 @@ namespace ElectronNET.IntegrationTests.Tests
child.Destroy();
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
public async Task Represented_filename_and_edited_flags()
{
var win = this.fx.MainWindow;
var temp = Path.Combine(Path.GetTempPath(), "electronnet_test.txt");
File.WriteAllText(temp, "test");
win.SetRepresentedFilename(temp);
await Task.Delay(500);
var represented = await win.GetRepresentedFilenameAsync();
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
represented.Should().Be(temp);
}
else
{
// Non-macOS platforms may not support represented filename; empty is acceptable
represented.Should().BeEmpty();
}
represented.Should().Be(temp);
win.SetDocumentEdited(true);
await Task.Delay(500);
var edited = await win.IsDocumentEditedAsync();
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
edited.Should().BeTrue();
}
else
{
edited.Should().BeFalse(); // unsupported on non-mac platforms
}
edited.Should().BeTrue();
win.SetDocumentEdited(false);
}
}
}
}

View File

@@ -1,5 +1,6 @@
namespace ElectronNET.IntegrationTests.Tests
{
using System.Runtime.Versioning;
using ElectronNET.API;
[Collection("ElectronCollection")]
@@ -31,7 +32,9 @@ namespace ElectronNET.IntegrationTests.Tests
formats.Should().Contain(f => f.Contains("text") || f.Contains("TEXT") || f.Contains("plain"));
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public async Task Bookmark_write_and_read()
{
var url = "https://electron-test.com";
@@ -40,4 +43,4 @@ namespace ElectronNET.IntegrationTests.Tests
bookmark.Url.Should().Be(url);
}
}
}
}

View File

@@ -1,5 +1,6 @@
namespace ElectronNET.IntegrationTests.Tests
{
using System.Runtime.Versioning;
using ElectronNET.API;
using ElectronNET.API.Entities;
@@ -12,15 +13,19 @@ namespace ElectronNET.IntegrationTests.Tests
// Capture initial
_ = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
// Force light
await Task.Delay(50);
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Light);
await Task.Delay(500);
var useDarkAfterLight = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
var themeSourceLight = await Electron.NativeTheme.GetThemeSourceAsync();
// Force dark
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Dark);
await Task.Delay(500);
var useDarkAfterDark = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
var themeSourceDark = await Electron.NativeTheme.GetThemeSourceAsync();
// Restore system
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.System);
await Task.Delay(500);
var themeSourceSystem = await Electron.NativeTheme.GetThemeSourceAsync();
// Assertions are tolerant (platform dependent)
useDarkAfterLight.Should().BeFalse("forcing Light should result in light colors");
@@ -46,18 +51,22 @@ namespace ElectronNET.IntegrationTests.Tests
fired.Should().BeTrue();
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public async Task Should_use_high_contrast_colors_check()
{
var metrics = await Electron.NativeTheme.ShouldUseHighContrastColorsAsync();
metrics.Should().Be(false);
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public async Task Should_use_inverted_colors_check()
{
var metrics = await Electron.NativeTheme.ShouldUseInvertedColorSchemeAsync();
metrics.Should().Be(false);
}
}
}
}

View File

@@ -1,19 +1,24 @@
namespace ElectronNET.IntegrationTests.Tests
{
using System.Runtime.InteropServices;
using ElectronNET.API;
using ElectronNET.API.Entities;
[Collection("ElectronCollection")]
public class NotificationTests
{
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
public async Task Notification_create_check()
{
Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Linux), "Always returns false. Might need full-blown desktop environment");
var tcs = new TaskCompletionSource();
var options = new NotificationOptions("Notification Title", "Notification test 123");
options.OnShow = () => tcs.SetResult();
await Task.Delay(500);
Electron.Notification.Show(options);
await Task.WhenAny(tcs.Task, Task.Delay(5_000));
@@ -21,9 +26,11 @@ namespace ElectronNET.IntegrationTests.Tests
tcs.Task.IsCompletedSuccessfully.Should().BeTrue();
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
public async Task Notification_is_supported_check()
{
Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Linux), "Always returns false. Might need full-blown desktop environment");
var supported = await Electron.Notification.IsSupportedAsync();
supported.Should().BeTrue();
}

View File

@@ -9,7 +9,7 @@ namespace ElectronNET.IntegrationTests.Tests
public async Task Process_info_is_accessible()
{
// Use renderer to fetch process info and round-trip
var execPath = await Electron.WindowManager.CreateWindowAsync(new API.Entities.BrowserWindowOptions { Show = false });
var execPath = await Electron.WindowManager.CreateWindowAsync(new API.Entities.BrowserWindowOptions { Show = false }, "about:blank");
var result = await execPath.WebContents.ExecuteJavaScriptAsync<string>("process.execPath && process.platform ? 'ok' : 'fail'");
result.Should().Be("ok");
}

View File

@@ -1,8 +1,9 @@
using ElectronNET.API.Entities;
namespace ElectronNET.IntegrationTests.Tests
{
using System.Runtime.Versioning;
using ElectronNET.API;
using ElectronNET.API.Entities;
using ElectronNET.IntegrationTests.Common;
[Collection("ElectronCollection")]
public class ScreenTests
@@ -15,7 +16,7 @@ namespace ElectronNET.IntegrationTests.Tests
this.fx = fx;
}
[Fact(Timeout = 20000)]
[SkipOnWslFact(Timeout = 20000)]
public async Task Primary_display_has_positive_dimensions()
{
var display = await Electron.Screen.GetPrimaryDisplayAsync();
@@ -23,7 +24,7 @@ namespace ElectronNET.IntegrationTests.Tests
display.Size.Height.Should().BeGreaterThan(0);
}
[Fact(Timeout = 20000)]
[SkipOnWslFact(Timeout = 20000)]
public async Task GetAllDisplays_returns_at_least_one()
{
var displays = await Electron.Screen.GetAllDisplaysAsync();
@@ -40,7 +41,8 @@ namespace ElectronNET.IntegrationTests.Tests
point.Y.Should().BeGreaterThanOrEqualTo(0);
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("macOS")]
public async Task GetMenuBarWorkArea_check()
{
var area = await Electron.Screen.GetMenuBarWorkAreaAsync();
@@ -51,7 +53,7 @@ namespace ElectronNET.IntegrationTests.Tests
area.Width.Should().BeGreaterThan(0);
}
[Fact(Timeout = 20000)]
[SkipOnWslFact(Timeout = 20000)]
public async Task GetDisplayNearestPoint_check()
{
var point = new Point
@@ -65,7 +67,7 @@ namespace ElectronNET.IntegrationTests.Tests
display.Size.Height.Should().BeGreaterThan(0);
}
[Fact(Timeout = 20000)]
[SkipOnWslFact(Timeout = 20000)]
public async Task GetDisplayMatching_check()
{
var rectangle = new Rectangle
@@ -81,4 +83,4 @@ namespace ElectronNET.IntegrationTests.Tests
display.Size.Height.Should().BeGreaterThan(0);
}
}
}
}

View File

@@ -5,7 +5,7 @@ namespace ElectronNET.IntegrationTests.Tests
[Collection("ElectronCollection")]
public class ShellTests
{
[Fact(Timeout = 20000)]
[Fact(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");

View File

@@ -1,6 +1,6 @@
namespace ElectronNET.IntegrationTests.Tests
{
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using ElectronNET.API.Entities;
[Collection("ElectronCollection")]
@@ -13,7 +13,8 @@ namespace ElectronNET.IntegrationTests.Tests
this.fx = fx;
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("Windows")]
public async Task SetThumbarButtons_returns_success()
{
var btn = new ThumbarButton("icon.png") { Tooltip = "Test" };
@@ -21,14 +22,10 @@ namespace ElectronNET.IntegrationTests.Tests
success.Should().BeTrue();
}
[Fact(Timeout = 20000)]
[SkippableFact(Timeout = 20000)]
[SupportedOSPlatform("Windows")]
public async Task Thumbar_button_click_invokes_callback()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return; // only meaningful on Windows taskbar
}
var icon = Path.Combine(Directory.GetCurrentDirectory(), "ElectronNET.WebApp", "wwwroot", "icon.png");
if (!File.Exists(icon))
{
@@ -41,4 +38,4 @@ namespace ElectronNET.IntegrationTests.Tests
ok.Should().BeTrue();
}
}
}
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"methodDisplay": "method",
"diagnosticMessages": true,
"parallelizeTestCollections": false,
"longRunningTestSeconds": 60
}

View File

@@ -4,7 +4,7 @@
<PropertyGroup Label="ElectronNetCommon">
<ElectronVersion>30.4.0</ElectronVersion>
<ElectronBuilderVersion>26.0</ElectronBuilderVersion>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == ''">win-x64</RuntimeIdentifier>
<ElectronSingleInstance>true</ElectronSingleInstance>
<ElectronSplashScreen></ElectronSplashScreen>
<ElectronIcon></ElectronIcon>

17
src/testEnvironments.json Normal file
View File

@@ -0,0 +1,17 @@
{
"version": "1",
"environments": [
// See https://aka.ms/remotetesting for more details
// about how to configure remote environments.
{
"name": "WSL Ubuntu",
"type": "wsl",
"wslDistribution": "UbuFresh"
}
//{
// "name": "Docker dotnet/sdk",
// "type": "docker",
// "dockerImage": "mcr.microsoft.com/dotnet/sdk"
//}
]
}