closed", StringComparison.Ordinal)).ConfigureAwait(false);
+ Assert.That(
+ await page.ContentAsync().ConfigureAwait(false),
+ Is.EqualTo("
light
"));
+ Assert.That(await page.ContentAsync(new() { IncludeShadow = true }).ConfigureAwait(false), Is.EqualTo(html));
+ }
+
[PlaywrightTest("page-set-content.spec.ts", "should respect timeout")]
[Test]
[Timeout(30_000)]
@@ -322,5 +341,23 @@ public async Task ShouldReturnEmptyContentThereIsNoIframeSrc()
string content = await child.ContentAsync().ConfigureAwait(false);
Assert.That(content, Is.EqualTo(EmptyHtml));
}
+
+ [PlaywrightTest("page-set-content.spec.ts", "should handle timeout properly")]
+ [Test]
+ [Timeout(30_000)]
+ public void ShouldHandleTimeoutProperly()
+ {
+ Assert.Ignore("Node-only: toImpl(page).mainFrame().evaluateExpression tampers console.debug in the server utility world. "
+ + "SetContent here does not use a persistent utility world or the console.debug tag.");
+ }
+
+ [PlaywrightTest("page-set-content.spec.ts", "should handle timeout properly 2")]
+ [Test]
+ [Timeout(30_000)]
+ public void ShouldHandleTimeoutProperly2()
+ {
+ Assert.Ignore("Node-only: toImpl(page).mainFrame().evaluateExpression replaces document.close in the server utility world. "
+ + "SetContent here does not use a persistent utility world.");
+ }
}
}
diff --git a/src/PlaywrightNative.Tests/PageWaitForSelector2ParityTests.cs b/src/PlaywrightNative.Tests/PageWaitForSelector2ParityTests.cs
index 8a45ad92..e6ed540c 100644
--- a/src/PlaywrightNative.Tests/PageWaitForSelector2ParityTests.cs
+++ b/src/PlaywrightNative.Tests/PageWaitForSelector2ParityTests.cs
@@ -41,6 +41,10 @@ namespace PlaywrightNative.Tests
[TestFixture]
public class PageWaitForSelector2ParityTests : PageTestEx
{
+ private const string NodeAdoptionHookReason =
+ "Node-only: drives the server-side __testHookBeforeAdoptNode between the utility-world match "
+ + "and its adoption into the main world. waitForSelector here polls the main world and has no adoption step.";
+
private const string AddElement = "tag => document.body.appendChild(document.createElement(tag))";
private static SimpleServer _ownedServer;
@@ -622,6 +626,22 @@ await page.SetContentAsync(@"
await page.WaitForSelectorAsync("div", WaitForSelectorState.Hidden).ConfigureAwait(false);
}
+ [PlaywrightTest("page-wait-for-selector-2.spec.ts", "should work when navigating before node adoption")]
+ [Test]
+ [Timeout(TestConstants.DefaultTestTimeout)]
+ public void ShouldWorkWhenNavigatingBeforeNodeAdoption()
+ {
+ Assert.Ignore(NodeAdoptionHookReason);
+ }
+
+ [PlaywrightTest("page-wait-for-selector-2.spec.ts", "should fail when navigating while on handle")]
+ [Test]
+ [Timeout(TestConstants.DefaultTestTimeout)]
+ public void ShouldFailWhenNavigatingWhileOnHandle()
+ {
+ Assert.Ignore(NodeAdoptionHookReason);
+ }
+
[PlaywrightTest("page-wait-for-selector-2.spec.ts", "should fail if element handle was detached while waiting")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
diff --git a/src/PlaywrightNative/Chromium/CRPage.cs b/src/PlaywrightNative/Chromium/CRPage.cs
index a3064605..c7f174e2 100644
--- a/src/PlaywrightNative/Chromium/CRPage.cs
+++ b/src/PlaywrightNative/Chromium/CRPage.cs
@@ -2001,9 +2001,10 @@ internal async Task
GetUtilityWorldAsync(Frame frame)
///
/// Returns the full HTML of the current document, including doctype and root element.
///
+ /// Whether to serialize open shadow roots as declarative shadow DOM.
/// The serialized document HTML.
- internal Task ContentAsync()
- => PageContent.ReadAsync(() => EvaluateAsync(PageContent.EvaluateExpression));
+ internal Task ContentAsync(bool includeShadow = false)
+ => PageContent.ReadAsync(() => EvaluateAsync(PageContent.Expression(includeShadow)));
///
/// Adds a <script> tag to the page by URL or inline content. For URL
diff --git a/src/PlaywrightNative/Firefox/FFPage.cs b/src/PlaywrightNative/Firefox/FFPage.cs
index ab38e92a..9cbdc389 100644
--- a/src/PlaywrightNative/Firefox/FFPage.cs
+++ b/src/PlaywrightNative/Firefox/FFPage.cs
@@ -20,6 +20,7 @@
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using PlaywrightNative.Helpers;
namespace PlaywrightNative.Firefox
{
@@ -372,19 +373,9 @@ internal IReadOnlyCollection SnapshotLifecycle()
///
/// Returns the full HTML content of the page.
///
- internal async Task ContentAsync()
- {
- return await EvaluateFunctionAsync(@"() => {
- let retVal = '';
- if (document.doctype) {
- retVal = new XMLSerializer().serializeToString(document.doctype);
- }
- if (document.documentElement) {
- retVal += document.documentElement.outerHTML;
- }
- return retVal;
- }").ConfigureAwait(false);
- }
+ /// Whether to serialize open shadow roots as declarative shadow DOM.
+ internal Task ContentAsync(bool includeShadow = false)
+ => EvaluateFunctionAsync("() => " + PageContent.Expression(includeShadow));
///
/// Sets the page HTML content.
diff --git a/src/PlaywrightNative/Firefox/FirefoxPage.cs b/src/PlaywrightNative/Firefox/FirefoxPage.cs
index 73110382..67033bf7 100644
--- a/src/PlaywrightNative/Firefox/FirefoxPage.cs
+++ b/src/PlaywrightNative/Firefox/FirefoxPage.cs
@@ -984,6 +984,11 @@ public Task DragAndDropAsync(
throw NotImplementedHelper.ForMethod(nameof(DragAndDropAsync));
}
+ /// Returns the page HTML, optionally including open shadow roots.
+ /// Whether to serialize open shadow roots as declarative shadow DOM.
+ /// The serialized document HTML.
+ internal Task ContentAsync(bool includeShadow) => _page.ContentAsync(includeShadow);
+
private async Task PauseInternalAsync()
{
int timeoutMs = TimeoutSettings.TimeoutMs(DefaultTimeout);
diff --git a/src/PlaywrightNative/Helpers/PageContent.cs b/src/PlaywrightNative/Helpers/PageContent.cs
index d2a99057..9e961193 100644
--- a/src/PlaywrightNative/Helpers/PageContent.cs
+++ b/src/PlaywrightNative/Helpers/PageContent.cs
@@ -42,6 +42,37 @@ internal static class PageContent
return retVal;
})()";
+ ///
+ /// JavaScript for official page.content({ includeShadow: true })
+ /// (injectedScript.documentContent): open shadow roots are serialized as
+ /// declarative shadow DOM via getHTML({ shadowRoots }).
+ ///
+ internal const string EvaluateWithShadowExpression =
+ @"(() => {
+ let content = '';
+ if (document.doctype) {
+ content = new XMLSerializer().serializeToString(document.doctype);
+ }
+ const root = document.documentElement;
+ if (!root) {
+ return content;
+ }
+ const shadowRoots = [];
+ const collectShadowRoots = node => {
+ for (const element of node.querySelectorAll('*')) {
+ if (element.shadowRoot) {
+ shadowRoots.push(element.shadowRoot);
+ collectShadowRoots(element.shadowRoot);
+ }
+ }
+ };
+ collectShadowRoots(document);
+ // getHTML() serializes children only, wrap them with the root element tags.
+ const emptyRoot = root.cloneNode(false).outerHTML;
+ const endTagIndex = emptyRoot.lastIndexOf('');
+ return content + emptyRoot.slice(0, endTagIndex) + root.getHTML({ shadowRoots }) + emptyRoot.slice(endTagIndex);
+ })()";
+
///
/// Official message when content() is evaluated while the document is
/// being replaced by a navigation.
@@ -49,6 +80,14 @@ internal static class PageContent
internal const string NavigationError =
"Unable to retrieve content because the page is navigating and changing the content.";
+ ///
+ /// Picks the content expression for .
+ ///
+ /// Whether to serialize open shadow roots.
+ /// The JavaScript expression.
+ internal static string Expression(bool includeShadow)
+ => includeShadow ? EvaluateWithShadowExpression : EvaluateExpression;
+
///
/// Runs and rewrites retriable evaluation
/// failures (destroyed context, mid-navigation) to
diff --git a/src/PlaywrightNative/MicrosoftSurfaceCompatExtensions.cs b/src/PlaywrightNative/MicrosoftSurfaceCompatExtensions.cs
index 8fc6d801..06c9f9b8 100644
--- a/src/PlaywrightNative/MicrosoftSurfaceCompatExtensions.cs
+++ b/src/PlaywrightNative/MicrosoftSurfaceCompatExtensions.cs
@@ -117,6 +117,31 @@ public static Task GoBackAsync(this IPage page, WaitUntilState waitUn
}
}
+ ///
+ /// Gets the full HTML contents of the page, including the doctype. Official
+ /// page.content({ includeShadow }), which Microsoft.Playwright does not expose yet.
+ ///
+ /// The page.
+ /// Call options.
+ /// The serialized document HTML.
+ public static Task ContentAsync(this IPage page, PageContentOptions options)
+ {
+ bool includeShadow = options?.IncludeShadow ?? false;
+ switch (page)
+ {
+ case Page chromium:
+ return chromium.ContentAsync(includeShadow);
+ case FirefoxPage firefox:
+ return firefox.ContentAsync(includeShadow);
+ case WKPage webkit:
+ return webkit.ContentAsync(includeShadow);
+ default:
+ return includeShadow
+ ? throw new NotSupportedException("includeShadow is not supported for " + page?.GetType().Name + ".")
+ : page.ContentAsync();
+ }
+ }
+
/// Legacy drag-and-drop with source/target positions.
public static Task DragAndDropAsync(
this IPage page,
diff --git a/src/PlaywrightNative/Page.cs b/src/PlaywrightNative/Page.cs
index 5b018218..749b8f11 100644
--- a/src/PlaywrightNative/Page.cs
+++ b/src/PlaywrightNative/Page.cs
@@ -1761,6 +1761,12 @@ public Task WaitForFileChooserAsync(float? timeout = default, Canc
public Task WaitForEventAsync(PlaywrightEvent pageEvent, Func predicate = null, float? timeout = null)
=> PageWaitForEventHelper.WaitAsync(this, pageEvent, predicate, timeout);
+ /// Returns the page HTML, optionally including open shadow roots.
+ /// Whether to serialize open shadow roots as declarative shadow DOM.
+ /// The serialized document HTML.
+ internal Task ContentAsync(bool includeShadow)
+ => _crPage.ContentAsync(includeShadow);
+
///
/// Returns the cached instance for .
///
diff --git a/src/PlaywrightNative/PageContentOptions.cs b/src/PlaywrightNative/PageContentOptions.cs
new file mode 100644
index 00000000..0a954f54
--- /dev/null
+++ b/src/PlaywrightNative/PageContentOptions.cs
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020 Dario Kondratiuk
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace PlaywrightNative
+{
+ ///
+ /// Options for .
+ ///
+ public class PageContentOptions
+ {
+ ///
+ /// Gets or sets a value indicating whether the contents of open shadow roots are
+ /// included as declarative shadow DOM, i.e. <template shadowrootmode="open">
+ /// elements nested inside their host elements. Closed shadow roots are never
+ /// included. Defaults to false.
+ ///
+ public bool? IncludeShadow { get; set; }
+ }
+}
diff --git a/src/PlaywrightNative/WebKit/WKPage.cs b/src/PlaywrightNative/WebKit/WKPage.cs
index b9853b40..dc1f8fe5 100644
--- a/src/PlaywrightNative/WebKit/WKPage.cs
+++ b/src/PlaywrightNative/WebKit/WKPage.cs
@@ -550,7 +550,7 @@ public Task TitleAsync()
///
public Task ContentAsync()
- => PageContent.ReadAsync(() => EvaluateExpressionAsync(PageContent.EvaluateExpression));
+ => ContentAsync(includeShadow: false);
///
/// Emulates CSS media features on the page via the inner target session. Maps
@@ -2069,6 +2069,12 @@ public Task DragAndDropAsync(
bool? strict = default)
=> DragAndDropHelper.RunAsync(this, source, target, sourcePosition, targetPosition, force, timeout, trial, steps, scroll, strict);
+ /// Returns the page HTML, optionally including open shadow roots.
+ /// Whether to serialize open shadow roots as declarative shadow DOM.
+ /// The serialized document HTML.
+ internal Task ContentAsync(bool includeShadow)
+ => PageContent.ReadAsync(() => EvaluateExpressionAsync(PageContent.Expression(includeShadow)));
+
///
/// Fires for a page opened from this page.
///