diff --git a/lib/PuppeteerSharp.Tests/AccessibilityTests/AccessibilityTests.cs b/lib/PuppeteerSharp.Tests/AccessibilityTests/AccessibilityTests.cs
new file mode 100644
index 000000000..87b97e3e9
--- /dev/null
+++ b/lib/PuppeteerSharp.Tests/AccessibilityTests/AccessibilityTests.cs
@@ -0,0 +1,340 @@
+using System.Threading.Tasks;
+using PuppeteerSharp.PageAccessibility;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace PuppeteerSharp.Tests.AccesibilityTests
+{
+ [Collection("PuppeteerLoaderFixture collection")]
+ public class AccesibilityTests : PuppeteerPageBaseTest
+ {
+ public AccesibilityTests(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public async Task ShouldWork()
+ {
+ await Page.SetContentAsync(@"
+
+ Accessibility Test
+
+
+ Hello World
+ Inputs
+
+
+
+
+
+
+ This is a description!
+
+
+ ");
+ Assert.Equal(
+ new SerializedAXNode
+ {
+ Role = "WebArea",
+ Name = "Accessibility Test",
+ Children = new SerializedAXNode[]
+ {
+ new SerializedAXNode
+ {
+ Role = "text",
+ Name = "Hello World"
+ },
+ new SerializedAXNode
+ {
+ Role = "heading",
+ Name = "Inputs",
+ Level = 1
+ },
+ new SerializedAXNode{
+ Role = "textbox",
+ Name = "Empty input",
+ Focused = true
+ },
+ new SerializedAXNode{
+ Role = "textbox",
+ Name = "readonly input",
+ Readonly = true
+ },
+ new SerializedAXNode{
+ Role = "textbox",
+ Name = "disabled input",
+ Disabled= true
+ },
+ new SerializedAXNode{
+ Role = "textbox",
+ Name = "Input with whitespace",
+ Value= " "
+ },
+ new SerializedAXNode{
+ Role = "textbox",
+ Name = "",
+ Value= "value only"
+ },
+ new SerializedAXNode{
+ Role = "textbox",
+ Name = "placeholder",
+ Value= "and a value"
+ },
+ new SerializedAXNode{
+ Role = "textbox",
+ Name = "placeholder",
+ Value= "and a value",
+ Description= "This is a description!"},
+ new SerializedAXNode{
+ Role= "combobox",
+ Name= "",
+ Value= "First Option",
+ Children= new SerializedAXNode[]{
+ new SerializedAXNode
+ {
+ Role = "menuitem",
+ Name = "First Option",
+ Selected= true
+ },
+ new SerializedAXNode
+ {
+ Role = "menuitem",
+ Name = "Second Option"
+ }
+ }
+ }
+ }
+ },
+ await Page.Accessibility.SnapshotAsync());
+ }
+
+ [Fact]
+ public async Task ShouldReportUninterestingNodes()
+ {
+ await Page.SetContentAsync("");
+ Assert.Equal(
+ new SerializedAXNode
+ {
+ Role = "textbox",
+ Name = "",
+ Value = "hi",
+ Focused = true,
+ Multiline = true,
+ Children = new SerializedAXNode[]
+ {
+ new SerializedAXNode
+ {
+ Role = "GenericContainer",
+ Name = "",
+ Children = new SerializedAXNode[]
+ {
+ new SerializedAXNode
+ {
+ Role = "text",
+ Name = "hi"
+ }
+ }
+ }
+ }
+ },
+ FindFocusedNode(await Page.Accessibility.SnapshotAsync(new AccessibilitySnapshotOptions
+ {
+ InterestingOnly = false
+ })));
+ }
+
+ [Fact]
+ public async Task ShouldNotReportTextNodesInsideControls()
+ {
+ await Page.SetContentAsync(@"
+ ");
+ Assert.Equal(
+ new SerializedAXNode
+ {
+ Role = "WebArea",
+ Name = "",
+ Children = new SerializedAXNode[]
+ {
+ new SerializedAXNode
+ {
+ Role = "tab",
+ Name = "Tab1",
+ Selected = true
+ },
+ new SerializedAXNode
+ {
+ Role = "tab",
+ Name = "Tab2"
+ }
+ }
+ },
+ await Page.Accessibility.SnapshotAsync());
+ }
+
+ [Fact]
+ public async Task RichTextEditableFieldsShouldHaveChildren()
+ {
+ await Page.SetContentAsync(@"
+
+ Edit this image:
+
");
+ Assert.Equal(
+ new SerializedAXNode
+ {
+ Role = "GenericContainer",
+ Name = "",
+ Value = "Edit this image: ",
+ Children = new SerializedAXNode[]
+ {
+ new SerializedAXNode
+ {
+ Role = "text",
+ Name = "Edit this image:"
+ },
+ new SerializedAXNode
+ {
+ Role = "img",
+ Name = "my fake image"
+ }
+ }
+ },
+ (await Page.Accessibility.SnapshotAsync()).Children[0]);
+ }
+
+ [Fact]
+ public async Task RichTextEditableFieldsWithRoleShouldHaveChildren()
+ {
+ await Page.SetContentAsync(@"
+
+ Edit this image:
+
");
+ Assert.Equal(
+ new SerializedAXNode
+ {
+ Role = "textbox",
+ Name = "",
+ Value = "Edit this image: ",
+ Children = new SerializedAXNode[]
+ {
+ new SerializedAXNode
+ {
+ Role = "text",
+ Name = "Edit this image:"
+ },
+ new SerializedAXNode
+ {
+ Role = "img",
+ Name = "my fake image"
+ }
+ }
+ },
+ (await Page.Accessibility.SnapshotAsync()).Children[0]);
+ }
+
+ [Fact]
+ public async Task PlainTextFieldWithRoleShouldNotHaveChildren()
+ {
+ await Page.SetContentAsync("Edit this image:
");
+ Assert.Equal(
+ new SerializedAXNode
+ {
+ Role = "textbox",
+ Name = "",
+ Value = "Edit this image:"
+ },
+ (await Page.Accessibility.SnapshotAsync()).Children[0]);
+ }
+
+ [Fact]
+ public async Task PlainTextFieldWithTabindexAndWithoutRoleShouldNotHaveContent()
+ {
+ await Page.SetContentAsync("Edit this image:
");
+ Assert.Equal(
+ new SerializedAXNode
+ {
+ Role = "textbox",
+ Name = "",
+ Value = "Edit this image:"
+ },
+ (await Page.Accessibility.SnapshotAsync()).Children[0]);
+ }
+
+ [Fact]
+ public async Task NonEditableTextboxWithRoleAndTabIndexAndLabelShouldNotHaveChildren()
+ {
+ await Page.SetContentAsync(@"
+
+ this is the inner content
+
+
");
+ Assert.Equal(
+ new SerializedAXNode
+ {
+ Role = "textbox",
+ Name = "my favorite textbox",
+ Value = "this is the inner content "
+ },
+ (await Page.Accessibility.SnapshotAsync()).Children[0]);
+ }
+
+ [Fact]
+ public async Task CheckboxWithAndTabIndexAndLabelShouldNotHaveChildren()
+ {
+ await Page.SetContentAsync(@"
+
+ this is the inner content
+
+
");
+ Assert.Equal(
+ new SerializedAXNode
+ {
+ Role = "checkbox",
+ Name = "my favorite checkbox",
+ Checked = CheckedState.True
+ },
+ (await Page.Accessibility.SnapshotAsync()).Children[0]);
+ }
+
+ [Fact]
+ public async Task CheckboxWithoutLabelShouldNotHaveChildren()
+ {
+ await Page.SetContentAsync(@"
+
+ this is the inner content
+
+
");
+ Assert.Equal(
+ new SerializedAXNode
+ {
+ Role = "checkbox",
+ Name = "this is the inner content yo",
+ Checked = CheckedState.True
+ },
+ (await Page.Accessibility.SnapshotAsync()).Children[0]);
+ }
+
+ private SerializedAXNode FindFocusedNode(SerializedAXNode serializedAXNode)
+ {
+ if (serializedAXNode.Focused)
+ {
+ return serializedAXNode;
+ }
+ foreach (var item in serializedAXNode.Children)
+ {
+ var focusedChild = FindFocusedNode(item);
+ if (focusedChild != null)
+ {
+ return focusedChild;
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/lib/PuppeteerSharp/Messaging/AccessibilityGetFullAXTreeResponse.cs b/lib/PuppeteerSharp/Messaging/AccessibilityGetFullAXTreeResponse.cs
new file mode 100644
index 000000000..49035e27c
--- /dev/null
+++ b/lib/PuppeteerSharp/Messaging/AccessibilityGetFullAXTreeResponse.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace PuppeteerSharp.Messaging
+{
+ internal class AccessibilityGetFullAXTreeResponse
+ {
+ [JsonProperty("nodes")]
+ public IEnumerable Nodes { get; set; }
+
+ public class AXTreeNode
+ {
+ [JsonProperty("nodeId")]
+ public string NodeId { get; set; }
+ [JsonProperty("childIds")]
+ public IEnumerable ChildIds { get; set; }
+ [JsonProperty("name")]
+ public AXTreePropertyValue Name { get; set; }
+ [JsonProperty("value")]
+ public AXTreePropertyValue Value { get; set; }
+ [JsonProperty("description")]
+ public AXTreePropertyValue Description { get; set; }
+ [JsonProperty("role")]
+ public AXTreePropertyValue Role { get; set; }
+ [JsonProperty("properties")]
+ public IEnumerable Properties { get; set; }
+ }
+
+ public class AXTreeProperty
+ {
+ [JsonProperty("name")]
+ public string Name { get; internal set; }
+ [JsonProperty("value")]
+ public AXTreePropertyValue Value { get; set; }
+ }
+
+ public class AXTreePropertyValue
+ {
+ [JsonProperty("type")]
+ public string Type { get; set; }
+ [JsonProperty("value")]
+ public JToken Value { get; set; }
+ }
+ }
+}
diff --git a/lib/PuppeteerSharp/Page.cs b/lib/PuppeteerSharp/Page.cs
index 151bc537f..8c0150268 100644
--- a/lib/PuppeteerSharp/Page.cs
+++ b/lib/PuppeteerSharp/Page.cs
@@ -14,6 +14,7 @@
using PuppeteerSharp.Media;
using PuppeteerSharp.Messaging;
using PuppeteerSharp.Mobile;
+using PuppeteerSharp.PageAccessibility;
using PuppeteerSharp.PageCoverage;
namespace PuppeteerSharp
@@ -71,7 +72,7 @@ private Page(
_pageBindings = new Dictionary();
_workers = new Dictionary();
_logger = Client.Connection.LoggerFactory.CreateLogger();
-
+ Accessibility = new Accessibility(client);
_ignoreHTTPSErrors = ignoreHTTPSErrors;
_screenshotTaskQueue = screenshotTaskQueue;
@@ -298,6 +299,11 @@ public int DefaultNavigationTimeout
///
public bool IsClosed { get; private set; }
+ ///
+ /// Gets the accessibility.
+ ///
+ public Accessibility Accessibility { get; }
+
internal bool JavascriptEnabled { get; set; } = true;
#endregion
diff --git a/lib/PuppeteerSharp/PageAccessibility/AXNode.cs b/lib/PuppeteerSharp/PageAccessibility/AXNode.cs
new file mode 100644
index 000000000..ce1f2d7b7
--- /dev/null
+++ b/lib/PuppeteerSharp/PageAccessibility/AXNode.cs
@@ -0,0 +1,244 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using PuppeteerSharp.Messaging;
+using Newtonsoft.Json.Linq;
+using PuppeteerSharp.Helpers;
+
+namespace PuppeteerSharp.PageAccessibility
+{
+ internal class AXNode
+ {
+ internal AccessibilityGetFullAXTreeResponse.AXTreeNode Payload { get; }
+ public List Children { get; }
+ public bool Focusable { get; set; }
+
+ private readonly string _name;
+ private string _role;
+ private readonly bool _richlyEditable;
+ private readonly bool _editable;
+ private readonly bool _expanded;
+ private bool? _cachedHasFocusableChild;
+
+ public AXNode(AccessibilityGetFullAXTreeResponse.AXTreeNode payload)
+ {
+ Payload = payload;
+ Children = new List();
+
+ _name = payload.Name != null ? payload.Name.Value.ToObject() : string.Empty;
+ _role = payload.Role != null ? payload.Role.Value.ToObject() : "Unknown";
+
+ _richlyEditable = payload.Properties.FirstOrDefault(p => p.Name == "editable")?.Value.Value.ToObject() == "richtext";
+ _editable |= _richlyEditable;
+ _expanded = payload.Properties.FirstOrDefault(p => p.Name == "expanded")?.Value.Value.ToObject() == true;
+ Focusable = payload.Properties.FirstOrDefault(p => p.Name == "focusable")?.Value.Value.ToObject() == true;
+ }
+
+ internal static AXNode CreateTree(IEnumerable payloads)
+ {
+ var nodeById = new Dictionary();
+ foreach (var payload in payloads)
+ {
+ nodeById[payload.NodeId] = new AXNode(payload);
+ }
+ foreach (var node in nodeById.Values)
+ {
+ foreach (var childId in node.Payload.ChildIds)
+ {
+ node.Children.Add(nodeById[childId]);
+ }
+ }
+ return nodeById.Values.FirstOrDefault();
+ }
+
+ private bool IsPlainTextField()
+ => !_richlyEditable && (_editable || _role == "textbox" || _role == "ComboBox" || _role == "searchbox");
+
+ private bool IsTextOnlyObject()
+ => _role == "LineBreak" ||
+ _role == "text" ||
+ _role == "InlineTextBox";
+
+ private bool HasFocusableChild()
+ {
+ if (!_cachedHasFocusableChild.HasValue)
+ {
+ _cachedHasFocusableChild = Children.Any(c => c.Focusable || c.HasFocusableChild());
+ }
+ return _cachedHasFocusableChild.Value;
+ }
+
+ internal bool IsLeafNode()
+ {
+ if (Children.Count == 0)
+ {
+ return true;
+ }
+
+ // These types of objects may have children that we use as internal
+ // implementation details, but we want to expose them as leaves to platform
+ // accessibility APIs because screen readers might be confused if they find
+ // any children.
+ if (IsPlainTextField() || IsTextOnlyObject())
+ {
+ return true;
+ }
+
+ // Roles whose children are only presentational according to the ARIA and
+ // HTML5 Specs should be hidden from screen readers.
+ // (Note that whilst ARIA buttons can have only presentational children, HTML5
+ // buttons are allowed to have content.)
+ switch (_role)
+ {
+ case "doc-cover":
+ case "graphics-symbol":
+ case "img":
+ case "Meter":
+ case "scrollbar":
+ case "slider":
+ case "separator":
+ case "progressbar":
+ return true;
+ }
+
+ // Here and below: Android heuristics
+ if (HasFocusableChild())
+ {
+ return false;
+ }
+ if (Focusable && !string.IsNullOrEmpty(_name))
+ {
+ return true;
+ }
+ if (_role == "heading" && !string.IsNullOrEmpty(_name))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ internal bool IsControl()
+ {
+ switch (_role)
+ {
+ case "button":
+ case "checkbox":
+ case "ColorWell":
+ case "combobox":
+ case "DisclosureTriangle":
+ case "listbox":
+ case "menu":
+ case "menubar":
+ case "menuitem":
+ case "menuitemcheckbox":
+ case "menuitemradio":
+ case "radio":
+ case "scrollbar":
+ case "searchbox":
+ case "slider":
+ case "spinbutton":
+ case "switch":
+ case "tab":
+ case "textbox":
+ case "tree":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal bool IsInteresting(bool insideControl)
+ {
+ if (_role == "Ignored")
+ {
+ return false;
+ }
+
+ if (Focusable || _richlyEditable)
+ {
+ return true;
+ }
+ // If it's not focusable but has a control role, then it's interesting.
+ if (IsControl())
+ {
+ return true;
+ }
+ // A non focusable child of a control is not interesting
+ if (insideControl)
+ {
+ return false;
+ }
+ return IsLeafNode() && !string.IsNullOrEmpty(_name);
+ }
+
+ internal SerializedAXNode Serialize()
+ {
+ var properties = new Dictionary();
+ foreach (var property in Payload.Properties)
+ {
+ properties[property.Name.ToLower()] = property.Value.Value;
+ }
+
+ if (Payload.Name != null)
+ {
+ properties["name"] = Payload.Name.Value;
+ }
+ if (Payload.Value != null)
+ {
+ properties["value"] = Payload.Value.Value;
+ }
+ if (Payload.Description != null)
+ {
+ properties["description"] = Payload.Description.Value;
+ }
+
+ var node = new SerializedAXNode
+ {
+ Role = _role,
+ Name = properties.GetValueOrDefault("name")?.ToObject(),
+ Value = properties.GetValueOrDefault("value")?.ToObject(),
+ Description = properties.GetValueOrDefault("description")?.ToObject(),
+ KeyShortcuts = properties.GetValueOrDefault("keyshortcuts")?.ToObject(),
+ RoleDescription = properties.GetValueOrDefault("roledescription")?.ToObject(),
+ ValueText = properties.GetValueOrDefault("valuetext")?.ToObject(),
+ Disabled = properties.GetValueOrDefault("disabled")?.ToObject() ?? false,
+ Expanded = properties.GetValueOrDefault("expanded")?.ToObject() ?? false,
+ // WebArea"s treat focus differently than other nodes. They report whether their frame has focus,
+ // not whether focus is specifically on the root node.
+ Focused = properties.GetValueOrDefault("focused")?.ToObject() == true && _role != "WebArea",
+ Modal = properties.GetValueOrDefault("modal")?.ToObject() ?? false,
+ Multiline = properties.GetValueOrDefault("multiline")?.ToObject() ?? false,
+ Multiselectable = properties.GetValueOrDefault("multiselectable")?.ToObject() ?? false,
+ Readonly = properties.GetValueOrDefault("readonly")?.ToObject() ?? false,
+ Required = properties.GetValueOrDefault("required")?.ToObject() ?? false,
+ Selected = properties.GetValueOrDefault("selected")?.ToObject() ?? false,
+ Checked = GetCheckedState(properties.GetValueOrDefault("checked")?.ToObject()),
+ Pressed = GetCheckedState(properties.GetValueOrDefault("pressed")?.ToObject()),
+ Level = properties.GetValueOrDefault("level")?.ToObject() ?? 0,
+ ValueMax = properties.GetValueOrDefault("valuemax")?.ToObject() ?? 0,
+ ValueMin = properties.GetValueOrDefault("valuemin")?.ToObject() ?? 0,
+ AutoComplete = GetIfNotFalse(properties.GetValueOrDefault("autocomplete")?.ToObject()),
+ HasPopup = GetIfNotFalse(properties.GetValueOrDefault("haspopup")?.ToObject()),
+ Invalid = GetIfNotFalse(properties.GetValueOrDefault("invalid")?.ToObject()),
+ Orientation = GetIfNotFalse(properties.GetValueOrDefault("orientation")?.ToObject())
+ };
+
+ return node;
+ }
+
+ private string GetIfNotFalse(string value) => value != null && value != "false" ? value : null;
+
+ private CheckedState GetCheckedState(string value)
+ {
+ switch (value)
+ {
+ case "mixed":
+ return CheckedState.Mixed;
+ case "true":
+ return CheckedState.True;
+ default:
+ return CheckedState.False;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/PuppeteerSharp/PageAccessibility/Accessibility.cs b/lib/PuppeteerSharp/PageAccessibility/Accessibility.cs
new file mode 100644
index 000000000..288e50732
--- /dev/null
+++ b/lib/PuppeteerSharp/PageAccessibility/Accessibility.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using PuppeteerSharp.Messaging;
+
+namespace PuppeteerSharp.PageAccessibility
+{
+ ///
+ /// The Accessibility class provides methods for inspecting Chromium's accessibility tree.
+ /// The accessibility tree is used by assistive technology such as screen readers.
+ ///
+ /// Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that might have wildly different output.
+ /// Blink - Chrome's rendering engine - has a concept of "accessibility tree", which is than translated into different platform-specific APIs.
+ /// Accessibility namespace gives users access to the Blink Accessibility Tree.
+ /// Most of the accessibility tree gets filtered out when converting from Blink AX Tree to Platform-specific AX-Tree or by screen readers themselves.
+ /// By default, Puppeteer tries to approximate this filtering, exposing only the "interesting" nodes of the tree.
+ ///
+ public class Accessibility
+ {
+ private readonly CDPSession _client;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Client.
+ public Accessibility(CDPSession client) => _client = client;
+
+ ///
+ /// Snapshots the async.
+ ///
+ /// The async.
+ /// Options.
+ public async Task SnapshotAsync(AccessibilitySnapshotOptions options = null)
+ {
+ var nodes = (await _client.SendAsync("Accessibility.getFullAXTree")).Nodes;
+ var root = AXNode.CreateTree(nodes);
+ if (options?.InterestingOnly == false)
+ {
+ return SerializeTree(root)[0];
+ }
+
+ var interestingNodes = new List();
+ CollectInterestingNodes(interestingNodes, root, false);
+ return SerializeTree(root, interestingNodes)[0];
+ }
+
+ private void CollectInterestingNodes(List collection, AXNode node, bool insideControl)
+ {
+ if (node.IsInteresting(insideControl))
+ {
+ collection.Add(node);
+ }
+ if (node.IsLeafNode())
+ {
+ return;
+ }
+ insideControl = insideControl || node.IsControl();
+ foreach (var child in node.Children)
+ {
+ CollectInterestingNodes(collection, child, insideControl);
+ }
+ }
+
+ private SerializedAXNode[] SerializeTree(AXNode node, List whitelistedNodes = null)
+ {
+ var children = new List();
+ foreach (var child in node.Children)
+ {
+ children.AddRange(SerializeTree(child, whitelistedNodes));
+ }
+ if (whitelistedNodes?.Contains(node) == false)
+ {
+ return children.ToArray();
+ }
+
+ var serializedNode = node.Serialize();
+ if (children.Count > 0)
+ {
+ serializedNode.Children = children.ToArray();
+ }
+ return new[] { serializedNode };
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/PuppeteerSharp/PageAccessibility/AccessibilitySnapshotOptions.cs b/lib/PuppeteerSharp/PageAccessibility/AccessibilitySnapshotOptions.cs
new file mode 100644
index 000000000..3a7caa47f
--- /dev/null
+++ b/lib/PuppeteerSharp/PageAccessibility/AccessibilitySnapshotOptions.cs
@@ -0,0 +1,14 @@
+namespace PuppeteerSharp.PageAccessibility
+{
+ ///
+ ///
+ ///
+ ///
+ public class AccessibilitySnapshotOptions
+ {
+ ///
+ /// Prune uninteresting nodes from the tree. Defaults to true.
+ ///
+ public bool InterestingOnly { get; set; } = true;
+ }
+}
\ No newline at end of file
diff --git a/lib/PuppeteerSharp/PageAccessibility/CheckedState.cs b/lib/PuppeteerSharp/PageAccessibility/CheckedState.cs
new file mode 100644
index 000000000..b05fc394b
--- /dev/null
+++ b/lib/PuppeteerSharp/PageAccessibility/CheckedState.cs
@@ -0,0 +1,21 @@
+namespace PuppeteerSharp.PageAccessibility
+{
+ ///
+ /// Three-state boolean. See and
+ ///
+ public enum CheckedState
+ {
+ ///
+ /// Flse.
+ ///
+ False = 0,
+ ///
+ /// True.
+ ///
+ True,
+ ///
+ /// Mixed.
+ ///
+ Mixed
+ }
+}
\ No newline at end of file
diff --git a/lib/PuppeteerSharp/PageAccessibility/SerializedAXNode.cs b/lib/PuppeteerSharp/PageAccessibility/SerializedAXNode.cs
new file mode 100644
index 000000000..51d57db34
--- /dev/null
+++ b/lib/PuppeteerSharp/PageAccessibility/SerializedAXNode.cs
@@ -0,0 +1,177 @@
+using System;
+using System.Linq;
+
+namespace PuppeteerSharp.PageAccessibility
+{
+ ///
+ /// AXNode.
+ ///
+ public class SerializedAXNode : IEquatable
+ {
+ ///
+ /// The role.
+ ///
+ public string Role { get; set; }
+ ///
+ /// A human readable name for the node.
+ ///
+ public string Name { get; set; }
+ ///
+ /// The current value of the node.
+ ///
+ public string Value { get; set; }
+ ///
+ /// An additional human readable description of the node.
+ ///
+ public string Description { get; set; }
+ ///
+ /// Keyboard shortcuts associated with this node.
+ ///
+ public string KeyShortcuts { get; set; }
+ ///
+ /// A human readable alternative to the role.
+ ///
+ public string RoleDescription { get; set; }
+ ///
+ /// A description of the current value.
+ ///
+ public string ValueText { get; set; }
+ ///
+ /// Whether the node is disabled.
+ ///
+ public bool Disabled { get; set; }
+ ///
+ /// Whether the node is expanded or collapsed.
+ ///
+ public bool Expanded { get; set; }
+ ///
+ /// Whether the node is focused.
+ ///
+ public bool Focused { get; set; }
+ ///
+ /// Whether the node is modal.
+ ///
+ public bool Modal { get; set; }
+ ///
+ /// Whether the node text input supports multiline.
+ ///
+ public bool Multiline { get; set; }
+ ///
+ /// Whether more than one child can be selected.
+ ///
+ public bool Multiselectable { get; set; }
+ ///
+ /// Whether the node is read only.
+ ///
+ public bool Readonly { get; set; }
+ ///
+ /// Whether the node is required.
+ ///
+ public bool Required { get; set; }
+ ///
+ /// Whether the node is selected in its parent node.
+ ///
+ public bool Selected { get; set; }
+ ///
+ /// Whether the checkbox is checked, or "mixed".
+ ///
+ public CheckedState Checked { get; set; }
+ ///
+ /// Whether the toggle button is checked, or "mixed".
+ ///
+ public CheckedState Pressed { get; set; }
+ ///
+ /// The level of a heading.
+ ///
+ public int Level { get; set; }
+ ///
+ /// The minimum value in a node.
+ ///
+ public int ValueMin { get; set; }
+ ///
+ /// The maximum value in a node.
+ ///
+ public int ValueMax { get; set; }
+ ///
+ /// What kind of autocomplete is supported by a control.
+ ///
+ public string AutoComplete { get; set; }
+ ///
+ /// What kind of popup is currently being shown for a node.
+ ///
+ public string HasPopup { get; set; }
+ ///
+ /// Whether and in what way this node's value is invalid.
+ ///
+ public string Invalid { get; set; }
+ ///
+ /// Whether the node is oriented horizontally or vertically.
+ ///
+ public string Orientation { get; set; }
+ ///
+ /// Child nodes of this node, if any.
+ ///
+ public SerializedAXNode[] Children { get; set; }
+
+ ///
+ public bool Equals(SerializedAXNode other)
+ => ReferenceEquals(this, other) ||
+ (
+ Role == other.Role &&
+ Name == other.Name &&
+ Value == other.Value &&
+ Description == other.Description &&
+ KeyShortcuts == other.KeyShortcuts &&
+ RoleDescription == other.RoleDescription &&
+ ValueText == other.ValueText &&
+ AutoComplete == other.AutoComplete &&
+ HasPopup == other.HasPopup &&
+ Orientation == other.Orientation &&
+ Disabled == other.Disabled &&
+ Expanded == other.Expanded &&
+ Focused == other.Focused &&
+ Modal == other.Modal &&
+ Multiline == other.Multiline &&
+ Multiselectable == other.Multiselectable &&
+ Readonly == other.Readonly &&
+ Required == other.Required &&
+ Selected == other.Selected &&
+ Checked == other.Checked &&
+ Pressed == other.Pressed &&
+ Level == other.Level &&
+ ValueMin == other.ValueMin &&
+ ValueMax == other.ValueMax &&
+ (Children == other.Children || Children.SequenceEqual(other.Children))
+ );
+
+ ///
+ public override bool Equals(object obj) => obj is SerializedAXNode s && Equals(s);
+ ///
+ public override int GetHashCode()
+ => Role.GetHashCode() ^
+ Name.GetHashCode() ^
+ Value.GetHashCode() ^
+ Description.GetHashCode() ^
+ KeyShortcuts.GetHashCode() ^
+ RoleDescription.GetHashCode() ^
+ ValueText.GetHashCode() ^
+ AutoComplete.GetHashCode() ^
+ HasPopup.GetHashCode() ^
+ Orientation.GetHashCode() ^
+ Disabled.GetHashCode() ^
+ Expanded.GetHashCode() ^
+ Focused.GetHashCode() ^
+ Modal.GetHashCode() ^
+ Multiline.GetHashCode() ^
+ Multiselectable.GetHashCode() ^
+ Readonly.GetHashCode() ^
+ Required.GetHashCode() ^
+ Selected.GetHashCode() ^
+ Pressed.GetHashCode() ^
+ Checked.GetHashCode() ^
+ Level.GetHashCode() ^
+ ValueMin.GetHashCode() ^
+ ValueMax.GetHashCode() ^
+ Children.GetHashCode();
+ }
+}
\ No newline at end of file