Bladeren bron

Refactor integration test app paging (#16551)

* Refactor IntegrationTestApp.

Use a `ListBox` to switch pages instead of a `TabControl`: the `TabControl` didn't adapt well to smaller screen sizes, and the `MainWindow` was getting unwieldy anyway.

* Update tests to use new pager.

Move logic for selecting the page to a base class as we may need to handle scrolling manually on macOS at some point (Appium on macOS doesn't scroll elements into view automatically).

* Add AutomationPeer.IsOffscreen.

This is needed in order for controls to be scrolled into view using WinAppDriver. The default is the same as WPF and the default value is overridden in the same controls as WPF (where present).
Steven Kirk 1 jaar geleden
bovenliggende
commit
fa8b1736de
71 gewijzigde bestanden met toevoegingen van 1349 en 1030 verwijderingen
  1. 0 1
      samples/IntegrationTestApp/App.axaml.cs
  2. 0 6
      samples/IntegrationTestApp/IntegrationTestApp.csproj
  3. 20 230
      samples/IntegrationTestApp/MainWindow.axaml
  4. 44 362
      samples/IntegrationTestApp/MainWindow.axaml.cs
  5. 6 0
      samples/IntegrationTestApp/Models/Page.cs
  6. 17 0
      samples/IntegrationTestApp/Pages/AutomationPage.axaml
  7. 11 0
      samples/IntegrationTestApp/Pages/AutomationPage.axaml.cs
  8. 22 0
      samples/IntegrationTestApp/Pages/ButtonPage.axaml
  9. 11 0
      samples/IntegrationTestApp/Pages/ButtonPage.axaml.cs
  10. 12 0
      samples/IntegrationTestApp/Pages/CheckBoxPage.axaml
  11. 11 0
      samples/IntegrationTestApp/Pages/CheckBoxPage.axaml.cs
  12. 16 0
      samples/IntegrationTestApp/Pages/ComboBoxPage.axaml
  13. 22 0
      samples/IntegrationTestApp/Pages/ComboBoxPage.axaml.cs
  14. 17 0
      samples/IntegrationTestApp/Pages/ContextMenuPage.axaml
  15. 11 0
      samples/IntegrationTestApp/Pages/ContextMenuPage.axaml.cs
  16. 14 0
      samples/IntegrationTestApp/Pages/DesktopPage.axaml
  17. 19 0
      samples/IntegrationTestApp/Pages/DesktopPage.axaml.cs
  18. 29 0
      samples/IntegrationTestApp/Pages/GesturesPage.axaml
  19. 44 0
      samples/IntegrationTestApp/Pages/GesturesPage.axaml.cs
  20. 15 0
      samples/IntegrationTestApp/Pages/ListBoxPage.axaml
  21. 23 0
      samples/IntegrationTestApp/Pages/ListBoxPage.axaml.cs
  22. 22 0
      samples/IntegrationTestApp/Pages/MenuPage.axaml
  23. 24 0
      samples/IntegrationTestApp/Pages/MenuPage.axaml.cs
  24. 19 0
      samples/IntegrationTestApp/Pages/PointerPage.axaml
  25. 47 0
      samples/IntegrationTestApp/Pages/PointerPage.axaml.cs
  26. 14 0
      samples/IntegrationTestApp/Pages/RadioButtonPage.axaml
  27. 11 0
      samples/IntegrationTestApp/Pages/RadioButtonPage.axaml.cs
  28. 19 0
      samples/IntegrationTestApp/Pages/ScreensPage.axaml
  29. 32 0
      samples/IntegrationTestApp/Pages/ScreensPage.axaml.cs
  30. 8 0
      samples/IntegrationTestApp/Pages/ScrollBarPage.axaml
  31. 11 0
      samples/IntegrationTestApp/Pages/ScrollBarPage.axaml.cs
  32. 17 0
      samples/IntegrationTestApp/Pages/SliderPage.axaml
  33. 17 0
      samples/IntegrationTestApp/Pages/SliderPage.axaml.cs
  34. 21 0
      samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml
  35. 63 0
      samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml.cs
  36. 45 0
      samples/IntegrationTestApp/Pages/WindowPage.axaml
  37. 199 0
      samples/IntegrationTestApp/Pages/WindowPage.axaml.cs
  38. 0 1
      samples/IntegrationTestApp/ShowWindowTest.axaml.cs
  39. 0 1
      samples/IntegrationTestApp/TopmostWindowTest.axaml.cs
  40. 23 0
      samples/IntegrationTestApp/ViewModels/MainWindowViewModel.cs
  41. 24 0
      samples/IntegrationTestApp/ViewModels/ViewModelBase.cs
  42. 2 0
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  43. 2 0
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  44. 2 0
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  45. 6 0
      src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
  46. 12 0
      src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
  47. 14 0
      src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
  48. 2 0
      src/Avalonia.Controls/ListBoxItem.cs
  49. 2 0
      src/Avalonia.Controls/MenuItem.cs
  50. 1 0
      src/Avalonia.Controls/TabItem.cs
  51. 2 0
      src/Avalonia.Controls/TreeViewItem.cs
  52. 1 0
      src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
  53. 7 14
      tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs
  54. 8 16
      tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs
  55. 6 13
      tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs
  56. 23 30
      tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs
  57. 5 11
      tests/Avalonia.IntegrationTests.Appium/ContextMenuTests.cs
  58. 37 44
      tests/Avalonia.IntegrationTests.Appium/GestureTests.cs
  59. 6 12
      tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs
  60. 32 41
      tests/Avalonia.IntegrationTests.Appium/MenuTests.cs
  61. 11 18
      tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs
  62. 5 12
      tests/Avalonia.IntegrationTests.Appium/PointerTests.cs
  63. 5 11
      tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs
  64. 15 21
      tests/Avalonia.IntegrationTests.Appium/ScreenTests.cs
  65. 3 9
      tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs
  66. 15 22
      tests/Avalonia.IntegrationTests.Appium/SliderTests.cs
  67. 33 0
      tests/Avalonia.IntegrationTests.Appium/TestBase.cs
  68. 17 23
      tests/Avalonia.IntegrationTests.Appium/TrayIconTests.cs
  69. 18 24
      tests/Avalonia.IntegrationTests.Appium/WindowDecorationsTests.cs
  70. 42 48
      tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
  71. 35 60
      tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

+ 0 - 1
samples/IntegrationTestApp/App.axaml.cs

@@ -4,7 +4,6 @@ using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Markup.Xaml;
-using Avalonia.Media;
 using MiniMvvm;
 
 namespace IntegrationTestApp

+ 0 - 6
samples/IntegrationTestApp/IntegrationTestApp.csproj

@@ -29,12 +29,6 @@
     <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
   </ItemGroup>
 
-  <ItemGroup>
-    <Compile Update="TopmostWindowTest.axaml.cs">
-      <DependentUpon>TopmostWindowTest.axaml</DependentUpon>
-    </Compile>
-  </ItemGroup>
-  
   <Import Project="..\..\build\BuildTargets.targets" />
   <Import Project="..\..\build\SampleApp.props" />
   <Import Project="..\..\build\ReferenceCoreLibraries.props" />

+ 20 - 230
samples/IntegrationTestApp/MainWindow.axaml

@@ -2,12 +2,12 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-        xmlns:integrationTestApp="using:IntegrationTestApp"
+        xmlns:vm="using:IntegrationTestApp.ViewModels"
         mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
         x:Class="IntegrationTestApp.MainWindow"
         Icon="/Assets/icon.ico"
         Title="IntegrationTestApp"
-        x:DataType="integrationTestApp:MainWindow">
+        x:DataType="vm:MainWindowViewModel">
   <NativeMenu.Menu>
     <NativeMenu>
       <NativeMenuItem Header="File">
@@ -18,7 +18,7 @@
       <NativeMenuItem Header="View">
         <NativeMenu/>
       </NativeMenuItem>
-      <NativeMenuItem Header="_Options">
+      <NativeMenuItem Header="_Options"> 
         <NativeMenu/>
       </NativeMenuItem>
     </NativeMenu>
@@ -27,235 +27,25 @@
     <NativeMenuBar DockPanel.Dock="Top"/>
     <StackPanel DockPanel.Dock="Bottom" Margin="4" Orientation="Horizontal">
       <TextBlock Margin="0,0,4,0">WindowState:</TextBlock>
-      <TextBlock Name="MainWindowState" Text="{Binding WindowState}"/>
+      <TextBlock Name="MainWindowState" Text="{Binding $parent[Window].WindowState}"/>
       <TextBlock Name="AppOverlayPopups" Margin="8 0"/>
     </StackPanel>
-    
-    <TabControl TabStripPlacement="Left" Name="MainTabs">
-      <TabItem Header="Automation">
-        <StackPanel>
-          <TextBlock Name="TextBlockWithName">TextBlockWithName</TextBlock>
-          <TextBlock Name="NotTheAutomationId" AutomationProperties.AutomationId="TextBlockWithNameAndAutomationId">
-            TextBlockWithNameAndAutomationId
-          </TextBlock>
-          <TextBlock Name="TextBlockAsLabel">Label for TextBox</TextBlock>
-          <TextBox Name="LabeledByTextBox" AutomationProperties.LabeledBy="{Binding #TextBlockAsLabel}">
-            Foo
-          </TextBox>
-        </StackPanel>
-      </TabItem>
-      
-      <TabItem Header="Button">
-        <StackPanel>
-          <Button Name="DisabledButton" IsEnabled="False">
-            Disabled Button
-          </Button>
-          <Button Name="EffectivelyDisabledButton" Command="{ReflectionBinding DoesntExist}">
-            Effectively Disabled Button
-          </Button>
-          <Button Name="BasicButton">
-            Basic Button
-          </Button>
-          <Button Name="ButtonWithTextBlock">
-            <TextBlock>Button with TextBlock</TextBlock>
-          </Button>
-          <Button Name="ButtonWithAcceleratorKey" HotKey="Ctrl+B">Button with Accelerator Key</Button>
-        </StackPanel>
-      </TabItem>
-
-      <TabItem Header="RadioButton">
-        <StackPanel Orientation="Vertical">
-          <RadioButton Name="BasicRadioButton">Sample RadioButton</RadioButton>
-          <StackPanel Orientation="Vertical">
-            <RadioButton Name="ThreeStatesRadioButton1" IsChecked="True" IsThreeState="True">Three States: Option 1</RadioButton>
-            <RadioButton Name="ThreeStatesRadioButton2" IsChecked="False" IsThreeState="True">Three States: Option 2</RadioButton>
-          </StackPanel>
-        </StackPanel>
-      </TabItem>
-      
-      <TabItem Header="CheckBox">
-        <StackPanel>
-          <CheckBox Name="UncheckedCheckBox">Unchecked</CheckBox>
-          <CheckBox Name="CheckedCheckBox" IsChecked="True">Checked</CheckBox>
-          <CheckBox Name="ThreeStateCheckBox" IsThreeState="True" IsChecked="{x:Null}">ThreeState</CheckBox>
-        </StackPanel>
-      </TabItem>
-
-      <TabItem Header="ComboBox">
-        <StackPanel>
-          <ComboBox Name="BasicComboBox">
-            <ComboBoxItem>Item 0</ComboBoxItem>
-            <ComboBoxItem>Item 1</ComboBoxItem>
-          </ComboBox>
-          <CheckBox Name="ComboBoxWrapSelection" IsChecked="{Binding #BasicComboBox.WrapSelection}">Wrap Selection</CheckBox>
-          <Button Name="ComboBoxSelectionClear">Clear Selection</Button>
-          <Button Name="ComboBoxSelectFirst">Select First</Button>
-        </StackPanel>
-      </TabItem>
-
-      <TabItem Header="ContextMenu">
-        <StackPanel>
-          <Button Name="ShowContextMenu" Content="Right-click to show context menu.">
-            <Button.ContextMenu>
-              <ContextMenu>
-                <MenuItem Name="ContextMenuItem1" Header="Item 1"/>
-                <MenuItem Name="ContextMenuItem2" Header="Item 2"/>
-              </ContextMenu>
-            </Button.ContextMenu>
-          </Button>
-        </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">
-            <Button Name="ResetGestures" DockPanel.Dock="Right">Reset</Button>
-            <TextBlock Name="LastGesture" />
-          </DockPanel>
-          <Panel>
-            <Border Name="GestureBorder" Background="Blue"
-                    AutomationProperties.AccessibilityView="Content"
-                    AutomationProperties.ControlTypeOverride="Image"/>
-            <Border Name="GestureBorder2" Background="Green" IsVisible="False"
-                    AutomationProperties.AccessibilityView="Content"
-                    AutomationProperties.ControlTypeOverride="Image"/>
-          </Panel>
-        </DockPanel>
-      </TabItem>
-
-      <TabItem Header="ListBox">
-        <DockPanel>
-          <StackPanel DockPanel.Dock="Bottom">
-            <Button Name="ListBoxSelectionClear">Clear Selection</Button>
-          </StackPanel>
-          <ListBox Name="BasicListBox" ItemsSource="{Binding ListBoxItems}" SelectionMode="Multiple"/>
-        </DockPanel>
-      </TabItem>
-      
-      <TabItem Header="Menu">
-        <DockPanel>
-          <Menu DockPanel.Dock="Top">
-            <MenuItem Name="RootMenuItem" Header="_Root">
-              <MenuItem Name="Child1MenuItem" Header="_Child 1" InputGesture="Ctrl+O" Click="MenuClicked"/>
-              <MenuItem Name="Child2MenuItem" Header="C_hild 2">
-                <MenuItem Name="GrandchildMenuItem" Header="_Grandchild" Click="MenuClicked"/>
-              </MenuItem>
-            </MenuItem>
-          </Menu>
-          <StackPanel>
-            <TextBlock Name="ClickedMenuItem">None</TextBlock>
-            <Button Name="MenuClickedMenuItemReset">Reset</Button>
-            <TextBox Name="MenuFocusTest"/>
-          </StackPanel>
-        </DockPanel>
-      </TabItem>
-
-      <TabItem Header="Pointer">
-        <StackPanel>
-          <!-- Trigger with PointerPressed rather using a Button so we have access to the pointer. -->
-          <Border Name="PointerPageShowDialog"
-                  Background="{DynamicResource ButtonBackground}"
-                  HorizontalAlignment="Left"
-                  Padding="{DynamicResource ButtonPadding}"
-                  AutomationProperties.AccessibilityView="Control"
-                  PointerPressed="PointerPageShowDialogPressed">
-            <TextBlock>Show Dialog</TextBlock>
-          </Border>
-          <TextBlock Name="PointerCaptureStatus"/>
-        </StackPanel>
-      </TabItem>
-      
-      <TabItem Header="Window">
-        <Grid ColumnDefinitions="*,8,*">
-          <StackPanel Grid.Column="0">
-            <TextBox Name="ShowWindowSize" Watermark="Window Size"/>
-            <ComboBox Name="ShowWindowMode" SelectedIndex="0">
-              <ComboBoxItem>NonOwned</ComboBoxItem>
-              <ComboBoxItem>Owned</ComboBoxItem>
-              <ComboBoxItem>Modal</ComboBoxItem>
-            </ComboBox>
-            <ComboBox Name="ShowWindowLocation" SelectedIndex="0">
-              <ComboBoxItem>Manual</ComboBoxItem>
-              <ComboBoxItem>CenterScreen</ComboBoxItem>
-              <ComboBoxItem>CenterOwner</ComboBoxItem>
-            </ComboBox>
-            <ComboBox Name="ShowWindowState" SelectedIndex="0">
-              <ComboBoxItem Name="ShowWindowStateNormal">Normal</ComboBoxItem>
-              <ComboBoxItem Name="ShowWindowStateMinimized">Minimized</ComboBoxItem>
-              <ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem>
-              <ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem>
-            </ComboBox>
-            <ComboBox Name="ShowWindowSystemDecorations" SelectedIndex="2">
-              <ComboBoxItem Name="ShowWindowSystemDecorationsNone">None</ComboBoxItem>
-              <ComboBoxItem Name="ShowWindowSystemDecorationsBorderOnly">BorderOnly</ComboBoxItem>
-              <ComboBoxItem Name="ShowWindowSystemDecorationsFull">Full</ComboBoxItem>
-            </ComboBox>
-            <CheckBox Name="ShowWindowExtendClientAreaToDecorationsHint">ExtendClientAreaToDecorationsHint</CheckBox>
-            <CheckBox Name="ShowWindowCanResize" IsChecked="True">Can Resize</CheckBox>
-            <Button Name="ShowWindow">Show Window</Button>
-            <Button Name="SendToBack">Send to Back</Button>
-            <Button Name="EnterFullscreen">Enter Fullscreen</Button>
-            <Button Name="ExitFullscreen">Exit Fullscreen</Button>
-            <Button Name="RestoreAll">Restore All</Button>
-            <Button Name="ShowTopmostWindow">Show Topmost Window</Button>
-          </StackPanel>
-          <StackPanel Grid.Column="2">
-            <Button Name="ShowTransparentWindow">Transparent Window</Button>
-            <Button Name="ShowTransparentPopup">Transparent Popup</Button>
-          </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">
-              <TextBox Name="HorizontalSliderValue"
-                       DockPanel.Dock="Right"
-                       Text="{Binding #HorizontalSlider.Value, Mode=OneWay, StringFormat=\{0:0\}}"
-                       VerticalAlignment="Top"/>
-              <Slider Name="HorizontalSlider" Value="50"/>
-            </DockPanel>
-            <Button Name="ResetSliders">Reset</Button>
-          </DockPanel>
-      </TabItem>
-      
-      <TabItem Header="ScrollBar">
-        <ScrollBar Name="MyScrollBar" Orientation="Horizontal" AllowAutoHide="False" Width="200" Height="30" Value="20"/>
-      </TabItem>
-      
-      <TabItem Header="Screens">
-        <StackPanel Spacing="4">
-          <Button Name="ScreenRefresh" Content="Refresh" />
-          <TextBox Name="ScreenName" Watermark="DisplayName" UseFloatingWatermark="true" />
-          <TextBox Name="ScreenHandle" Watermark="Handle" UseFloatingWatermark="true" />
-          <TextBox Name="ScreenScaling" Watermark="Scaling" UseFloatingWatermark="true" />
-          <TextBox Name="ScreenBounds" Watermark="Bounds" UseFloatingWatermark="true" />
-          <TextBox Name="ScreenWorkArea" Watermark="WorkArea" UseFloatingWatermark="true" />
-          <TextBox Name="ScreenOrientation" Watermark="Orientation" UseFloatingWatermark="true" />
-          <TextBox Name="ScreenSameReference" Watermark="Is same reference" UseFloatingWatermark="true" />
-        </StackPanel>
-      </TabItem>
-    </TabControl>
+    <DockPanel>
+      <ListBox Name="Pager"
+               DockPanel.Dock="Left"
+               DisplayMemberBinding="{Binding Name}"
+               ItemsSource="{Binding Pages}"
+               SelectedItem="{Binding SelectedPage}"
+               SelectionChanged="Pager_SelectionChanged">
+        <ListBox.ItemsPanel>
+          <ItemsPanelTemplate>
+            <StackPanel/>
+          </ItemsPanelTemplate>
+        </ListBox.ItemsPanel>
+      </ListBox>
+      <Decorator Name="PagerContent"/>
+    </DockPanel>
+    
   </DockPanel>
 </Window>

+ 44 - 362
samples/IntegrationTestApp/MainWindow.axaml.cs

@@ -1,18 +1,8 @@
-using System;
 using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using Avalonia;
-using Avalonia.Automation;
 using Avalonia.Controls;
-using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Controls.Primitives;
-using Avalonia.Controls.Primitives.PopupPositioning;
-using Avalonia.Input;
-using Avalonia.Interactivity;
-using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.VisualTree;
+using IntegrationTestApp.Models;
+using IntegrationTestApp.Pages;
+using IntegrationTestApp.ViewModels;
 
 namespace IntegrationTestApp
 {
@@ -24,374 +14,66 @@ namespace IntegrationTestApp
             Name = "MainWindow";
 
             InitializeComponent();
-            InitializeViewMenu();
-            InitializeGesturesTab();
-            this.AttachDevTools();
 
-            AppOverlayPopups.Text = Program.OverlayPopups ? "Overlay Popups" : "Native Popups";
+            var viewModel = new MainWindowViewModel(CreatePages());
+            InitializeViewMenu(viewModel.Pages);
 
-            AddHandler(Button.ClickEvent, OnButtonClick);
-            ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList();
-            DataContext = this;
+            DataContext = viewModel;
+            AppOverlayPopups.Text = Program.OverlayPopups ? "Overlay Popups" : "Native Popups";
         }
 
-        public List<string> ListBoxItems { get; }
+        private MainWindowViewModel? ViewModel => (MainWindowViewModel?)DataContext;
 
-        private void InitializeViewMenu()
+        private void InitializeViewMenu(IEnumerable<Page> pages)
         {
             var viewMenu = (NativeMenuItem?)NativeMenu.GetMenu(this)?.Items[1];
 
-            foreach (var tabItem in MainTabs.Items.Cast<TabItem>())
+            foreach (var page in pages)
             {
                 var menuItem = new NativeMenuItem
                 {
-                    Header = (string?)tabItem.Header,
-                    ToolTip = $"Tip:{(string?)tabItem.Header}",
-                    IsChecked = tabItem.IsSelected,
+                    Header = (string?)page.Name,
+                    ToolTip = $"Tip:{(string?)page.Name}",
                     ToggleType = NativeMenuItemToggleType.Radio,
                 };
 
-                menuItem.Click += (_, _) => tabItem.IsSelected = true;
-                viewMenu?.Menu?.Items.Add(menuItem);
-            }
-        }
-
-        private void OnShowWindow()
-        {
-            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 = ShowWindowSystemDecorations;
-            var extendClientArea = ShowWindowExtendClientAreaToDecorationsHint;
-            var canResizeCheckBox = ShowWindowCanResize;
-            var owner = (Window)this.GetVisualRoot()!;
-
-            var window = new ShowWindowTest
-            {
-                WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex,
-                CanResize = canResizeCheckBox.IsChecked ?? false,
-            };
-
-            if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
-            {
-                // Make sure the windows have unique names and AutomationIds.
-                var existing = lifetime.Windows.OfType<ShowWindowTest>().Count();
-                if (existing > 0)
-                {
-                    AutomationProperties.SetAutomationId(window, window.Name + (existing + 1));
-                    window.Title += $" {existing + 1}";
-                }
-            }
-            
-            if (size.HasValue)
-            {
-                window.Width = size.Value.Width;
-                window.Height = size.Value.Height;
-            }
-
-            sizeTextBox.Text = string.Empty;
-            window.ExtendClientAreaToDecorationsHint = extendClientArea.IsChecked ?? false;
-            window.SystemDecorations = (SystemDecorations)systemDecorations.SelectedIndex;
-            window.WindowState = (WindowState)stateComboBox.SelectedIndex;
-
-            switch (modeComboBox.SelectedIndex)
-            {
-                case 0:
-                    window.Show();
-                    break;
-                case 1:
-                    window.Show(owner);
-                    break;
-                case 2:
-                    window.ShowDialog(owner);
-                    break;
-            }
-        }
-
-        private void OnShowTransparentWindow()
-        {
-            // Show a background window to make sure the color behind the transparent window is
-            // a known color (green).
-            var backgroundWindow = new Window
-            {
-                Title = "Transparent Window Background",
-                Name = "TransparentWindowBackground",
-                Width = 300,
-                Height = 300,
-                Background = Brushes.Green,
-                WindowStartupLocation = WindowStartupLocation.CenterOwner,
-            };
-
-            // This is the transparent window with a red circle.
-            var window = new Window
-            {
-                Title = "Transparent Window",
-                Name = "TransparentWindow",
-                SystemDecorations = SystemDecorations.None,
-                Background = Brushes.Transparent,
-                TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent },
-                WindowStartupLocation = WindowStartupLocation.CenterOwner,
-                Width = 200,
-                Height = 200,
-                Content = new Border
+                menuItem.Click += (_, _) =>
                 {
-                    Background = Brushes.Red,
-                    CornerRadius = new CornerRadius(100),
-                }
-            };
-
-            window.PointerPressed += (_, _) =>
-            {
-                window.Close();
-                backgroundWindow.Close();
-            };
-
-            backgroundWindow.Show(this);
-            window.Show(backgroundWindow);
-        }
-
-        private void OnShowTransparentPopup()
-        {
-            var popup = new Popup
-            {
-                WindowManagerAddShadowHint = false,
-                Placement = PlacementMode.AnchorAndGravity,
-                PlacementAnchor = PopupAnchor.Top,
-                PlacementGravity = PopupGravity.Bottom,
-                Width= 200,
-                Height= 200,
-                Child = new Border
-                {
-                    Background = Brushes.Red,
-                    CornerRadius = new CornerRadius(100),
-                }
-            };
-
-            // Show a background window to make sure the color behind the transparent window is
-            // a known color (green).
-            var backgroundWindow = new Window
-            {
-                Title = "Transparent Popup Background",
-                Name = "TransparentPopupBackground",
-                Width = 200,
-                Height = 200,
-                Background = Brushes.Green,
-                WindowStartupLocation = WindowStartupLocation.CenterOwner,
-                Content = new Border
-                {
-                    Name = "PopupContainer",
-                    Child = popup,
-                    [AutomationProperties.AccessibilityViewProperty] = AccessibilityView.Content,
-                }
-            };
-
-            backgroundWindow.PointerPressed += (_, _) => backgroundWindow.Close();
-            backgroundWindow.Show(this);
-
-            popup.Open();
-        }
-
-        private void OnSendToBack()
-        {
-            var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
-
-            foreach (var window in lifetime.Windows.ToArray())
-            {
-                window.Activate();
-            }
-        }
-
-        private void OnRestoreAll()
-        {
-            var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
-
-            foreach (var window in lifetime.Windows.ToArray())
-            {
-                window.Show();
-                if (window.WindowState == WindowState.Minimized)
-                    window.WindowState = WindowState.Normal;
-            }
-        }
-        
-        private void OnShowTopmostWindow()
-        {
-            var mainWindow = new TopmostWindowTest("OwnerWindow") { Topmost = true, Title = "Owner Window"};
-            var ownedWindow = new TopmostWindowTest("OwnedWindow") { WindowStartupLocation = WindowStartupLocation.CenterOwner, Title = "Owned Window"};
-            mainWindow.Show();
-            
-            ownedWindow.Show(mainWindow);
-        }
-
-        private void OnToggleTrayIconVisible()
-        {
-            var icon = TrayIcon.GetIcons(Application.Current!)!.FirstOrDefault()!;
-            icon.IsVisible = !icon.IsVisible;
-        }
-
-        private void InitializeGesturesTab()
-        {
-            var gestureBorder = GestureBorder;
-            var gestureBorder2 = GestureBorder2;
-            var lastGesture = LastGesture;
-            var resetGestures = ResetGestures;
-            gestureBorder.Tapped += (_, _) => lastGesture.Text = "Tapped";
-            
-            gestureBorder.DoubleTapped += (_, _) =>
-            {
-                lastGesture.Text = "DoubleTapped";
-
-                // Testing #8733
-                gestureBorder.IsVisible = false;
-                gestureBorder2.IsVisible = true;
-            };
-
-            gestureBorder2.DoubleTapped += (_, _) =>
-            {
-                lastGesture.Text = "DoubleTapped2";
-            };
-
-            Gestures.AddRightTappedHandler(gestureBorder, (_, _) => lastGesture.Text = "RightTapped");
-            
-            resetGestures.Click += (_, _) =>
-            {
-                lastGesture.Text = string.Empty;
-                gestureBorder.IsVisible = true;
-                gestureBorder2.IsVisible = false;
-            };
-        }
-
-        private void MenuClicked(object? sender, RoutedEventArgs e)
-        {
-            var clickedMenuItemTextBlock = ClickedMenuItem;
-            clickedMenuItemTextBlock.Text = (sender as MenuItem)?.Header?.ToString();
-        }
-
-        private void OnButtonClick(object? sender, RoutedEventArgs e)
-        {
-            var source = e.Source as Button;
-
-            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 == nameof(ExitFullscreen))
-                WindowState = WindowState.Normal;
-            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();
-            if (source?.Name == nameof(ToggleTrayIconVisible))
-                OnToggleTrayIconVisible();
-            if (source?.Name == nameof(ScreenRefresh))
-                OnScreenRefresh();
-        }
-
-        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();
-        }
-
-        private void PointerPageShowDialogPressed(object? sender, PointerPressedEventArgs e)
-        {
-            void CaptureLost(object? sender, PointerCaptureLostEventArgs e)
-            {
-                PointerCaptureStatus.Text = "None";
-                ((Control)sender!).PointerCaptureLost -= CaptureLost;
-            }
-
-            var captured = e.Pointer.Captured as Control;
+                    if (ViewModel is { } viewModel)
+                        viewModel.SelectedPage = page;
+                };
 
-            if (captured is not null)
-            {
-                captured.PointerCaptureLost += CaptureLost;
+                viewMenu?.Menu?.Items.Add(menuItem);
             }
-
-            PointerCaptureStatus.Text = captured?.ToString() ?? "None";
-
-            var dialog = new Window
-            {
-                Width = 200,
-                Height = 200,
-            };
-            
-            dialog.Content = new Button
-            {
-                Content = "Close",
-                Command = new DelegateCommand(() => dialog.Close()),
-            };
-
-            dialog.ShowDialog(this);
         }
 
-        private Screen? _lastScreen;
-        private void OnScreenRefresh()
-        {
-            var lastScreen = _lastScreen;
-            var screen = _lastScreen = Screens.ScreenFromWindow(this);
-            ScreenName.Text = screen?.DisplayName;
-            ScreenHandle.Text = screen?.TryGetPlatformHandle()?.ToString();
-            ScreenBounds.Text = screen?.Bounds.ToString();
-            ScreenWorkArea.Text = screen?.WorkingArea.ToString();
-            ScreenScaling.Text = screen?.Scaling.ToString(CultureInfo.InvariantCulture);
-            ScreenOrientation.Text = screen?.CurrentOrientation.ToString();
-            ScreenSameReference.Text = ReferenceEquals(lastScreen, screen).ToString();
+        private void Pager_SelectionChanged(object? sender, SelectionChangedEventArgs e)
+        {
+            if (Pager.SelectedItem is Page page)
+                PagerContent.Child = page.CreateContent();
+        }
+
+        private static IEnumerable<Page> CreatePages()
+        {
+            return
+            [
+                new("Automation", () => new AutomationPage()),
+                new("Button", () => new ButtonPage()),
+                new("CheckBox", () => new CheckBoxPage()),
+                new("ComboBox", () => new ComboBoxPage()),
+                new("ContextMenu", () => new ContextMenuPage()),
+                new("DesktopPage", () => new DesktopPage()),
+                new("Gestures", () => new GesturesPage()),
+                new("ListBox", () => new ListBoxPage()),
+                new("Menu", () => new MenuPage()),
+                new("Pointer", () => new PointerPage()),
+                new("RadioButton", () => new RadioButtonPage()),
+                new("Screens", () => new ScreensPage()),
+                new("ScrollBar", () => new ScrollBarPage()),
+                new("Slider", () => new SliderPage()),
+                new("Window Decorations", () => new WindowDecorationsPage()),
+                new("Window", () => new WindowPage()),
+            ];
         }
     }
 }

+ 6 - 0
samples/IntegrationTestApp/Models/Page.cs

@@ -0,0 +1,6 @@
+using System;
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Models;
+
+internal record Page(string Name, Func<Control> CreateContent);

+ 17 - 0
samples/IntegrationTestApp/Pages/AutomationPage.axaml

@@ -0,0 +1,17 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.AutomationPage">
+  <StackPanel>
+    <TextBlock Name="TextBlockWithName">TextBlockWithName</TextBlock>
+    <TextBlock Name="NotTheAutomationId" AutomationProperties.AutomationId="TextBlockWithNameAndAutomationId">
+      TextBlockWithNameAndAutomationId
+    </TextBlock>
+    <TextBlock Name="TextBlockAsLabel">Label for TextBox</TextBlock>
+    <TextBox Name="LabeledByTextBox" AutomationProperties.LabeledBy="{Binding #TextBlockAsLabel}">
+      Foo
+    </TextBox>
+  </StackPanel>
+</UserControl>

+ 11 - 0
samples/IntegrationTestApp/Pages/AutomationPage.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class AutomationPage : UserControl
+{
+    public AutomationPage()
+    {
+        InitializeComponent();
+    }
+}

+ 22 - 0
samples/IntegrationTestApp/Pages/ButtonPage.axaml

@@ -0,0 +1,22 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.ButtonPage">
+  <StackPanel>
+    <Button Name="DisabledButton" IsEnabled="False">
+      Disabled Button
+    </Button>
+    <Button Name="EffectivelyDisabledButton" Command="{ReflectionBinding DoesntExist}">
+      Effectively Disabled Button
+    </Button>
+    <Button Name="BasicButton">
+      Basic Button
+    </Button>
+    <Button Name="ButtonWithTextBlock">
+      <TextBlock>Button with TextBlock</TextBlock>
+    </Button>
+    <Button Name="ButtonWithAcceleratorKey" HotKey="Ctrl+B">Button with Accelerator Key</Button>
+  </StackPanel>
+</UserControl>

+ 11 - 0
samples/IntegrationTestApp/Pages/ButtonPage.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class ButtonPage : UserControl
+{
+    public ButtonPage()
+    {
+        InitializeComponent();
+    }
+}

+ 12 - 0
samples/IntegrationTestApp/Pages/CheckBoxPage.axaml

@@ -0,0 +1,12 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.CheckBoxPage">
+  <StackPanel>
+    <CheckBox Name="UncheckedCheckBox">Unchecked</CheckBox>
+    <CheckBox Name="CheckedCheckBox" IsChecked="True">Checked</CheckBox>
+    <CheckBox Name="ThreeStateCheckBox" IsThreeState="True" IsChecked="{x:Null}">ThreeState</CheckBox>
+  </StackPanel>
+</UserControl>

+ 11 - 0
samples/IntegrationTestApp/Pages/CheckBoxPage.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class CheckBoxPage : UserControl
+{
+    public CheckBoxPage()
+    {
+        InitializeComponent();
+    }
+}

+ 16 - 0
samples/IntegrationTestApp/Pages/ComboBoxPage.axaml

@@ -0,0 +1,16 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.ComboBoxPage">
+  <StackPanel>
+    <ComboBox Name="BasicComboBox">
+      <ComboBoxItem>Item 0</ComboBoxItem>
+      <ComboBoxItem>Item 1</ComboBoxItem>
+    </ComboBox>
+    <CheckBox Name="ComboBoxWrapSelection" IsChecked="{Binding #BasicComboBox.WrapSelection}">Wrap Selection</CheckBox>
+    <Button Name="ComboBoxSelectionClear" Click="ComboBoxSelectionClear_Click">Clear Selection</Button>
+    <Button Name="ComboBoxSelectFirst" Click="ComboBoxSelectFirst_Click">Select First</Button>
+  </StackPanel>
+</UserControl>

+ 22 - 0
samples/IntegrationTestApp/Pages/ComboBoxPage.axaml.cs

@@ -0,0 +1,22 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class ComboBoxPage : UserControl
+{
+    public ComboBoxPage()
+    {
+        InitializeComponent();
+    }
+
+    private void ComboBoxSelectionClear_Click(object? sender, RoutedEventArgs e)
+    {
+        BasicComboBox.SelectedIndex = -1;
+    }
+
+    private void ComboBoxSelectFirst_Click(object? sender, RoutedEventArgs e)
+    {
+        BasicComboBox.SelectedIndex = 0;
+    }
+}

+ 17 - 0
samples/IntegrationTestApp/Pages/ContextMenuPage.axaml

@@ -0,0 +1,17 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.ContextMenuPage">
+  <StackPanel>
+    <Button Name="ShowContextMenu" Content="Right-click to show context menu.">
+      <Button.ContextMenu>
+        <ContextMenu>
+          <MenuItem Name="ContextMenuItem1" Header="Item 1"/>
+          <MenuItem Name="ContextMenuItem2" Header="Item 2"/>
+        </ContextMenu>
+      </Button.ContextMenu>
+    </Button>
+  </StackPanel>
+</UserControl>

+ 11 - 0
samples/IntegrationTestApp/Pages/ContextMenuPage.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class ContextMenuPage : UserControl
+{
+    public ContextMenuPage()
+    {
+        InitializeComponent();
+    }
+}

+ 14 - 0
samples/IntegrationTestApp/Pages/DesktopPage.axaml

@@ -0,0 +1,14 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.DesktopPage">
+  <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"
+            Click="ToggleTrayIconVisible_Click"/>
+  </StackPanel>
+</UserControl>

+ 19 - 0
samples/IntegrationTestApp/Pages/DesktopPage.axaml.cs

@@ -0,0 +1,19 @@
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class DesktopPage : UserControl
+{
+    public DesktopPage()
+    {
+        InitializeComponent();
+    }
+
+    private void ToggleTrayIconVisible_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+    {
+        var icon = TrayIcon.GetIcons(Application.Current!)!.FirstOrDefault()!;
+        icon.IsVisible = !icon.IsVisible;
+    }
+}

+ 29 - 0
samples/IntegrationTestApp/Pages/GesturesPage.axaml

@@ -0,0 +1,29 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.GesturesPage">
+  <DockPanel>
+    <DockPanel DockPanel.Dock="Top">
+      <Button Name="ResetGestures"
+              DockPanel.Dock="Right"
+              Click="ResetGestures_Click">
+        Reset
+      </Button>
+      <TextBlock Name="LastGesture" />
+    </DockPanel>
+    <Panel>
+      <Border Name="GestureBorder" Background="Blue"
+              AutomationProperties.AccessibilityView="Content"
+              AutomationProperties.ControlTypeOverride="Image"
+              Tapped="GestureBorder_Tapped"
+              DoubleTapped="GestureBorder_DoubleTapped"
+              Gestures.RightTapped="GestureBorder_RightTapped"/>
+      <Border Name="GestureBorder2" Background="Green" IsVisible="False"
+              AutomationProperties.AccessibilityView="Content"
+              AutomationProperties.ControlTypeOverride="Image"
+              DoubleTapped="GestureBorder2_DoubleTapped"/>
+    </Panel>
+  </DockPanel>
+</UserControl>

+ 44 - 0
samples/IntegrationTestApp/Pages/GesturesPage.axaml.cs

@@ -0,0 +1,44 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class GesturesPage : UserControl
+{
+    public GesturesPage()
+    {
+        InitializeComponent();
+    }
+
+    private void GestureBorder_Tapped(object? sender, TappedEventArgs e)
+    {
+        LastGesture.Text = "Tapped";
+    }
+
+    private void GestureBorder_DoubleTapped(object? sender, TappedEventArgs e)
+    {
+        LastGesture.Text = "DoubleTapped";
+
+        // Testing #8733
+        GestureBorder.IsVisible = false;
+        GestureBorder2.IsVisible = true;
+    }
+
+    private void GestureBorder_RightTapped(object? sender, RoutedEventArgs e)
+    {
+        LastGesture.Text = "RightTapped";
+    }
+
+    private void GestureBorder2_DoubleTapped(object? sender, TappedEventArgs e)
+    {
+        LastGesture.Text = "DoubleTapped2";
+    }
+
+    private void ResetGestures_Click(object? sender, RoutedEventArgs e)
+    {
+        LastGesture.Text = string.Empty;
+        GestureBorder.IsVisible = true;
+        GestureBorder2.IsVisible = false;
+    }
+}

+ 15 - 0
samples/IntegrationTestApp/Pages/ListBoxPage.axaml

@@ -0,0 +1,15 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:pages="using:IntegrationTestApp.Pages"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.ListBoxPage"
+             x:DataType="pages:ListBoxPage">
+  <DockPanel>
+    <StackPanel DockPanel.Dock="Bottom">
+      <Button Name="ListBoxSelectionClear" Click="ListBoxSelectionClear_Click">Clear Selection</Button>
+    </StackPanel>
+    <ListBox Name="BasicListBox" ItemsSource="{Binding ListBoxItems}" SelectionMode="Multiple"/>
+  </DockPanel>
+</UserControl>

+ 23 - 0
samples/IntegrationTestApp/Pages/ListBoxPage.axaml.cs

@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class ListBoxPage : UserControl
+{
+    public ListBoxPage()
+    {
+        InitializeComponent();
+        ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList();
+        DataContext = this;
+    }
+
+    public List<string> ListBoxItems { get; }
+
+    private void ListBoxSelectionClear_Click(object? sender, RoutedEventArgs e)
+    {
+        BasicListBox.SelectedIndex = -1;
+    }
+}

+ 22 - 0
samples/IntegrationTestApp/Pages/MenuPage.axaml

@@ -0,0 +1,22 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.MenuPage">
+  <DockPanel>
+    <Menu DockPanel.Dock="Top">
+      <MenuItem Name="RootMenuItem" Header="_Root">
+        <MenuItem Name="Child1MenuItem" Header="_Child 1" InputGesture="Ctrl+O" Click="MenuClicked"/>
+        <MenuItem Name="Child2MenuItem" Header="C_hild 2">
+          <MenuItem Name="GrandchildMenuItem" Header="_Grandchild" Click="MenuClicked"/>
+        </MenuItem>
+      </MenuItem>
+    </Menu>
+    <StackPanel>
+      <TextBlock Name="ClickedMenuItem">None</TextBlock>
+      <Button Name="MenuClickedMenuItemReset" Click="MenuClickedMenuItemReset_Click">Reset</Button>
+      <TextBox Name="MenuFocusTest"/>
+    </StackPanel>
+  </DockPanel>
+</UserControl>

+ 24 - 0
samples/IntegrationTestApp/Pages/MenuPage.axaml.cs

@@ -0,0 +1,24 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class MenuPage : UserControl
+{
+    public MenuPage()
+    {
+        InitializeComponent();
+    }
+
+    private void MenuClicked(object? sender, RoutedEventArgs e)
+    {
+        var clickedMenuItemTextBlock = ClickedMenuItem;
+        clickedMenuItemTextBlock.Text = (sender as MenuItem)?.Header?.ToString();
+    }
+
+
+    private void MenuClickedMenuItemReset_Click(object? sender, RoutedEventArgs e)
+    {
+        ClickedMenuItem.Text = "None";
+    }
+}

+ 19 - 0
samples/IntegrationTestApp/Pages/PointerPage.axaml

@@ -0,0 +1,19 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.PointerPage">
+  <StackPanel>
+    <!-- Trigger with PointerPressed rather using a Button so we have access to the pointer. -->
+    <Border Name="PointerPageShowDialog"
+            Background="{DynamicResource ButtonBackground}"
+            HorizontalAlignment="Left"
+            Padding="{DynamicResource ButtonPadding}"
+            AutomationProperties.AccessibilityView="Control"
+            PointerPressed="PointerPageShowDialog_PointerPressed">
+      <TextBlock>Show Dialog</TextBlock>
+    </Border>
+    <TextBlock Name="PointerCaptureStatus"/>
+  </StackPanel>
+</UserControl>

+ 47 - 0
samples/IntegrationTestApp/Pages/PointerPage.axaml.cs

@@ -0,0 +1,47 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class PointerPage : UserControl
+{
+    public PointerPage()
+    {
+        InitializeComponent();
+    }
+
+    private void PointerPageShowDialog_PointerPressed(object? sender, PointerPressedEventArgs e)
+    {
+        void CaptureLost(object? sender, PointerCaptureLostEventArgs e)
+        {
+            PointerCaptureStatus.Text = "None";
+            ((Control)sender!).PointerCaptureLost -= CaptureLost;
+        }
+
+        var window = TopLevel.GetTopLevel(this) as Window ??
+            throw new AvaloniaInternalException("PointerPage is not attached to a Window.");
+        var captured = e.Pointer.Captured as Control;
+
+        if (captured is not null)
+        {
+            captured.PointerCaptureLost += CaptureLost;
+        }
+
+        PointerCaptureStatus.Text = captured?.ToString() ?? "None";
+
+        var dialog = new Window
+        {
+            Width = 200,
+            Height = 200,
+        };
+
+        dialog.Content = new Button
+        {
+            Content = "Close",
+            Command = new DelegateCommand(() => dialog.Close()),
+        };
+
+        dialog.ShowDialog(window);
+    }
+}

+ 14 - 0
samples/IntegrationTestApp/Pages/RadioButtonPage.axaml

@@ -0,0 +1,14 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.RadioButtonPage">
+  <StackPanel Orientation="Vertical">
+    <RadioButton Name="BasicRadioButton">Sample RadioButton</RadioButton>
+    <StackPanel Orientation="Vertical">
+      <RadioButton Name="ThreeStatesRadioButton1" IsChecked="True" IsThreeState="True">Three States: Option 1</RadioButton>
+      <RadioButton Name="ThreeStatesRadioButton2" IsChecked="False" IsThreeState="True">Three States: Option 2</RadioButton>
+    </StackPanel>
+  </StackPanel>
+</UserControl>

+ 11 - 0
samples/IntegrationTestApp/Pages/RadioButtonPage.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class RadioButtonPage : UserControl
+{
+    public RadioButtonPage()
+    {
+        InitializeComponent();
+    }
+}

+ 19 - 0
samples/IntegrationTestApp/Pages/ScreensPage.axaml

@@ -0,0 +1,19 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.ScreensPage">
+  <StackPanel Spacing="4">
+    <Button Name="ScreenRefresh"
+            Content="Refresh"
+            Click="ScreenRefresh_Click"/>
+    <TextBox Name="ScreenName" Watermark="DisplayName" UseFloatingWatermark="true" />
+    <TextBox Name="ScreenHandle" Watermark="Handle" UseFloatingWatermark="true" />
+    <TextBox Name="ScreenScaling" Watermark="Scaling" UseFloatingWatermark="true" />
+    <TextBox Name="ScreenBounds" Watermark="Bounds" UseFloatingWatermark="true" />
+    <TextBox Name="ScreenWorkArea" Watermark="WorkArea" UseFloatingWatermark="true" />
+    <TextBox Name="ScreenOrientation" Watermark="Orientation" UseFloatingWatermark="true" />
+    <TextBox Name="ScreenSameReference" Watermark="Is same reference" UseFloatingWatermark="true" />
+  </StackPanel>
+</UserControl>

+ 32 - 0
samples/IntegrationTestApp/Pages/ScreensPage.axaml.cs

@@ -0,0 +1,32 @@
+using System.Globalization;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Platform;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class ScreensPage : UserControl
+{
+    private Screen? _lastScreen;
+
+    public ScreensPage()
+    {
+        InitializeComponent();
+    }
+
+    private void ScreenRefresh_Click(object? sender, RoutedEventArgs e)
+    {
+        var window = TopLevel.GetTopLevel(this) as Window ??
+            throw new AvaloniaInternalException("ScreensPage is not attached to a Window.");
+        var lastScreen = _lastScreen;
+        var screen = _lastScreen = window.Screens.ScreenFromWindow(window);
+        ScreenName.Text = screen?.DisplayName;
+        ScreenHandle.Text = screen?.TryGetPlatformHandle()?.ToString();
+        ScreenBounds.Text = screen?.Bounds.ToString();
+        ScreenWorkArea.Text = screen?.WorkingArea.ToString();
+        ScreenScaling.Text = screen?.Scaling.ToString(CultureInfo.InvariantCulture);
+        ScreenOrientation.Text = screen?.CurrentOrientation.ToString();
+        ScreenSameReference.Text = ReferenceEquals(lastScreen, screen).ToString();
+    }
+}

+ 8 - 0
samples/IntegrationTestApp/Pages/ScrollBarPage.axaml

@@ -0,0 +1,8 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.ScrollBarPage">
+  <ScrollBar Name="MyScrollBar" Orientation="Horizontal" AllowAutoHide="False" Width="200" Height="30" Value="20"/>
+</UserControl>

+ 11 - 0
samples/IntegrationTestApp/Pages/ScrollBarPage.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class ScrollBarPage : UserControl
+{
+    public ScrollBarPage()
+    {
+        InitializeComponent();
+    }
+}

+ 17 - 0
samples/IntegrationTestApp/Pages/SliderPage.axaml

@@ -0,0 +1,17 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.SliderPage">
+  <DockPanel>
+    <DockPanel DockPanel.Dock="Top">
+      <TextBox Name="HorizontalSliderValue"
+               DockPanel.Dock="Right"
+               Text="{Binding #HorizontalSlider.Value, Mode=OneWay, StringFormat=\{0:0\}}"
+               VerticalAlignment="Top"/>
+      <Slider Name="HorizontalSlider" Value="50"/>
+    </DockPanel>
+    <Button Name="ResetSliders" Click="ResetSliders_Click">Reset</Button>
+  </DockPanel>
+</UserControl>

+ 17 - 0
samples/IntegrationTestApp/Pages/SliderPage.axaml.cs

@@ -0,0 +1,17 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class SliderPage : UserControl
+{
+    public SliderPage()
+    {
+        InitializeComponent();
+    }
+
+    private void ResetSliders_Click(object? sender, RoutedEventArgs e)
+    {
+        HorizontalSlider.Value = 50;
+    }
+}

+ 21 - 0
samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml

@@ -0,0 +1,21 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.WindowDecorationsPage">
+  <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"
+            Click="ApplyWindowDecorations_Click"/>
+    <Button Name="ShowNewWindowDecorations"
+            Content="Show new Window with decorations"
+            Click="ShowNewWindowDecorations_Click"/>
+    <TextBox Name="WindowDecorationProperties" AcceptsReturn="True" />
+  </StackPanel>
+</UserControl>

+ 63 - 0
samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml.cs

@@ -0,0 +1,63 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Platform;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class WindowDecorationsPage : UserControl
+{
+    public WindowDecorationsPage()
+    {
+        InitializeComponent();
+    }
+
+    private void SetWindowDecorations(Window window)
+    {
+        window.ExtendClientAreaToDecorationsHint = WindowExtendClientAreaToDecorationsHint.IsChecked!.Value;
+        window.ExtendClientAreaTitleBarHeightHint =
+            int.TryParse(WindowTitleBarHeightHint.Text, out var val) ? val / window.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 == Window.OffScreenMarginProperty || e.Property == Window.WindowDecorationMarginProperty)
+            {
+                AdjustOffsets(window);
+            }
+        }
+
+        void AdjustOffsets(Window window)
+        {
+            var scaling = window.DesktopScaling;
+
+            window.Padding = window.OffScreenMargin;
+            ((Control)window.Content!).Margin = window.WindowDecorationMargin;
+
+            WindowDecorationProperties.Text =
+                $"{window.OffScreenMargin.Top * scaling} {window.WindowDecorationMargin.Top * scaling} {window.IsExtendedIntoWindowDecorations}";
+        }
+    }
+
+    private void ApplyWindowDecorations_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+    {
+        var window = TopLevel.GetTopLevel(this) as Window ??
+            throw new AvaloniaInternalException("WindowDecorationsPage is not attached to a Window.");
+        SetWindowDecorations(window);
+    }
+
+    private void ShowNewWindowDecorations_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+    {
+        var window = new ShowWindowTest();
+        SetWindowDecorations(window);
+        window.Show();
+    }
+}

+ 45 - 0
samples/IntegrationTestApp/Pages/WindowPage.axaml

@@ -0,0 +1,45 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="IntegrationTestApp.Pages.WindowPage">
+  <Grid ColumnDefinitions="*,8,*">
+    <StackPanel Grid.Column="0">
+      <TextBox Name="ShowWindowSize" Watermark="Window Size"/>
+      <ComboBox Name="ShowWindowMode" SelectedIndex="0">
+        <ComboBoxItem>NonOwned</ComboBoxItem>
+        <ComboBoxItem>Owned</ComboBoxItem>
+        <ComboBoxItem>Modal</ComboBoxItem>
+      </ComboBox>
+      <ComboBox Name="ShowWindowLocation" SelectedIndex="0">
+        <ComboBoxItem>Manual</ComboBoxItem>
+        <ComboBoxItem>CenterScreen</ComboBoxItem>
+        <ComboBoxItem>CenterOwner</ComboBoxItem>
+      </ComboBox>
+      <ComboBox Name="ShowWindowState" SelectedIndex="0">
+        <ComboBoxItem Name="ShowWindowStateNormal">Normal</ComboBoxItem>
+        <ComboBoxItem Name="ShowWindowStateMinimized">Minimized</ComboBoxItem>
+        <ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem>
+        <ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem>
+      </ComboBox>
+      <ComboBox Name="ShowWindowSystemDecorations" SelectedIndex="2">
+        <ComboBoxItem Name="ShowWindowSystemDecorationsNone">None</ComboBoxItem>
+        <ComboBoxItem Name="ShowWindowSystemDecorationsBorderOnly">BorderOnly</ComboBoxItem>
+        <ComboBoxItem Name="ShowWindowSystemDecorationsFull">Full</ComboBoxItem>
+      </ComboBox>
+      <CheckBox Name="ShowWindowExtendClientAreaToDecorationsHint">ExtendClientAreaToDecorationsHint</CheckBox>
+      <CheckBox Name="ShowWindowCanResize" IsChecked="True">Can Resize</CheckBox>
+      <Button Name="ShowWindow" Click="ShowWindow_Click">Show Window</Button>
+      <Button Name="SendToBack" Click="SendToBack_Click">Send to Back</Button>
+      <Button Name="EnterFullscreen" Click="EnterFullscreen_Click">Enter Fullscreen</Button>
+      <Button Name="ExitFullscreen" Click="ExitFullscreen_Click">Exit Fullscreen</Button>
+      <Button Name="RestoreAll" Click="RestoreAll_Click">Restore All</Button>
+      <Button Name="ShowTopmostWindow" Click="ShowTopmostWindow_Click">Show Topmost Window</Button>
+    </StackPanel>
+    <StackPanel Grid.Column="2">
+      <Button Name="ShowTransparentWindow" Click="ShowTransparentWindow_Click">Transparent Window</Button>
+      <Button Name="ShowTransparentPopup" Click="ShowTransparentPopup_Click">Transparent Popup</Button>
+    </StackPanel>
+  </Grid>
+</UserControl>

+ 199 - 0
samples/IntegrationTestApp/Pages/WindowPage.axaml.cs

@@ -0,0 +1,199 @@
+using System.Linq;
+using Avalonia;
+using Avalonia.Automation;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Primitives.PopupPositioning;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class WindowPage : UserControl
+{
+    public WindowPage()
+    {
+        InitializeComponent();
+    }
+
+    private Window Window => TopLevel.GetTopLevel(this) as Window ??
+        throw new AvaloniaInternalException("WindowPage is not attached to a Window.");
+
+    private void ShowWindow_Click(object? sender, RoutedEventArgs e)
+    {
+        var size = !string.IsNullOrWhiteSpace(ShowWindowSize.Text) ? Size.Parse(ShowWindowSize.Text) : (Size?)null;
+        var window = new ShowWindowTest
+        {
+            WindowStartupLocation = (WindowStartupLocation)ShowWindowLocation.SelectedIndex,
+            CanResize = ShowWindowCanResize.IsChecked ?? false,
+        };
+
+        if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
+        {
+            // Make sure the windows have unique names and AutomationIds.
+            var existing = lifetime.Windows.OfType<ShowWindowTest>().Count();
+            if (existing > 0)
+            {
+                AutomationProperties.SetAutomationId(window, window.Name + (existing + 1));
+                window.Title += $" {existing + 1}";
+            }
+        }
+
+        if (size.HasValue)
+        {
+            window.Width = size.Value.Width;
+            window.Height = size.Value.Height;
+        }
+
+        ShowWindowSize.Text = string.Empty;
+        window.ExtendClientAreaToDecorationsHint = ShowWindowExtendClientAreaToDecorationsHint.IsChecked ?? false;
+        window.SystemDecorations = (SystemDecorations)ShowWindowSystemDecorations.SelectedIndex;
+        window.WindowState = (WindowState)ShowWindowState.SelectedIndex;
+
+        switch (ShowWindowMode.SelectedIndex)
+        {
+            case 0:
+                window.Show();
+                break;
+            case 1:
+                window.Show(Window);
+                break;
+            case 2:
+                window.ShowDialog(Window);
+                break;
+        }
+    }
+
+    private void ShowTransparentWindow_Click(object? sender, RoutedEventArgs e)
+    {
+        // Show a background window to make sure the color behind the transparent window is
+        // a known color (green).
+        var backgroundWindow = new Window
+        {
+            Title = "Transparent Window Background",
+            Name = "TransparentWindowBackground",
+            Width = 300,
+            Height = 300,
+            Background = Brushes.Green,
+            WindowStartupLocation = WindowStartupLocation.CenterOwner,
+        };
+
+        // This is the transparent window with a red circle.
+        var window = new Window
+        {
+            Title = "Transparent Window",
+            Name = "TransparentWindow",
+            SystemDecorations = SystemDecorations.None,
+            Background = Brushes.Transparent,
+            TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent },
+            WindowStartupLocation = WindowStartupLocation.CenterOwner,
+            Width = 200,
+            Height = 200,
+            Content = new Border
+            {
+                Background = Brushes.Red,
+                CornerRadius = new CornerRadius(100),
+            }
+        };
+
+        window.PointerPressed += (_, _) =>
+        {
+            window.Close();
+            backgroundWindow.Close();
+        };
+
+        backgroundWindow.Show(Window);
+        window.Show(backgroundWindow);
+    }
+
+    private void ShowTransparentPopup_Click(object? sender, RoutedEventArgs e)
+    {
+        var popup = new Popup
+        {
+            WindowManagerAddShadowHint = false,
+            Placement = PlacementMode.AnchorAndGravity,
+            PlacementAnchor = PopupAnchor.Top,
+            PlacementGravity = PopupGravity.Bottom,
+            Width = 200,
+            Height = 200,
+            Child = new Border
+            {
+                Background = Brushes.Red,
+                CornerRadius = new CornerRadius(100),
+            }
+        };
+
+        // Show a background window to make sure the color behind the transparent window is
+        // a known color (green).
+        var backgroundWindow = new Window
+        {
+            Title = "Transparent Popup Background",
+            Name = "TransparentPopupBackground",
+            Width = 200,
+            Height = 200,
+            Background = Brushes.Green,
+            WindowStartupLocation = WindowStartupLocation.CenterOwner,
+            Content = new Border
+            {
+                Name = "PopupContainer",
+                Child = popup,
+                [AutomationProperties.AccessibilityViewProperty] = AccessibilityView.Content,
+            }
+        };
+
+        backgroundWindow.PointerPressed += (_, _) => backgroundWindow.Close();
+        backgroundWindow.Show(Window);
+
+        popup.Open();
+    }
+
+    private void SendToBack_Click(object? sender, RoutedEventArgs e)
+    {
+        var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
+
+        foreach (var window in lifetime.Windows.ToArray())
+        {
+            window.Activate();
+        }
+    }
+
+    private void EnterFullscreen_Click(object? sender, RoutedEventArgs e)
+    {
+        Window.WindowState = WindowState.FullScreen;
+    }
+
+    private void ExitFullscreen_Click(object? sender, RoutedEventArgs e)
+    {
+        Window.WindowState = WindowState.Normal;
+    }
+
+    private void RestoreAll_Click(object? sender, RoutedEventArgs e)
+    {
+        var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
+
+        foreach (var window in lifetime.Windows.ToArray())
+        {
+            window.Show();
+            if (window.WindowState == WindowState.Minimized)
+                window.WindowState = WindowState.Normal;
+        }
+    }
+
+    private void ShowTopmostWindow_Click(object? sender, RoutedEventArgs e)
+    {
+        var mainWindow = new TopmostWindowTest("OwnerWindow") 
+        { 
+            Topmost = true,
+            Title = "Owner Window" 
+        };
+        var ownedWindow = new TopmostWindowTest("OwnedWindow")
+        { 
+            WindowStartupLocation = WindowStartupLocation.CenterOwner,
+            Title = "Owned Window" 
+        };
+
+        mainWindow.Show();
+        ownedWindow.Show(mainWindow);
+    }
+}

+ 0 - 1
samples/IntegrationTestApp/ShowWindowTest.axaml.cs

@@ -2,7 +2,6 @@ using System;
 using System.Runtime.InteropServices;
 using Avalonia;
 using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
 using Avalonia.Threading;
 
 namespace IntegrationTestApp

+ 0 - 1
samples/IntegrationTestApp/TopmostWindowTest.axaml.cs

@@ -1,7 +1,6 @@
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Interactivity;
-using Avalonia.Markup.Xaml;
 
 namespace IntegrationTestApp;
 

+ 23 - 0
samples/IntegrationTestApp/ViewModels/MainWindowViewModel.cs

@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using IntegrationTestApp.Models;
+
+namespace IntegrationTestApp.ViewModels;
+
+internal class MainWindowViewModel : ViewModelBase
+{
+    private Page? _selectedPage;
+
+    public MainWindowViewModel(IEnumerable<Page> pages)
+    {
+        Pages = new(pages);
+    }
+
+    public ObservableCollection<Page> Pages { get; }
+    
+    public Page? SelectedPage
+    { 
+        get => _selectedPage;
+        set => RaiseAndSetIfChanged(ref _selectedPage, value);
+    }
+}

+ 24 - 0
samples/IntegrationTestApp/ViewModels/ViewModelBase.cs

@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace IntegrationTestApp.ViewModels;
+
+internal class ViewModelBase : INotifyPropertyChanged
+{
+    public event PropertyChangedEventHandler? PropertyChanged;
+
+    protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
+    {
+        if (!EqualityComparer<T>.Default.Equals(field, value))
+        {
+            field = value;
+            RaisePropertyChanged(propertyName);
+            return true;
+        }
+        return false;
+    }
+
+    protected void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
+        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+}

+ 2 - 0
src/Avalonia.Controls.DataGrid/DataGridCell.cs

@@ -3,6 +3,7 @@
 // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 // All other rights reserved.
 
+using Avalonia.Automation;
 using Avalonia.Automation.Peers;
 using Avalonia.Controls.Automation.Peers;
 using Avalonia.Controls.Metadata;
@@ -37,6 +38,7 @@ namespace Avalonia.Controls
                 (x,e) => x.DataGridCell_PointerPressed(e), handledEventsToo: true);
             FocusableProperty.OverrideDefaultValue<DataGridCell>(true);
             IsTabStopProperty.OverrideDefaultValue<DataGridCell>(false);
+            AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue<DataGridCell>(IsOffscreenBehavior.FromClip);
         }
         public DataGridCell()
         { }

+ 2 - 0
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@@ -6,6 +6,7 @@
 using System;
 using System.ComponentModel;
 using System.Diagnostics;
+using Avalonia.Automation;
 using Avalonia.Automation.Peers;
 using Avalonia.Collections;
 using Avalonia.Controls.Automation.Peers;
@@ -74,6 +75,7 @@ namespace Avalonia.Controls
             AreSeparatorsVisibleProperty.Changed.AddClassHandler<DataGridColumnHeader>((x, e) => x.OnAreSeparatorsVisibleChanged(e));
             PressedMixin.Attach<DataGridColumnHeader>();
             IsTabStopProperty.OverrideDefaultValue<DataGridColumnHeader>(false);
+            AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue<DataGridColumnHeader>(IsOffscreenBehavior.FromClip);
         }
 
         /// <summary>

+ 2 - 0
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@@ -17,6 +17,7 @@ using System;
 using System.Diagnostics;
 using Avalonia.Automation.Peers;
 using Avalonia.Reactive;
+using Avalonia.Automation;
 
 namespace Avalonia.Controls
 {
@@ -143,6 +144,7 @@ namespace Avalonia.Controls
             AreDetailsVisibleProperty.Changed.AddClassHandler<DataGridRow>((x, e) => x.OnAreDetailsVisibleChanged(e));
             PointerPressedEvent.AddClassHandler<DataGridRow>((x, e) => x.DataGridRow_PointerPressed(e), handledEventsToo: true);
             IsTabStopProperty.OverrideDefaultValue<DataGridRow>(false);
+            AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue<DataGridRow>(IsOffscreenBehavior.FromClip);
         }
 
         /// <summary>

+ 6 - 0
src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs

@@ -3,6 +3,7 @@
 // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 // All other rights reserved.
 
+using Avalonia.Automation;
 using Avalonia.Controls.Metadata;
 using Avalonia.Input;
 using Avalonia.Media;
@@ -50,6 +51,11 @@ namespace Avalonia.Controls.Primitives
             AddHandler(PointerPressedEvent, DataGridRowHeader_PointerPressed, handledEventsToo: true);
         }
 
+        static DataGridRowHeader()
+        {
+            AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue<DataGridRowHeader>(IsOffscreenBehavior.FromClip);
+        }
+
         internal Control Owner
         {
             get;

+ 12 - 0
src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs

@@ -153,6 +153,17 @@ namespace Avalonia.Automation.Peers
         /// <returns></returns>
         public bool IsKeyboardFocusable() => IsKeyboardFocusableCore();
 
+        /// <summary>
+        /// Gets a value that indicates whether an element is off the screen.
+        /// </summary>
+        /// <remarks>
+        /// This property does not indicate whether the element is visible. In some circumstances,
+        /// an element is on the screen but is still not visible. For example, if the element is
+        /// on the screen but obscured by other elements, it might not be visible. In this case,
+        /// the method returns false.
+        /// </remarks>
+        public bool IsOffscreen() => IsOffscreenCore();
+
         /// <summary>
         /// Sets the keyboard focus on the element that is associated with this automation peer.
         /// </summary>
@@ -245,6 +256,7 @@ namespace Avalonia.Automation.Peers
         protected abstract bool IsControlElementCore();
         protected abstract bool IsEnabledCore();
         protected abstract bool IsKeyboardFocusableCore();
+        protected virtual bool IsOffscreenCore() => false;
         protected abstract void SetFocusCore();
         protected abstract bool ShowContextMenuCore();
 

+ 14 - 0
src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using Avalonia.Controls;
+using Avalonia.Utilities;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Automation.Peers
@@ -201,6 +202,19 @@ namespace Avalonia.Automation.Peers
             return view == AccessibilityView.Default ? IsControlElementCore() : view >= AccessibilityView.Control;
         }
 
+        protected override bool IsOffscreenCore()
+        {
+            return AutomationProperties.GetIsOffscreenBehavior(Owner) switch
+            {
+                IsOffscreenBehavior.Onscreen => false,
+                IsOffscreenBehavior.Offscreen => true,
+                IsOffscreenBehavior.FromClip => Owner.GetTransformedBounds() is not { } bounds ||
+                    MathUtilities.IsZero(bounds.Clip.Width) ||
+                    MathUtilities.IsZero(bounds.Clip.Height),
+                _ => !Owner.IsVisible,
+            };
+        }
+
         private static Rect GetBounds(Control control)
         {
             var root = control.GetVisualRoot();

+ 2 - 0
src/Avalonia.Controls/ListBoxItem.cs

@@ -1,3 +1,4 @@
+using Avalonia.Automation;
 using Avalonia.Automation.Peers;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Mixins;
@@ -30,6 +31,7 @@ namespace Avalonia.Controls
             SelectableMixin.Attach<ListBoxItem>(IsSelectedProperty);
             PressedMixin.Attach<ListBoxItem>();
             FocusableProperty.OverrideDefaultValue<ListBoxItem>(true);
+            AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue<ListBoxItem>(IsOffscreenBehavior.FromClip);
         }
 
         /// <summary>

+ 2 - 0
src/Avalonia.Controls/MenuItem.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Windows.Input;
+using Avalonia.Automation;
 using Avalonia.Automation.Peers;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Mixins;
@@ -141,6 +142,7 @@ namespace Avalonia.Controls
             ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
             ClickEvent.AddClassHandler<MenuItem>((x, e) => x.OnClick(e));
             SubmenuOpenedEvent.AddClassHandler<MenuItem>((x, e) => x.OnSubmenuOpened(e));
+            AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue<MenuItem>(IsOffscreenBehavior.FromClip);
         }
 
         public MenuItem()

+ 1 - 0
src/Avalonia.Controls/TabItem.cs

@@ -39,6 +39,7 @@ namespace Avalonia.Controls
             FocusableProperty.OverrideDefaultValue(typeof(TabItem), true);
             DataContextProperty.Changed.AddClassHandler<TabItem>((x, e) => x.UpdateHeader(e));
             AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<TabItem>(AutomationControlType.TabItem);
+            AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue<TabItem>(IsOffscreenBehavior.FromClip);
             AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<TabItem>((tabItem, args) => tabItem.TabItemActivated(args));
         }
 

+ 2 - 0
src/Avalonia.Controls/TreeViewItem.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Linq;
+using Avalonia.Automation;
 using Avalonia.Automation.Peers;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Mixins;
@@ -62,6 +63,7 @@ namespace Avalonia.Controls
             PressedMixin.Attach<TreeViewItem>();
             FocusableProperty.OverrideDefaultValue<TreeViewItem>(true);
             ItemsPanelProperty.OverrideDefaultValue<TreeViewItem>(DefaultPanel);
+            AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue<TreeViewItem>(IsOffscreenBehavior.FromClip);
             RequestBringIntoViewEvent.AddClassHandler<TreeViewItem>((x, e) => x.OnRequestBringIntoView(e));
         }
 

+ 1 - 0
src/Windows/Avalonia.Win32/Automation/AutomationNode.cs

@@ -118,6 +118,7 @@ namespace Avalonia.Win32.Automation
                 UiaPropertyId.IsControlElement => InvokeSync(() => Peer.IsControlElement()),
                 UiaPropertyId.IsEnabled => InvokeSync(() => Peer.IsEnabled()),
                 UiaPropertyId.IsKeyboardFocusable => InvokeSync(() => Peer.IsKeyboardFocusable()),
+                UiaPropertyId.IsOffscreen => InvokeSync(() => Peer.IsOffscreen()),
                 UiaPropertyId.LocalizedControlType => InvokeSync(() => Peer.GetLocalizedControlType()),
                 UiaPropertyId.Name => InvokeSync(() => Peer.GetName()),
                 UiaPropertyId.ProcessId => Process.GetCurrentProcess().Id,

+ 7 - 14
tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs

@@ -1,20 +1,13 @@
-using OpenQA.Selenium.Appium;
-using Xunit;
+using Xunit;
 
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class AutomationTests
+    public class AutomationTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public AutomationTests(DefaultAppFixture fixture)
+            : base(fixture, "Automation")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("Automation");
-            tab.Click();
         }
 
         [Fact]
@@ -22,15 +15,15 @@ namespace Avalonia.IntegrationTests.Appium
         {
             // AutomationID can be specified by the Name or AutomationProperties.AutomationId
             // properties, with the latter taking precedence.
-            var byName = _session.FindElementByAccessibilityId("TextBlockWithName");
-            var byAutomationId = _session.FindElementByAccessibilityId("TextBlockWithNameAndAutomationId");
+            var byName = Session.FindElementByAccessibilityId("TextBlockWithName");
+            var byAutomationId = Session.FindElementByAccessibilityId("TextBlockWithNameAndAutomationId");
         }
 
         [Fact]
         public void LabeledBy()
         {
-            var label = _session.FindElementByAccessibilityId("TextBlockAsLabel");
-            var labeledTextBox = _session.FindElementByAccessibilityId("LabeledByTextBox");
+            var label = Session.FindElementByAccessibilityId("TextBlockAsLabel");
+            var labeledTextBox = Session.FindElementByAccessibilityId("LabeledByTextBox");
 
             Assert.Equal("Label for TextBox", label.Text);
             Assert.Equal("Label for TextBox", labeledTextBox.GetName());

+ 8 - 16
tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs

@@ -1,27 +1,19 @@
-using System.Runtime.InteropServices;
-using OpenQA.Selenium.Appium;
-using Xunit;
+using Xunit;
 
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class ButtonTests
+    public class ButtonTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public ButtonTests(DefaultAppFixture fixture)
+            : base(fixture, "Button")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("Button");
-            tab.Click();
         }
 
         [Fact]
         public void DisabledButton()
         {
-            var button = _session.FindElementByAccessibilityId("DisabledButton");
+            var button = Session.FindElementByAccessibilityId("DisabledButton");
 
             Assert.Equal("Disabled Button", button.Text);
             Assert.False(button.Enabled);
@@ -30,7 +22,7 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void EffectivelyDisabledButton()
         {
-            var button = _session.FindElementByAccessibilityId("EffectivelyDisabledButton");
+            var button = Session.FindElementByAccessibilityId("EffectivelyDisabledButton");
 
             Assert.Equal("Effectively Disabled Button", button.Text);
             Assert.False(button.Enabled);
@@ -39,7 +31,7 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void BasicButton()
         {
-            var button = _session.FindElementByAccessibilityId("BasicButton");
+            var button = Session.FindElementByAccessibilityId("BasicButton");
 
             Assert.Equal("Basic Button", button.Text);
             Assert.True(button.Enabled);
@@ -48,7 +40,7 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void ButtonWithTextBlock()
         {
-            var button = _session.FindElementByAccessibilityId("ButtonWithTextBlock");
+            var button = Session.FindElementByAccessibilityId("ButtonWithTextBlock");
 
             Assert.Equal("Button with TextBlock", button.Text);
         }
@@ -56,7 +48,7 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.Windows)]
         public void ButtonWithAcceleratorKey()
         {
-            var button = _session.FindElementByAccessibilityId("ButtonWithAcceleratorKey");
+            var button = Session.FindElementByAccessibilityId("ButtonWithAcceleratorKey");
 
             Assert.Equal("Ctrl+B", button.GetAttribute("AcceleratorKey"));
         }

+ 6 - 13
tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs

@@ -1,26 +1,19 @@
-using OpenQA.Selenium.Appium;
-using Xunit;
+using Xunit;
 
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class CheckBoxTests
+    public class CheckBoxTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public CheckBoxTests(DefaultAppFixture fixture)
+            : base(fixture, "CheckBox")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("CheckBox");
-            tab.Click();
         }
 
         [Fact]
         public void UncheckedCheckBox()
         {
-            var checkBox = _session.FindElementByAccessibilityId("UncheckedCheckBox");
+            var checkBox = Session.FindElementByAccessibilityId("UncheckedCheckBox");
 
             Assert.Equal("Unchecked", checkBox.GetName());
             Assert.Equal(false, checkBox.GetIsChecked());
@@ -32,7 +25,7 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void CheckedCheckBox()
         {
-            var checkBox = _session.FindElementByAccessibilityId("CheckedCheckBox");
+            var checkBox = Session.FindElementByAccessibilityId("CheckedCheckBox");
 
             Assert.Equal("Checked", checkBox.GetName());
             Assert.Equal(true, checkBox.GetIsChecked());
@@ -44,7 +37,7 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void ThreeStateCheckBox()
         {
-            var checkBox = _session.FindElementByAccessibilityId("ThreeStateCheckBox");
+            var checkBox = Session.FindElementByAccessibilityId("ThreeStateCheckBox");
 
             Assert.Equal("ThreeState", checkBox.GetName());
             Assert.Null(checkBox.GetIsChecked());

+ 23 - 30
tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs

@@ -1,32 +1,25 @@
 using OpenQA.Selenium;
-using OpenQA.Selenium.Appium;
 using Xunit;
 
 namespace Avalonia.IntegrationTests.Appium
 {
-    public abstract class ComboBoxTests
+    public abstract class ComboBoxTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public ComboBoxTests(DefaultAppFixture fixture)
+            : base(fixture, "ComboBox")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("ComboBox");
-            tab.Click();
         }
 
         [Fact]
         public void Can_Change_Selection_Using_Mouse()
         {
-            var comboBox = _session.FindElementByAccessibilityId("BasicComboBox");
+            var comboBox = Session.FindElementByAccessibilityId("BasicComboBox");
 
-            _session.FindElementByAccessibilityId("ComboBoxSelectFirst").Click();
+            Session.FindElementByAccessibilityId("ComboBoxSelectFirst").Click();
             Assert.Equal("Item 0", comboBox.GetComboBoxValue());
 
             comboBox.Click();
-            _session.FindElementByName("Item 1").SendClick();
+            Session.FindElementByName("Item 1").SendClick();
 
             Assert.Equal("Item 1", comboBox.GetComboBoxValue());
         }
@@ -34,13 +27,13 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void Can_Change_Selection_From_Unselected_Using_Mouse()
         {
-            var comboBox = _session.FindElementByAccessibilityId("BasicComboBox");
+            var comboBox = Session.FindElementByAccessibilityId("BasicComboBox");
 
-            _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click();
+            Session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click();
             Assert.Equal(string.Empty, comboBox.GetComboBoxValue());
 
             comboBox.Click();
-            _session.FindElementByName("Item 0").SendClick();
+            Session.FindElementByName("Item 0").SendClick();
 
             Assert.Equal("Item 0", comboBox.GetComboBoxValue());
         }
@@ -48,13 +41,13 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.Windows)]
         public void Can_Change_Selection_With_Keyboard_When_Closed()
         {
-            var comboBox = _session.FindElementByAccessibilityId("BasicComboBox");
-            var wrap = _session.FindElementByAccessibilityId("ComboBoxWrapSelection");
+            var comboBox = Session.FindElementByAccessibilityId("BasicComboBox");
+            var wrap = Session.FindElementByAccessibilityId("ComboBoxWrapSelection");
 
             if (wrap.GetIsChecked() != false)
                 wrap.Click();
 
-            _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click();
+            Session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click();
 
             comboBox.SendKeys(Keys.ArrowDown);
             Assert.Equal("Item 0", comboBox.GetComboBoxValue());
@@ -75,13 +68,13 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.Windows)]
         public void Can_Change_Wrapping_Selection_With_Keyboard_When_Closed()
         {
-            var comboBox = _session.FindElementByAccessibilityId("BasicComboBox");
-            var wrap = _session.FindElementByAccessibilityId("ComboBoxWrapSelection");
+            var comboBox = Session.FindElementByAccessibilityId("BasicComboBox");
+            var wrap = Session.FindElementByAccessibilityId("ComboBoxWrapSelection");
 
             if (wrap.GetIsChecked() != true)
                 wrap.Click();
 
-            _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click();
+            Session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click();
 
             comboBox.SendKeys(Keys.ArrowDown);
             Assert.Equal("Item 0", comboBox.GetComboBoxValue());
@@ -105,15 +98,15 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.Windows)]
         public void Can_Change_Selection_When_Open_With_Keyboard()
         {
-            var comboBox = _session.FindElementByAccessibilityId("BasicComboBox");
+            var comboBox = Session.FindElementByAccessibilityId("BasicComboBox");
 
-            _session.FindElementByAccessibilityId("ComboBoxSelectFirst").Click();
+            Session.FindElementByAccessibilityId("ComboBoxSelectFirst").Click();
             Assert.Equal("Item 0", comboBox.GetComboBoxValue());
 
             comboBox.SendKeys(Keys.LeftAlt + Keys.ArrowDown);
             comboBox.SendKeys(Keys.ArrowDown);
 
-            var item = _session.FindElementByName("Item 1");
+            var item = Session.FindElementByName("Item 1");
             item.SendKeys(Keys.Enter);
 
             Assert.Equal("Item 1", comboBox.GetComboBoxValue());
@@ -122,15 +115,15 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.Windows)]
         public void Can_Change_Selection_When_Open_With_Keyboard_From_Unselected()
         {
-            var comboBox = _session.FindElementByAccessibilityId("BasicComboBox");
+            var comboBox = Session.FindElementByAccessibilityId("BasicComboBox");
 
-            _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click();
+            Session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click();
             Assert.Equal(string.Empty, comboBox.GetComboBoxValue());
 
             comboBox.SendKeys(Keys.LeftAlt + Keys.ArrowDown);
             comboBox.SendKeys(Keys.ArrowDown);
 
-            var item = _session.FindElementByName("Item 0");
+            var item = Session.FindElementByName("Item 0");
             item.SendKeys(Keys.Enter);
 
             Assert.Equal("Item 0", comboBox.GetComboBoxValue());
@@ -139,15 +132,15 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.Windows)]
         public void Can_Cancel_Keyboard_Selection_With_Escape()
         {
-            var comboBox = _session.FindElementByAccessibilityId("BasicComboBox");
+            var comboBox = Session.FindElementByAccessibilityId("BasicComboBox");
 
-            _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click();
+            Session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click();
             Assert.Equal(string.Empty, comboBox.GetComboBoxValue());
 
             comboBox.SendKeys(Keys.LeftAlt + Keys.ArrowDown);
             comboBox.SendKeys(Keys.ArrowDown);
 
-            var item = _session.FindElementByName("Item 0");
+            var item = Session.FindElementByName("Item 0");
             item.SendKeys(Keys.Escape);
 
             Assert.Equal(string.Empty, comboBox.GetComboBoxValue());

+ 5 - 11
tests/Avalonia.IntegrationTests.Appium/ContextMenuTests.cs

@@ -6,30 +6,24 @@ using Xunit;
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class ContextMenuTests
+    public class ContextMenuTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public ContextMenuTests(DefaultAppFixture fixture)
+            : base(fixture, "ContextMenu")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("ContextMenu");
-            tab.Click();
         }
         
         [PlatformFact(TestPlatforms.Windows)]
         public void Select_First_Item_With_Down_Arrow_Key()
         {
-            var control = _session.FindElementByAccessibilityId("ShowContextMenu");
+            var control = Session.FindElementByAccessibilityId("ShowContextMenu");
 
-            new Actions(_session)
+            new Actions(Session)
                 .ContextClick(control)
                 .SendKeys(Keys.ArrowDown)
                 .Perform();
 
-            var clickedMenuItem = _session.FindElementByAccessibilityId("ContextMenuItem1");
+            var clickedMenuItem = Session.FindElementByAccessibilityId("ContextMenuItem1");
             Assert.True(clickedMenuItem.GetIsFocused());
         }
     }

+ 37 - 44
tests/Avalonia.IntegrationTests.Appium/GestureTests.cs

@@ -1,34 +1,27 @@
 using System;
 using System.Threading;
-using OpenQA.Selenium.Appium;
 using OpenQA.Selenium.Interactions;
 using Xunit;
 
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class GestureTests
+    public class GestureTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public GestureTests(DefaultAppFixture fixture)
+            : base(fixture, "Gestures")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("Gestures");
-            tab.Click();
-            var clear = _session.FindElementByAccessibilityId("ResetGestures");
+            var clear = Session.FindElementByAccessibilityId("ResetGestures");
             clear.Click();
         }
 
         [Fact]
         public void Tapped_Is_Raised()
         {
-            var border = _session.FindElementByAccessibilityId("GestureBorder");
-            var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+            var border = Session.FindElementByAccessibilityId("GestureBorder");
+            var lastGesture = Session.FindElementByAccessibilityId("LastGesture");
 
-            new Actions(_session).Click(border).Perform();
+            new Actions(Session).Click(border).Perform();
 
             Assert.Equal("Tapped", lastGesture.Text);
         }
@@ -36,14 +29,14 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void Tapped_Is_Raised_Slow()
         {
-            var border = _session.FindElementByAccessibilityId("GestureBorder");
-            var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+            var border = Session.FindElementByAccessibilityId("GestureBorder");
+            var lastGesture = Session.FindElementByAccessibilityId("LastGesture");
 
-            new Actions(_session).ClickAndHold(border).Perform();
+            new Actions(Session).ClickAndHold(border).Perform();
 
             Thread.Sleep(2000);
 
-            new Actions(_session).Release(border).Perform();
+            new Actions(Session).Release(border).Perform();
 
             Assert.Equal("Tapped", lastGesture.Text);
         }
@@ -51,10 +44,10 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void Tapped_Is_Not_Raised_For_Drag()
         {
-            var border = _session.FindElementByAccessibilityId("GestureBorder");
-            var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+            var border = Session.FindElementByAccessibilityId("GestureBorder");
+            var lastGesture = Session.FindElementByAccessibilityId("LastGesture");
 
-            new Actions(_session)
+            new Actions(Session)
                 .ClickAndHold(border)
                 .MoveByOffset(50, 50)
                 .Release()
@@ -66,10 +59,10 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void DoubleTapped_Is_Raised()
         {
-            var border = _session.FindElementByAccessibilityId("GestureBorder");
-            var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+            var border = Session.FindElementByAccessibilityId("GestureBorder");
+            var lastGesture = Session.FindElementByAccessibilityId("LastGesture");
 
-            new Actions(_session).DoubleClick(border).Perform();
+            new Actions(Session).DoubleClick(border).Perform();
 
             Assert.Equal("DoubleTapped", lastGesture.Text);
         }
@@ -77,15 +70,15 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.Windows | TestPlatforms.Linux)]
         public void DoubleTapped_Is_Raised_2()
         {
-            var border = _session.FindElementByAccessibilityId("GestureBorder");
-            var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+            var border = Session.FindElementByAccessibilityId("GestureBorder");
+            var lastGesture = Session.FindElementByAccessibilityId("LastGesture");
 
-            new Actions(_session).ClickAndHold(border).Release().Perform();
+            new Actions(Session).ClickAndHold(border).Release().Perform();
 
             Thread.Sleep(50);
 
             // DoubleTapped is raised on second pointer press, not release.
-            new Actions(_session).ClickAndHold(border).Perform();
+            new Actions(Session).ClickAndHold(border).Perform();
 
             try
             {
@@ -94,21 +87,21 @@ namespace Avalonia.IntegrationTests.Appium
             finally
             {
                 
-                new Actions(_session).MoveToElement(lastGesture).Release().Perform();
+                new Actions(Session).MoveToElement(lastGesture).Release().Perform();
             }
         }
 
         [Fact]
         public void DoubleTapped_Is_Raised_Not_Raised_If_Too_Slow()
         {
-            var border = _session.FindElementByAccessibilityId("GestureBorder");
-            var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+            var border = Session.FindElementByAccessibilityId("GestureBorder");
+            var lastGesture = Session.FindElementByAccessibilityId("LastGesture");
 
-            new Actions(_session).ClickAndHold(border).Release().Perform();
+            new Actions(Session).ClickAndHold(border).Release().Perform();
 
             Thread.Sleep(2000);
 
-            new Actions(_session).ClickAndHold(border).Release().Perform();
+            new Actions(Session).ClickAndHold(border).Release().Perform();
 
             Assert.Equal("Tapped", lastGesture.Text);
         }
@@ -117,17 +110,17 @@ namespace Avalonia.IntegrationTests.Appium
         public void DoubleTapped_Is_Raised_After_Control_Changes()
         {
             // #8733
-            var border = _session.FindElementByAccessibilityId("GestureBorder");
-            var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+            var border = Session.FindElementByAccessibilityId("GestureBorder");
+            var lastGesture = Session.FindElementByAccessibilityId("LastGesture");
             
-            new Actions(_session)
+            new Actions(Session)
                 .MoveToElement(border)
                 .DoubleClick()
                 .Perform();
             
             Thread.Sleep(100);
             
-            new Actions(_session).MoveToElement(lastGesture, 200, 200).DoubleClick().Perform();
+            new Actions(Session).MoveToElement(lastGesture, 200, 200).DoubleClick().Perform();
 
             Assert.Equal("DoubleTapped2", lastGesture.Text);
         }
@@ -135,10 +128,10 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void RightTapped_Is_Raised()
         {
-            var border = _session.FindElementByAccessibilityId("GestureBorder");
-            var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+            var border = Session.FindElementByAccessibilityId("GestureBorder");
+            var lastGesture = Session.FindElementByAccessibilityId("LastGesture");
 
-            new Actions(_session).ContextClick(border).Perform();
+            new Actions(Session).ContextClick(border).Perform();
 
             Assert.Equal("RightTapped", lastGesture.Text);
         }
@@ -146,8 +139,8 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.MacOS)]
         public void RightTapped_Is_Raised_2()
         {
-            var border = _session.FindElementByAccessibilityId("GestureBorder");
-            var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+            var border = Session.FindElementByAccessibilityId("GestureBorder");
+            var lastGesture = Session.FindElementByAccessibilityId("LastGesture");
             var device = new PointerInputDevice(PointerKind.Mouse);
             var b = new ActionBuilder();
 
@@ -155,7 +148,7 @@ namespace Avalonia.IntegrationTests.Appium
             b.AddAction(device.CreatePointerDown(MouseButton.Right));
             b.AddAction(device.CreatePointerMove(border, 52, 52, TimeSpan.FromMilliseconds(50)));
             b.AddAction(device.CreatePointerUp(MouseButton.Right));
-            _session.PerformActions(b.ToActionSequenceList());
+            Session.PerformActions(b.ToActionSequenceList());
 
             Assert.Equal("RightTapped", lastGesture.Text);
         }
@@ -163,8 +156,8 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.MacOS)]
         public void RightTapped_Is_Not_Raised_For_Drag()
         {
-            var border = _session.FindElementByAccessibilityId("GestureBorder");
-            var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+            var border = Session.FindElementByAccessibilityId("GestureBorder");
+            var lastGesture = Session.FindElementByAccessibilityId("LastGesture");
             var device = new PointerInputDevice(PointerKind.Mouse);
             var b = new ActionBuilder();
 

+ 6 - 12
tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs

@@ -7,17 +7,11 @@ using Xunit;
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class ListBoxTests
+    public class ListBoxTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public ListBoxTests(DefaultAppFixture fixture)
+            : base(fixture, "ListBox")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("ListBox");
-            tab.Click();
         }
 
         [Fact]
@@ -49,7 +43,7 @@ namespace Avalonia.IntegrationTests.Appium
             Assert.False(item2.Selected);
             Assert.False(item4.Selected);
             
-            new Actions(_session)
+            new Actions(Session)
                 .Click(item2)
                 .KeyDown(Keys.Control)
                 .Click(item4)
@@ -73,7 +67,7 @@ namespace Avalonia.IntegrationTests.Appium
             Assert.False(item3.Selected);
             Assert.False(item4.Selected);
 
-            new Actions(_session)
+            new Actions(Session)
                 .Click(item2)
                 .KeyDown(Keys.Shift)
                 .Click(item4)
@@ -96,8 +90,8 @@ namespace Avalonia.IntegrationTests.Appium
 
         private AppiumWebElement GetTarget()
         {
-            _session.FindElementByAccessibilityId("ListBoxSelectionClear").Click();
-            return _session.FindElementByAccessibilityId("BasicListBox");
+            Session.FindElementByAccessibilityId("ListBoxSelectionClear").Click();
+            return Session.FindElementByAccessibilityId("BasicListBox");
         }
     }
 }

+ 32 - 41
tests/Avalonia.IntegrationTests.Appium/MenuTests.cs

@@ -1,59 +1,50 @@
-using System.Threading;
-using OpenQA.Selenium;
-using OpenQA.Selenium.Appium;
+using OpenQA.Selenium;
 using OpenQA.Selenium.Interactions;
 using Xunit;
 
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public abstract class MenuTests
+    public abstract class MenuTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public MenuTests(DefaultAppFixture fixture)
+            : base(fixture, "Menu")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("Menu");
-            tab.Click();
-
-            var reset = _session.FindElementByAccessibilityId("MenuClickedMenuItemReset");
+            var reset = Session.FindElementByAccessibilityId("MenuClickedMenuItemReset");
             reset.Click();
 
-            var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem");
+            var clickedMenuItem = Session.FindElementByAccessibilityId("ClickedMenuItem");
             Assert.Equal("None", clickedMenuItem.Text);
         }
 
         [Fact]
         public void Click_Child()
         {
-            var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem");
+            var rootMenuItem = Session.FindElementByAccessibilityId("RootMenuItem");
             
             rootMenuItem.SendClick();
 
-            var childMenuItem = _session.FindElementByAccessibilityId("Child1MenuItem");
+            var childMenuItem = Session.FindElementByAccessibilityId("Child1MenuItem");
             childMenuItem.SendClick();
 
-            var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem");
+            var clickedMenuItem = Session.FindElementByAccessibilityId("ClickedMenuItem");
             Assert.Equal("_Child 1", clickedMenuItem.Text);
         }
 
         [Fact]
         public void Click_Grandchild()
         {
-            var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem");
+            var rootMenuItem = Session.FindElementByAccessibilityId("RootMenuItem");
             
             rootMenuItem.SendClick();
 
-            var childMenuItem = _session.FindElementByAccessibilityId("Child2MenuItem");
+            var childMenuItem = Session.FindElementByAccessibilityId("Child2MenuItem");
             childMenuItem.SendClick();
 
-            var grandchildMenuItem = _session.FindElementByAccessibilityId("GrandchildMenuItem");
+            var grandchildMenuItem = Session.FindElementByAccessibilityId("GrandchildMenuItem");
             grandchildMenuItem.SendClick();
 
-            var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem");
+            var clickedMenuItem = Session.FindElementByAccessibilityId("ClickedMenuItem");
             Assert.Equal("_Grandchild", clickedMenuItem.Text);
         }
 
@@ -62,12 +53,12 @@ namespace Avalonia.IntegrationTests.Appium
         {
             MovePointerOutOfTheWay();
 
-            new Actions(_session)
+            new Actions(Session)
                 .KeyDown(Keys.Alt).KeyUp(Keys.Alt)
                 .SendKeys(Keys.Down + Keys.Enter)
                 .Perform();
 
-            var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem");
+            var clickedMenuItem = Session.FindElementByAccessibilityId("ClickedMenuItem");
             Assert.Equal("_Child 1", clickedMenuItem.Text);
         }
 
@@ -76,12 +67,12 @@ namespace Avalonia.IntegrationTests.Appium
         {
             MovePointerOutOfTheWay();
 
-            new Actions(_session)
+            new Actions(Session)
                 .KeyDown(Keys.Alt).KeyUp(Keys.Alt)
                 .SendKeys(Keys.Down + Keys.Down + Keys.Right + Keys.Enter)
                 .Perform();
 
-            var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem");
+            var clickedMenuItem = Session.FindElementByAccessibilityId("ClickedMenuItem");
             Assert.Equal("_Grandchild", clickedMenuItem.Text);
         }
 
@@ -90,12 +81,12 @@ namespace Avalonia.IntegrationTests.Appium
         {
             MovePointerOutOfTheWay();
 
-            new Actions(_session)
+            new Actions(Session)
                 .KeyDown(Keys.Alt).KeyUp(Keys.Alt)
                 .SendKeys("rc")
                 .Perform();
 
-            var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem");
+            var clickedMenuItem = Session.FindElementByAccessibilityId("ClickedMenuItem");
             Assert.Equal("_Child 1", clickedMenuItem.Text);
         }
 
@@ -104,55 +95,55 @@ namespace Avalonia.IntegrationTests.Appium
         {
             MovePointerOutOfTheWay();
 
-            new Actions(_session)
+            new Actions(Session)
                 .KeyDown(Keys.Alt).KeyUp(Keys.Alt)
                 .SendKeys("rhg")
                 .Perform();
 
-            var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem");
+            var clickedMenuItem = Session.FindElementByAccessibilityId("ClickedMenuItem");
             Assert.Equal("_Grandchild", clickedMenuItem.Text);
         }
 
         [PlatformFact(TestPlatforms.Windows)]
         public void Select_Child_With_Click_Arrow_Keys()
         {
-            var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem");
+            var rootMenuItem = Session.FindElementByAccessibilityId("RootMenuItem");
             rootMenuItem.SendClick();
 
             MovePointerOutOfTheWay();
 
-            new Actions(_session)
+            new Actions(Session)
                 .SendKeys(Keys.Down + Keys.Enter)
                 .Perform();
 
-            var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem");
+            var clickedMenuItem = Session.FindElementByAccessibilityId("ClickedMenuItem");
             Assert.Equal("_Child 1", clickedMenuItem.Text);
         }
 
         [PlatformFact(TestPlatforms.Windows)]
         public void Select_Grandchild_With_Click_Arrow_Keys()
         {
-            var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem");
+            var rootMenuItem = Session.FindElementByAccessibilityId("RootMenuItem");
             rootMenuItem.SendClick();
 
             MovePointerOutOfTheWay();
 
-            new Actions(_session)
+            new Actions(Session)
                 .SendKeys(Keys.Down + Keys.Down + Keys.Right + Keys.Enter)
                 .Perform();
 
-            var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem");
+            var clickedMenuItem = Session.FindElementByAccessibilityId("ClickedMenuItem");
             Assert.Equal("_Grandchild", clickedMenuItem.Text);
         }
 
         [PlatformFact(TestPlatforms.Windows)]
         public void Child_AcceleratorKey()
         {
-            var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem");
+            var rootMenuItem = Session.FindElementByAccessibilityId("RootMenuItem");
 
             rootMenuItem.SendClick();
 
-            var childMenuItem = _session.FindElementByAccessibilityId("Child1MenuItem");
+            var childMenuItem = Session.FindElementByAccessibilityId("Child1MenuItem");
 
             Assert.Equal("Ctrl+O", childMenuItem.GetAttribute("AcceleratorKey"));
         }
@@ -161,12 +152,12 @@ namespace Avalonia.IntegrationTests.Appium
         public void PointerOver_Does_Not_Steal_Focus()
         {
             // Issue #7906
-            var textBox = _session.FindElementByAccessibilityId("MenuFocusTest");
+            var textBox = Session.FindElementByAccessibilityId("MenuFocusTest");
             textBox.Click();
 
             Assert.True(textBox.GetIsFocused());
 
-            var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem");
+            var rootMenuItem = Session.FindElementByAccessibilityId("RootMenuItem");
             rootMenuItem.MovePointerOver();
 
             Assert.True(textBox.GetIsFocused());
@@ -175,9 +166,9 @@ namespace Avalonia.IntegrationTests.Appium
         private void MovePointerOutOfTheWay()
         {
             // Move the pointer to the menu tab item so that it's not over the menu in preparation
-            // for key press tests. This prevents the mouse accidentially selecting the wrong item
+            // for key press tests. This prevents the mouse accidentally selecting the wrong item
             // by hovering.
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
+            var tabs = Session.FindElementByAccessibilityId("Pager");
             var tab = tabs.FindElementByName("Menu");
             tab.MovePointerOver();
         }

+ 11 - 18
tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs

@@ -1,29 +1,22 @@
 using System.Threading;
-using OpenQA.Selenium.Appium;
 using Xunit;
 
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class NativeMenuTests
+    public class NativeMenuTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public NativeMenuTests(DefaultAppFixture fixture)
+            : base(fixture, "Automation")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("Automation");
-            tab.Click();
         }
 
         [PlatformFact(TestPlatforms.MacOS)]
         public void MacOS_View_Menu_Select_Button_Tab()
         {
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
+            var tabs = Session.FindElementByAccessibilityId("Pager");
             var buttonTab = tabs.FindElementByName("Button");
-            var menuBar = _session.FindElementByXPath("/XCUIElementTypeApplication/XCUIElementTypeMenuBar");
+            var menuBar = Session.FindElementByXPath("/XCUIElementTypeApplication/XCUIElementTypeMenuBar");
             var viewMenu = menuBar.FindElementByName("View");
             
             Assert.False(buttonTab.Selected);
@@ -38,9 +31,9 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.Windows)]
         public void Win32_View_Menu_Select_Button_Tab()
         {
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
+            var tabs = Session.FindElementByAccessibilityId("Pager");
             var buttonTab = tabs.FindElementByName("Button");
-            var viewMenu = _session.FindElementByXPath("//MenuItem[@Name='View']");
+            var viewMenu = Session.FindElementByXPath("//MenuItem[@Name='View']");
 
             Assert.False(buttonTab.Selected);
 
@@ -54,7 +47,7 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.MacOS)]
         public void MacOS_Sanitizes_Access_Key_Markers_When_Included_In_Menu_Title()
         {
-            var menuBar = _session.FindElementByXPath("/XCUIElementTypeApplication/XCUIElementTypeMenuBar");
+            var menuBar = Session.FindElementByXPath("/XCUIElementTypeApplication/XCUIElementTypeMenuBar");
 
             Assert.True(menuBar.FindElementsByName("_Options").Count == 0);
             Assert.True(menuBar.FindElementsByName("Options").Count == 1);
@@ -63,7 +56,7 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.Windows)]
         public void Win32_Avalonia_Menu_Has_ToolTip_If_Defined()
         {
-            var viewMenu = _session.FindElementByXPath("//MenuItem[@Name='View']");
+            var viewMenu = Session.FindElementByXPath("//MenuItem[@Name='View']");
             viewMenu.Click();
 
             var buttonMenuItem = viewMenu.FindElementByName("Button");
@@ -72,14 +65,14 @@ namespace Avalonia.IntegrationTests.Appium
             // Wait for tooltip to open.
             Thread.Sleep(2000);
 
-            var toolTipCandidates = _session.FindElementsByClassName("TextBlock");
+            var toolTipCandidates = Session.FindElementsByClassName("TextBlock");
             Assert.Contains(toolTipCandidates, x => x.Text == "Tip:Button");
         }
 
         [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test")]
         public void MacOS_Native_Menu_Has_ToolTip_If_Defined()
         {
-            var menuBar = _session.FindElementByXPath("/XCUIElementTypeApplication/XCUIElementTypeMenuBar");
+            var menuBar = Session.FindElementByXPath("/XCUIElementTypeApplication/XCUIElementTypeMenuBar");
             var viewMenu = menuBar.FindElementByName("View");
             viewMenu.Click();
 
@@ -89,7 +82,7 @@ namespace Avalonia.IntegrationTests.Appium
             // Wait for tooltip to open.
             Thread.Sleep(4000);
 
-            var toolTipCandidates = _session.FindElementsByClassName("XCUIElementTypeStaticText");
+            var toolTipCandidates = Session.FindElementsByClassName("XCUIElementTypeStaticText");
             Assert.Contains(toolTipCandidates, x => x.Text == "Tip:Button");
         }
     }

+ 5 - 12
tests/Avalonia.IntegrationTests.Appium/PointerTests.cs

@@ -1,30 +1,23 @@
-using OpenQA.Selenium.Interactions;
-using Xunit;
+using Xunit;
 
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class PointerTests
+    public class PointerTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public PointerTests(DefaultAppFixture fixture)
+            : base(fixture, "Pointer")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("Pointer");
-            tab.Click();
         }
 
         [Fact]
         public void Pointer_Capture_Is_Released_When_Showing_Dialog()
         {
-            var button = _session.FindElementByAccessibilityId("PointerPageShowDialog");
+            var button = Session.FindElementByAccessibilityId("PointerPageShowDialog");
 
             button.OpenWindowWithClick().Dispose();
 
-            var status = _session.FindElementByAccessibilityId("PointerCaptureStatus");
+            var status = Session.FindElementByAccessibilityId("PointerCaptureStatus");
             Assert.Equal("None", status.Text);
         }
     }

+ 5 - 11
tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs

@@ -4,23 +4,17 @@ using Xunit;
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class RadioButtonTests
+    public class RadioButtonTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public RadioButtonTests(DefaultAppFixture fixture)
+            : base(fixture, "RadioButton")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            tabs.FindElementByName("RadioButton").Click();
         }
 
-
         [Fact]
         public void RadioButton_IsChecked_True_When_Clicked()
         {
-            var button = _session.FindElementByAccessibilityId("BasicRadioButton");
+            var button = Session.FindElementByAccessibilityId("BasicRadioButton");
             Assert.False(button.GetIsChecked());
             button.Click();
             Assert.True(button.GetIsChecked());
@@ -29,8 +23,8 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void ThreeState_RadioButton_IsChecked_False_When_Other_ThreeState_RadioButton_Checked()
         {
-            var button1 = _session.FindElementByAccessibilityId("ThreeStatesRadioButton1");
-            var button2 = _session.FindElementByAccessibilityId("ThreeStatesRadioButton2");
+            var button1 = Session.FindElementByAccessibilityId("ThreeStatesRadioButton1");
+            var button2 = Session.FindElementByAccessibilityId("ThreeStatesRadioButton2");
             Assert.True(button1.GetIsChecked());
             Assert.False(button2.GetIsChecked());
             button2.Click();

+ 15 - 21
tests/Avalonia.IntegrationTests.Appium/ScreenTests.cs

@@ -6,31 +6,25 @@ using Xunit;
 namespace Avalonia.IntegrationTests.Appium;
 
 [Collection("Default")]
-public class ScreenTests
+public class ScreenTests : TestBase
 {
-    private readonly AppiumDriver _session;
-
     public ScreenTests(DefaultAppFixture fixture)
+        : base(fixture, "Screens")
     {
-        _session = fixture.Session;
-
-        var tabs = _session.FindElementByAccessibilityId("MainTabs");
-        var tab = tabs.FindElementByName("Screens");
-        tab.Click();
     }
 
     [Fact]
     public void Can_Read_Current_Screen_Info()
     {
-        var refreshButton = _session.FindElementByAccessibilityId("ScreenRefresh");
+        var refreshButton = Session.FindElementByAccessibilityId("ScreenRefresh");
         refreshButton.SendClick();
 
-        var screenName = _session.FindElementByAccessibilityId("ScreenName").Text;
-        var screenHandle = _session.FindElementByAccessibilityId("ScreenHandle").Text;
-        var screenBounds = Rect.Parse(_session.FindElementByAccessibilityId("ScreenBounds").Text);
-        var screenWorkArea = Rect.Parse(_session.FindElementByAccessibilityId("ScreenWorkArea").Text);
-        var screenScaling = double.Parse(_session.FindElementByAccessibilityId("ScreenScaling").Text, NumberStyles.Float, CultureInfo.InvariantCulture);
-        var screenOrientation = Enum.Parse<ScreenOrientation>(_session.FindElementByAccessibilityId("ScreenOrientation").Text);
+        var screenName = Session.FindElementByAccessibilityId("ScreenName").Text;
+        var screenHandle = Session.FindElementByAccessibilityId("ScreenHandle").Text;
+        var screenBounds = Rect.Parse(Session.FindElementByAccessibilityId("ScreenBounds").Text);
+        var screenWorkArea = Rect.Parse(Session.FindElementByAccessibilityId("ScreenWorkArea").Text);
+        var screenScaling = double.Parse(Session.FindElementByAccessibilityId("ScreenScaling").Text, NumberStyles.Float, CultureInfo.InvariantCulture);
+        var screenOrientation = Enum.Parse<ScreenOrientation>(Session.FindElementByAccessibilityId("ScreenOrientation").Text);
 
         Assert.NotNull(screenName);
         Assert.NotNull(screenHandle);
@@ -45,17 +39,17 @@ public class ScreenTests
     [Fact]
     public void Returns_The_Same_Screen_Instance()
     {
-        var refreshButton = _session.FindElementByAccessibilityId("ScreenRefresh");
+        var refreshButton = Session.FindElementByAccessibilityId("ScreenRefresh");
         refreshButton.SendClick();
 
-        var screenName1 = _session.FindElementByAccessibilityId("ScreenName").Text;
-        var screenHandle1 = _session.FindElementByAccessibilityId("ScreenHandle").Text;
+        var screenName1 = Session.FindElementByAccessibilityId("ScreenName").Text;
+        var screenHandle1 = Session.FindElementByAccessibilityId("ScreenHandle").Text;
 
         refreshButton.SendClick();
 
-        var screenName2 = _session.FindElementByAccessibilityId("ScreenName").Text;
-        var screenHandle2 = _session.FindElementByAccessibilityId("ScreenHandle").Text;
-        var screenSameReference = bool.Parse(_session.FindElementByAccessibilityId("ScreenSameReference").Text);
+        var screenName2 = Session.FindElementByAccessibilityId("ScreenName").Text;
+        var screenHandle2 = Session.FindElementByAccessibilityId("ScreenHandle").Text;
+        var screenSameReference = bool.Parse(Session.FindElementByAccessibilityId("ScreenSameReference").Text);
 
         Assert.Equal(screenName1, screenName2);
         Assert.Equal(screenHandle1, screenHandle2);

+ 3 - 9
tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs

@@ -4,23 +4,17 @@ using Xunit;
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class ScrollBarTests
+    public class ScrollBarTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public ScrollBarTests(DefaultAppFixture fixture)
+            : base(fixture, "ScrollBar")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("ScrollBar");
-            tab.Click();
         }
 
         [Fact]
         public void ScrollBar_Increases_Value_By_LargeChange_When_IncreaseButton_Is_Clicked()
         {
-            var button = _session.FindElementByAccessibilityId("MyScrollBar");
+            var button = Session.FindElementByAccessibilityId("MyScrollBar");
             Assert.True(double.Parse(button.Text) == 20);
 
             button.Click();

+ 15 - 22
tests/Avalonia.IntegrationTests.Appium/SliderTests.cs

@@ -7,34 +7,27 @@ using Xunit;
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class SliderTests
+    public class SliderTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public SliderTests(DefaultAppFixture fixture)
+            : base(fixture, "Slider")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("Slider");
-            tab.Click();
-
-            var reset = _session.FindElementByAccessibilityId("ResetSliders");
+            var reset = Session.FindElementByAccessibilityId("ResetSliders");
             reset.Click();
         }
 
         [Fact(Skip = "Flaky test, slider value is sometimes off by 1 or 2 steps.")]
         public void Horizontal_Changes_Value_Dragging_Thumb_Right()
         {
-            var slider = _session.FindElementByAccessibilityId("HorizontalSlider");
+            var slider = Session.FindElementByAccessibilityId("HorizontalSlider");
             var thumb = slider.FindElementByAccessibilityId("thumb");
             var initialThumbRect = thumb.Rect;
 
-            new Actions(_session).ClickAndHold(thumb).MoveByOffset(100, 0).Release().Perform();
+            new Actions(Session).ClickAndHold(thumb).MoveByOffset(100, 0).Release().Perform();
 
             var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture));
             var boundValue = double.Parse(
-                _session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
+                Session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
                 CultureInfo.InvariantCulture);
 
             Assert.True(value > 50);
@@ -47,15 +40,15 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact(Skip = "Flaky test, slider value is sometimes off by 1 or 2 steps.")]
         public void Horizontal_Changes_Value_Dragging_Thumb_Left()
         {
-            var slider = _session.FindElementByAccessibilityId("HorizontalSlider");
+            var slider = Session.FindElementByAccessibilityId("HorizontalSlider");
             var thumb = slider.FindElementByAccessibilityId("thumb");
             var initialThumbRect = thumb.Rect;
 
-            new Actions(_session).ClickAndHold(thumb).MoveByOffset(-100, 0).Release().Perform();
+            new Actions(Session).ClickAndHold(thumb).MoveByOffset(-100, 0).Release().Perform();
 
             var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture));
             var boundValue = double.Parse(
-                _session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
+                Session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
                 CultureInfo.InvariantCulture);
 
             Assert.True(value < 50);
@@ -68,15 +61,15 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void Horizontal_Changes_Value_When_Clicking_Increase_Button()
         {
-            var slider = _session.FindElementByAccessibilityId("HorizontalSlider");
+            var slider = Session.FindElementByAccessibilityId("HorizontalSlider");
             var thumb = slider.FindElementByAccessibilityId("thumb");
             var initialThumbRect = thumb.Rect;
 
-            new Actions(_session).MoveToElementCenter(slider, 100, 0).Click().Perform();
+            new Actions(Session).MoveToElementCenter(slider, 100, 0).Click().Perform();
 
             var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture));
             var boundValue = double.Parse(
-                _session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
+                Session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
                 CultureInfo.InvariantCulture);
 
             Assert.True(value > 50);
@@ -89,15 +82,15 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void Horizontal_Changes_Value_When_Clicking_Decrease_Button()
         {
-            var slider = _session.FindElementByAccessibilityId("HorizontalSlider");
+            var slider = Session.FindElementByAccessibilityId("HorizontalSlider");
             var thumb = slider.FindElementByAccessibilityId("thumb");
             var initialThumbRect = thumb.Rect;
 
-            new Actions(_session).MoveToElementCenter(slider, -100, 0).Click().Perform();
+            new Actions(Session).MoveToElementCenter(slider, -100, 0).Click().Perform();
 
             var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture));
             var boundValue = double.Parse(
-                _session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
+                Session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
                 CultureInfo.InvariantCulture);
 
             Assert.True(value < 50);

+ 33 - 0
tests/Avalonia.IntegrationTests.Appium/TestBase.cs

@@ -0,0 +1,33 @@
+using OpenQA.Selenium;
+using System.Threading;
+
+namespace Avalonia.IntegrationTests.Appium;
+
+public class TestBase
+{
+    protected TestBase(DefaultAppFixture fixture, string pageName)
+    {
+        Session = fixture.Session;
+
+        var retry = 0;
+
+        for (;;)
+        {
+            try
+            {
+                var pager = Session.FindElementByAccessibilityId("Pager");
+                var page = pager.FindElementByName(pageName);
+                page.Click();
+                break;
+            }
+            catch (WebDriverException) 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);
+            }
+        }
+    }
+
+    protected AppiumDriver Session { get; }
+}

+ 17 - 23
tests/Avalonia.IntegrationTests.Appium/TrayIconTests.cs

@@ -10,49 +10,43 @@ using Xunit;
 namespace Avalonia.IntegrationTests.Appium;
 
 [Collection("Default")]
-public class TrayIconTests : IDisposable
+public class TrayIconTests : TestBase, IDisposable
 {
-    private readonly AppiumDriver _session;
     private readonly AppiumDriver? _rootSession;
     private const string TrayIconName = "IntegrationTestApp TrayIcon";
 
     public TrayIconTests(DefaultAppFixture fixture)
+        : base(fixture, "Desktop")
     {
-        _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, Skip = "Flaky test")]
     public void Should_Handle_Left_Click()
     {
-        var avaloinaTrayIconButton = GetTrayIconButton(_rootSession ?? _session, TrayIconName);
-        Assert.NotNull(avaloinaTrayIconButton);
+        var avaloniaTrayIconButton = GetTrayIconButton(_rootSession ?? Session, TrayIconName);
+        Assert.NotNull(avaloniaTrayIconButton);
 
-        avaloinaTrayIconButton.SendClick();
+        avaloniaTrayIconButton.SendClick();
 
         Thread.Sleep(2000);
         
-        var checkBox = _session.FindElementByAccessibilityId("TrayIconClicked");
+        var checkBox = Session.FindElementByAccessibilityId("TrayIconClicked");
         Assert.True(checkBox.GetIsChecked());
     }
 
     [Fact(Skip = "Flaky test")]
     public void Should_Handle_Context_Menu_Item_Click()
     {
-        var avaloinaTrayIconButton = GetTrayIconButton(_rootSession ?? _session, TrayIconName);
-        Assert.NotNull(avaloinaTrayIconButton);
+        var avaloniaTrayIconButton = GetTrayIconButton(_rootSession ?? Session, TrayIconName);
+        Assert.NotNull(avaloniaTrayIconButton);
 
-        var contextMenu = ShowAndGetTrayMenu(avaloinaTrayIconButton, TrayIconName);
+        var contextMenu = ShowAndGetTrayMenu(avaloniaTrayIconButton, TrayIconName);
         Assert.NotNull(contextMenu);
 
         var menuItem = contextMenu.FindElementByName("Raise Menu Clicked");
@@ -60,26 +54,26 @@ public class TrayIconTests : IDisposable
 
         Thread.Sleep(2000);
 
-        var checkBox = _session.FindElementByAccessibilityId("TrayIconMenuClicked");
+        var checkBox = Session.FindElementByAccessibilityId("TrayIconMenuClicked");
         Assert.True(checkBox.GetIsChecked());
     }
 
     [Fact(Skip = "Flaky test")]
     public void Can_Toggle_TrayIcon_Visibility()
     {
-        var avaloinaTrayIconButton = GetTrayIconButton(_rootSession ?? _session, TrayIconName);
-        Assert.NotNull(avaloinaTrayIconButton);
+        var avaloniaTrayIconButton = GetTrayIconButton(_rootSession ?? Session, TrayIconName);
+        Assert.NotNull(avaloniaTrayIconButton);
 
-        var toggleButton = _session.FindElementByAccessibilityId("ToggleTrayIconVisible");
+        var toggleButton = Session.FindElementByAccessibilityId("ToggleTrayIconVisible");
         toggleButton.SendClick();
 
-        avaloinaTrayIconButton = GetTrayIconButton(_rootSession ?? _session, TrayIconName);
-        Assert.Null(avaloinaTrayIconButton);
+        avaloniaTrayIconButton = GetTrayIconButton(_rootSession ?? Session, TrayIconName);
+        Assert.Null(avaloniaTrayIconButton);
 
         toggleButton.SendClick();
 
-        avaloinaTrayIconButton = GetTrayIconButton(_rootSession ?? _session, TrayIconName);
-        Assert.NotNull(avaloinaTrayIconButton);
+        avaloniaTrayIconButton = GetTrayIconButton(_rootSession ?? Session, TrayIconName);
+        Assert.NotNull(avaloniaTrayIconButton);
     }
 
     private static AppiumWebElement? GetTrayIconButton(AppiumDriver session, string trayIconName)

+ 18 - 24
tests/Avalonia.IntegrationTests.Appium/WindowDecorationsTests.cs

@@ -5,23 +5,17 @@ using Xunit;
 namespace Avalonia.IntegrationTests.Appium;
 
 [Collection("WindowDecorations")]
-public class WindowDecorationsTests : IDisposable
+public class WindowDecorationsTests : TestBase, IDisposable
 {
-    private readonly AppiumDriver _session;
-
     public WindowDecorationsTests(DefaultAppFixture fixture)
+        : base(fixture, "Window Decorations")
     {
-        _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 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.
@@ -53,13 +47,13 @@ public class WindowDecorationsTests : IDisposable
         SetParameters(false, false, false, false, 1000);
         ApplyToCurrentWindow();
 
-        var currentWindow = _session.GetCurrentSingleWindow();
+        var currentWindow = Session.GetCurrentSingleWindow();
         var systemChrome = currentWindow.GetSystemChromeButtons();
         var clientChrome = currentWindow.GetClientChromeButtons();
 
         AssertSystemChrome(systemChrome, clientChrome, false);
 
-        var props = _session.FindElementByAccessibilityId("WindowDecorationProperties");
+        var props = Session.FindElementByAccessibilityId("WindowDecorationProperties");
         Assert.Equal($"0 0 False", props.Text);
     }
 
@@ -75,13 +69,13 @@ public class WindowDecorationsTests : IDisposable
 
         Thread.Sleep(500);
 
-        var currentWindow = _session.GetCurrentSingleWindow();
+        var currentWindow = Session.GetCurrentSingleWindow();
         var systemChrome = currentWindow.GetSystemChromeButtons();
         var clientChrome = currentWindow.GetClientChromeButtons();
 
         AssertClientChrome(systemChrome, clientChrome, titleBarHeight);
 
-        var props = _session.FindElementByAccessibilityId("WindowDecorationProperties");
+        var props = Session.FindElementByAccessibilityId("WindowDecorationProperties");
         if (titleBarHeight > 0)
         {
             Assert.Equal($"0 {titleBarHeight} True", props.Text);
@@ -100,13 +94,13 @@ public class WindowDecorationsTests : IDisposable
 
         Thread.Sleep(500);
 
-        var currentWindow = _session.GetCurrentSingleWindow();
+        var currentWindow = Session.GetCurrentSingleWindow();
         var systemChrome = currentWindow.GetSystemChromeButtons();
         var clientChrome = currentWindow.GetClientChromeButtons();
 
         AssertSystemChrome(systemChrome, clientChrome, true);
 
-        var props = _session.FindElementByAccessibilityId("WindowDecorationProperties");
+        var props = Session.FindElementByAccessibilityId("WindowDecorationProperties");
         if (titleBarHeight > 0)
         {
             Assert.Equal($"0 {titleBarHeight} True", props.Text);
@@ -125,7 +119,7 @@ public class WindowDecorationsTests : IDisposable
         {
             Thread.Sleep(500);
 
-            var secondaryWindow = _session.GetWindowById("SecondaryWindow");
+            var secondaryWindow = Session.GetWindowById("SecondaryWindow");
             var systemChrome = secondaryWindow.GetSystemChromeButtons();
             var clientChrome = secondaryWindow.GetClientChromeButtons();
 
@@ -145,7 +139,7 @@ public class WindowDecorationsTests : IDisposable
         {
             Thread.Sleep(500);
 
-            var secondaryWindow = _session.GetWindowById("SecondaryWindow");
+            var secondaryWindow = Session.GetWindowById("SecondaryWindow");
             var systemChrome = secondaryWindow.GetSystemChromeButtons();
             var clientChrome = secondaryWindow.GetClientChromeButtons();
 
@@ -202,11 +196,11 @@ public class WindowDecorationsTests : IDisposable
         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");
+        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();
@@ -225,13 +219,13 @@ public class WindowDecorationsTests : IDisposable
 
     private void ApplyToCurrentWindow()
     {
-        var applyWindowDecorations = _session.FindElementByAccessibilityId("ApplyWindowDecorations");
+        var applyWindowDecorations = Session.FindElementByAccessibilityId("ApplyWindowDecorations");
         applyWindowDecorations.Click();
     }
 
     private IDisposable ApplyOnNewWindow()
     {
-        var showNewWindowDecorations = _session.FindElementByAccessibilityId("ShowNewWindowDecorations");
+        var showNewWindowDecorations = Session.FindElementByAccessibilityId("ShowNewWindowDecorations");
         return showNewWindowDecorations.OpenWindowWithClick();
     }
 

+ 42 - 48
tests/Avalonia.IntegrationTests.Appium/WindowTests.cs

@@ -13,17 +13,11 @@ using Xunit.Sdk;
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class WindowTests
+    public class WindowTests : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public WindowTests(DefaultAppFixture fixture)
+            : base(fixture, "Window")
         {
-            _session = fixture.Session;
-
-            var tabs = _session.FindElementByAccessibilityId("MainTabs");
-            var tab = tabs.FindElementByName("Window");
-            tab.Click();
         }
 
         [Theory]
@@ -90,8 +84,8 @@ namespace Avalonia.IntegrationTests.Appium
                 {
                     try
                     {
-                        _session.FindElementByAccessibilityId("CurrentWindowState").SendClick();
-                        _session.FindElementByAccessibilityId("WindowStateNormal").SendClick();
+                        Session.FindElementByAccessibilityId("CurrentWindowState").SendClick();
+                        Session.FindElementByAccessibilityId("WindowStateNormal").SendClick();
 
                         // Wait for animations to run.
                         if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
@@ -110,14 +104,14 @@ namespace Avalonia.IntegrationTests.Appium
         {
             using (OpenWindow(new Size(400, 400), ShowWindowMode.NonOwned, WindowStartupLocation.Manual))
             {
-                var windowState = _session.FindElementByAccessibilityId("CurrentWindowState");
+                var windowState = Session.FindElementByAccessibilityId("CurrentWindowState");
 
                 Assert.Equal("Normal", windowState.GetComboBoxValue());
                 
                 
-                var window = _session.FindElements(By.XPath("//Window")).First();
+                var window = Session.FindElements(By.XPath("//Window")).First();
                 
-                new Actions(_session)
+                new Actions(Session)
                     .KeyDown(Keys.Meta)
                     .SendKeys(Keys.Left)
                     .KeyUp(Keys.Meta)
@@ -126,9 +120,9 @@ namespace Avalonia.IntegrationTests.Appium
                 var original = GetWindowInfo();
                 
                 windowState.Click();
-                _session.FindElementByName("Minimized").SendClick();
+                Session.FindElementByName("Minimized").SendClick();
                 
-                new Actions(_session)
+                new Actions(Session)
                     .KeyDown(Keys.Alt)
                     .SendKeys(Keys.Tab)
                     .KeyUp(Keys.Alt)
@@ -147,8 +141,8 @@ namespace Avalonia.IntegrationTests.Appium
         {
             using (OpenWindow(new Size(4000, 2200), ShowWindowMode.NonOwned, WindowStartupLocation.Manual))
             {
-                var screenRectTextBox = _session.FindElementByAccessibilityId("CurrentClientSize");
-                var measuredWithTextBlock = _session.FindElementByAccessibilityId("CurrentMeasuredWithText");
+                var screenRectTextBox = Session.FindElementByAccessibilityId("CurrentClientSize");
+                var measuredWithTextBlock = Session.FindElementByAccessibilityId("CurrentMeasuredWithText");
                 
                 var measuredWithString = measuredWithTextBlock.Text;
                 var workingAreaString = screenRectTextBox.Text;
@@ -167,17 +161,17 @@ namespace Avalonia.IntegrationTests.Appium
         public void ShowMode(ShowWindowMode mode)
         {
             using var window = OpenWindow(null, mode, WindowStartupLocation.Manual);
-            var windowState = _session.FindElementByAccessibilityId("CurrentWindowState");
+            var windowState = Session.FindElementByAccessibilityId("CurrentWindowState");
             var original = GetWindowInfo();
 
             Assert.Equal("Normal", windowState.GetComboBoxValue());
 
             windowState.Click();
-            _session.FindElementByAccessibilityId("WindowStateMaximized").SendClick();
+            Session.FindElementByAccessibilityId("WindowStateMaximized").SendClick();
             Assert.Equal("Maximized", windowState.GetComboBoxValue());
 
             windowState.Click();
-            _session.FindElementByAccessibilityId("WindowStateNormal").SendClick();
+            Session.FindElementByAccessibilityId("WindowStateNormal").SendClick();
 
             var current = GetWindowInfo();
             Assert.Equal(original.Position, current.Position);
@@ -187,7 +181,7 @@ namespace Avalonia.IntegrationTests.Appium
             if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || mode == ShowWindowMode.NonOwned)
             {
                 windowState.Click();
-                _session.FindElementByAccessibilityId("WindowStateFullScreen").SendClick();
+                Session.FindElementByAccessibilityId("WindowStateFullScreen").SendClick();
                 Assert.Equal("FullScreen", windowState.GetComboBoxValue());
 
                 current = GetWindowInfo();
@@ -197,7 +191,7 @@ namespace Avalonia.IntegrationTests.Appium
 
                 windowState.SendClick();
                 
-                _session.FindElementByAccessibilityId("WindowStateNormal").SendClick();
+                Session.FindElementByAccessibilityId("WindowStateNormal").SendClick();
 
                 current = GetWindowInfo();
                 Assert.Equal(original.Position, current.Position);
@@ -210,7 +204,7 @@ namespace Avalonia.IntegrationTests.Appium
         {
             var clientSize = new Size(400, 400);
             using var window = OpenWindow(clientSize, ShowWindowMode.NonOwned, WindowStartupLocation.CenterScreen, extendClientArea: true);
-            var windowState = _session.FindElementByAccessibilityId("CurrentWindowState");
+            var windowState = Session.FindElementByAccessibilityId("CurrentWindowState");
             var current = GetWindowInfo();
 
             Assert.Equal(current.ClientSize, clientSize);
@@ -219,11 +213,11 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void TransparentWindow()
         {
-            var showTransparentWindow = _session.FindElementByAccessibilityId("ShowTransparentWindow");
+            var showTransparentWindow = Session.FindElementByAccessibilityId("ShowTransparentWindow");
             showTransparentWindow.Click();
             Thread.Sleep(1000);
 
-            var window = _session.FindElementByAccessibilityId("TransparentWindow");
+            var window = Session.FindElementByAccessibilityId("TransparentWindow");
             var screenshot = window.GetScreenshot();
 
             window.Click();
@@ -239,11 +233,11 @@ namespace Avalonia.IntegrationTests.Appium
         [Fact]
         public void TransparentPopup()
         {
-            var showTransparentWindow = _session.FindElementByAccessibilityId("ShowTransparentPopup");
+            var showTransparentWindow = Session.FindElementByAccessibilityId("ShowTransparentPopup");
             showTransparentWindow.Click();
             Thread.Sleep(1000);
 
-            var window = _session.FindElementByAccessibilityId("TransparentPopupBackground");
+            var window = Session.FindElementByAccessibilityId("TransparentPopupBackground");
             var container = window.FindElementByAccessibilityId("PopupContainer");
             var screenshot = container.GetScreenshot();
 
@@ -260,11 +254,11 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.Windows)]
         public void Owned_Window_Should_Appear_Above_Topmost_Owner()
         {
-            var showTopmostWindow = _session.FindElementByAccessibilityId("ShowTopmostWindow");
+            var showTopmostWindow = Session.FindElementByAccessibilityId("ShowTopmostWindow");
             using var window = showTopmostWindow.OpenWindowWithClick();
             Thread.Sleep(1000);
-            var ownerWindow = _session.GetWindowById("OwnerWindow");
-            var ownedWindow = _session.GetWindowById("OwnedWindow");
+            var ownerWindow = Session.GetWindowById("OwnerWindow");
+            var ownedWindow = Session.GetWindowById("OwnedWindow");
 
             Assert.NotNull(ownerWindow);
             Assert.NotNull(ownedWindow);
@@ -297,7 +291,7 @@ namespace Avalonia.IntegrationTests.Appium
         {
             using (OpenWindow(null, mode, WindowStartupLocation.Manual, canResize: false, extendClientArea: extendClientArea))
             {
-                var secondaryWindow = _session.GetWindowById("SecondaryWindow");
+                var secondaryWindow = Session.GetWindowById("SecondaryWindow");
                 AppiumWebElement? maximizeButton;
 
                 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -404,13 +398,13 @@ namespace Avalonia.IntegrationTests.Appium
             bool canResize = true,
             bool extendClientArea = false)
         {
-            var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize");
-            var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode");
-            var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation");
-            var stateComboBox = _session.FindElementByAccessibilityId("ShowWindowState");
-            var canResizeCheckBox = _session.FindElementByAccessibilityId("ShowWindowCanResize");
-            var showButton = _session.FindElementByAccessibilityId("ShowWindow");
-            var extendClientAreaCheckBox = _session.FindElementByAccessibilityId("ShowWindowExtendClientAreaToDecorationsHint");
+            var sizeTextBox = Session.FindElementByAccessibilityId("ShowWindowSize");
+            var modeComboBox = Session.FindElementByAccessibilityId("ShowWindowMode");
+            var locationComboBox = Session.FindElementByAccessibilityId("ShowWindowLocation");
+            var stateComboBox = Session.FindElementByAccessibilityId("ShowWindowState");
+            var canResizeCheckBox = Session.FindElementByAccessibilityId("ShowWindowCanResize");
+            var showButton = Session.FindElementByAccessibilityId("ShowWindow");
+            var extendClientAreaCheckBox = Session.FindElementByAccessibilityId("ShowWindowExtendClientAreaToDecorationsHint");
 
             if (size.HasValue)
                 sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}");
@@ -418,19 +412,19 @@ namespace Avalonia.IntegrationTests.Appium
             if (modeComboBox.GetComboBoxValue() != mode.ToString())
             {
                 modeComboBox.Click();
-                _session.FindElementByName(mode.ToString()).SendClick();
+                Session.FindElementByName(mode.ToString()).SendClick();
             }
 
             if (locationComboBox.GetComboBoxValue() != location.ToString())
             {
                 locationComboBox.Click();
-                _session.FindElementByName(location.ToString()).SendClick();
+                Session.FindElementByName(location.ToString()).SendClick();
             }
 
             if (stateComboBox.GetComboBoxValue() != state.ToString())
             {
                 stateComboBox.Click();
-                _session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick();
+                Session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick();
             }
 
             if (canResizeCheckBox.GetIsChecked() != canResize)
@@ -446,7 +440,7 @@ namespace Avalonia.IntegrationTests.Appium
         {
             PixelRect? ReadOwnerRect()
             {
-                var text = _session.FindElementByAccessibilityId("CurrentOwnerRect").Text;
+                var text = Session.FindElementByAccessibilityId("CurrentOwnerRect").Text;
                 return !string.IsNullOrWhiteSpace(text) ? PixelRect.Parse(text) : null;
             }
 
@@ -457,13 +451,13 @@ namespace Avalonia.IntegrationTests.Appium
                 try
                 {
                     return new(
-                        Size.Parse(_session.FindElementByAccessibilityId("CurrentClientSize").Text),
-                        Size.Parse(_session.FindElementByAccessibilityId("CurrentFrameSize").Text),
-                        PixelPoint.Parse(_session.FindElementByAccessibilityId("CurrentPosition").Text),
+                        Size.Parse(Session.FindElementByAccessibilityId("CurrentClientSize").Text),
+                        Size.Parse(Session.FindElementByAccessibilityId("CurrentFrameSize").Text),
+                        PixelPoint.Parse(Session.FindElementByAccessibilityId("CurrentPosition").Text),
                         ReadOwnerRect(),
-                        PixelRect.Parse(_session.FindElementByAccessibilityId("CurrentScreenRect").Text),
-                        double.Parse(_session.FindElementByAccessibilityId("CurrentScaling").Text),
-                        Enum.Parse<WindowState>(_session.FindElementByAccessibilityId("CurrentWindowState").Text));
+                        PixelRect.Parse(Session.FindElementByAccessibilityId("CurrentScreenRect").Text),
+                        double.Parse(Session.FindElementByAccessibilityId("CurrentScaling").Text),
+                        Enum.Parse<WindowState>(Session.FindElementByAccessibilityId("CurrentWindowState").Text));
                 }
                 catch (OpenQA.Selenium.NoSuchElementException) when (retry++ < 3)
                 {

+ 35 - 60
tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

@@ -1,10 +1,7 @@
 using System;
-using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
 using Avalonia.Controls;
-using Avalonia.Utilities;
-using OpenQA.Selenium;
 using OpenQA.Selenium.Appium;
 using OpenQA.Selenium.Interactions;
 using Xunit;
@@ -12,39 +9,17 @@ using Xunit;
 namespace Avalonia.IntegrationTests.Appium
 {
     [Collection("Default")]
-    public class WindowTests_MacOS
+    public class WindowTests_MacOS : TestBase
     {
-        private readonly AppiumDriver _session;
-
         public WindowTests_MacOS(DefaultAppFixture fixture)
+            : base(fixture, "Window")
         {
-            var retry = 0;
-
-            _session = fixture.Session;
-
-            for (;;)
-            {
-                try
-                {
-                    var tabs = _session.FindElementByAccessibilityId("MainTabs");
-                    var tab = tabs.FindElementByName("Window");
-                    tab.Click();
-                    return;
-                }
-                catch (WebDriverException) 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)]
         public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent()
         {
-            var mainWindow = _session.FindElementByAccessibilityId("MainWindow");
+            var mainWindow = Session.FindElementByAccessibilityId("MainWindow");
 
             using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.Manual))
             {
@@ -63,14 +38,14 @@ namespace Avalonia.IntegrationTests.Appium
 
             using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.Manual))
             {
-                new Actions(_session)
+                new Actions(Session)
                     .MoveToElement(mainWindow, 100, 1)
                     .ClickAndHold()
                     .Perform();
 
                 var secondaryWindowIndex = GetWindowOrder("SecondaryWindow");
 
-                new Actions(_session)
+                new Actions(Session)
                     .MoveToElement(mainWindow, 100, 1)
                     .Release()
                     .Perform();
@@ -99,14 +74,14 @@ namespace Avalonia.IntegrationTests.Appium
             }
             finally
             {
-                _session.FindElementByAccessibilityId("ExitFullscreen").Click();
+                Session.FindElementByAccessibilityId("ExitFullscreen").Click();
             }
         }
 
         [PlatformFact(TestPlatforms.MacOS)]
         public void WindowOrder_Owned_Dialog_Stays_InFront_Of_Parent()
         {
-            var mainWindow = _session.FindElementByAccessibilityId("MainWindow");
+            var mainWindow = Session.FindElementByAccessibilityId("MainWindow");
 
             using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Owned, WindowStartupLocation.Manual))
             {
@@ -119,7 +94,7 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.MacOS)]
         public void WindowOrder_Owned_Dialog_Stays_InFront_Of_FullScreen_Parent()
         {
-            var mainWindow = _session.FindElementByAccessibilityId("MainWindow");
+            var mainWindow = Session.FindElementByAccessibilityId("MainWindow");
 
             // Enter fullscreen
             mainWindow.FindElementByAccessibilityId("EnterFullscreen").Click();
@@ -146,7 +121,7 @@ namespace Avalonia.IntegrationTests.Appium
             Thread.Sleep(1000);
 
             // Make sure we exited fullscreen.
-            mainWindow = _session.FindElementByAccessibilityId("MainWindow");
+            mainWindow = Session.FindElementByAccessibilityId("MainWindow");
             windowState = mainWindow.FindElementByAccessibilityId("MainWindowState");
             Assert.Equal("Normal", windowState.Text);
         }
@@ -167,7 +142,7 @@ namespace Avalonia.IntegrationTests.Appium
         public void Does_Not_Switch_Space_From_FullScreen_To_Main_Desktop_When_FullScreen_Window_Clicked()
         {
             // Issue #9565
-            var mainWindow = _session.FindElementByAccessibilityId("MainWindow");
+            var mainWindow = Session.FindElementByAccessibilityId("MainWindow");
             AppiumWebElement windowState;
 
             // Open child window.
@@ -180,7 +155,7 @@ namespace Avalonia.IntegrationTests.Appium
                 Thread.Sleep(1000);
 
                 // Make sure we entered fullscreen.
-                mainWindow = _session.FindElementByAccessibilityId("MainWindow");
+                mainWindow = Session.FindElementByAccessibilityId("MainWindow");
                 windowState = mainWindow.FindElementByAccessibilityId("MainWindowState");
                 Assert.Equal("FullScreen", windowState.Text);
                 
@@ -196,7 +171,7 @@ namespace Avalonia.IntegrationTests.Appium
             }
 
             // Make sure we exited fullscreen.
-            mainWindow = _session.FindElementByAccessibilityId("MainWindow");
+            mainWindow = Session.FindElementByAccessibilityId("MainWindow");
             windowState = mainWindow.FindElementByAccessibilityId("MainWindowState");
             Assert.Equal("Normal", windowState.Text);
         }
@@ -204,7 +179,7 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.MacOS)]
         public void WindowOrder_NonOwned_Window_Does_Not_Stay_InFront_Of_Parent()
         {
-            var mainWindow = _session.FindElementByAccessibilityId("MainWindow");
+            var mainWindow = Session.FindElementByAccessibilityId("MainWindow");
 
             using (OpenWindow(new PixelSize(800, 100), ShowWindowMode.NonOwned, WindowStartupLocation.Manual))
             {
@@ -214,7 +189,7 @@ namespace Avalonia.IntegrationTests.Appium
 
                 Assert.Equal(2, secondaryWindowIndex);
 
-                var sendToBack = _session.FindElementByAccessibilityId("SendToBack");
+                var sendToBack = Session.FindElementByAccessibilityId("SendToBack");
                 sendToBack.Click();
             }
         }
@@ -294,14 +269,14 @@ namespace Avalonia.IntegrationTests.Appium
                 miniaturizeButton.Click();
                 Thread.Sleep(1000);
 
-                var hittable = _session.FindElementsByXPath("/XCUIElementTypeApplication/XCUIElementTypeWindow")
+                var hittable = Session.FindElementsByXPath("/XCUIElementTypeApplication/XCUIElementTypeWindow")
                     .Select(x => x.GetAttribute("hittable")).ToList();
                 Assert.Equal(new[] { "true", "false" }, hittable);
 
-                _session.FindElementByAccessibilityId("RestoreAll").Click();
+                Session.FindElementByAccessibilityId("RestoreAll").Click();
                 Thread.Sleep(1000);
 
-                hittable = _session.FindElementsByXPath("/XCUIElementTypeApplication/XCUIElementTypeWindow")
+                hittable = Session.FindElementsByXPath("/XCUIElementTypeApplication/XCUIElementTypeWindow")
                     .Select(x => x.GetAttribute("hittable")).ToList();
                 Assert.Equal(new[] { "true", "true" }, hittable);
             }
@@ -310,7 +285,7 @@ namespace Avalonia.IntegrationTests.Appium
         [PlatformFact(TestPlatforms.MacOS)]
         public void Hidden_Child_Window_Is_Not_Reshown_When_Parent_Clicked()
         {
-            var mainWindow = _session.FindElementByAccessibilityId("MainWindow");
+            var mainWindow = Session.FindElementByAccessibilityId("MainWindow");
 
             // We don't use dispose to close the window here, because it seems that hiding and re-showing a window
             // causes Appium to think it's a different window.
@@ -321,15 +296,15 @@ namespace Avalonia.IntegrationTests.Appium
 
             hideButton.Click();
                 
-            var windows = _session.FindElementsByXPath("XCUIElementTypeWindow");
+            var windows = Session.FindElementsByXPath("XCUIElementTypeWindow");
             Assert.Single(windows);
                 
             mainWindow.Click();
                 
-            windows = _session.FindElementsByXPath("XCUIElementTypeWindow");
+            windows = Session.FindElementsByXPath("XCUIElementTypeWindow");
             Assert.Single(windows);
                 
-            _session.FindElementByAccessibilityId("RestoreAll").Click();
+            Session.FindElementByAccessibilityId("RestoreAll").Click();
 
             // Close the window manually.
             secondaryWindow = GetWindow("SecondaryWindow");
@@ -352,9 +327,9 @@ namespace Avalonia.IntegrationTests.Appium
                 Assert.Equal(0, titleBar);
 
                 secondaryWindow.FindElementByAccessibilityId("CurrentSystemDecorations").Click();
-                _session.FindElementByAccessibilityId("SystemDecorationsNone").SendClick();
+                Session.FindElementByAccessibilityId("SystemDecorationsNone").SendClick();
                 secondaryWindow.FindElementByAccessibilityId("CurrentSystemDecorations").Click();
-                _session.FindElementByAccessibilityId("SystemDecorationsFull").SendClick();
+                Session.FindElementByAccessibilityId("SystemDecorationsFull").SendClick();
 
                 titleBar = secondaryWindow.FindElementsByXPath("/*/XCUIElementTypeStaticText").Count;
                 Assert.Equal(0, titleBar);
@@ -394,7 +369,7 @@ namespace Avalonia.IntegrationTests.Appium
                     if (decorations != SystemDecorations.Full)
                     {
                         secondaryWindow.FindElementByAccessibilityId("CurrentSystemDecorations").Click();
-                        _session.FindElementByAccessibilityId("SystemDecorationsFull").SendClick();
+                        Session.FindElementByAccessibilityId("SystemDecorationsFull").SendClick();
                     }
                 }
             }
@@ -408,13 +383,13 @@ namespace Avalonia.IntegrationTests.Appium
             SystemDecorations systemDecorations = SystemDecorations.Full,
             bool extendClientArea = false)
         {
-            var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize");
-            var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode");
-            var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation");
-            var canResizeCheckBox = _session.FindElementByAccessibilityId("ShowWindowCanResize");
-            var showButton = _session.FindElementByAccessibilityId("ShowWindow");
-            var systemDecorationsComboBox = _session.FindElementByAccessibilityId("ShowWindowSystemDecorations");
-            var extendClientAreaCheckBox = _session.FindElementByAccessibilityId("ShowWindowExtendClientAreaToDecorationsHint");
+            var sizeTextBox = Session.FindElementByAccessibilityId("ShowWindowSize");
+            var modeComboBox = Session.FindElementByAccessibilityId("ShowWindowMode");
+            var locationComboBox = Session.FindElementByAccessibilityId("ShowWindowLocation");
+            var canResizeCheckBox = Session.FindElementByAccessibilityId("ShowWindowCanResize");
+            var showButton = Session.FindElementByAccessibilityId("ShowWindow");
+            var systemDecorationsComboBox = Session.FindElementByAccessibilityId("ShowWindowSystemDecorations");
+            var extendClientAreaCheckBox = Session.FindElementByAccessibilityId("ShowWindowExtendClientAreaToDecorationsHint");
 
             if (size.HasValue)
                 sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}");
@@ -422,13 +397,13 @@ namespace Avalonia.IntegrationTests.Appium
             if (modeComboBox.GetComboBoxValue() != mode.ToString())
             {
                 modeComboBox.Click();
-                _session.FindElementByName(mode.ToString()).SendClick();
+                Session.FindElementByName(mode.ToString()).SendClick();
             }
 
             if (locationComboBox.GetComboBoxValue() != location.ToString())
             {
                 locationComboBox.Click();
-                _session.FindElementByName(location.ToString()).SendClick();
+                Session.FindElementByName(location.ToString()).SendClick();
             }
             
             if (canResizeCheckBox.GetIsChecked() != canResize)
@@ -437,7 +412,7 @@ namespace Avalonia.IntegrationTests.Appium
             if (systemDecorationsComboBox.GetComboBoxValue() != systemDecorations.ToString())
             {
                 systemDecorationsComboBox.Click();
-                _session.FindElementByName(systemDecorations.ToString()).SendClick();
+                Session.FindElementByName(systemDecorations.ToString()).SendClick();
             }
             
             if (extendClientAreaCheckBox.GetIsChecked() != extendClientArea)
@@ -448,7 +423,7 @@ namespace Avalonia.IntegrationTests.Appium
 
         private AppiumWebElement GetWindow(string identifier)
         {
-            return _session.GetWindowById(identifier);
+            return Session.GetWindowById(identifier);
         }
 
         private int GetWindowOrder(string identifier)