Browse Source

Started adding ComboBox integration tests.

And fixed some issues with ComboBox/popups.
ui-automation-test
Steven Kirk 6 years ago
parent
commit
e8801c2aca
  1. 3
      samples/IntegrationTestApp/IntegrationTestApp.csproj
  2. 17
      samples/IntegrationTestApp/MainWindow.axaml
  3. 1
      samples/IntegrationTestApp/MainWindow.axaml.cs
  4. 3
      src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs
  5. 52
      src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs
  6. 7
      src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs
  7. 5
      src/Avalonia.Controls/Primitives/Popup.cs
  8. 11
      src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
  9. 24
      tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs
  10. 55
      tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs

3
samples/IntegrationTestApp/IntegrationTestApp.csproj

@ -7,4 +7,7 @@
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\ReferenceCoreLibraries.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
</ItemGroup>
</Project>

17
samples/IntegrationTestApp/MainWindow.axaml

@ -8,6 +8,9 @@
<TabControl TabStripPlacement="Left" AutomationProperties.AutomationId="MainTabs">
<TabItem Header="Button">
<StackPanel>
<Button AutomationProperties.AutomationId="DisabledButton" IsEnabled="False">
Disabled Button
</Button>
<Button AutomationProperties.AutomationId="BasicButton">
Basic Button
</Button>
@ -17,6 +20,20 @@
</StackPanel>
</TabItem>
<TabItem Header="ComboBox">
<StackPanel>
<ComboBox AutomationProperties.AutomationId="UnselectedComboBox">
<ComboBoxItem>Foo</ComboBoxItem>
<ComboBoxItem>Bar</ComboBoxItem>
</ComboBox>
<ComboBox AutomationProperties.AutomationId="SelectedIndex0ComboBox" SelectedIndex="0">
<ComboBoxItem>Foo</ComboBoxItem>
<ComboBoxItem>Bar</ComboBoxItem>
</ComboBox>
<ComboBox AutomationProperties.AutomationId="SelectedIndex1ComboBox" SelectedIndex="1">
<ComboBoxItem>Foo</ComboBoxItem>
<ComboBoxItem>Bar</ComboBoxItem>
</ComboBox>
</StackPanel>
</TabItem>
</TabControl>
</Window>

1
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -9,6 +9,7 @@ namespace IntegrationTestApp
public MainWindow()
{
InitializeComponent();
this.AttachDevTools();
}
private void InitializeComponent()

3
src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs

@ -78,5 +78,8 @@ namespace Avalonia.Automation.Peers
{
return AutomationControlType.ListItem;
}
protected override bool IsContentElementCore() => true;
protected override bool IsControlElementCore() => true;
}
}

52
src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using Avalonia.Automation.Platform;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class PopupAutomationPeer : ControlAutomationPeer
{
public PopupAutomationPeer(IAutomationNodeFactory factory, Popup owner)
: base(factory, owner)
{
owner.Opened += PopupOpenedClosed;
owner.Closed += PopupOpenedClosed;
}
protected override IReadOnlyList<AutomationPeer>? GetChildrenCore()
{
var host = (IVisualTreeHost)Owner;
System.Diagnostics.Debug.WriteLine($"Popup children='{host}'");
return host.Root is Control c ? new[] { GetOrCreatePeer(c) } : null;
}
protected override bool IsContentElementCore() => false;
protected override bool IsControlElementCore() => false;
private void PopupOpenedClosed(object sender, EventArgs e)
{
// This is golden. We're following WPF's automation peer API here where the
// parent of a peer is set when another peer returns it as a child. We want to
// add the popup root as a child of the popup, so we need to return it as a
// child right? Yeah except invalidating children doesn't automatically cause
// UIA to re-read the children meaning that the parent doesn't get set. So the
// MAIN MECHANISM FOR PARENTING CONTROLS IS BROKEN WITH THE ONLY AUTOMATION API
// IT WAS WRITTEN FOR. Luckily WPF provides an escape-hatch by exposing the
// TrySetParent API internally to work around this. We're exposing it publicly
// to shame whoever came up with this abomination of an API.
GetPopupRoot()?.TrySetParent(this);
InvalidateChildren();
}
private AutomationPeer? GetPopupRoot()
{
var popupRoot = ((IVisualTreeHost)Owner).Root as Control;
return popupRoot is object ? GetOrCreatePeer(popupRoot) : null;
}
}
}

7
src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs

@ -21,6 +21,13 @@ namespace Avalonia.Automation.Peers
protected override bool IsContentElementCore() => false;
protected override bool IsControlElementCore() => false;
protected override AutomationPeer? GetParentCore()
{
var parent = base.GetParentCore();
return parent;
}
private void OnOpened(object sender, EventArgs e)
{
((PopupRoot)Owner).Opened -= OnOpened;

5
src/Avalonia.Controls/Primitives/Popup.cs

@ -516,6 +516,11 @@ namespace Avalonia.Controls.Primitives
Close();
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new PopupAutomationPeer(factory, this);
}
private static IDisposable SubscribeToEventHandler<T, TEventHandler>(T target, TEventHandler handler, Action<T, TEventHandler> subscribe, Action<T, TEventHandler> unsubscribe)
{
subscribe(target, handler);

11
src/Windows/Avalonia.Win32/Automation/AutomationNode.cs

@ -82,8 +82,8 @@ namespace Avalonia.Win32.Automation
UiaCoreProviderApi.UiaRaiseStructureChangedEvent(
this,
StructureChangeType.ChildrenInvalidated,
_runtimeId,
_runtimeId.Length);
null,
0);
}
public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue)
@ -156,6 +156,11 @@ namespace Avalonia.Win32.Automation
return null;
}
if (Peer.GetType().Name == "PopupAutomationPeer")
{
System.Diagnostics.Debug.WriteLine("Popup automation node navigate " + direction);
}
return InvokeSync(() =>
{
return direction switch
@ -269,7 +274,7 @@ namespace Avalonia.Win32.Automation
var peer = Peer;
var parent = peer.GetParent();
while (parent is object)
while (peer is not AAP.IRootProvider && parent is object)
{
peer = parent;
parent = peer.GetParent();

24
tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs

@ -9,32 +9,44 @@ namespace Avalonia.IntegrationTests.Win32
private WindowsDriver<WindowsElement> _session;
public ButtonTests(TestAppFixture fixture) => _session = fixture.Session;
[Fact]
public void DisabledButton()
{
SelectTab();
var button = _session.FindElementByAccessibilityId("DisabledButton");
Assert.Equal("Disabled Button", button.Text);
Assert.False(button.Enabled);
}
[Fact]
public void BasicButton()
{
SelectButtonTab();
SelectTab();
var button = _session.FindElementByAccessibilityId("BasicButton");
Assert.Equal("Basic Button", button.Text);
Assert.True(button.Enabled);
}
[Fact]
public void ButtonWithTextBlock()
{
SelectButtonTab();
SelectTab();
var button = _session.FindElementByAccessibilityId("ButtonWithTextBlock");
Assert.Equal("Button with TextBlock", button.Text);
}
private WindowsElement SelectButtonTab()
private WindowsElement SelectTab()
{
var tabs = _session.FindElementByAccessibilityId("MainTabs");
var buttonTab = tabs.FindElementByName("Button");
buttonTab.Click();
return (WindowsElement)buttonTab;
var tab = tabs.FindElementByName("Button");
tab.Click();
return (WindowsElement)tab;
}
}
}

55
tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs

@ -0,0 +1,55 @@
using OpenQA.Selenium.Appium.Windows;
using Xunit;
namespace Avalonia.IntegrationTests.Win32
{
[Collection("IntegrationTestApp collection")]
public class ComboBoxTests
{
private WindowsDriver<WindowsElement> _session;
public ComboBoxTests(TestAppFixture fixture) => _session = fixture.Session;
[Fact]
public void UnselectedComboBox()
{
SelectTab();
var comboBox = _session.FindElementByAccessibilityId("UnselectedComboBox");
Assert.Equal(string.Empty, comboBox.Text);
comboBox.Click();
comboBox.FindElementByName("Bar").Click();
Assert.Equal("Bar", comboBox.Text);
}
[Fact]
public void SelectedIndex0ComboBox()
{
SelectTab();
var comboBox = _session.FindElementByAccessibilityId("SelectedIndex0ComboBox");
Assert.Equal("Foo", comboBox.Text);
}
[Fact]
public void SelectedIndex1ComboBox()
{
SelectTab();
var comboBox = _session.FindElementByAccessibilityId("SelectedIndex1ComboBox");
Assert.Equal("Bar", comboBox.Text);
}
private WindowsElement SelectTab()
{
var tabs = _session.FindElementByAccessibilityId("MainTabs");
var tab = tabs.FindElementByName("ComboBox");
tab.Click();
return (WindowsElement)tab;
}
}
}
Loading…
Cancel
Save