From 355ba94fa7c34b3f85ca4015cde8992c6dbe09f6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 24 Jun 2022 16:53:58 +0200 Subject: [PATCH 01/16] Don't return FrameSize until window is shown. Since #8629, the content size isn't set until the window is shown, so we can't know the frame size. --- native/Avalonia.Native/src/OSX/WindowBaseImpl.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index f133fa34f6..45d78b3926 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -224,7 +224,7 @@ HRESULT WindowBaseImpl::GetFrameSize(AvnSize *ret) { if (ret == nullptr) return E_POINTER; - if(Window != nullptr){ + if(Window != nullptr && _shown){ auto frame = [Window frame]; ret->Width = frame.size.width; ret->Height = frame.size.height; From 20ddbad2adf97e93765cbe89f6532a9b945242e2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 24 Jun 2022 17:16:31 +0200 Subject: [PATCH 02/16] Fix window minimization. A non-client click was being received after the click to minimize the window, which caused a call to `_parent->BringToFront();`, meaning the window got immediately restored. Ignore clicks when the window is minimized. Fixes #8335 --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index ebd9f39d30..9569ea1cbe 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -440,6 +440,9 @@ { case NSEventTypeLeftMouseDown: { + if ([self isMiniaturized]) + break; + AvnView* view = _parent->View; NSPoint windowPoint = [event locationInWindow]; NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil]; From 328aef882b8fe19452a8d3287d42c98b7b0fdf31 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 25 Jun 2022 01:20:18 +0200 Subject: [PATCH 03/16] Added failing tests for WindowState on mac. --- .../IntegrationTestApp/ShowWindowTest.axaml | 19 +++-- .../ShowWindowTest.axaml.cs | 4 +- .../WindowTests.cs | 70 ++++++++++++++++--- 3 files changed, 76 insertions(+), 17 deletions(-) diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml index a263d8ab46..a861130cd0 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -3,15 +3,18 @@ x:Class="IntegrationTestApp.ShowWindowTest" Name="SecondaryWindow" Title="Show Window Test"> - + - + - + - + @@ -21,5 +24,13 @@ + + + + Normal + Minimized + Maximized + Fullscreen + diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs index 720f7b1c12..69b6d64557 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs @@ -11,6 +11,7 @@ namespace IntegrationTestApp public ShowWindowTest() { InitializeComponent(); + DataContext = this; } private void InitializeComponent() @@ -21,9 +22,6 @@ namespace IntegrationTestApp protected override void OnOpened(EventArgs e) { base.OnOpened(e); - this.GetControl("ClientSize").Text = $"{Width}, {Height}"; - this.GetControl("FrameSize").Text = $"{FrameSize}"; - this.GetControl("Position").Text = $"{Position}"; this.GetControl("ScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}"; this.GetControl("Scaling").Text = $"{PlatformImpl?.DesktopScaling}"; diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index f1a625dbb4..727a0b5406 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -26,28 +26,60 @@ namespace Avalonia.IntegrationTests.Appium public void StartupLocation(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) { using var window = OpenWindow(size, mode, location); - var clientSize = Size.Parse(_session.FindElementByAccessibilityId("ClientSize").Text); - var frameSize = Size.Parse(_session.FindElementByAccessibilityId("FrameSize").Text); - var position = PixelPoint.Parse(_session.FindElementByAccessibilityId("Position").Text); - var screenRect = PixelRect.Parse(_session.FindElementByAccessibilityId("ScreenRect").Text); - var scaling = double.Parse(_session.FindElementByAccessibilityId("Scaling").Text); + var info = GetWindowInfo(); - Assert.True(frameSize.Width >= clientSize.Width, "Expected frame width >= client width."); - Assert.True(frameSize.Height > clientSize.Height, "Expected frame height > client height."); + Assert.True(info.FrameSize.Width >= info.ClientSize.Width, "Expected frame width >= client width."); + Assert.True(info.FrameSize.Height > info.ClientSize.Height, "Expected frame height > client height."); - var frameRect = new PixelRect(position, PixelSize.FromSize(frameSize, scaling)); + var frameRect = new PixelRect(info.Position, PixelSize.FromSize(info.FrameSize, info.Scaling)); switch (location) { case WindowStartupLocation.CenterScreen: { - var expected = screenRect.CenterRect(frameRect); + var expected = info.ScreenRect.CenterRect(frameRect); AssertCloseEnough(expected.Position, frameRect.Position); break; } } } - + + + [Theory] + [InlineData(ShowWindowMode.NonOwned)] + [InlineData(ShowWindowMode.Owned)] + [InlineData(ShowWindowMode.Modal)] + public void WindowState(ShowWindowMode mode) + { + using var window = OpenWindow(null, mode, WindowStartupLocation.Manual); + var windowState = _session.FindElementByAccessibilityId("WindowState"); + var original = GetWindowInfo(); + + Assert.Equal("Normal", windowState.GetComboBoxValue()); + + windowState.Click(); + _session.FindElementByName("Maximized").SendClick(); + Assert.Equal("Maximized", windowState.GetComboBoxValue()); + + windowState.Click(); + _session.FindElementByName("Normal").SendClick(); + + var current = GetWindowInfo(); + Assert.Equal(original.Position, current.Position); + Assert.Equal(original.FrameSize, current.FrameSize); + + windowState.Click(); + _session.FindElementByName("Fullscreen").SendClick(); + Assert.Equal("Fullscreen", windowState.GetComboBoxValue()); + + windowState.Click(); + _session.FindElementByName("Normal").SendClick(); + + current = GetWindowInfo(); + Assert.Equal(original.Position, current.Position); + Assert.Equal(original.FrameSize, current.FrameSize); + } + public static TheoryData StartupLocationData() { var sizes = new PixelSize?[] { null, new PixelSize(400, 300) }; @@ -115,11 +147,29 @@ namespace Avalonia.IntegrationTests.Appium return showButton.OpenWindowWithClick(); } + private WindowInfo GetWindowInfo() + { + return new( + Size.Parse(_session.FindElementByAccessibilityId("ClientSize").Text), + Size.Parse(_session.FindElementByAccessibilityId("FrameSize").Text), + PixelPoint.Parse(_session.FindElementByAccessibilityId("Position").Text), + PixelRect.Parse(_session.FindElementByAccessibilityId("ScreenRect").Text), + double.Parse(_session.FindElementByAccessibilityId("Scaling").Text)); + + } + public enum ShowWindowMode { NonOwned, Owned, Modal } + + private record WindowInfo( + Size ClientSize, + Size FrameSize, + PixelPoint Position, + PixelRect ScreenRect, + double Scaling); } } From dfa0c44269a05206bcbb288ea575ad4bc44c3ffa Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Jun 2022 10:16:37 +0200 Subject: [PATCH 04/16] Set actualWindowState earlier. Previously, `_actualWindowState` was being set after the window state had finished changing, but `ExitFullScreenMode` calls `UpdateStyle` which expects `_actualWindowState` to be up-to-date, meaning that previously we ended up with an invalid size when exiting full screen mode programatically. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 6db586f3ca..5b0c7813f7 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -488,6 +488,8 @@ HRESULT WindowImpl::SetWindowState(AvnWindowState state) { } if (_shown) { + _actualWindowState = _lastWindowState; + switch (state) { case Maximized: if (currentState == FullScreen) { @@ -545,7 +547,6 @@ HRESULT WindowImpl::SetWindowState(AvnWindowState state) { break; } - _actualWindowState = _lastWindowState; WindowEvents->WindowStateChanged(_actualWindowState); } From 519e850bf838a42dbc8243c37ecf0348175424ed Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Jun 2022 12:49:08 +0200 Subject: [PATCH 05/16] Correctly update position. `Window.Position` isn't an Avalonia property so doesn't auto-update, we need to listen for `PositionChanged`. --- samples/IntegrationTestApp/ShowWindowTest.axaml | 3 +-- samples/IntegrationTestApp/ShowWindowTest.axaml.cs | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml index a861130cd0..40c1642e91 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -13,8 +13,7 @@ Text="{Binding FrameSize, Mode=OneWay}"/> - + diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs index 69b6d64557..42df0b704f 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs @@ -12,6 +12,7 @@ namespace IntegrationTestApp { InitializeComponent(); DataContext = this; + PositionChanged += (s, e) => this.GetControl("Position").Text = $"{Position}"; } private void InitializeComponent() @@ -22,6 +23,7 @@ namespace IntegrationTestApp protected override void OnOpened(EventArgs e) { base.OnOpened(e); + this.GetControl("Position").Text = $"{Position}"; this.GetControl("ScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}"; this.GetControl("Scaling").Text = $"{PlatformImpl?.DesktopScaling}"; From 267710787e933c3719e20b3337b3f5e504f24edd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Jun 2022 12:51:36 +0200 Subject: [PATCH 06/16] Tweak tests. - Add a retry to `GetWindowInfo` - Owned windows can't go fullscreen on macOS. --- .../WindowTests.cs | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 727a0b5406..d763848899 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.Threading; using Avalonia.Controls; using OpenQA.Selenium.Appium; using Xunit; @@ -68,16 +69,24 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal(original.Position, current.Position); Assert.Equal(original.FrameSize, current.FrameSize); - windowState.Click(); - _session.FindElementByName("Fullscreen").SendClick(); - Assert.Equal("Fullscreen", windowState.GetComboBoxValue()); + // On macOS, only non-owned windows can go fullscreen. + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || mode == ShowWindowMode.NonOwned) + { + windowState.Click(); + _session.FindElementByName("Fullscreen").SendClick(); + Assert.Equal("Fullscreen", windowState.GetComboBoxValue()); - windowState.Click(); - _session.FindElementByName("Normal").SendClick(); + current = GetWindowInfo(); + Assert.True(current.ClientSize.Width >= current.ScreenRect.Width); + Assert.True(current.ClientSize.Height >= current.ScreenRect.Height); - current = GetWindowInfo(); - Assert.Equal(original.Position, current.Position); - Assert.Equal(original.FrameSize, current.FrameSize); + windowState.Click(); + _session.FindElementByName("Normal").SendClick(); + + current = GetWindowInfo(); + Assert.Equal(original.Position, current.Position); + Assert.Equal(original.FrameSize, current.FrameSize); + } } public static TheoryData StartupLocationData() @@ -149,13 +158,26 @@ namespace Avalonia.IntegrationTests.Appium private WindowInfo GetWindowInfo() { - return new( - Size.Parse(_session.FindElementByAccessibilityId("ClientSize").Text), - Size.Parse(_session.FindElementByAccessibilityId("FrameSize").Text), - PixelPoint.Parse(_session.FindElementByAccessibilityId("Position").Text), - PixelRect.Parse(_session.FindElementByAccessibilityId("ScreenRect").Text), - double.Parse(_session.FindElementByAccessibilityId("Scaling").Text)); + var retry = 0; + for (;;) + { + try + { + return new( + Size.Parse(_session.FindElementByAccessibilityId("ClientSize").Text), + Size.Parse(_session.FindElementByAccessibilityId("FrameSize").Text), + PixelPoint.Parse(_session.FindElementByAccessibilityId("Position").Text), + PixelRect.Parse(_session.FindElementByAccessibilityId("ScreenRect").Text), + double.Parse(_session.FindElementByAccessibilityId("Scaling").Text)); + } + catch (OpenQA.Selenium.NoSuchElementException e) when (retry++ < 3) + { + // MacOS sometimes seems to need a bit of time to get itself back in order after switching out + // of fullscreen. + Thread.Sleep(1000); + } + } } public enum ShowWindowMode From 4b08e659f69a3133a22653d4a73c9a5c320bf3b7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Jun 2022 13:48:22 +0200 Subject: [PATCH 07/16] Revert "Fix window minimization." This reverts commit 20ddbad2adf97e93765cbe89f6532a9b945242e2. It didn't work with owned windows, there's something else going on here. --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 3 --- 1 file changed, 3 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index 9569ea1cbe..ebd9f39d30 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -440,9 +440,6 @@ { case NSEventTypeLeftMouseDown: { - if ([self isMiniaturized]) - break; - AvnView* view = _parent->View; NSPoint windowPoint = [event locationInWindow]; NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil]; From 2822f5bfcb35344eabea8efbdaae526b32b2512c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Jun 2022 14:02:25 +0200 Subject: [PATCH 08/16] Fix window minimization attempt 2. The previous solution didn't work for owned windows. Hopefully this fixes the problem at its root: that `BringToFront` activates a window even if it's miniaturized. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 5b0c7813f7..85a89955f4 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -119,13 +119,16 @@ void WindowImpl::BringToFront() { if(Window != nullptr) { - if(IsDialog()) + if (![Window isMiniaturized]) { - Activate(); - } - else - { - [Window orderFront:nullptr]; + if(IsDialog()) + { + Activate(); + } + else + { + [Window orderFront:nullptr]; + } } [Window invalidateShadow]; From cd04964c850545cafd54e42cbfc1cd64de145d86 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Jun 2022 17:37:44 +0200 Subject: [PATCH 09/16] Added a retry to macOS window tests. MacOS sometimes seems to need a bit of time to get itself back in order after switching out of fullscreen. --- .../WindowTests_MacOS.cs | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index a460dc1d68..57c1476d95 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -17,11 +17,27 @@ namespace Avalonia.IntegrationTests.Appium public WindowTests_MacOS(TestAppFixture fixture) { + var retry = 0; + _session = fixture.Session; - var tabs = _session.FindElementByAccessibilityId("MainTabs"); - var tab = tabs.FindElementByName("Window"); - tab.Click(); + for (;;) + { + try + { + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("Window"); + tab.Click(); + return; + } + catch (WebDriverException e) when (retry++ < 3) + { + // MacOS sometimes seems to need a bit of time to get itself back in order after switching out + // of fullscreen. + Thread.Sleep(1000); + } + } + } [PlatformFact(TestPlatforms.MacOS)] @@ -41,7 +57,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal(1, mainWindowIndex); } } - + [PlatformFact(TestPlatforms.MacOS)] public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() { @@ -57,7 +73,7 @@ namespace Avalonia.IntegrationTests.Appium var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); var mainWindowIndex = GetWindowOrder(windows, "MainWindow"); var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow"); - + new Actions(_session) .MoveToElement(mainWindow, 100, 1) .Release() @@ -67,13 +83,13 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal(1, mainWindowIndex); } } - + [PlatformFact(TestPlatforms.MacOS)] public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_In_Fullscreen() { var mainWindow = FindWindow(_session, "MainWindow"); var buttons = mainWindow.GetChromeButtons(); - + buttons.maximize.Click(); Thread.Sleep(500); @@ -88,6 +104,8 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal(0, secondaryWindowIndex); Assert.Equal(1, mainWindowIndex); + + Thread.Sleep(5000); } } finally @@ -95,7 +113,7 @@ namespace Avalonia.IntegrationTests.Appium _session.FindElementByAccessibilityId("ExitFullscreen").Click(); } } - + [PlatformFact(TestPlatforms.MacOS)] public void WindowOrder_Owned_Dialog_Stays_InFront_Of_Parent() { @@ -140,7 +158,7 @@ namespace Avalonia.IntegrationTests.Appium { var window = FindWindow(_session, "MainWindow"); var (closeButton, miniaturizeButton, zoomButton) = window.GetChromeButtons(); - + Assert.True(closeButton.Enabled); Assert.True(zoomButton.Enabled); Assert.True(miniaturizeButton.Enabled); @@ -152,7 +170,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.False(miniaturizeButton.Enabled); } } - + [PlatformFact(TestPlatforms.MacOS)] public void Minimize_Button_Is_Disabled_On_Modal_Dialog() { @@ -160,7 +178,7 @@ namespace Avalonia.IntegrationTests.Appium { var secondaryWindow = FindWindow(_session, "SecondaryWindow"); var (closeButton, miniaturizeButton, zoomButton) = secondaryWindow.GetChromeButtons(); - + Assert.True(closeButton.Enabled); Assert.True(zoomButton.Enabled); Assert.False(miniaturizeButton.Enabled); @@ -195,7 +213,7 @@ namespace Avalonia.IntegrationTests.Appium private static AppiumWebElement FindWindow(AppiumDriver session, string identifier) { var windows = session.FindElementsByXPath("XCUIElementTypeWindow"); - return windows.First(x => + return windows.First(x => x.FindElementsByXPath("XCUIElementTypeWindow") .Any(y => y.GetAttribute("identifier") == identifier)); } From 19c55102e5667f1bed4715f96b01a4f0a0abf81d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Jun 2022 18:52:45 +0200 Subject: [PATCH 10/16] Use manual window startup location in macOS tests. Seems to cause less flaky tests; `mainWindow.Click()` clicks the main window in the middle, where the secondary window is shown, so don't go out of our way to put the secondary window in front of where we're clicking. --- .../WindowTests_MacOS.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 57c1476d95..c88f87562d 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -45,7 +45,7 @@ namespace Avalonia.IntegrationTests.Appium { var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.Manual)) { mainWindow.Click(); @@ -63,7 +63,7 @@ namespace Avalonia.IntegrationTests.Appium { var mainWindow = FindWindow(_session, "MainWindow"); - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.Manual)) { new Actions(_session) .MoveToElement(mainWindow, 100, 1) @@ -96,7 +96,7 @@ namespace Avalonia.IntegrationTests.Appium try { - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.Manual)) { var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); var mainWindowIndex = GetWindowOrder(windows, "MainWindow"); @@ -119,7 +119,7 @@ namespace Avalonia.IntegrationTests.Appium { var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Owned, WindowStartupLocation.CenterOwner)) + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Owned, WindowStartupLocation.Manual)) { mainWindow.Click(); @@ -137,7 +137,7 @@ namespace Avalonia.IntegrationTests.Appium { var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); - using (OpenWindow(new PixelSize(1400, 100), ShowWindowMode.NonOwned, WindowStartupLocation.CenterOwner)) + using (OpenWindow(new PixelSize(1400, 100), ShowWindowMode.NonOwned, WindowStartupLocation.Manual)) { mainWindow.Click(); From 4f84950c41d00f97ecf0be91802b24c3800f4fc2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Jun 2022 21:33:26 +0200 Subject: [PATCH 11/16] Added tests for #8335. --- samples/IntegrationTestApp/MainWindow.axaml | 1 + .../IntegrationTestApp/MainWindow.axaml.cs | 17 +++++++++-- .../PlatformTheoryAttribute.cs | 29 +++++++++++++++++++ .../WindowTests_MacOS.cs | 26 +++++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 tests/Avalonia.IntegrationTests.Appium/PlatformTheoryAttribute.cs diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index aa2191c26b..3377979199 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -112,6 +112,7 @@ + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 1aba10ec30..9e180b12c5 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -82,7 +82,7 @@ namespace IntegrationTestApp break; } } - + private void SendToBack() { var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!; @@ -92,7 +92,18 @@ namespace IntegrationTestApp window.Activate(); } } - + + private void RestoreAll() + { + var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!; + + foreach (var window in lifetime.Windows) + { + if (window.WindowState == WindowState.Minimized) + window.WindowState = WindowState.Normal; + } + } + private void MenuClicked(object? sender, RoutedEventArgs e) { var clickedMenuItemTextBlock = this.FindControl("ClickedMenuItem"); @@ -117,6 +128,8 @@ namespace IntegrationTestApp SendToBack(); if (source?.Name == "ExitFullscreen") WindowState = WindowState.Normal; + if (source?.Name == "RestoreAll") + RestoreAll(); } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/PlatformTheoryAttribute.cs b/tests/Avalonia.IntegrationTests.Appium/PlatformTheoryAttribute.cs new file mode 100644 index 0000000000..7ac30ee11b --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Appium/PlatformTheoryAttribute.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using Xunit; + +namespace Avalonia.IntegrationTests.Appium +{ + internal class PlatformTheoryAttribute : TheoryAttribute + { + public PlatformTheoryAttribute(TestPlatforms platforms = TestPlatforms.All) => Platforms = platforms; + + public TestPlatforms Platforms { get; } + + public override string? Skip + { + get => IsSupported() ? null : $"Ignored on {RuntimeInformation.OSDescription}"; + set => throw new NotSupportedException(); + } + + private bool IsSupported() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return Platforms.HasAnyFlag(TestPlatforms.Windows); + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return Platforms.HasAnyFlag(TestPlatforms.MacOS); + return false; + } + } +} diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index c88f87562d..15ca78fdac 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -185,6 +185,32 @@ namespace Avalonia.IntegrationTests.Appium } } + [PlatformTheory(TestPlatforms.MacOS)] + [InlineData(ShowWindowMode.NonOwned)] + [InlineData(ShowWindowMode.Owned)] + public void Minimize_Button_Minimizes_Window(ShowWindowMode mode) + { + using (OpenWindow(new PixelSize(200, 100), mode, WindowStartupLocation.Manual)) + { + var secondaryWindow = FindWindow(_session, "SecondaryWindow"); + var (_, miniaturizeButton, _) = secondaryWindow.GetChromeButtons(); + + miniaturizeButton.Click(); + Thread.Sleep(1000); + + var hittable = _session.FindElementsByXPath("/XCUIElementTypeApplication/XCUIElementTypeWindow") + .Select(x => x.GetAttribute("hittable")).ToList(); + Assert.Equal(new[] { "true", "false" }, hittable); + + _session.FindElementByAccessibilityId("RestoreAll").Click(); + Thread.Sleep(1000); + + hittable = _session.FindElementsByXPath("/XCUIElementTypeApplication/XCUIElementTypeWindow") + .Select(x => x.GetAttribute("hittable")).ToList(); + Assert.Equal(new[] { "true", "true" }, hittable); + } + } + private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) { var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); From 715633930496163c094e0fdaa34aea9dbc5c12ed Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Jun 2022 22:25:27 +0200 Subject: [PATCH 12/16] Test CenterOwner window position. --- .../ShowWindowTest.axaml.cs | 6 ++- .../WindowTests.cs | 38 ++++++++++++++----- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs index 42df0b704f..001f186761 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs @@ -1,4 +1,5 @@ using System; +using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; @@ -23,15 +24,16 @@ namespace IntegrationTestApp protected override void OnOpened(EventArgs e) { base.OnOpened(e); + var scaling = PlatformImpl!.DesktopScaling; this.GetControl("Position").Text = $"{Position}"; this.GetControl("ScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}"; - this.GetControl("Scaling").Text = $"{PlatformImpl?.DesktopScaling}"; + this.GetControl("Scaling").Text = $"{scaling}"; if (Owner is not null) { var ownerRect = this.GetControl("OwnerRect"); var owner = (Window)Owner; - ownerRect.Text = $"{owner.Position}, {owner.FrameSize}"; + ownerRect.Text = $"{owner.Position}, {PixelSize.FromSize(owner.FrameSize!.Value, scaling)}"; } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index d763848899..6a364e5fc0 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -24,11 +24,14 @@ namespace Avalonia.IntegrationTests.Appium [Theory] [MemberData(nameof(StartupLocationData))] - public void StartupLocation(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) + public void StartupLocation(Size? size, ShowWindowMode mode, WindowStartupLocation location) { using var window = OpenWindow(size, mode, location); var info = GetWindowInfo(); + if (size.HasValue) + Assert.Equal(size.Value, info.ClientSize); + Assert.True(info.FrameSize.Width >= info.ClientSize.Width, "Expected frame width >= client width."); Assert.True(info.FrameSize.Height > info.ClientSize.Height, "Expected frame height > client height."); @@ -37,11 +40,18 @@ namespace Avalonia.IntegrationTests.Appium switch (location) { case WindowStartupLocation.CenterScreen: - { - var expected = info.ScreenRect.CenterRect(frameRect); - AssertCloseEnough(expected.Position, frameRect.Position); - break; - } + { + var expected = info.ScreenRect.CenterRect(frameRect); + AssertCloseEnough(expected.Position, frameRect.Position); + break; + } + case WindowStartupLocation.CenterOwner: + { + Assert.NotNull(info.OwnerRect); + var expected = info.OwnerRect!.Value.CenterRect(frameRect); + AssertCloseEnough(expected.Position, frameRect.Position); + break; + } } } @@ -89,10 +99,10 @@ namespace Avalonia.IntegrationTests.Appium } } - public static TheoryData StartupLocationData() + public static TheoryData StartupLocationData() { - var sizes = new PixelSize?[] { null, new PixelSize(400, 300) }; - var data = new TheoryData(); + var sizes = new Size?[] { null, new Size(400, 300) }; + var data = new TheoryData(); foreach (var size in sizes) { @@ -137,7 +147,7 @@ namespace Avalonia.IntegrationTests.Appium } } - private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) + private IDisposable OpenWindow(Size? size, ShowWindowMode mode, WindowStartupLocation location) { var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); @@ -158,6 +168,12 @@ namespace Avalonia.IntegrationTests.Appium private WindowInfo GetWindowInfo() { + PixelRect? ReadOwnerRect() + { + var text = _session.FindElementByAccessibilityId("OwnerRect").Text; + return !string.IsNullOrWhiteSpace(text) ? PixelRect.Parse(text) : null; + } + var retry = 0; for (;;) @@ -168,6 +184,7 @@ namespace Avalonia.IntegrationTests.Appium Size.Parse(_session.FindElementByAccessibilityId("ClientSize").Text), Size.Parse(_session.FindElementByAccessibilityId("FrameSize").Text), PixelPoint.Parse(_session.FindElementByAccessibilityId("Position").Text), + ReadOwnerRect(), PixelRect.Parse(_session.FindElementByAccessibilityId("ScreenRect").Text), double.Parse(_session.FindElementByAccessibilityId("Scaling").Text)); } @@ -191,6 +208,7 @@ namespace Avalonia.IntegrationTests.Appium Size ClientSize, Size FrameSize, PixelPoint Position, + PixelRect? OwnerRect, PixelRect ScreenRect, double Scaling); } From 91fc1335a0bd0d97ddf3286eb912ef58462c0193 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Jun 2022 23:00:52 +0200 Subject: [PATCH 13/16] Sigh, winappdriver. --- .../ElementExtensions.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs index 16d37e4beb..4b361c6716 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs @@ -130,17 +130,10 @@ namespace Avalonia.IntegrationTests.Appium public static void SendClick(this AppiumWebElement element) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - element.Click(); - } - else - { - // The Click() method seems to correspond to accessibilityPerformPress on macOS but certain controls - // such as list items don't support this action, so instead simulate a physical click as VoiceOver - // does. - new Actions(element.WrappedDriver).MoveToElement(element).Click().Perform(); - } + // The Click() method seems to correspond to accessibilityPerformPress on macOS but certain controls + // such as list items don't support this action, so instead simulate a physical click as VoiceOver + // does. On Windows, Click() seems to fail with the WindowState checkbox for some reason. + new Actions(element.WrappedDriver).MoveToElement(element).Click().Perform(); } public static void MovePointerOver(this AppiumWebElement element) From 4e63f19a0b24b699370b0bb78d7162d371d8f70f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Jun 2022 23:01:01 +0200 Subject: [PATCH 14/16] Account for scaling. --- tests/Avalonia.IntegrationTests.Appium/WindowTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 6a364e5fc0..2b10c302bc 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using System.Threading; using Avalonia.Controls; using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Interactions; using Xunit; using Xunit.Sdk; @@ -87,8 +88,9 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Fullscreen", windowState.GetComboBoxValue()); current = GetWindowInfo(); - Assert.True(current.ClientSize.Width >= current.ScreenRect.Width); - Assert.True(current.ClientSize.Height >= current.ScreenRect.Height); + var clientSize = PixelSize.FromSize(current.ClientSize, current.Scaling); + Assert.True(clientSize.Width >= current.ScreenRect.Width); + Assert.True(clientSize.Height >= current.ScreenRect.Height); windowState.Click(); _session.FindElementByName("Normal").SendClick(); From cebd5b169de369b47aa6490e9c95c1789d84138a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 28 Jun 2022 10:57:29 +0200 Subject: [PATCH 15/16] Added script to run automation tests on macOS. --- .../macos-clean-build-test.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100755 tests/Avalonia.IntegrationTests.Appium/macos-clean-build-test.sh diff --git a/tests/Avalonia.IntegrationTests.Appium/macos-clean-build-test.sh b/tests/Avalonia.IntegrationTests.Appium/macos-clean-build-test.sh new file mode 100755 index 0000000000..30a4a79f4a --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Appium/macos-clean-build-test.sh @@ -0,0 +1,8 @@ +# Cleans, builds, and runs integration tests on macOS. +# Can be used by `git bisect run` to automatically find the commit which introduced a problem. +SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) +cd "$SCRIPT_DIR"/../.. || exit +git clean -xdf +./build.sh CompileNative +./samples/IntegrationTestApp/bundle.sh +dotnet test tests/Avalonia.IntegrationTests.Appium/ -l "console;verbosity=detailed" From 15c26b9dd55ac83590b79ae61e73db937f580161 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 28 Jun 2022 12:06:21 +0200 Subject: [PATCH 16/16] Use frame size for CenterOwner startup location. --- src/Avalonia.Controls/Window.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 92f74530e2..2dd391945b 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -901,10 +901,10 @@ namespace Avalonia.Controls { if (owner != null) { - // TODO: We really need non-client size here. + var ownerSize = owner.FrameSize ?? owner.ClientSize; var ownerRect = new PixelRect( owner.Position, - PixelSize.FromSize(owner.ClientSize, scaling)); + PixelSize.FromSize(ownerSize, scaling)); Position = ownerRect.CenterRect(rect).Position; } }