diff --git a/lib/PuppeteerSharp.Nunit/TestExpectations/TestExpectations.upstream.json b/lib/PuppeteerSharp.Nunit/TestExpectations/TestExpectations.upstream.json index 1ef891ffd..8ede6453e 100644 --- a/lib/PuppeteerSharp.Nunit/TestExpectations/TestExpectations.upstream.json +++ b/lib/PuppeteerSharp.Nunit/TestExpectations/TestExpectations.upstream.json @@ -480,13 +480,6 @@ "expectations": ["SKIP"], "comment": "Firefox does not support --remote-debugging-pipe argument" }, - { - "testIdPattern": "[frame.spec] Frame specs Frame.prototype.frameElement should handle shadow roots", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "https://github.com/w3c/webdriver-bidi/issues/794" - }, { "testIdPattern": "[idle_override.spec] *", "platforms": ["darwin", "linux", "win32"], diff --git a/lib/PuppeteerSharp.Tests/FrameTests/FrameElementTests.cs b/lib/PuppeteerSharp.Tests/FrameTests/FrameElementTests.cs index d6a6c3ad1..42c526762 100644 --- a/lib/PuppeteerSharp.Tests/FrameTests/FrameElementTests.cs +++ b/lib/PuppeteerSharp.Tests/FrameTests/FrameElementTests.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Threading.Tasks; using NUnit.Framework; using PuppeteerSharp.Nunit; @@ -6,6 +7,39 @@ namespace PuppeteerSharp.Tests.FrameTests { public class FrameElementTests : PuppeteerPageBaseTest { + [Test, PuppeteerTest("frame.spec", "Frame specs Frame.prototype.frameElement", "should work")] + public async Task ShouldWork() + { + await FrameUtils.AttachFrameAsync(Page, "theFrameId", TestConstants.EmptyPage); + await Page.EvaluateFunctionAsync(@"(url) => { + const frame = document.createElement('iframe'); + frame.name = 'theFrameName'; + frame.src = url; + document.body.appendChild(frame); + return new Promise(x => frame.onload = x); + }", TestConstants.EmptyPage); + + await using var mainFrameElement = await Page.MainFrame.FrameElementAsync(); + Assert.That(mainFrameElement, Is.Null); + + var childFrames = Page.Frames.Where(f => f != Page.MainFrame).ToArray(); + Assert.That(childFrames, Has.Length.EqualTo(2)); + + await using var frame1 = await childFrames[0].FrameElementAsync(); + Assert.That(frame1, Is.Not.Null); + + await using var frame2 = await childFrames[1].FrameElementAsync(); + Assert.That(frame2, Is.Not.Null); + + var name1 = await frame1.EvaluateFunctionAsync("frame => frame.id"); + var name2 = await frame2.EvaluateFunctionAsync("frame => frame.name"); + + // The order of child frames may vary, so check both possibilities + Assert.That( + new[] { name1, name2 }, + Is.EquivalentTo(new[] { "theFrameId", "theFrameName" })); + } + [Test, PuppeteerTest("frame.spec", "Frame specs Frame.prototype.frameElement", "should handle shadow roots")] public async Task ShouldHandleShadowRoots() { @@ -22,12 +56,30 @@ await Page.SetContentAsync(@" // Wait for the iframe to load inside shadow DOM await Page.WaitForFrameAsync(f => f != Page.MainFrame); Assert.That(Page.Frames, Has.Length.EqualTo(2)); - var frame = Page.Frames[1]; + var frame = Page.MainFrame.ChildFrames.First(); await using var frameElement = await frame.FrameElementAsync(); Assert.That(frameElement, Is.Not.Null); Assert.That( await frameElement.EvaluateFunctionAsync("el => el.tagName.toLocaleLowerCase()"), Is.EqualTo("iframe")); } + + [Test, PuppeteerTest("frame.spec", "Frame specs Frame.prototype.frameElement", "should return ElementHandle in the correct world")] + public async Task ShouldReturnElementHandleInTheCorrectWorld() + { + await FrameUtils.AttachFrameAsync(Page, "theFrameId", TestConstants.EmptyPage); + await Page.EvaluateFunctionAsync(@"() => { + globalThis.isMainWorld = true; + }"); + + Assert.That(Page.Frames, Has.Length.EqualTo(2)); + + var childFrame = Page.MainFrame.ChildFrames.First(); + await using var frameElement = await childFrame.FrameElementAsync(); + Assert.That(frameElement, Is.Not.Null); + + var isMainWorld = await frameElement.EvaluateFunctionAsync("() => globalThis.isMainWorld"); + Assert.That(isMainWorld, Is.True); + } } } diff --git a/lib/PuppeteerSharp/Bidi/BidiFrame.cs b/lib/PuppeteerSharp/Bidi/BidiFrame.cs index 46fd61622..624990b24 100644 --- a/lib/PuppeteerSharp/Bidi/BidiFrame.cs +++ b/lib/PuppeteerSharp/Bidi/BidiFrame.cs @@ -529,6 +529,27 @@ void OnHistoryUpdated(object sender, EventArgs args) } } + /// + public override async Task FrameElementAsync() + { + var parentFrame = ParentFrame as BidiFrame; + if (parentFrame == null) + { + return null; + } + + var nodes = await parentFrame.BrowsingContext.LocateNodesAsync( + new WebDriverBiDi.BrowsingContext.ContextLocator(Id)).ConfigureAwait(false); + + var node = nodes.FirstOrDefault(); + if (node == null) + { + return null; + } + + return BidiElementHandle.From(node, (BidiRealm)parentFrame.MainRealm) as ElementHandle; + } + internal static BidiFrame From(BidiPage parentPage, BidiFrame parentFrame, BrowsingContext browsingContext) { parentFrame = new BidiFrame(parentPage, parentFrame, browsingContext); diff --git a/lib/PuppeteerSharp/Bidi/Core/BrowsingContext.cs b/lib/PuppeteerSharp/Bidi/Core/BrowsingContext.cs index a6282aef2..6fb23fbee 100644 --- a/lib/PuppeteerSharp/Bidi/Core/BrowsingContext.cs +++ b/lib/PuppeteerSharp/Bidi/Core/BrowsingContext.cs @@ -297,7 +297,7 @@ internal async Task SetFilesAsync(WebDriverBiDi.Script.SharedReference element, await Session.Driver.Input.SetFilesAsync(parameters).ConfigureAwait(false); } - internal async Task> LocateNodesAsync(Locator locator, SharedReference[] startNodes) + internal async Task> LocateNodesAsync(Locator locator, SharedReference[] startNodes = null) { var parameters = new LocateNodesCommandParameters(Id, locator); if (startNodes?.Length > 0)