Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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.
*/
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Playwright;
using NUnit.Framework;
using PlaywrightNative.NUnit;

namespace PlaywrightNative.Tests
{
/// <summary>
/// Official <c>page-transition.spec.ts</c>.
/// </summary>
[TestFixture]
public class PageTransitionTests : PageTestEx
{
[PlaywrightTest("page-transition.spec.ts", "should not crash when filter transition completes")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public async Task ShouldNotCrashWhenFilterTransitionCompletes()
{
if (TestConstants.IsWebKit)
{
Assert.Ignore("official fixme(browserName === 'webkit'): Web process crashes on the compositor thread when a filter transitions to none");
return;
}

await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false);
await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false);
IPage page = await context.NewPageAsync().ConfigureAwait(false);
await page.SetContentAsync(@"
<style>
div { transition: filter 0.1s; }
.dim { filter: brightness(0.95); }
</style>
" + string.Concat(Enumerable.Repeat("<div>hello</div>", 10)) + @"
").ConfigureAwait(false);
bool crashed = false;
page.Crash += (_, _) => crashed = true;
for (int i = 0; i < 10 && !crashed; i++)
{
try
{
await page.EvaluateAsync(@"async () => {
const divs = [...document.querySelectorAll('div')];
const settled = () => Promise.all(divs.map(div => new Promise(f => div.addEventListener('transitionend', f, { once: true }))));
let promise = settled();
divs.forEach(div => div.classList.add('dim'));
await promise;
promise = settled();
divs.forEach(div => div.classList.remove('dim'));
await promise;
}").ConfigureAwait(false);
}
catch (PlaywrightException)
{
}
}

Assert.That(crashed, Is.False);
}
}
}
23 changes: 23 additions & 0 deletions src/PlaywrightNative.Tests/PageNetworkResponseParityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,29 @@ public async Task ShouldReturnUncompressedTextForBrotliEncoding()
Assert.That(await response.TextAsync().ConfigureAwait(false), Is.EqualTo(SimpleJsonBody));
}

[PlaywrightTest("page-network-response.spec.ts", "should return text for identity encoding")]
[Test]
[Timeout(30_000)]
public async Task ShouldReturnTextForIdentityEncoding()
{
EnsureServer();
const string text = "<div>hello</div>";
Server.SetRoute("/identity.html", async http =>
{
http.Response.StatusCode = 200;
http.Response.Headers["Content-Type"] = "text/html; charset=utf-8";
http.Response.Headers["Content-Encoding"] = "Identity";
await http.Response.WriteAsync(text).ConfigureAwait(false);
});

await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false);
await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false);
IPage page = await context.NewPageAsync().ConfigureAwait(false);
IResponse response = await page.GoToAsync(Prefix + "/identity.html").ConfigureAwait(false);
Assert.That(HeaderMap.All(response.Headers)["content-encoding"], Is.EqualTo("Identity"));
Assert.That(await response.TextAsync().ConfigureAwait(false), Is.EqualTo(text));
}

[PlaywrightTest("page-network-response.spec.ts", "should throw when requesting body of redirected response")]
[Test]
[Timeout(30_000)]
Expand Down
80 changes: 77 additions & 3 deletions src/PlaywrightNative.Tests/PageScreenshotParityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@
using System.Text.Json;
using System.Threading.Tasks;
using NUnit.Framework;
using PlaywrightNative.Helpers;
using PlaywrightNative.NUnit;
using PlaywrightNative.TestServer;

namespace PlaywrightNative.Tests
{
/// <summary>
/// Official <c>page-screenshot.spec.ts</c> titles.
/// Skipped (Node-only): <c>__testHookBeforeScreenshot</c>,
/// <c>should capture screenshots after layoutchanges in transitionend event</c>
/// (uses Node <c>window.builtins.Date</c>).
/// Do not edit leftover <c>PageScreenshot*</c> /
/// <c>ExpectScreenshot*</c>.
/// </summary>
Expand Down Expand Up @@ -863,6 +861,36 @@ public async Task ShouldNotCaptureCssAnimationsInShadowDom()
}
}

[PlaywrightTest("page-screenshot.spec.ts", "should stop animations that happen right before screenshot")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public async Task ShouldStopAnimationsThatHappenRightBeforeScreenshot()
{
await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false);
await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false);
IPage page = await context.NewPageAsync().ConfigureAwait(false);
await page.GoToAsync(Prefix + "/rotate-z.html").ConfigureAwait(false);

// Stop rotating bar.
await page.EvalOnSelectorAsync("div", "el => el.style.setProperty('animation', 'none')").ConfigureAwait(false);
byte[] buffer1;

// Start rotating bar right before screenshot.
ScreenshotDecorations.TestHookBeforeScreenshot.Value = () => page.EvalOnSelectorAsync("div", "el => el.style.removeProperty('animation')");
try
{
buffer1 = await page.ScreenshotAsync(new() { Animations = ScreenshotAnimations.Disabled }).ConfigureAwait(false);
}
finally
{
ScreenshotDecorations.TestHookBeforeScreenshot.Value = null;
}

await RafRafAsync(page).ConfigureAwait(false);
byte[] buffer2 = await page.ScreenshotAsync(new() { Animations = ScreenshotAnimations.Disabled }).ConfigureAwait(false);
Assert.That(ScreenshotComparer.Matches(buffer1, buffer2, threshold: 0.2f), Is.True);
}

[PlaywrightTest("page-screenshot.spec.ts", "should resume infinite animations")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
Expand Down Expand Up @@ -925,6 +953,52 @@ public async Task ShouldFireTransitionendForFiniteTransitions()
Assert.That(await page.EvaluateAsync<bool>("() => window['__TRANSITION_END']").ConfigureAwait(false), Is.True);
}

[PlaywrightTest("page-screenshot.spec.ts", "should capture screenshots after layoutchanges in transitionend event")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public async Task ShouldCaptureScreenshotsAfterLayoutchangesInTransitionendEvent()
{
await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false);
await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false);
IPage page = await context.NewPageAsync().ConfigureAwait(false);
await page.GoToAsync(Prefix + "/css-transition.html").ConfigureAwait(false);
ILocator div = page.Locator("div");

// Upstream reads the unpatched Date from window.builtins (test-only injected
// script); without a clock installed that is the page Date.
await div.EvaluateAsync<object>(@"el => {
el.addEventListener('transitionend', () => {
const builtinDate = (window.builtins && window.builtins.Date) || Date;
const time = builtinDate.now();
// Block main thread for 200ms, emulating heavy layout.
while (builtinDate.now() - time < 200) {}
const h1 = document.createElement('h1');
h1.textContent = 'woof-woof';
document.body.append(h1);
}, false);
}").ConfigureAwait(false);

// make sure transition is actually running
byte[] running1 = await page.ScreenshotAsync().ConfigureAwait(false);
await RafRafAsync(page).ConfigureAwait(false);
byte[] running2 = await page.ScreenshotAsync().ConfigureAwait(false);
Assert.That(running2, Is.Not.EqualTo(running1));

// 1. Make a screenshot that finishes all finite animations
// and triggers layout.
byte[] screenshot1 = await page.ScreenshotAsync(new() { Animations = ScreenshotAnimations.Disabled }).ConfigureAwait(false);

// 2. Make a second screenshot after h1 is on screen.
await Assertions.Expect(page.Locator("h1")).ToBeVisibleAsync().ConfigureAwait(false);
await Assertions.Expect(page.Locator("h1")).ToHaveTextAsync("woof-woof").ConfigureAwait(false);
byte[] screenshot2 = await page.ScreenshotAsync().ConfigureAwait(false);

// 3. Make sure both screenshots are equal, meaning that
// first screenshot actually was taken after transitionend
// changed layout.
Assert.That(screenshot2, Is.EqualTo(screenshot1));
}

[PlaywrightTest("page-screenshot.spec.ts", "should not change animation with playbackRate equal to 0")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
Expand Down
11 changes: 11 additions & 0 deletions src/PlaywrightNative/Helpers/ScreenshotDecorations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ internal static class ScreenshotDecorations
style.remove();
})()";

/// <summary>
/// Test-only hook (upstream <c>__testHookBeforeScreenshot</c>). Runs after the
/// page is prepared and right before the capture.
/// </summary>
internal static readonly AsyncLocal<Func<Task>> TestHookBeforeScreenshot = new AsyncLocal<Func<Task>>();

private static readonly ConcurrentDictionary<int, SemaphoreSlim> ScreenshotGates = new ConcurrentDictionary<int, SemaphoreSlim>();

/// <summary>
Expand Down Expand Up @@ -295,6 +301,11 @@ internal static async Task<byte[]> CaptureAsync(
await ScreenshotMask.ApplyAsync(page, mask, maskColor, tags).ConfigureAwait(false);
await WaitForFontsAsync(page).ConfigureAwait(false);

if (TestHookBeforeScreenshot.Value != null)
{
await TestHookBeforeScreenshot.Value().ConfigureAwait(false);
}

return await capture().ConfigureAwait(false);
}
catch (Exception ex) when (DestroyedContext.IsDestroyedContext(ex)
Expand Down
Loading