From 0d4ebb941bad8becf8ffb74e91cc162054fbff68 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 3 Aug 2024 10:56:12 +0200 Subject: [PATCH] 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). #Conflicts: # samples/IntegrationTestApp/App.axaml.cs # samples/IntegrationTestApp/MainWindow.axaml # samples/IntegrationTestApp/MainWindow.axaml.cs # samples/IntegrationTestApp/TopmostWindowTest.axaml.cs # src/Avalonia.Controls/TreeViewItem.cs # tests/Avalonia.IntegrationTests.Appium/ContextMenuTests.cs # tests/Avalonia.IntegrationTests.Appium/PointerTests.cs # tests/Avalonia.IntegrationTests.Appium/ScreenTests.cs # tests/Avalonia.IntegrationTests.Appium/TrayIconTests.cs # tests/Avalonia.IntegrationTests.Appium/WindowDecorationsTests.cs # tests/Avalonia.IntegrationTests.Appium/WindowTests.cs # tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs --- samples/IntegrationTestApp/App.axaml.cs | 1 + .../IntegrationTestApp.csproj | 3 + samples/IntegrationTestApp/MainWindow.axaml | 200 ++---------- .../IntegrationTestApp/MainWindow.axaml.cs | 292 +++--------------- samples/IntegrationTestApp/Models/Page.cs | 6 + .../Pages/AutomationPage.axaml | 17 + .../Pages/AutomationPage.axaml.cs | 11 + .../IntegrationTestApp/Pages/ButtonPage.axaml | 22 ++ .../Pages/ButtonPage.axaml.cs | 11 + .../Pages/CheckBoxPage.axaml | 12 + .../Pages/CheckBoxPage.axaml.cs | 11 + .../Pages/ComboBoxPage.axaml | 16 + .../Pages/ComboBoxPage.axaml.cs | 22 ++ .../Pages/ContextMenuPage.axaml | 17 + .../Pages/ContextMenuPage.axaml.cs | 11 + .../Pages/DesktopPage.axaml | 14 + .../Pages/DesktopPage.axaml.cs | 19 ++ .../Pages/GesturesPage.axaml | 29 ++ .../Pages/GesturesPage.axaml.cs | 44 +++ .../Pages/ListBoxPage.axaml | 15 + .../Pages/ListBoxPage.axaml.cs | 23 ++ .../IntegrationTestApp/Pages/MenuPage.axaml | 22 ++ .../Pages/MenuPage.axaml.cs | 24 ++ .../Pages/PointerPage.axaml | 19 ++ .../Pages/PointerPage.axaml.cs | 47 +++ .../Pages/RadioButtonPage.axaml | 14 + .../Pages/RadioButtonPage.axaml.cs | 11 + .../Pages/ScreensPage.axaml | 19 ++ .../Pages/ScreensPage.axaml.cs | 32 ++ .../Pages/ScrollBarPage.axaml | 8 + .../Pages/ScrollBarPage.axaml.cs | 11 + .../IntegrationTestApp/Pages/SliderPage.axaml | 17 + .../Pages/SliderPage.axaml.cs | 17 + .../Pages/WindowDecorationsPage.axaml | 21 ++ .../Pages/WindowDecorationsPage.axaml.cs | 63 ++++ .../IntegrationTestApp/Pages/WindowPage.axaml | 45 +++ .../Pages/WindowPage.axaml.cs | 199 ++++++++++++ .../ShowWindowTest.axaml.cs | 1 - .../ViewModels/MainWindowViewModel.cs | 23 ++ .../ViewModels/ViewModelBase.cs | 24 ++ .../DataGridCell.cs | 2 + .../DataGridColumnHeader.cs | 2 + src/Avalonia.Controls.DataGrid/DataGridRow.cs | 2 + .../DataGridRowHeader.cs | 6 + .../Automation/Peers/AutomationPeer.cs | 12 + .../Automation/Peers/ControlAutomationPeer.cs | 14 + src/Avalonia.Controls/ListBoxItem.cs | 2 + src/Avalonia.Controls/MenuItem.cs | 2 + src/Avalonia.Controls/TabItem.cs | 1 + src/Avalonia.Controls/TreeViewItem.cs | 3 + .../Automation/AutomationNode.cs | 1 + .../AutomationTests.cs | 21 +- .../ButtonTests.cs | 24 +- .../CheckBoxTests.cs | 19 +- .../ComboBoxTests.cs | 53 ++-- .../ContextMenuTests.cs | 16 +- .../GestureTests.cs | 81 +++-- .../ListBoxTests.cs | 18 +- .../MenuTests.cs | 73 ++--- .../NativeMenuTests.cs | 29 +- .../RadioButtonTests.cs | 16 +- .../ScrollBarTests.cs | 12 +- .../SliderTests.cs | 37 +-- .../TestBase.cs | 33 ++ .../WindowTests.cs | 84 +++-- .../WindowTests_MacOS.cs | 98 +++--- 66 files changed, 1292 insertions(+), 782 deletions(-) create mode 100644 samples/IntegrationTestApp/Models/Page.cs create mode 100644 samples/IntegrationTestApp/Pages/AutomationPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/AutomationPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/ButtonPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/ButtonPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/CheckBoxPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/CheckBoxPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/ComboBoxPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/ComboBoxPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/ContextMenuPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/ContextMenuPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/DesktopPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/DesktopPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/GesturesPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/GesturesPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/ListBoxPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/ListBoxPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/MenuPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/MenuPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/PointerPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/PointerPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/RadioButtonPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/RadioButtonPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/ScreensPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/ScreensPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/ScrollBarPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/ScrollBarPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/SliderPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/SliderPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml.cs create mode 100644 samples/IntegrationTestApp/Pages/WindowPage.axaml create mode 100644 samples/IntegrationTestApp/Pages/WindowPage.axaml.cs create mode 100644 samples/IntegrationTestApp/ViewModels/MainWindowViewModel.cs create mode 100644 samples/IntegrationTestApp/ViewModels/ViewModelBase.cs create mode 100644 tests/Avalonia.IntegrationTests.Appium/TestBase.cs diff --git a/samples/IntegrationTestApp/App.axaml.cs b/samples/IntegrationTestApp/App.axaml.cs index 022931366d..ed1eecb263 100644 --- a/samples/IntegrationTestApp/App.axaml.cs +++ b/samples/IntegrationTestApp/App.axaml.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using MiniMvvm; namespace IntegrationTestApp { diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj index 77bfa828a7..82a9d87b0a 100644 --- a/samples/IntegrationTestApp/IntegrationTestApp.csproj +++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj @@ -27,6 +27,9 @@ + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 29a8a254e5..3f6d507866 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -2,13 +2,13 @@ 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" Name="MainWindow" Icon="/Assets/icon.ico" Title="IntegrationTestApp" - x:DataType="integrationTestApp:MainWindow"> + x:DataType="vm:MainWindowViewModel"> @@ -19,7 +19,7 @@ - + @@ -28,185 +28,25 @@ WindowState: - + - - - - - TextBlockWithName - - TextBlockWithNameAndAutomationId - - Label for TextBox - - Foo - - - - - - - - - - - - - - - - - Sample RadioButton - - Three States: Option 1 - Three States: Option 2 - - - - - - - Unchecked - Checked - ThreeState - - - - - - - Item 0 - Item 1 - - Wrap Selection - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - None - - - - - - - - - - - - NonOwned - Owned - Modal - - - Manual - CenterScreen - CenterOwner - - - Normal - Minimized - Maximized - FullScreen - - - None - BorderOnly - Full - - ExtendClientAreaToDecorationsHint - Can Resize - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 986eb920a3..62ef970aa1 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -1,17 +1,8 @@ using System.Collections.Generic; -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.Markup.Xaml; -using Avalonia.Media; -using Avalonia.VisualTree; -using Microsoft.CodeAnalysis; +using IntegrationTestApp.Models; +using IntegrationTestApp.Pages; +using IntegrationTestApp.ViewModels; namespace IntegrationTestApp { @@ -20,270 +11,67 @@ namespace IntegrationTestApp public MainWindow() { InitializeComponent(); - InitializeViewMenu(); - InitializeGesturesTab(); - this.AttachDevTools(); - var overlayPopups = this.Get("AppOverlayPopups"); - overlayPopups.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 ListBoxItems { get; } + private MainWindowViewModel? ViewModel => (MainWindowViewModel?)DataContext; - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } - - private void InitializeViewMenu() + private void InitializeViewMenu(IEnumerable pages) { var mainTabs = this.Get("MainTabs"); var viewMenu = (NativeMenuItem?)NativeMenu.GetMenu(this)?.Items[1]; - foreach (var tabItem in mainTabs.Items.Cast()) + 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 ShowWindow() - { - var sizeTextBox = this.GetControl("ShowWindowSize"); - var modeComboBox = this.GetControl("ShowWindowMode"); - var locationComboBox = this.GetControl("ShowWindowLocation"); - var stateComboBox = this.GetControl("ShowWindowState"); - var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null; - var systemDecorations = this.GetControl("ShowWindowSystemDecorations"); - var extendClientArea = this.GetControl("ShowWindowExtendClientAreaToDecorationsHint"); - var canResizeCheckBox = this.GetControl("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().Count(); - if (existing > 0) + menuItem.Click += (_, _) => { - 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 ShowTransparentWindow() - { - // 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(this); - window.Show(backgroundWindow); - } - - private void ShowTransparentPopup() - { - 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 SendToBack() - { - var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!; - - foreach (var window in lifetime.Windows.ToArray()) - { - window.Activate(); - } - } - - private void RestoreAll() - { - var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!; + if (ViewModel is { } viewModel) + viewModel.SelectedPage = page; + }; - foreach (var window in lifetime.Windows.ToArray()) - { - window.Show(); - if (window.WindowState == WindowState.Minimized) - window.WindowState = WindowState.Normal; + viewMenu?.Menu?.Items.Add(menuItem); } } - private void InitializeGesturesTab() + private void Pager_SelectionChanged(object? sender, SelectionChangedEventArgs e) { - var gestureBorder = this.GetControl("GestureBorder"); - var gestureBorder2 = this.GetControl("GestureBorder2"); - var lastGesture = this.GetControl("LastGesture"); - var resetGestures = this.GetControl + + + + + + diff --git a/samples/IntegrationTestApp/Pages/ButtonPage.axaml.cs b/samples/IntegrationTestApp/Pages/ButtonPage.axaml.cs new file mode 100644 index 0000000000..540ce839a3 --- /dev/null +++ b/samples/IntegrationTestApp/Pages/ButtonPage.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace IntegrationTestApp.Pages; + +public partial class ButtonPage : UserControl +{ + public ButtonPage() + { + InitializeComponent(); + } +} diff --git a/samples/IntegrationTestApp/Pages/CheckBoxPage.axaml b/samples/IntegrationTestApp/Pages/CheckBoxPage.axaml new file mode 100644 index 0000000000..cdb61b53a4 --- /dev/null +++ b/samples/IntegrationTestApp/Pages/CheckBoxPage.axaml @@ -0,0 +1,12 @@ + + + Unchecked + Checked + ThreeState + + diff --git a/samples/IntegrationTestApp/Pages/CheckBoxPage.axaml.cs b/samples/IntegrationTestApp/Pages/CheckBoxPage.axaml.cs new file mode 100644 index 0000000000..6863f62387 --- /dev/null +++ b/samples/IntegrationTestApp/Pages/CheckBoxPage.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace IntegrationTestApp.Pages; + +public partial class CheckBoxPage : UserControl +{ + public CheckBoxPage() + { + InitializeComponent(); + } +} diff --git a/samples/IntegrationTestApp/Pages/ComboBoxPage.axaml b/samples/IntegrationTestApp/Pages/ComboBoxPage.axaml new file mode 100644 index 0000000000..6068b06e85 --- /dev/null +++ b/samples/IntegrationTestApp/Pages/ComboBoxPage.axaml @@ -0,0 +1,16 @@ + + + + Item 0 + Item 1 + + Wrap Selection + + + + diff --git a/samples/IntegrationTestApp/Pages/ComboBoxPage.axaml.cs b/samples/IntegrationTestApp/Pages/ComboBoxPage.axaml.cs new file mode 100644 index 0000000000..eb9b66de76 --- /dev/null +++ b/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; + } +} diff --git a/samples/IntegrationTestApp/Pages/ContextMenuPage.axaml b/samples/IntegrationTestApp/Pages/ContextMenuPage.axaml new file mode 100644 index 0000000000..7d494bb277 --- /dev/null +++ b/samples/IntegrationTestApp/Pages/ContextMenuPage.axaml @@ -0,0 +1,17 @@ + + + + + diff --git a/samples/IntegrationTestApp/Pages/ContextMenuPage.axaml.cs b/samples/IntegrationTestApp/Pages/ContextMenuPage.axaml.cs new file mode 100644 index 0000000000..e42d9f232e --- /dev/null +++ b/samples/IntegrationTestApp/Pages/ContextMenuPage.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace IntegrationTestApp.Pages; + +public partial class ContextMenuPage : UserControl +{ + public ContextMenuPage() + { + InitializeComponent(); + } +} diff --git a/samples/IntegrationTestApp/Pages/DesktopPage.axaml b/samples/IntegrationTestApp/Pages/DesktopPage.axaml new file mode 100644 index 0000000000..a5495bd347 --- /dev/null +++ b/samples/IntegrationTestApp/Pages/DesktopPage.axaml @@ -0,0 +1,14 @@ + + + Tray Icon Clicked + Tray Icon Menu Clicked + + + + + + + + + diff --git a/samples/IntegrationTestApp/Pages/GesturesPage.axaml.cs b/samples/IntegrationTestApp/Pages/GesturesPage.axaml.cs new file mode 100644 index 0000000000..907edb973c --- /dev/null +++ b/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; + } +} diff --git a/samples/IntegrationTestApp/Pages/ListBoxPage.axaml b/samples/IntegrationTestApp/Pages/ListBoxPage.axaml new file mode 100644 index 0000000000..4e23cd8e37 --- /dev/null +++ b/samples/IntegrationTestApp/Pages/ListBoxPage.axaml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/samples/IntegrationTestApp/Pages/ListBoxPage.axaml.cs b/samples/IntegrationTestApp/Pages/ListBoxPage.axaml.cs new file mode 100644 index 0000000000..3bff7d9231 --- /dev/null +++ b/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 ListBoxItems { get; } + + private void ListBoxSelectionClear_Click(object? sender, RoutedEventArgs e) + { + BasicListBox.SelectedIndex = -1; + } +} diff --git a/samples/IntegrationTestApp/Pages/MenuPage.axaml b/samples/IntegrationTestApp/Pages/MenuPage.axaml new file mode 100644 index 0000000000..36c2636f3d --- /dev/null +++ b/samples/IntegrationTestApp/Pages/MenuPage.axaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + None + + + + + diff --git a/samples/IntegrationTestApp/Pages/MenuPage.axaml.cs b/samples/IntegrationTestApp/Pages/MenuPage.axaml.cs new file mode 100644 index 0000000000..8be695f8ab --- /dev/null +++ b/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"; + } +} diff --git a/samples/IntegrationTestApp/Pages/PointerPage.axaml b/samples/IntegrationTestApp/Pages/PointerPage.axaml new file mode 100644 index 0000000000..ba6016c9b5 --- /dev/null +++ b/samples/IntegrationTestApp/Pages/PointerPage.axaml @@ -0,0 +1,19 @@ + + + + + Show Dialog + + + + diff --git a/samples/IntegrationTestApp/Pages/PointerPage.axaml.cs b/samples/IntegrationTestApp/Pages/PointerPage.axaml.cs new file mode 100644 index 0000000000..b34798be10 --- /dev/null +++ b/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); + } +} diff --git a/samples/IntegrationTestApp/Pages/RadioButtonPage.axaml b/samples/IntegrationTestApp/Pages/RadioButtonPage.axaml new file mode 100644 index 0000000000..f66b9b9f7b --- /dev/null +++ b/samples/IntegrationTestApp/Pages/RadioButtonPage.axaml @@ -0,0 +1,14 @@ + + + Sample RadioButton + + Three States: Option 1 + Three States: Option 2 + + + diff --git a/samples/IntegrationTestApp/Pages/RadioButtonPage.axaml.cs b/samples/IntegrationTestApp/Pages/RadioButtonPage.axaml.cs new file mode 100644 index 0000000000..115ff6f2f0 --- /dev/null +++ b/samples/IntegrationTestApp/Pages/RadioButtonPage.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace IntegrationTestApp.Pages; + +public partial class RadioButtonPage : UserControl +{ + public RadioButtonPage() + { + InitializeComponent(); + } +} diff --git a/samples/IntegrationTestApp/Pages/ScreensPage.axaml b/samples/IntegrationTestApp/Pages/ScreensPage.axaml new file mode 100644 index 0000000000..2d95c4719a --- /dev/null +++ b/samples/IntegrationTestApp/Pages/ScreensPage.axaml @@ -0,0 +1,19 @@ + + + + + diff --git a/samples/IntegrationTestApp/Pages/SliderPage.axaml.cs b/samples/IntegrationTestApp/Pages/SliderPage.axaml.cs new file mode 100644 index 0000000000..72f0174fed --- /dev/null +++ b/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; + } +} diff --git a/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml b/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml new file mode 100644 index 0000000000..21a5b1d883 --- /dev/null +++ b/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs b/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs new file mode 100644 index 0000000000..5549e537d3 --- /dev/null +++ b/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().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); + } +} diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs index f0be34fdaa..0c5774bf53 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs +++ b/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 diff --git a/samples/IntegrationTestApp/ViewModels/MainWindowViewModel.cs b/samples/IntegrationTestApp/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000000..86eb13ec5a --- /dev/null +++ b/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 pages) + { + Pages = new(pages); + } + + public ObservableCollection Pages { get; } + + public Page? SelectedPage + { + get => _selectedPage; + set => RaiseAndSetIfChanged(ref _selectedPage, value); + } +} diff --git a/samples/IntegrationTestApp/ViewModels/ViewModelBase.cs b/samples/IntegrationTestApp/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000000..521382b863 --- /dev/null +++ b/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(ref T field, T value, [CallerMemberName] string? propertyName = null) + { + if (!EqualityComparer.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)); +} diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs index 852b85ff01..3518738485 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs +++ b/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(true); IsTabStopProperty.OverrideDefaultValue(false); + AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip); } public DataGridCell() { } diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 579c4d04ad..a3dcc433ee 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/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((x, e) => x.OnAreSeparatorsVisibleChanged(e)); PressedMixin.Attach(); IsTabStopProperty.OverrideDefaultValue(false); + AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip); } /// diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index 12dbd01d07..e94eecbb91 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/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((x, e) => x.OnAreDetailsVisibleChanged(e)); PointerPressedEvent.AddClassHandler((x, e) => x.DataGridRow_PointerPressed(e), handledEventsToo: true); IsTabStopProperty.OverrideDefaultValue(false); + AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip); } /// diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs index a480a7af15..ba6ad94777 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs +++ b/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(IsOffscreenBehavior.FromClip); + } + internal Control Owner { get; diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index fb7cdd87ed..6a45ba4cea 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -153,6 +153,17 @@ namespace Avalonia.Automation.Peers /// public bool IsKeyboardFocusable() => IsKeyboardFocusableCore(); + /// + /// Gets a value that indicates whether an element is off the screen. + /// + /// + /// 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. + /// + public bool IsOffscreen() => IsOffscreenCore(); + /// /// Sets the keyboard focus on the element that is associated with this automation peer. /// @@ -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(); diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index b0dd30a878..0c043e7577 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/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(); diff --git a/src/Avalonia.Controls/ListBoxItem.cs b/src/Avalonia.Controls/ListBoxItem.cs index 5ee4854554..55fc81653a 100644 --- a/src/Avalonia.Controls/ListBoxItem.cs +++ b/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(IsSelectedProperty); PressedMixin.Attach(); FocusableProperty.OverrideDefaultValue(true); + AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip); } /// diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index a2a54d5550..13cd78f2fc 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/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(DefaultPanel); ClickEvent.AddClassHandler((x, e) => x.OnClick(e)); SubmenuOpenedEvent.AddClassHandler((x, e) => x.OnSubmenuOpened(e)); + AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip); } public MenuItem() diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index 9bffacd921..0a0c3f5fc5 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -39,6 +39,7 @@ namespace Avalonia.Controls FocusableProperty.OverrideDefaultValue(typeof(TabItem), true); DataContextProperty.Changed.AddClassHandler((x, e) => x.UpdateHeader(e)); AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(AutomationControlType.TabItem); + AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip); AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler((tabItem, args) => tabItem.TabItemActivated(args)); } diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index a48706cb19..76a01fab35 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -2,6 +2,8 @@ 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; using Avalonia.Controls.Primitives; @@ -61,6 +63,7 @@ namespace Avalonia.Controls PressedMixin.Attach(); FocusableProperty.OverrideDefaultValue(true); ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); + AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip); RequestBringIntoViewEvent.AddClassHandler((x, e) => x.OnRequestBringIntoView(e)); } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index 569f7da738..8489e6ec29 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/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, diff --git a/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs b/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs index ce6e10807d..1924e27fc6 100644 --- a/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs +++ b/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()); diff --git a/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs index 22b68732c7..e2af1eef14 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs +++ b/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")); } diff --git a/tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs index 490210ae31..ab15a59af3 100644 --- a/tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs +++ b/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()); diff --git a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs index 231a50c89b..78b00ba641 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs +++ b/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()); diff --git a/tests/Avalonia.IntegrationTests.Appium/ContextMenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/ContextMenuTests.cs index 2d4ef14f45..af468e6f10 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ContextMenuTests.cs +++ b/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()); } } diff --git a/tests/Avalonia.IntegrationTests.Appium/GestureTests.cs b/tests/Avalonia.IntegrationTests.Appium/GestureTests.cs index 0fc8a68f44..b2d06be28e 100644 --- a/tests/Avalonia.IntegrationTests.Appium/GestureTests.cs +++ b/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(); diff --git a/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs index 24a02a7844..2291bcd433 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs +++ b/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"); } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs index f9fa9312ee..6fd9505583 100644 --- a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs +++ b/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(); } diff --git a/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs index ec4f9d705d..2406ff5958 100644 --- a/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs +++ b/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"); } } diff --git a/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs b/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs index c58c57bddc..d949eb635d 100644 --- a/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs +++ b/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(); diff --git a/tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs b/tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs index 1a285c5ce6..1e1748facf 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ScrollBarTests.cs +++ b/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(); diff --git a/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs index 1eede63aae..0cec55ad63 100644 --- a/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs +++ b/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] 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] 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); diff --git a/tests/Avalonia.IntegrationTests.Appium/TestBase.cs b/tests/Avalonia.IntegrationTests.Appium/TestBase.cs new file mode 100644 index 0000000000..5306d71c8f --- /dev/null +++ b/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; } +} diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index e172980981..67ac84b2a6 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/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(); @@ -268,7 +262,7 @@ namespace Avalonia.IntegrationTests.Appium { using (OpenWindow(null, mode, WindowStartupLocation.Manual, canResize: false, extendClientArea: extendClientArea)) { - var secondaryWindow = GetWindow("SecondaryWindow"); + var secondaryWindow = Session.GetWindowById("SecondaryWindow"); AppiumWebElement? maximizeButton; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -375,13 +369,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}"); @@ -389,19 +383,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) @@ -432,7 +426,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; } @@ -443,13 +437,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(_session.FindElementByAccessibilityId("CurrentWindowState").Text)); + PixelRect.Parse(Session.FindElementByAccessibilityId("CurrentScreenRect").Text), + double.Parse(Session.FindElementByAccessibilityId("CurrentScaling").Text), + Enum.Parse(Session.FindElementByAccessibilityId("CurrentWindowState").Text)); } catch (OpenQA.Selenium.NoSuchElementException) when (retry++ < 3) { diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 53a41f5d46..ed57f9bed1 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/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,10 +423,7 @@ namespace Avalonia.IntegrationTests.Appium private AppiumWebElement GetWindow(string identifier) { - // The Avalonia a11y tree currently exposes two nested Window elements, this is a bug and should be fixed - // but in the meantime use the `parent::' selector to return the parent "real" window. - return _session.FindElementByXPath( - $"XCUIElementTypeWindow//*[@identifier='{identifier}']/parent::XCUIElementTypeWindow"); + return Session.GetWindowById(identifier); } private int GetWindowOrder(string identifier)