Procházet zdrojové kódy

[Windows] WindowDecorations related integration tests (#15561)

* Enable IncludeAvaloniaGenerators on integartion tests app

* Implement basic TitleBarAutomationPeer

* Add WindowDecorationsTests (windows only for now)

* Implement window decoration tests on macOS

* Fix build on appium 1

* Fix some windows tests

* Extract WindowDecorationsTests into a separated collection, so it won't conflict

* Fix build

* Fix build
Max Katz před 1 rokem
rodič
revize
d4d322654e

+ 2 - 0
samples/IntegrationTestApp/IntegrationTestApp.csproj

@@ -4,6 +4,7 @@
     <TargetFramework>$(AvsCurrentTargetFramework)</TargetFramework>
     <Nullable>enable</Nullable>
     <NoWarn>$(NoWarn);AVP1012</NoWarn>
+    <IncludeAvaloniaGenerators>true</IncludeAvaloniaGenerators>
   </PropertyGroup>
 
   <PropertyGroup>
@@ -37,5 +38,6 @@
   <Import Project="..\..\build\BuildTargets.targets" />
   <Import Project="..\..\build\SampleApp.props" />
   <Import Project="..\..\build\ReferenceCoreLibraries.props" />
+  <Import Project="..\..\build\SourceGenerators.props" />
   
 </Project>

+ 15 - 3
samples/IntegrationTestApp/MainWindow.axaml

@@ -5,7 +5,6 @@
         xmlns:integrationTestApp="using:IntegrationTestApp"
         mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
         x:Class="IntegrationTestApp.MainWindow"
-        Name="MainWindow"
         Icon="/Assets/icon.ico"
         Title="IntegrationTestApp"
         x:DataType="integrationTestApp:MainWindow">
@@ -24,7 +23,7 @@
       </NativeMenuItem>
     </NativeMenu>
   </NativeMenu.Menu>
-  <DockPanel>
+  <DockPanel Background="{DynamicResource SystemRegionBrush}">
     <NativeMenuBar DockPanel.Dock="Top"/>
     <StackPanel DockPanel.Dock="Bottom" Margin="4" Orientation="Horizontal">
       <TextBlock Margin="0,0,4,0">WindowState:</TextBlock>
@@ -178,7 +177,20 @@
           </StackPanel>
         </Grid>
       </TabItem>
-      
+
+      <TabItem Header="Window Decorations">
+        <StackPanel Spacing="4">
+          <CheckBox Name="WindowExtendClientAreaToDecorationsHint" Content="Extend Client Area to Decorations" />
+          <CheckBox Name="WindowForceSystemChrome" Content="Force SystemChrome" />
+          <CheckBox Name="WindowPreferSystemChrome" Content="Prefer SystemChrome" />
+          <CheckBox Name="WindowMacThickSystemChrome" Content="Mac Thick SystemChrome" />
+          <TextBox Name="WindowTitleBarHeightHint" Text="-1" Watermark="In dips" />
+          <Button Name="ApplyWindowDecorations" Content="Apply decorations on this Window"  />
+          <Button Name="ShowNewWindowDecorations" Content="Show new Window with decorations" />
+          <TextBox Name="WindowDecorationProperties" AcceptsReturn="True" />
+        </StackPanel>
+      </TabItem>
+
       <TabItem Header="Slider">
           <DockPanel>
             <DockPanel DockPanel.Dock="Top">

+ 93 - 54
samples/IntegrationTestApp/MainWindow.axaml.cs

@@ -8,24 +8,25 @@ using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Input;
 using Avalonia.Interactivity;
-using Avalonia.Markup.Xaml;
 using Avalonia.Media;
+using Avalonia.Platform;
 using Avalonia.VisualTree;
-using Microsoft.CodeAnalysis;
 
 namespace IntegrationTestApp
 {
-    public class MainWindow : Window
+    public partial class MainWindow : Window
     {
         public MainWindow()
         {
+            // Set name in code behind, so source generator will ignore it.
+            Name = "MainWindow";
+
             InitializeComponent();
             InitializeViewMenu();
             InitializeGesturesTab();
             this.AttachDevTools();
 
-            var overlayPopups = this.Get<TextBlock>("AppOverlayPopups");
-            overlayPopups.Text = Program.OverlayPopups ? "Overlay Popups" : "Native Popups";
+            AppOverlayPopups.Text = Program.OverlayPopups ? "Overlay Popups" : "Native Popups";
 
             AddHandler(Button.ClickEvent, OnButtonClick);
             ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList();
@@ -34,17 +35,11 @@ namespace IntegrationTestApp
 
         public List<string> ListBoxItems { get; }
 
-        private void InitializeComponent()
-        {
-            AvaloniaXamlLoader.Load(this);
-        }
-
         private void InitializeViewMenu()
         {
-            var mainTabs = this.Get<TabControl>("MainTabs");
             var viewMenu = (NativeMenuItem?)NativeMenu.GetMenu(this)?.Items[1];
 
-            foreach (var tabItem in mainTabs.Items.Cast<TabItem>())
+            foreach (var tabItem in MainTabs.Items.Cast<TabItem>())
             {
                 var menuItem = new NativeMenuItem
                 {
@@ -59,16 +54,16 @@ namespace IntegrationTestApp
             }
         }
 
-        private void ShowWindow()
+        private void OnShowWindow()
         {
-            var sizeTextBox = this.GetControl<TextBox>("ShowWindowSize");
-            var modeComboBox = this.GetControl<ComboBox>("ShowWindowMode");
-            var locationComboBox = this.GetControl<ComboBox>("ShowWindowLocation");
-            var stateComboBox = this.GetControl<ComboBox>("ShowWindowState");
+            var sizeTextBox = ShowWindowSize;
+            var modeComboBox = ShowWindowMode;
+            var locationComboBox = ShowWindowLocation;
+            var stateComboBox = ShowWindowState;
             var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
-            var systemDecorations = this.GetControl<ComboBox>("ShowWindowSystemDecorations");
-            var extendClientArea = this.GetControl<CheckBox>("ShowWindowExtendClientAreaToDecorationsHint");
-            var canResizeCheckBox = this.GetControl<CheckBox>("ShowWindowCanResize");
+            var systemDecorations = ShowWindowSystemDecorations;
+            var extendClientArea = ShowWindowExtendClientAreaToDecorationsHint;
+            var canResizeCheckBox = ShowWindowCanResize;
             var owner = (Window)this.GetVisualRoot()!;
 
             var window = new ShowWindowTest
@@ -113,7 +108,7 @@ namespace IntegrationTestApp
             }
         }
 
-        private void ShowTransparentWindow()
+        private void OnShowTransparentWindow()
         {
             // Show a background window to make sure the color behind the transparent window is
             // a known color (green).
@@ -155,7 +150,7 @@ namespace IntegrationTestApp
             window.Show(backgroundWindow);
         }
 
-        private void ShowTransparentPopup()
+        private void OnShowTransparentPopup()
         {
             var popup = new Popup
             {
@@ -196,7 +191,7 @@ namespace IntegrationTestApp
             popup.Open();
         }
 
-        private void SendToBack()
+        private void OnSendToBack()
         {
             var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
 
@@ -206,7 +201,7 @@ namespace IntegrationTestApp
             }
         }
 
-        private void RestoreAll()
+        private void OnRestoreAll()
         {
             var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
 
@@ -218,7 +213,7 @@ namespace IntegrationTestApp
             }
         }
         
-        private void ShowTopmostWindow()
+        private void OnShowTopmostWindow()
         {
             var mainWindow = new TopmostWindowTest("OwnerWindow") { Topmost = true, Title = "Owner Window"};
             var ownedWindow = new TopmostWindowTest("OwnedWindow") { WindowStartupLocation = WindowStartupLocation.CenterOwner, Title = "Owned Window"};
@@ -229,10 +224,10 @@ namespace IntegrationTestApp
 
         private void InitializeGesturesTab()
         {
-            var gestureBorder = this.GetControl<Border>("GestureBorder");
-            var gestureBorder2 = this.GetControl<Border>("GestureBorder2");
-            var lastGesture = this.GetControl<TextBlock>("LastGesture");
-            var resetGestures = this.GetControl<Button>("ResetGestures");
+            var gestureBorder = GestureBorder;
+            var gestureBorder2 = GestureBorder2;
+            var lastGesture = LastGesture;
+            var resetGestures = ResetGestures;
             gestureBorder.Tapped += (_, _) => lastGesture.Text = "Tapped";
             
             gestureBorder.DoubleTapped += (_, _) =>
@@ -261,7 +256,7 @@ namespace IntegrationTestApp
 
         private void MenuClicked(object? sender, RoutedEventArgs e)
         {
-            var clickedMenuItemTextBlock = this.Get<TextBlock>("ClickedMenuItem");
+            var clickedMenuItemTextBlock = ClickedMenuItem;
             clickedMenuItemTextBlock.Text = (sender as MenuItem)?.Header?.ToString();
         }
 
@@ -269,32 +264,76 @@ namespace IntegrationTestApp
         {
             var source = e.Source as Button;
 
-            if (source?.Name == "ComboBoxSelectionClear")
-                this.Get<ComboBox>("BasicComboBox").SelectedIndex = -1;
-            if (source?.Name == "ComboBoxSelectFirst")
-                this.Get<ComboBox>("BasicComboBox").SelectedIndex = 0;
-            if (source?.Name == "ListBoxSelectionClear")
-                this.Get<ListBox>("BasicListBox").SelectedIndex = -1;
-            if (source?.Name == "MenuClickedMenuItemReset")
-                this.Get<TextBlock>("ClickedMenuItem").Text = "None";
-            if (source?.Name == "ResetSliders")
-                this.Get<Slider>("HorizontalSlider").Value = 50;
-            if (source?.Name == "ShowTransparentWindow")
-                ShowTransparentWindow();
-            if (source?.Name == "ShowTransparentPopup")
-                ShowTransparentPopup();
-            if (source?.Name == "ShowWindow")
-                ShowWindow();
-            if (source?.Name == "SendToBack")
-                SendToBack();
-            if (source?.Name == "EnterFullscreen")
+            if (source?.Name == nameof(ComboBoxSelectionClear))
+                BasicComboBox.SelectedIndex = -1;
+            if (source?.Name == nameof(ComboBoxSelectFirst))
+                BasicComboBox.SelectedIndex = 0;
+            if (source?.Name == nameof(ListBoxSelectionClear))
+                BasicListBox.SelectedIndex = -1;
+            if (source?.Name == nameof(MenuClickedMenuItemReset))
+                ClickedMenuItem.Text = "None";
+            if (source?.Name == nameof(ResetSliders))
+                HorizontalSlider.Value = 50;
+            if (source?.Name == nameof(ShowTransparentWindow))
+                OnShowTransparentWindow();
+            if (source?.Name == nameof(ShowTransparentPopup))
+                OnShowTransparentPopup();
+            if (source?.Name == nameof(ShowWindow))
+                OnShowWindow();
+            if (source?.Name == nameof(SendToBack))
+                OnSendToBack();
+            if (source?.Name == nameof(EnterFullscreen))
                 WindowState = WindowState.FullScreen;
-            if (source?.Name == "ExitFullscreen")
+            if (source?.Name == nameof(ExitFullscreen))
                 WindowState = WindowState.Normal;
-            if (source?.Name == "RestoreAll")
-                RestoreAll();
-            if (source?.Name == "ShowTopmostWindow")
-                ShowTopmostWindow();
+            if (source?.Name == nameof(ShowTopmostWindow))
+                OnShowTopmostWindow();
+            if (source?.Name == nameof(RestoreAll))
+                OnRestoreAll();
+            if (source?.Name == nameof(ApplyWindowDecorations))
+                OnApplyWindowDecorations(this);
+            if (source?.Name == nameof(ShowNewWindowDecorations))
+                OnShowNewWindowDecorations();
+        }
+
+        private void OnApplyWindowDecorations(Window window)
+        {
+            window.ExtendClientAreaToDecorationsHint = WindowExtendClientAreaToDecorationsHint.IsChecked!.Value;
+            window.ExtendClientAreaTitleBarHeightHint =
+                int.TryParse(WindowTitleBarHeightHint.Text, out var val) ? val / DesktopScaling : -1;
+            window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome
+                | (WindowForceSystemChrome.IsChecked == true ? ExtendClientAreaChromeHints.SystemChrome : 0)
+                | (WindowPreferSystemChrome.IsChecked == true ? ExtendClientAreaChromeHints.PreferSystemChrome : 0)
+                | (WindowMacThickSystemChrome.IsChecked == true ? ExtendClientAreaChromeHints.OSXThickTitleBar : 0);
+            AdjustOffsets(window);
+
+            window.Background = Brushes.Transparent;
+            window.PropertyChanged += WindowOnPropertyChanged;
+
+            void WindowOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+            {
+                var window = (Window)sender!;
+                if (e.Property == OffScreenMarginProperty || e.Property == WindowDecorationMarginProperty)
+                {
+                    AdjustOffsets(window);
+                }
+            }
+
+            void AdjustOffsets(Window window)
+            {
+                window.Padding = window.OffScreenMargin;
+                ((Control)window.Content!).Margin = window.WindowDecorationMargin;
+
+                WindowDecorationProperties.Text =
+                    $"{window.OffScreenMargin.Top * DesktopScaling} {window.WindowDecorationMargin.Top * DesktopScaling} {window.IsExtendedIntoWindowDecorations}";
+            }
+        }
+
+        private void OnShowNewWindowDecorations()
+        {
+            var window = new ShowWindowTest();
+            OnApplyWindowDecorations(window);
+            window.Show();
         }
     }
 }

+ 1 - 1
samples/IntegrationTestApp/ShowWindowTest.axaml

@@ -5,7 +5,7 @@
         Name="SecondaryWindow"
         x:DataType="Window"
         Title="Show Window Test">
-  <integrationTestApp:MeasureBorder Name="MyBorder">
+  <integrationTestApp:MeasureBorder Name="MyBorder" Background="{DynamicResource SystemRegionBrush}">
     <Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
       <Label Grid.Column="0" Grid.Row="1">Client Size</Label>
       <TextBox Name="CurrentClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True"

+ 8 - 14
samples/IntegrationTestApp/ShowWindowTest.axaml.cs

@@ -26,44 +26,38 @@ namespace IntegrationTestApp
         }
     }
     
-    public class ShowWindowTest : Window
+    public partial class ShowWindowTest : Window
     {
         private readonly DispatcherTimer? _timer;
         private readonly TextBox? _orderTextBox;
-        
+
         public ShowWindowTest()
         {
             InitializeComponent();
             DataContext = this;
-            PositionChanged += (s, e) => this.GetControl<TextBox>("CurrentPosition").Text = $"{Position}";
+            PositionChanged += (s, e) => CurrentPosition.Text = $"{Position}";
 
             if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
             {
-                _orderTextBox = this.GetControl<TextBox>("CurrentOrder");
+                _orderTextBox = CurrentOrder;
                 _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
                 _timer.Tick += TimerOnTick;
                 _timer.Start();
             }
         }
-        
-        private void InitializeComponent()
-        {
-            AvaloniaXamlLoader.Load(this);
-        }
 
         protected override void OnOpened(EventArgs e)
         {
             base.OnOpened(e);
             var scaling = PlatformImpl!.DesktopScaling;
-            this.GetControl<TextBox>("CurrentPosition").Text = $"{Position}";
-            this.GetControl<TextBox>("CurrentScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}";
-            this.GetControl<TextBox>("CurrentScaling").Text = $"{scaling}";
+            CurrentPosition.Text = $"{Position}";
+            CurrentScreenRect.Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}";
+            CurrentScaling.Text = $"{scaling}";
 
             if (Owner is not null)
             {
-                var ownerRect = this.GetControl<TextBox>("CurrentOwnerRect");
                 var owner = (Window)Owner;
-                ownerRect.Text = $"{owner.Position}, {PixelSize.FromSize(owner.FrameSize!.Value, scaling)}";
+                CurrentOwnerRect.Text = $"{owner.Position}, {PixelSize.FromSize(owner.FrameSize!.Value, scaling)}";
             }
         }
 

+ 2 - 6
samples/IntegrationTestApp/TopmostWindowTest.axaml.cs

@@ -5,17 +5,13 @@ using Avalonia.Markup.Xaml;
 
 namespace IntegrationTestApp;
 
-public class TopmostWindowTest : Window
+public partial class TopmostWindowTest : Window
 {
     public TopmostWindowTest(string name)
     {
         Name = name;
         InitializeComponent();
-        PositionChanged += (s, e) => this.GetControl<TextBox>("CurrentPosition").Text = $"{Position}";
-    }
-    private void InitializeComponent()
-    {
-        AvaloniaXamlLoader.Load(this);
+        PositionChanged += (s, e) => CurrentPosition.Text = $"{Position}";
     }
 
     private void Button_OnClick(object? sender, RoutedEventArgs e)

+ 26 - 0
src/Avalonia.Controls/Automation/Peers/TitleBarAutomationPeer.cs

@@ -0,0 +1,26 @@
+using Avalonia.Automation;
+using Avalonia.Automation.Peers;
+using Avalonia.Controls.Chrome;
+
+namespace Avalonia.Controls.Automation.Peers;
+
+internal class TitleBarAutomationPeer : ControlAutomationPeer
+{
+    public TitleBarAutomationPeer(TitleBar owner) : base(owner)
+    {
+    }
+
+    protected override bool IsContentElementCore() => true;
+
+    protected override string GetClassNameCore()
+    {
+        return "TitleBar";
+    }
+
+    protected override string? GetAutomationIdCore() => base.GetAutomationIdCore() ?? "AvaloniaTitleBar";
+
+    protected override AutomationControlType GetAutomationControlTypeCore()
+    {
+        return AutomationControlType.TitleBar;
+    }
+}

+ 5 - 0
src/Avalonia.Controls/Chrome/TitleBar.cs

@@ -1,4 +1,6 @@
 using System;
+using Avalonia.Automation.Peers;
+using Avalonia.Controls.Automation.Peers;
 using Avalonia.Reactive;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
@@ -94,5 +96,8 @@ namespace Avalonia.Controls.Chrome
             _captionButtons?.Detach();
             _captionButtons = null;
         }
+
+        /// <inheritdoc />
+        protected override AutomationPeer OnCreateAutomationPeer() => new TitleBarAutomationPeer(this);
     }
 }

+ 6 - 0
tests/Avalonia.IntegrationTests.Appium/AppiumDriverEx.cs

@@ -67,6 +67,12 @@ public static class AppiumDriverEx
     public static IReadOnlyList<AppiumWebElement> FindElementsByClassName(this IFindsElement session, string criteria) =>
         session.FindElements(By.ClassName(criteria));
 
+    public static AppiumWebElement FindElementByTagName(this IFindsElement session, string criteria) =>
+        session.FindElement(By.TagName(criteria));
+
+    public static IReadOnlyList<AppiumWebElement> FindElementsByTagName(this IFindsElement session, string criteria) =>
+        session.FindElements(By.TagName(criteria));
+
     public static void AddAdditionalCapability(this AppiumOptions options, string name, object value)
     {
         if (name == MobileCapabilityType.AutomationName)

+ 5 - 0
tests/Avalonia.IntegrationTests.Appium/CollectionDefinitions.cs

@@ -7,6 +7,11 @@ namespace Avalonia.IntegrationTests.Appium
     {
     }
 
+    [CollectionDefinition("WindowDecorations")]
+    public class WindowDecorationsCollection : ICollectionFixture<DefaultAppFixture>
+    {
+    }
+
     [CollectionDefinition("OverlayPopups")]
     public class OverlayPopupsCollection : ICollectionFixture<OverlayPopupsAppFixture>
     {

+ 67 - 5
tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs

@@ -11,31 +11,64 @@ using Xunit;
 
 namespace Avalonia.IntegrationTests.Appium
 {
-    public record class WindowChrome(
+    public record WindowChrome(
         AppiumWebElement? Close,
         AppiumWebElement? Minimize,
         AppiumWebElement? Maximize,
-        AppiumWebElement? FullScreen);
+        AppiumWebElement? FullScreen,
+        AppiumWebElement? TitleBar)
+    {
+        public bool IsAnyButtonEnabled => (TitleBar is null || TitleBar.Enabled) &&
+                                          (Close?.Enabled == true
+                                           || Minimize?.Enabled == true
+                                           || Maximize?.Enabled == true
+                                           || FullScreen?.Enabled == true);
+
+        public int TitleBarHeight => TitleBar?.Size.Height ?? -1;
+
+        public int MaxButtonHeight =>
+            Math.Max(
+                Math.Max(Close?.Size.Height ?? -1, Minimize?.Size.Height ?? -1),
+                Math.Max(Maximize?.Size.Height ?? -1, FullScreen?.Size.Height ?? -1));
+    }
 
     internal static class ElementExtensions
     {
         public static IReadOnlyList<AppiumWebElement> GetChildren(this AppiumWebElement element) =>
             element.FindElementsByXPath("*/*");
 
-        public static WindowChrome GetChromeButtons(this AppiumWebElement window)
+        public static WindowChrome GetSystemChromeButtons(this AppiumWebElement window)
         {
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+            if (OperatingSystem.IsMacOS())
             {
                 var closeButton = window.FindElementsByAccessibilityId("_XCUI:CloseWindow").FirstOrDefault();
                 var fullscreenButton = window.FindElementsByAccessibilityId("_XCUI:FullScreenWindow").FirstOrDefault();
                 var minimizeButton = window.FindElementsByAccessibilityId("_XCUI:MinimizeWindow").FirstOrDefault();
                 var zoomButton = window.FindElementsByAccessibilityId("_XCUI:ZoomWindow").FirstOrDefault();
-                return new(closeButton, minimizeButton, zoomButton, fullscreenButton);
+                return new(closeButton, minimizeButton, zoomButton, fullscreenButton, null);
+            }
+
+            if (OperatingSystem.IsWindows())
+            {
+                var titlebar = window.FindElementsByTagName("TitleBar").FirstOrDefault();
+                var closeButton = titlebar?.FindElementByName("Close");
+                var minimizeButton = titlebar?.FindElementByName("Minimize");
+                var maximizeButton = titlebar?.FindElementByName("Maximize");
+                return new(closeButton, minimizeButton, maximizeButton, null, titlebar);
             }
 
             throw new NotSupportedException("GetChromeButtons not supported on this platform.");
         }
 
+        public static WindowChrome GetClientChromeButtons(this AppiumWebElement window)
+        {
+            var titlebar = window.FindElementsByAccessibilityId("AvaloniaTitleBar")?.FirstOrDefault();
+            var closeButton = titlebar?.FindElementByName("Close");
+            var minimizeButton = titlebar?.FindElementByName("Minimize");
+            var maximizeButton = titlebar?.FindElementByName("Maximize");
+            return new(closeButton, minimizeButton, maximizeButton, null, titlebar);
+        }
+
         public static string GetComboBoxValue(this AppiumWebElement element)
         {
             return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
@@ -68,6 +101,35 @@ namespace Avalonia.IntegrationTests.Appium
             }
         }
 
+        public static AppiumWebElement GetCurrentSingleWindow(this AppiumDriver session)
+        {
+            if (OperatingSystem.IsMacOS())
+            {
+                // The Avalonia a11y tree currently exposes two nested Window elements, this is a bug and should be fixed 
+                // but in the meantime use the `parent::' selector to return the parent "real" window. 
+                return session.FindElementByXPath(
+                    $"XCUIElementTypeWindow//*/parent::XCUIElementTypeWindow");
+            }
+            else
+            {
+                return session.FindElementByXPath($"//Window");
+            }
+        }
+
+        public static AppiumWebElement GetWindowById(this AppiumDriver session, string identifier)
+        {
+            if (OperatingSystem.IsMacOS())
+            {
+                return session.FindElementByXPath(
+                    $"XCUIElementTypeWindow[@identifier='{identifier}']");
+            }
+            else
+            {
+                return session.FindElementByXPath($"//Window[@AutomationId='{identifier}']");
+            }
+        }
+
+
         /// <summary>
         /// Clicks a button which is expected to open a new window.
         /// </summary>

+ 243 - 0
tests/Avalonia.IntegrationTests.Appium/WindowDecorationsTests.cs

@@ -0,0 +1,243 @@
+using System;
+using System.Threading;
+using Xunit;
+
+namespace Avalonia.IntegrationTests.Appium;
+
+[Collection("WindowDecorations")]
+public class WindowDecorationsTests : IDisposable
+{
+    private readonly AppiumDriver _session;
+
+    public WindowDecorationsTests(DefaultAppFixture fixture)
+    {
+        _session = fixture.Session;
+
+        var tabs = _session.FindElementByAccessibilityId("MainTabs");
+        var tab = tabs.FindElementByName("Window Decorations");
+        tab.Click();
+    }
+
+    [PlatformFact(TestPlatforms.MacOS)] // TODO fix me on Windows
+    public void Window_Size_Should_Be_Consistent_Between_Toggles()
+    {
+        var window = _session.FindElementByAccessibilityId("MainWindow");
+        var original = window.Size;
+
+        // Step 1: keep extend client area to false, but adjust some value that should not have any effect.
+        SetParameters(false, false, false, false, 10);
+        ApplyToCurrentWindow();
+        Assert.Equal(original, window.Size);
+
+        // Step 2: enable and disable extended system chrome. 
+        SetParameters(true, true, false, false, 20);
+        ApplyToCurrentWindow();
+        SetParameters(false, false, false, false, 20);
+        ApplyToCurrentWindow();
+        Assert.Equal(original, window.Size);
+
+        // Step 3: enable and disable extended client chrome. 
+        SetParameters(true, false, true, false, 30);
+        ApplyToCurrentWindow();
+        SetParameters(false, false, true, false, 20);
+        ApplyToCurrentWindow();
+        Assert.Equal(original, window.Size);
+    }
+
+    [Fact]
+    public void Can_Restore_To_Non_Extended_State()
+    {
+        SetParameters(true, true, false, false, 20);
+        ApplyToCurrentWindow();
+
+        SetParameters(false, false, false, false, 1000);
+        ApplyToCurrentWindow();
+
+        var currentWindow = _session.GetCurrentSingleWindow();
+        var systemChrome = currentWindow.GetSystemChromeButtons();
+        var clientChrome = currentWindow.GetClientChromeButtons();
+
+        AssertSystemChrome(systemChrome, clientChrome, false);
+
+        var props = _session.FindElementByAccessibilityId("WindowDecorationProperties");
+        Assert.Equal($"0 0 False", props.Text);
+    }
+
+    [PlatformTheory(TestPlatforms.Windows)]  // We don't have client chrome on macOS
+    [InlineData(-1)]
+    [InlineData(25)]
+    [InlineData(50)]
+    public void Should_Apply_Client_Chrome(int titleBarHeight)
+    {
+        SetParameters(true, false, true, false, titleBarHeight);
+
+        ApplyToCurrentWindow();
+
+        Thread.Sleep(500);
+
+        var currentWindow = _session.GetCurrentSingleWindow();
+        var systemChrome = currentWindow.GetSystemChromeButtons();
+        var clientChrome = currentWindow.GetClientChromeButtons();
+
+        AssertClientChrome(systemChrome, clientChrome, titleBarHeight);
+
+        var props = _session.FindElementByAccessibilityId("WindowDecorationProperties");
+        if (titleBarHeight > 0)
+        {
+            Assert.Equal($"0 {titleBarHeight} True", props.Text);
+        }
+    }
+
+    [Theory]
+    [InlineData(-1)]
+    [InlineData(25)]
+    [InlineData(50)]
+    public void Should_Apply_System_Chrome(int titleBarHeight)
+    {
+        SetParameters(true, true, false, false, titleBarHeight);
+
+        ApplyToCurrentWindow();
+
+        Thread.Sleep(500);
+
+        var currentWindow = _session.GetCurrentSingleWindow();
+        var systemChrome = currentWindow.GetSystemChromeButtons();
+        var clientChrome = currentWindow.GetClientChromeButtons();
+
+        AssertSystemChrome(systemChrome, clientChrome, true);
+
+        var props = _session.FindElementByAccessibilityId("WindowDecorationProperties");
+        if (titleBarHeight > 0)
+        {
+            Assert.Equal($"0 {titleBarHeight} True", props.Text);
+        }
+    }
+
+    [PlatformTheory(TestPlatforms.Windows)] // We don't have client chrome on macOS
+    [InlineData(-1)]
+    [InlineData(25)]
+    [InlineData(50)]
+    public void Should_Apply_Client_Chrome_On_New_Window(int titleBarHeight)
+    {
+        SetParameters(true, false, true, false, titleBarHeight);
+
+        using (ApplyOnNewWindow())
+        {
+            Thread.Sleep(500);
+
+            var secondaryWindow = _session.GetWindowById("SecondaryWindow");
+            var systemChrome = secondaryWindow.GetSystemChromeButtons();
+            var clientChrome = secondaryWindow.GetClientChromeButtons();
+
+            AssertClientChrome(systemChrome, clientChrome, titleBarHeight);
+        }
+    }
+
+    [PlatformTheory(TestPlatforms.MacOS)] // fix me, for some reason Windows doesn't return TitleBar system chrome for a child window. 
+    [InlineData(-1)]
+    [InlineData(25)]
+    [InlineData(50)]
+    public void Should_Apply_System_Chrome_On_New_Window(int titleBarHeight)
+    {
+        SetParameters(true, true, false, false, titleBarHeight);
+
+        using (ApplyOnNewWindow())
+        {
+            Thread.Sleep(500);
+
+            var secondaryWindow = _session.GetWindowById("SecondaryWindow");
+            var systemChrome = secondaryWindow.GetSystemChromeButtons();
+            var clientChrome = secondaryWindow.GetClientChromeButtons();
+
+            AssertSystemChrome(systemChrome, clientChrome, true);
+        }
+    }
+
+    private void AssertClientChrome(WindowChrome systemChrome, WindowChrome clientChrome, int titleBarHeight)
+    {
+        // Ignore windows, it always reports full sized and enabled buttons and title bar. Just drawn behind.
+        if (!OperatingSystem.IsWindows())
+        {
+            // All system chrome buttons are hidden.
+            Assert.False(systemChrome.IsAnyButtonEnabled);
+            Assert.Equal(-1, systemChrome.MaxButtonHeight);
+            Assert.True(systemChrome.TitleBarHeight is -1 or 0);
+        }
+
+        // At least some client chrome buttons are shown.
+        Assert.True(clientChrome.IsAnyButtonEnabled);
+        Assert.True(clientChrome.MaxButtonHeight > 0);
+        if (titleBarHeight != -1)
+        {
+            Assert.Equal(titleBarHeight, clientChrome.TitleBarHeight);
+        }
+    }
+
+    private void AssertSystemChrome(WindowChrome systemChrome, WindowChrome clientChrome, bool isExtended)
+    {
+        // At least some server chrome buttons are shown.
+        Assert.True(systemChrome.IsAnyButtonEnabled);
+        Assert.True(systemChrome.MaxButtonHeight > 0);
+
+        // All client chrome buttons are hidden.
+        Assert.False(clientChrome.IsAnyButtonEnabled);
+        Assert.Equal(-1, clientChrome.MaxButtonHeight);
+
+        // System chrome is always 0px height, when client area is extended.
+        if (isExtended)
+        {
+            Assert.True(systemChrome.TitleBarHeight is -1 or 0);
+        }
+        // Can't get titlebar height on macOS.
+        else if (!OperatingSystem.IsMacOS())
+        {
+            Assert.True(systemChrome.TitleBarHeight > 0);
+        }
+    }
+
+    private void SetParameters(
+        bool extendClientArea,
+        bool forceSystemChrome,
+        bool preferSystemChrome,
+        bool macOsThickSystemChrome,
+        int titleBarHeight)
+    {
+        var extendClientAreaCheckBox = _session.FindElementByAccessibilityId("WindowExtendClientAreaToDecorationsHint");
+        var forceSystemChromeCheckBox = _session.FindElementByAccessibilityId("WindowForceSystemChrome");
+        var preferSystemChromeCheckBox = _session.FindElementByAccessibilityId("WindowPreferSystemChrome");
+        var macOsThickSystemChromeCheckBox = _session.FindElementByAccessibilityId("WindowMacThickSystemChrome");
+        var titleBarHeightBox = _session.FindElementByAccessibilityId("WindowTitleBarHeightHint");
+
+        if (extendClientAreaCheckBox.GetIsChecked() != extendClientArea)
+            extendClientAreaCheckBox.Click();
+        if (forceSystemChromeCheckBox.GetIsChecked() != forceSystemChrome)
+            forceSystemChromeCheckBox.Click();
+        if (preferSystemChromeCheckBox.GetIsChecked() != preferSystemChrome)
+            preferSystemChromeCheckBox.Click();
+        if (macOsThickSystemChromeCheckBox.GetIsChecked() != macOsThickSystemChrome)
+            macOsThickSystemChromeCheckBox.Click();
+
+        titleBarHeightBox.Click();
+        titleBarHeightBox.Clear();
+        if (titleBarHeight >= 0)
+            titleBarHeightBox.SendKeys(titleBarHeight.ToString());
+    }
+
+    private void ApplyToCurrentWindow()
+    {
+        var applyWindowDecorations = _session.FindElementByAccessibilityId("ApplyWindowDecorations");
+        applyWindowDecorations.Click();
+    }
+
+    private IDisposable ApplyOnNewWindow()
+    {
+        var showNewWindowDecorations = _session.FindElementByAccessibilityId("ShowNewWindowDecorations");
+        return showNewWindowDecorations.OpenWindowWithClick();
+    }
+
+    public void Dispose()
+    {
+        SetParameters(false, false, false, false, -1);
+        ApplyToCurrentWindow();
+    }
+}

+ 3 - 16
tests/Avalonia.IntegrationTests.Appium/WindowTests.cs

@@ -263,8 +263,8 @@ namespace Avalonia.IntegrationTests.Appium
             var showTopmostWindow = _session.FindElementByAccessibilityId("ShowTopmostWindow");
             using var window = showTopmostWindow.OpenWindowWithClick();
             Thread.Sleep(1000);
-            var ownerWindow = GetWindow("OwnerWindow");
-            var ownedWindow = GetWindow("OwnedWindow");
+            var ownerWindow = _session.GetWindowById("OwnerWindow");
+            var ownedWindow = _session.GetWindowById("OwnedWindow");
 
             Assert.NotNull(ownerWindow);
             Assert.NotNull(ownedWindow);
@@ -297,7 +297,7 @@ namespace Avalonia.IntegrationTests.Appium
         {
             using (OpenWindow(null, mode, WindowStartupLocation.Manual, canResize: false, extendClientArea: extendClientArea))
             {
-                var secondaryWindow = GetWindow("SecondaryWindow");
+                var secondaryWindow = _session.GetWindowById("SecondaryWindow");
                 AppiumWebElement? maximizeButton;
 
                 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -442,19 +442,6 @@ namespace Avalonia.IntegrationTests.Appium
             return showButton.OpenWindowWithClick();
         }
 
-        private AppiumWebElement GetWindow(string identifier)
-        {
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-            {
-                return _session.FindElementByXPath(
-                    $"XCUIElementTypeWindow[@identifier='{identifier}']");
-            }
-            else
-            {
-                return _session.FindElementByXPath($"//Window[@AutomationId='{identifier}']");
-            }
-        }
-
         private WindowInfo GetWindowInfo()
         {
             PixelRect? ReadOwnerRect()

+ 4 - 4
tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

@@ -239,7 +239,7 @@ namespace Avalonia.IntegrationTests.Appium
         public void Parent_Window_Has_Disabled_ChromeButtons_When_Modal_Dialog_Shown()
         {
             var window = GetWindow("MainWindow");
-            var windowChrome = window.GetChromeButtons();
+            var windowChrome = window.GetSystemChromeButtons();
 
             Assert.True(windowChrome.Close!.Enabled);
             Assert.True(windowChrome.FullScreen!.Enabled);
@@ -260,7 +260,7 @@ namespace Avalonia.IntegrationTests.Appium
             using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner))
             {
                 var secondaryWindow = GetWindow("SecondaryWindow");
-                var windowChrome = secondaryWindow.GetChromeButtons();
+                var windowChrome = secondaryWindow.GetSystemChromeButtons();
 
                 Assert.True(windowChrome.Close!.Enabled);
                 Assert.True(windowChrome.Maximize!.Enabled);
@@ -374,7 +374,7 @@ namespace Avalonia.IntegrationTests.Appium
 
                 try
                 {
-                    var chrome = secondaryWindow.GetChromeButtons();
+                    var chrome = secondaryWindow.GetSystemChromeButtons();
                 
                     if (decorations == SystemDecorations.Full)
                     {
@@ -448,7 +448,7 @@ namespace Avalonia.IntegrationTests.Appium
 
         private AppiumWebElement GetWindow(string identifier)
         {
-            return _session.FindElementByXPath($"XCUIElementTypeWindow[@identifier='{identifier}']");
+            return _session.GetWindowById(identifier);
         }
 
         private int GetWindowOrder(string identifier)