Skip to content

Commit 394ef5a

Browse files
authored
root option in Page.Accessibility.SnapshotAsync() (#1132)
* `root` option in Page.Accessibility.SnapshotAsync() * CodeFactor * Add missing ConfigureAwait
1 parent d109124 commit 394ef5a

File tree

5 files changed

+173
-4
lines changed

5 files changed

+173
-4
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using System.Threading.Tasks;
2+
using PuppeteerSharp.PageAccessibility;
3+
using Xunit;
4+
using Xunit.Abstractions;
5+
6+
namespace PuppeteerSharp.Tests.AccesibilityTests
7+
{
8+
[Collection("PuppeteerLoaderFixture collection")]
9+
public class RootOptionTests : PuppeteerPageBaseTest
10+
{
11+
public RootOptionTests(ITestOutputHelper output) : base(output)
12+
{
13+
}
14+
15+
[Fact]
16+
public async Task ShouldWorkAButton()
17+
{
18+
await Page.SetContentAsync("<button>My Button</button>");
19+
20+
var button = await Page.QuerySelectorAsync("button");
21+
Assert.Equal(
22+
new SerializedAXNode
23+
{
24+
Role = "button",
25+
Name = "My Button"
26+
},
27+
await Page.Accessibility.SnapshotAsync(new AccessibilitySnapshotOptions { Root = button }));
28+
}
29+
30+
[Fact]
31+
public async Task ShouldWorkAnInput()
32+
{
33+
await Page.SetContentAsync("<input title='My Input' value='My Value'>");
34+
35+
var input = await Page.QuerySelectorAsync("input");
36+
Assert.Equal(
37+
new SerializedAXNode
38+
{
39+
Role = "textbox",
40+
Name = "My Input",
41+
Value = "My Value"
42+
},
43+
await Page.Accessibility.SnapshotAsync(new AccessibilitySnapshotOptions { Root = input }));
44+
}
45+
46+
[Fact]
47+
public async Task ShouldWorkAMenu()
48+
{
49+
await Page.SetContentAsync(@"
50+
<div role=""menu"" title=""My Menu"" >
51+
<div role=""menuitem"">First Item</div>
52+
<div role=""menuitem"">Second Item</div>
53+
<div role=""menuitem"">Third Item</div>
54+
</div>
55+
");
56+
57+
var menu = await Page.QuerySelectorAsync("div[role=\"menu\"]");
58+
Assert.Equal(
59+
new SerializedAXNode
60+
{
61+
Role = "menu",
62+
Name = "My Menu",
63+
Children = new[]
64+
{
65+
new SerializedAXNode
66+
{
67+
Role = "menuitem",
68+
Name = "First Item"
69+
},
70+
new SerializedAXNode
71+
{
72+
Role = "menuitem",
73+
Name = "Second Item"
74+
},
75+
new SerializedAXNode
76+
{
77+
Role = "menuitem",
78+
Name = "Third Item"
79+
}
80+
}
81+
},
82+
await Page.Accessibility.SnapshotAsync(new AccessibilitySnapshotOptions { Root = menu }));
83+
}
84+
85+
[Fact]
86+
public async Task ShouldReturnNullWhenTheElementIsNoLongerInDOM()
87+
{
88+
await Page.SetContentAsync("<button>My Button</button>");
89+
var button = await Page.QuerySelectorAsync("button");
90+
await Page.EvaluateFunctionAsync("button => button.remove()", button);
91+
Assert.Null(await Page.Accessibility.SnapshotAsync(new AccessibilitySnapshotOptions { Root = button }));
92+
}
93+
94+
[Fact]
95+
public async Task ShouldSupportTheInterestingOnlyOption()
96+
{
97+
await Page.SetContentAsync("<div><button>My Button</button></div>");
98+
var div = await Page.QuerySelectorAsync("div");
99+
Assert.Null(await Page.Accessibility.SnapshotAsync(new AccessibilitySnapshotOptions
100+
{
101+
Root = div
102+
}));
103+
Assert.Equal(
104+
new SerializedAXNode
105+
{
106+
Role = "GenericContainer",
107+
Name = "",
108+
Children = new[]
109+
{
110+
new SerializedAXNode
111+
{
112+
Role = "button",
113+
Name = "My Button"
114+
}
115+
}
116+
},
117+
await Page.Accessibility.SnapshotAsync(new AccessibilitySnapshotOptions
118+
{
119+
Root = div,
120+
InterestingOnly = false
121+
}));
122+
}
123+
}
124+
}

lib/PuppeteerSharp/Messaging/AccessibilityGetFullAXTreeResponse.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class AXTreeNode
1616
public AXTreePropertyValue Description { get; set; }
1717
public AXTreePropertyValue Role { get; set; }
1818
public IEnumerable<AXTreeProperty> Properties { get; set; }
19+
public int BackendDOMNodeId { get; set; }
1920
}
2021

2122
public class AXTreeProperty

lib/PuppeteerSharp/PageAccessibility/AXNode.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,23 @@ private bool HasFocusableChild()
6868
return _cachedHasFocusableChild.Value;
6969
}
7070

71+
internal AXNode Find(Func<AXNode, bool> predicate)
72+
{
73+
if (predicate(this))
74+
{
75+
return this;
76+
}
77+
foreach (var child in Children)
78+
{
79+
var result = child.Find(predicate);
80+
if (result != null)
81+
{
82+
return result;
83+
}
84+
}
85+
return null;
86+
}
87+
7188
internal bool IsLeafNode()
7289
{
7390
if (Children.Count == 0)

lib/PuppeteerSharp/PageAccessibility/Accessibility.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,38 @@ public async Task<SerializedAXNode> SnapshotAsync(AccessibilitySnapshotOptions o
3434
{
3535
var response = await _client.SendAsync<AccessibilityGetFullAXTreeResponse>("Accessibility.getFullAXTree").ConfigureAwait(false);
3636
var nodes = response.Nodes;
37-
var root = AXNode.CreateTree(nodes);
37+
int? backendNodeId = null;
38+
if (options?.Root != null)
39+
{
40+
var node = await _client.SendAsync<DomDescribeNodeResponse>("DOM.describeNode", new DomDescribeNodeRequest
41+
{
42+
ObjectId = options.Root.RemoteObject.ObjectId
43+
}).ConfigureAwait(false);
44+
backendNodeId = node.Node.BackendNodeId;
45+
}
46+
var defaultRoot = AXNode.CreateTree(nodes);
47+
var needle = defaultRoot;
48+
if (backendNodeId.HasValue)
49+
{
50+
needle = defaultRoot.Find(node => node.Payload.BackendDOMNodeId == backendNodeId);
51+
if (needle == null)
52+
{
53+
return null;
54+
}
55+
}
56+
3857
if (options?.InterestingOnly == false)
3958
{
40-
return SerializeTree(root)[0];
59+
return SerializeTree(needle)[0];
4160
}
4261

4362
var interestingNodes = new List<AXNode>();
44-
CollectInterestingNodes(interestingNodes, root, false);
45-
return SerializeTree(root, interestingNodes)[0];
63+
CollectInterestingNodes(interestingNodes, defaultRoot, false);
64+
if (!interestingNodes.Contains(needle))
65+
{
66+
return null;
67+
}
68+
return SerializeTree(needle, interestingNodes)[0];
4669
}
4770

4871
private void CollectInterestingNodes(List<AXNode> collection, AXNode node, bool insideControl)

lib/PuppeteerSharp/PageAccessibility/AccessibilitySnapshotOptions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,9 @@ public class AccessibilitySnapshotOptions
1010
/// Prune uninteresting nodes from the tree. Defaults to true.
1111
/// </summary>
1212
public bool InterestingOnly { get; set; } = true;
13+
/// <summary>
14+
/// The root DOM element for the snapshot. Defaults to the whole page.
15+
/// </summary>
16+
public ElementHandle Root { get; set; }
1317
}
1418
}

0 commit comments

Comments
 (0)