diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
index 353e01dca70..58e8aef1e97 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -56,6 +56,16 @@
+
+
+ Sample RadioButton
+
+ Three States: Option 1
+ Three States: Option 2
+
+
+
+
Unchecked
diff --git a/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs
new file mode 100644
index 00000000000..b0f83c1f2ad
--- /dev/null
+++ b/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs
@@ -0,0 +1,63 @@
+using System;
+using Avalonia.Automation;
+using Avalonia.Automation.Peers;
+using Avalonia.Automation.Provider;
+
+namespace Avalonia.Controls.Automation.Peers
+{
+ public class RadioButtonAutomationPeer : ToggleButtonAutomationPeer, ISelectionItemProvider
+ {
+ public RadioButtonAutomationPeer(RadioButton owner) : base(owner)
+ {
+ owner.PropertyChanged += (a, e) =>
+ {
+ if (e.Property == RadioButton.IsCheckedProperty)
+ {
+ RaiseToggleStatePropertyChangedEvent((bool?)e.OldValue, (bool?)e.NewValue);
+ }
+ };
+ }
+
+ override protected string GetClassNameCore()
+ {
+ return "RadioButton";
+ }
+
+ override protected AutomationControlType GetAutomationControlTypeCore()
+ {
+ return AutomationControlType.RadioButton;
+ }
+
+ public bool IsSelected => ((RadioButton)Owner).IsChecked == true;
+
+ public ISelectionProvider? SelectionContainer => null;
+
+ public void AddToSelection()
+ {
+ if (((RadioButton)Owner).IsChecked != true)
+ throw new InvalidOperationException("Operation cannot be performed");
+ }
+
+ public void RemoveFromSelection()
+ {
+ if (((RadioButton)Owner).IsChecked == true)
+ throw new InvalidOperationException("Operation cannot be performed");
+ }
+
+ public void Select()
+ {
+ if (!IsEnabled())
+ throw new InvalidOperationException("Element is disabled thus it cannot be selected");
+
+ ((RadioButton)Owner).IsChecked = true;
+ }
+
+ internal virtual void RaiseToggleStatePropertyChangedEvent(bool? oldValue, bool? newValue)
+ {
+ RaisePropertyChangedEvent(
+ SelectionItemPatternIdentifiers.IsSelectedProperty,
+ oldValue == true,
+ newValue == true);
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/Automation/SelectionItemPatternIdentifiers.cs b/src/Avalonia.Controls/Automation/SelectionItemPatternIdentifiers.cs
new file mode 100644
index 00000000000..418ae1f1fe3
--- /dev/null
+++ b/src/Avalonia.Controls/Automation/SelectionItemPatternIdentifiers.cs
@@ -0,0 +1,16 @@
+using Avalonia.Automation.Provider;
+
+namespace Avalonia.Automation
+{
+ ///
+ /// Contains values used as identifiers by .
+ ///
+ public static class SelectionItemPatternIdentifiers
+ {
+ /// Indicates the element is currently selected.
+ public static AutomationProperty IsSelectedProperty { get; } = new AutomationProperty();
+
+ /// Indicates the element is currently selected.
+ public static AutomationProperty SelectionContainerProperty { get; } = new AutomationProperty();
+ }
+}
diff --git a/src/Avalonia.Controls/RadioButton.cs b/src/Avalonia.Controls/RadioButton.cs
index b87be34a9d0..87772aced70 100644
--- a/src/Avalonia.Controls/RadioButton.cs
+++ b/src/Avalonia.Controls/RadioButton.cs
@@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
+using Avalonia.Automation.Peers;
+using Avalonia.Controls.Automation.Peers;
using Avalonia.Controls.Primitives;
using Avalonia.Reactive;
using Avalonia.Rendering;
@@ -147,6 +149,11 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e
}
}
+ protected override AutomationPeer OnCreateAutomationPeer()
+ {
+ return new RadioButtonAutomationPeer(this);
+ }
+
private void SetGroupName(string? newGroupName)
{
var oldGroupName = GroupName;
diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
index 48ebd4068e8..3eeedc4b5da 100644
--- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
+++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
@@ -41,6 +41,8 @@ internal partial class AutomationNode : MarshalByRefObject,
{ SelectionPatternIdentifiers.CanSelectMultipleProperty, UiaPropertyId.SelectionCanSelectMultiple },
{ SelectionPatternIdentifiers.IsSelectionRequiredProperty, UiaPropertyId.SelectionIsSelectionRequired },
{ SelectionPatternIdentifiers.SelectionProperty, UiaPropertyId.SelectionSelection },
+ { SelectionItemPatternIdentifiers.IsSelectedProperty, UiaPropertyId.SelectionItemIsSelected },
+ { SelectionItemPatternIdentifiers.SelectionContainerProperty, UiaPropertyId.SelectionItemSelectionContainer }
};
private static ConditionalWeakTable s_nodes = new();
diff --git a/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs b/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs
new file mode 100644
index 00000000000..5bd0a051554
--- /dev/null
+++ b/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using OpenQA.Selenium.Appium;
+using Xunit;
+
+namespace Avalonia.IntegrationTests.Appium
+{
+ [Collection("Default")]
+ public class RadioButtonTests
+ {
+ private readonly AppiumDriver _session;
+
+ public RadioButtonTests(TestAppFixture fixture)
+ {
+ _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");
+ Assert.False(button.GetIsChecked());
+ button.Click();
+ Assert.True(button.GetIsChecked());
+ }
+
+ [Fact]
+ public void ThreeState_RadioButton_IsChecked_False_When_Other_ThreeState_RadioButton_Checked()
+ {
+ var button1 = _session.FindElementByAccessibilityId("ThreeStatesRadioButton1");
+ var button2 = _session.FindElementByAccessibilityId("ThreeStatesRadioButton2");
+ Assert.True(button1.GetIsChecked());
+ Assert.False(button2.GetIsChecked());
+ button2.Click();
+ Assert.False(button1.GetIsChecked());
+ Assert.True(button2.GetIsChecked());
+ }
+
+ }
+}