diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
index 19ac68b15b..5151d80932 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -94,6 +94,23 @@
+
+
+
+
+
+ NonOwned
+ Owned
+ Modal
+
+
+ Manual
+ CenterScreen
+ CenterOwner
+
+
+
+
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
index 9a612aa94d..580548a433 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml.cs
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -5,6 +5,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
+using Avalonia.VisualTree;
namespace IntegrationTestApp
{
@@ -46,6 +47,41 @@ namespace IntegrationTestApp
}
}
+ private void ShowWindow()
+ {
+ var sizeTextBox = this.GetControl("ShowWindowSize");
+ var modeComboBox = this.GetControl("ShowWindowMode");
+ var locationComboBox = this.GetControl("ShowWindowLocation");
+ var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
+ var owner = (Window)this.GetVisualRoot()!;
+
+ var window = new ShowWindowTest
+ {
+ WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex,
+ };
+
+ if (size.HasValue)
+ {
+ window.Width = size.Value.Width;
+ window.Height = size.Value.Height;
+ }
+
+ sizeTextBox.Text = string.Empty;
+
+ switch (modeComboBox.SelectedIndex)
+ {
+ case 0:
+ window.Show();
+ break;
+ case 1:
+ window.Show(owner);
+ break;
+ case 2:
+ window.ShowDialog(owner);
+ break;
+ }
+ }
+
private void MenuClicked(object? sender, RoutedEventArgs e)
{
var clickedMenuItemTextBlock = this.FindControl("ClickedMenuItem");
@@ -64,6 +100,8 @@ namespace IntegrationTestApp
this.FindControl("BasicListBox").SelectedIndex = -1;
if (source?.Name == "MenuClickedMenuItemReset")
this.FindControl("ClickedMenuItem").Text = "None";
+ if (source?.Name == "ShowWindow")
+ ShowWindow();
}
}
}
diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml
new file mode 100644
index 0000000000..e87ae0f32c
--- /dev/null
+++ b/samples/IntegrationTestApp/ShowWindowTest.axaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs
new file mode 100644
index 0000000000..9b55864caa
--- /dev/null
+++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs
@@ -0,0 +1,40 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using Avalonia.Rendering;
+
+namespace IntegrationTestApp
+{
+ public class ShowWindowTest : Window
+ {
+ public ShowWindowTest()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ 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 = $"{((IRenderRoot)this).RenderScaling}";
+
+ if (Owner is not null)
+ {
+ var ownerRect = this.GetControl("OwnerRect");
+ var owner = (Window)Owner;
+ ownerRect.Text = $"{owner.Position}, {owner.FrameSize}";
+ }
+ }
+
+ private void CloseWindow_Click(object sender, RoutedEventArgs e) => Close();
+ }
+}
diff --git a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj
index 095f0e63e0..03d9332051 100644
--- a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj
+++ b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj
@@ -5,6 +5,10 @@
enable
+
+
+
+
diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
new file mode 100644
index 0000000000..771faa1925
--- /dev/null
+++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Avalonia.Controls;
+using OpenQA.Selenium.Appium;
+using Xunit;
+using Xunit.Sdk;
+
+namespace Avalonia.IntegrationTests.Appium
+{
+ [Collection("Default")]
+ public class WindowTests
+ {
+ private readonly AppiumDriver _session;
+
+ public WindowTests(TestAppFixture fixture)
+ {
+ _session = fixture.Session;
+
+ var tabs = _session.FindElementByAccessibilityId("MainTabs");
+ var tab = tabs.FindElementByName("Window");
+ tab.Click();
+ }
+
+ [Theory]
+ [MemberData(nameof(StartupLocationData))]
+ public void StartupLocation(string? size, ShowWindowMode mode, WindowStartupLocation location)
+ {
+ var mainWindowHandle = GetCurrentWindowHandleHack();
+
+ try
+ {
+ var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize");
+ var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode");
+ var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation");
+ var showButton = _session.FindElementByAccessibilityId("ShowWindow");
+
+ if (size is not null)
+ sizeTextBox.SendKeys(size);
+
+ modeComboBox.Click();
+ _session.FindElementByName(mode.ToString()).SendClick();
+
+ locationComboBox.Click();
+ _session.FindElementByName(location.ToString()).SendClick();
+
+ showButton.Click();
+ SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle);
+
+ 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);
+
+ Assert.True(frameSize.Width >= clientSize.Width, "Expected frame width >= client width.");
+ Assert.True(frameSize.Height > clientSize.Height, "Expected frame height > client height.");
+
+ var frameRect = new PixelRect(position, PixelSize.FromSize(frameSize, scaling));
+
+ switch (location)
+ {
+ case WindowStartupLocation.CenterScreen:
+ {
+ var expected = screenRect.CenterRect(frameRect);
+ AssertCloseEnough(expected.Position, frameRect.Position);
+ break;
+ }
+ }
+ }
+ finally
+ {
+ try
+ {
+ var closeButton = _session.FindElementByAccessibilityId("CloseWindow");
+ closeButton.Click();
+ SwitchToMainWindowHack(mainWindowHandle);
+ }
+ catch { }
+ }
+ }
+
+ public static TheoryData StartupLocationData()
+ {
+ var sizes = new[] { null, "400,300" };
+ var data = new TheoryData();
+
+ foreach (var size in sizes)
+ {
+ foreach (var mode in Enum.GetValues())
+ {
+ foreach (var location in Enum.GetValues())
+ {
+ if (!(location == WindowStartupLocation.CenterOwner && mode == ShowWindowMode.NonOwned))
+ {
+ data.Add(size, mode, location);
+ }
+ }
+ }
+ }
+
+ return data;
+ }
+
+ private string? GetCurrentWindowHandleHack()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ // HACK: WinAppDriver only seems to switch to a newly opened window if the window has an owner,
+ // otherwise the session remains targeting the previous window. Return the handle for the
+ // current window so we know which window to switch to when another is opened.
+ return _session.WindowHandles.Single();
+ }
+
+ return null;
+ }
+
+ private void SwitchToNewWindowHack(string? oldWindowHandle)
+ {
+ if (oldWindowHandle is not null)
+ {
+ var newWindowHandle = _session.WindowHandles.FirstOrDefault(x => x != oldWindowHandle);
+
+ // HACK: Looks like WinAppDriver only adds window handles for non-owned windows, but luckily
+ // non-owned windows is where we're having the problem, so if we find a window handle that
+ // isn't the main window handle then switch to it.
+ if (newWindowHandle is not null)
+ _session.SwitchTo().Window(newWindowHandle);
+ }
+ }
+
+ private void SwitchToMainWindowHack(string? mainWindowHandle)
+ {
+ if (mainWindowHandle is not null)
+ _session.SwitchTo().Window(mainWindowHandle);
+ }
+
+ private static void AssertCloseEnough(PixelPoint expected, PixelPoint actual)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ // On win32, accurate frame information cannot be obtained until a window is shown but
+ // WindowStartupLocation needs to be calculated before the window is shown, meaning that
+ // the position of a centered window can be off by a bit. From initial testing, looks
+ // like this shouldn't be more than 10 pixels.
+ if (Math.Abs(expected.X - actual.X) > 10)
+ throw new EqualException(expected, actual);
+ if (Math.Abs(expected.Y - actual.Y) > 10)
+ throw new EqualException(expected, actual);
+ }
+ else
+ {
+ Assert.Equal(expected, actual);
+ }
+ }
+
+ public enum ShowWindowMode
+ {
+ NonOwned,
+ Owned,
+ Modal
+ }
+ }
+}