Browse Source

TrayIcon integration tests (#16154)

* Add accessibility ID to the TrayPopupRoot on Windows

* [Windows] Add left click and menu item click e2e tests for TrayIcon

* [Windows] Add TrayIcon visibility toggle tests

* Implement macOS tray icon tests

* Make it easier to read tray icon logs

* Try to handle win10 accessibility names

* Try to upload PageSource

* Set condition: always

* Hopefully, it works on CI

* Try to upload PageSource #2

* Fix win10, hopefully for the last time
pull/16265/head
Max Katz 2 years ago
committed by GitHub
parent
commit
ad0bc0dabf
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 9
      samples/IntegrationTestApp/App.axaml
  2. 12
      samples/IntegrationTestApp/App.axaml.cs
  3. 8
      samples/IntegrationTestApp/MainWindow.axaml
  4. 8
      samples/IntegrationTestApp/MainWindow.axaml.cs
  5. 3
      src/Windows/Avalonia.Win32/TrayIconImpl.cs
  6. 35
      tests/Avalonia.IntegrationTests.Appium/DefaultAppFixture.cs
  7. 8
      tests/Avalonia.IntegrationTests.Appium/OverlayPopupsAppFixture.cs
  8. 152
      tests/Avalonia.IntegrationTests.Appium/TrayIconTests.cs

9
samples/IntegrationTestApp/App.axaml

@ -9,10 +9,15 @@
</Application.Styles>
<TrayIcon.Icons>
<TrayIcons>
<TrayIcon Icon="/Assets/icon.ico">
<TrayIcon Icon="/Assets/icon.ico"
ToolTipText="IntegrationTestApp TrayIcon"
Command="{Binding TrayIconCommand}"
CommandParameter="TrayIconClicked">
<TrayIcon.Menu>
<NativeMenu>
<NativeMenuItem Header="Show _Test Window" Command="{Binding ShowWindowCommand}" />
<NativeMenuItem Header="Raise Menu Clicked"
Command="{Binding TrayIconCommand}"
CommandParameter="TrayIconMenuClicked" />
</NativeMenu>
</TrayIcon.Menu>
</TrayIcon>

12
samples/IntegrationTestApp/App.axaml.cs

@ -4,18 +4,20 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using MiniMvvm;
namespace IntegrationTestApp
{
public class App : Application
{
private MainWindow? _mainWindow;
public App()
{
ShowWindowCommand = MiniCommand.Create(() =>
TrayIconCommand = MiniCommand.Create<string>(name =>
{
var window = new Window() { Title = "TrayIcon demo window" };
window.Show();
_mainWindow!.Get<CheckBox>(name).IsChecked = true;
});
DataContext = this;
}
@ -29,12 +31,12 @@ namespace IntegrationTestApp
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
desktop.MainWindow = _mainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
public ICommand ShowWindowCommand { get; }
public ICommand TrayIconCommand { get; }
}
}

8
samples/IntegrationTestApp/MainWindow.axaml

@ -93,6 +93,14 @@
</StackPanel>
</TabItem>
<TabItem Header="Desktop">
<StackPanel>
<CheckBox x:FieldModifier="public" Name="TrayIconClicked">Tray Icon Clicked</CheckBox>
<CheckBox x:FieldModifier="public" Name="TrayIconMenuClicked">Tray Icon Menu Clicked</CheckBox>
<Button Name="ToggleTrayIconVisible" Content="Toggle TrayIcon Visible" />
</StackPanel>
</TabItem>
<TabItem Header="Gestures">
<DockPanel>
<DockPanel DockPanel.Dock="Top">

8
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -223,6 +223,12 @@ namespace IntegrationTestApp
ownedWindow.Show(mainWindow);
}
private void OnToggleTrayIconVisible()
{
var icon = TrayIcon.GetIcons(Application.Current!)!.FirstOrDefault()!;
icon.IsVisible = !icon.IsVisible;
}
private void InitializeGesturesTab()
{
var gestureBorder = GestureBorder;
@ -295,6 +301,8 @@ namespace IntegrationTestApp
OnApplyWindowDecorations(this);
if (source?.Name == nameof(ShowNewWindowDecorations))
OnShowNewWindowDecorations();
if (source?.Name == nameof(ToggleTrayIconVisible))
OnToggleTrayIconVisible();
}
private void OnApplyWindowDecorations(Window window)

3
src/Windows/Avalonia.Win32/TrayIconImpl.cs

@ -218,8 +218,9 @@ namespace Avalonia.Win32
return;
}
var _trayMenu = new TrayPopupRoot()
var _trayMenu = new TrayPopupRoot
{
Name = "AvaloniaTrayPopupRoot_" + _tooltipText,
SystemDecorations = SystemDecorations.None,
SizeToContent = SizeToContent.WidthAndHeight,
Background = null,

35
tests/Avalonia.IntegrationTests.Appium/DefaultAppFixture.cs

@ -16,7 +16,7 @@ namespace Avalonia.IntegrationTests.Appium
{
var options = new AppiumOptions();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (OperatingSystem.IsWindows())
{
ConfigureWin32Options(options);
Session = new WindowsDriver(
@ -28,7 +28,7 @@ namespace Avalonia.IntegrationTests.Appium
Session.WindowHandles[0].Substring(2),
NumberStyles.AllowHexSpecifier)));
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
else if (OperatingSystem.IsMacOS())
{
ConfigureMacOptions(options);
Session = new MacDriver(
@ -37,21 +37,20 @@ namespace Avalonia.IntegrationTests.Appium
}
else
{
throw new NotSupportedException("Unsupported platform.");
throw new PlatformNotSupportedException();
}
}
protected virtual void ConfigureWin32Options(AppiumOptions options)
protected virtual void ConfigureWin32Options(AppiumOptions options, string? app = null)
{
var path = Path.GetFullPath(TestAppPath);
options.AddAdditionalCapability(MobileCapabilityType.App, path);
options.AddAdditionalCapability(MobileCapabilityType.App, app ?? Path.GetFullPath(TestAppPath));
options.AddAdditionalCapability(MobileCapabilityType.PlatformName, MobilePlatform.Windows);
options.AddAdditionalCapability(MobileCapabilityType.DeviceName, "WindowsPC");
}
protected virtual void ConfigureMacOptions(AppiumOptions options)
protected virtual void ConfigureMacOptions(AppiumOptions options, string? app = null)
{
options.AddAdditionalCapability("appium:bundleId", TestAppBundleId);
options.AddAdditionalCapability("appium:bundleId", app ?? TestAppBundleId);
options.AddAdditionalCapability(MobileCapabilityType.PlatformName, MobilePlatform.MacOS);
options.AddAdditionalCapability(MobileCapabilityType.AutomationName, "mac2");
options.AddAdditionalCapability("appium:showServerLogs", true);
@ -71,6 +70,26 @@ namespace Avalonia.IntegrationTests.Appium
}
}
public AppiumDriver CreateNestedSession(string appName)
{
var options = new AppiumOptions();
if (OperatingSystem.IsWindows())
{
ConfigureWin32Options(options, appName);
return new WindowsDriver(new Uri("http://127.0.0.1:4723"), options);
}
else if (OperatingSystem.IsMacOS())
{
ConfigureMacOptions(options, appName);
return new MacDriver(new Uri("http://127.0.0.1:4723/wd/hub"), options);
}
else
{
throw new PlatformNotSupportedException();
}
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetForegroundWindow(IntPtr hWnd);

8
tests/Avalonia.IntegrationTests.Appium/OverlayPopupsAppFixture.cs

@ -4,15 +4,15 @@ namespace Avalonia.IntegrationTests.Appium
{
public class OverlayPopupsAppFixture : DefaultAppFixture
{
protected override void ConfigureWin32Options(AppiumOptions options)
protected override void ConfigureWin32Options(AppiumOptions options, string? app = null)
{
base.ConfigureWin32Options(options);
base.ConfigureWin32Options(options, app);
options.AddAdditionalCapability("appArguments", "--overlayPopups");
}
protected override void ConfigureMacOptions(AppiumOptions options)
protected override void ConfigureMacOptions(AppiumOptions options, string? app = null)
{
base.ConfigureMacOptions(options);
base.ConfigureMacOptions(options, app);
options.AddAdditionalCapability("appium:arguments", new[] { "--overlayPopups" });
}
}

152
tests/Avalonia.IntegrationTests.Appium/TrayIconTests.cs

@ -0,0 +1,152 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Interactions;
using Xunit;
namespace Avalonia.IntegrationTests.Appium;
[Collection("Default")]
public class TrayIconTests : IDisposable
{
private readonly AppiumDriver _session;
private readonly AppiumDriver? _rootSession;
private const string TrayIconName = "IntegrationTestApp TrayIcon";
public TrayIconTests(DefaultAppFixture fixture)
{
_session = fixture.Session;
// "Root" is a special name for windows the desktop session, that has access to task bar.
if (OperatingSystem.IsWindows())
{
_rootSession = fixture.CreateNestedSession("Root");
}
var tabs = _session.FindElementByAccessibilityId("MainTabs");
var tab = tabs.FindElementByName("Desktop");
tab.Click();
}
// Left click is only supported on Windows.
[PlatformFact(TestPlatforms.Windows)]
public void Should_Handle_Left_Click()
{
var avaloinaTrayIconButton = GetTrayIconButton(_rootSession ?? _session, TrayIconName);
Assert.NotNull(avaloinaTrayIconButton);
avaloinaTrayIconButton.SendClick();
Thread.Sleep(2000);
var checkBox = _session.FindElementByAccessibilityId("TrayIconClicked");
Assert.True(checkBox.GetIsChecked());
}
[Fact]
public void Should_Handle_Context_Menu_Item_Click()
{
var avaloinaTrayIconButton = GetTrayIconButton(_rootSession ?? _session, TrayIconName);
Assert.NotNull(avaloinaTrayIconButton);
var contextMenu = ShowAndGetTrayMenu(avaloinaTrayIconButton, TrayIconName);
Assert.NotNull(contextMenu);
var menuItem = contextMenu.FindElementByName("Raise Menu Clicked");
menuItem.SendClick();
Thread.Sleep(2000);
var checkBox = _session.FindElementByAccessibilityId("TrayIconMenuClicked");
Assert.True(checkBox.GetIsChecked());
}
[Fact]
public void Can_Toggle_TrayIcon_Visibility()
{
var avaloinaTrayIconButton = GetTrayIconButton(_rootSession ?? _session, TrayIconName);
Assert.NotNull(avaloinaTrayIconButton);
var toggleButton = _session.FindElementByAccessibilityId("ToggleTrayIconVisible");
toggleButton.SendClick();
avaloinaTrayIconButton = GetTrayIconButton(_rootSession ?? _session, TrayIconName);
Assert.Null(avaloinaTrayIconButton);
toggleButton.SendClick();
avaloinaTrayIconButton = GetTrayIconButton(_rootSession ?? _session, TrayIconName);
Assert.NotNull(avaloinaTrayIconButton);
}
private static AppiumWebElement? GetTrayIconButton(AppiumDriver session, string trayIconName)
{
if (OperatingSystem.IsWindows())
{
var taskBar = session.FindElementsByClassName("Shell_TrayWnd")
.FirstOrDefault() ?? throw new InvalidOperationException("Couldn't find Taskbar on current system.");
if (TryToGetIcon(taskBar, trayIconName) is { } trayIcon)
{
return trayIcon;
}
else
{
// Add a sleep here, as previous test might still run popup closing animation.
Thread.Sleep(1000);
// win11: SystemTrayIcon
// win10: Notification Chevron
var trayIconsButton = taskBar.FindElementsByAccessibilityId("SystemTrayIcon").FirstOrDefault()
?? taskBar.FindElementsByName("Notification Chevron").FirstOrDefault()
?? throw new InvalidOperationException("SystemTrayIcon cannot be found.");
trayIconsButton.Click();
// win11: TopLevelWindowForOverflowXamlIsland
// win10: NotifyIconOverflowWindow
var trayIconsFlyout = session.FindElementsByClassName("TopLevelWindowForOverflowXamlIsland").FirstOrDefault()
?? session.FindElementsByClassName("NotifyIconOverflowWindow").FirstOrDefault()
?? throw new InvalidOperationException("System tray overflow window cannot be found.");
return TryToGetIcon(trayIconsFlyout, trayIconName);
}
static AppiumWebElement? TryToGetIcon(AppiumWebElement parent, string trayIconName) =>
parent.FindElementsByName(trayIconName).LastOrDefault()
// Some icons (including Avalonia) for some reason include leading whitespace in their name.
// Couldn't find any info on that, which is weird.
?? parent.FindElementsByName(" " + trayIconName).LastOrDefault();
}
if (OperatingSystem.IsMacOS())
{
return session.FindElementsByXPath("//XCUIElementTypeStatusItem").FirstOrDefault();
}
throw new PlatformNotSupportedException();
}
private static AppiumWebElement ShowAndGetTrayMenu(AppiumWebElement trayIcon, string trayIconName)
{
if (OperatingSystem.IsWindows())
{
var session = (AppiumDriver)trayIcon.WrappedDriver;
new Actions(trayIcon.WrappedDriver).ContextClick(trayIcon).Perform();
Thread.Sleep(1000);
return session.FindElementByXPath($"//Window[@AutomationId='AvaloniaTrayPopupRoot_{trayIconName}']");
}
else
{
trayIcon.Click();
return trayIcon.FindElementByXPath("//XCUIElementTypeStatusItem/XCUIElementTypeMenu");
}
}
public void Dispose()
{
_rootSession?.Dispose();
}
}
Loading…
Cancel
Save