File: Integration\Playwright\ResourcePaneMotionTests.cs
Web Access
Project: src\tests\Aspire.Dashboard.Playwright.Tests\Aspire.Dashboard.Playwright.Tests.csproj (Aspire.Dashboard.Playwright.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Dashboard.Tests.Integration.Playwright.Infrastructure;
using Aspire.Dashboard.Utils;
using Aspire.TestUtilities;
using Microsoft.Playwright;
using Xunit;
 
namespace Aspire.Dashboard.Tests.Integration.Playwright;
 
[RequiresFeature(TestFeature.Playwright)]
public sealed class ResourcePaneMotionTests(ResourcePaneResponsiveTests.MetadataResourcesFixture fixture)
    : PlaywrightTestsBase<ResourcePaneResponsiveTests.MetadataResourcesFixture>(fixture)
{
    [Theory]
    [InlineData(420, false)]
    [InlineData(560, false)]
    [InlineData(420, true)]
    [InlineData(560, true)]
    [OuterloopTest("Resource-intensive Playwright browser test")]
    public async Task CollapseRestore_AnimatesClippedTrackAndRetainsSavedWidth(int width, bool reducedMotion)
    {
        await RunTestAsync(async page =>
        {
            await page.SetViewportSizeAsync(1440, 900);
            await page.EmulateMediaAsync(new() { ReducedMotion = reducedMotion ? ReducedMotion.Reduce : ReducedMotion.NoPreference });
            await page.AddInitScriptAsync($"localStorage.setItem('{BrowserStorageKeys.ResourcePaneWidth}', '{width}')");
            await page.GotoAsync("/resources/cache?pane=tags");
            var separator = page.GetByRole(AriaRole.Separator, new() { Name = Resources.Layout.ResourcePaneResize });
            await Assertions.Expect(separator).ToHaveAttributeAsync("aria-disabled", "false");
            await Assertions.Expect(separator).ToHaveAttributeAsync("aria-valuenow", width.ToString(System.Globalization.CultureInfo.InvariantCulture));
            var toggle = page.Locator(".resource-header-row .resource-pane-toggle-button");
            var pane = page.Locator(".resource-pane");
            var originalRow = await page.Locator(".resource-row[data-resource-name]").First.BoundingBoxAsync();
            Assert.NotNull(originalRow);
            for (var iteration = 0; iteration < 3; iteration++)
            {
                foreach (var collapse in new[] { true, false })
                {
                    await page.EvaluateAsync("""
                        () => {
                            const layout=document.querySelector('.resources-layout');
                            window.collapseFrames=new Promise(resolve => {
                                const observer=new MutationObserver(() => {
                                    observer.disconnect();
                                    const frames=[];
                                    const sample=() => {
                                        const pane=layout.querySelector('.resource-pane'), row=pane.querySelector('.resource-row[data-resource-name]');
                                        const b=pane.getBoundingClientRect(),r=row.getBoundingClientRect(),
                                            content=layout.querySelector('.resource-content'),c=content.getBoundingClientRect(),
                                            workspace=layout.getBoundingClientRect();
                                        frames.push([b.width,r.width,r.height,document.documentElement.scrollWidth,
                                            document.documentElement.clientWidth,c.width,c.left,c.right,workspace.left,
                                            workspace.right,b.right,getComputedStyle(content).transform==='none'?0:1]);
                                        if(layout.getAnimations().some(a => a.playState==='running')) requestAnimationFrame(sample);
                                        else resolve(frames);
                                    };
                                    requestAnimationFrame(sample);
                                });
                                observer.observe(layout,{attributes:true,attributeFilter:['class']});
                            });
                        }
                        """);
                    await toggle.FocusAsync();
                    await toggle.PressAsync("Enter");
                    var frames = await page.EvaluateAsync<double[][]>("() => window.collapseFrames");
                    Assert.NotEmpty(frames);
                    Assert.Equal(collapse ? 0 : width, frames[^1][0]);
                    Assert.Equal(!reducedMotion, frames.Any(f => f[0] > .1 && f[0] < width - .1));
                    for (var i = 1; i < frames.Length; i++)
                    {
                        Assert.True(collapse ? frames[i][0] <= frames[i - 1][0] : frames[i][0] >= frames[i - 1][0]);
                        Assert.True(collapse ? frames[i][5] >= frames[i - 1][5] : frames[i][5] <= frames[i - 1][5]);
                    }
                    Assert.All(frames, frame =>
                    {
                        Assert.Equal(originalRow.Width, frame[1]);
                        Assert.Equal(originalRow.Height, frame[2]);
                        Assert.True(frame[3] <= frame[4]);
                        Assert.InRange(Math.Abs(frame[7] - frame[9]), 0, .5);
                        Assert.InRange(Math.Abs(frame[6] - frame[10]), 0, .5);
                        Assert.InRange(Math.Abs(frame[5] - (frame[9] - frame[8] - frame[0])), 0, .5);
                        Assert.Equal(0, frame[11]);
                    });
                    await Assertions.Expect(toggle).ToBeFocusedAsync();
                    await Assertions.Expect(toggle).ToHaveAttributeAsync("aria-expanded", collapse ? "false" : "true");
                    if (collapse)
                    {
                        await Assertions.Expect(pane).Not.ToBeVisibleAsync();
                        await Assertions.Expect(pane).ToHaveAttributeAsync("inert", "");
                        await Assertions.Expect(separator).ToHaveCountAsync(0);
                        var content = await page.Locator(".resource-content").BoundingBoxAsync();
                        var layout = await page.Locator(".resources-layout").BoundingBoxAsync();
                        Assert.NotNull(content);
                        Assert.NotNull(layout);
                        Assert.Equal(layout.Width, content.Width);
                    }
                    else
                    {
                        await Assertions.Expect(pane).ToBeVisibleAsync();
                        await Assertions.Expect(pane).Not.ToHaveAttributeAsync("inert", "");
                        await Assertions.Expect(separator).ToHaveAttributeAsync("aria-valuenow", width.ToString(System.Globalization.CultureInfo.InvariantCulture));
                    }
                    Assert.Equal(0, await page.Locator(".pane-full-canvas,.pane-selection-reveal").CountAsync());
                }
            }
        });
    }
 
    [Theory]
    [InlineData(1440, false, false)]
    [InlineData(1440, false, true)]
    [InlineData(2000, false, false)]
    [InlineData(2000, false, true)]
    [InlineData(2000, true, false)]
    [InlineData(2000, true, true)]
    [InlineData(769, false, false)]
    [InlineData(769, true, true)]
    [OuterloopTest("Resource-intensive Playwright browser test")]
    public async Task SelectionReveal_PreservesRigidDetailsAndFocusesAfterSettling(int width, bool reducedMotion, bool keyboard)
    {
        await RunTestAsync(async page =>
        {
            await page.SetViewportSizeAsync(width, 1000);
            await page.EmulateMediaAsync(new() { ReducedMotion = reducedMotion ? ReducedMotion.Reduce : ReducedMotion.NoPreference });
            await page.GotoAsync("/resources/cache?pane=tags");
            var pane = page.Locator(".resource-pane");
            if (width <= 768)
            {
                await page.Locator(".resource-picker").ClickAsync();
                await Assertions.Expect(pane).ToBeVisibleAsync();
            }
            else
            {
                await page.GetByRole(AriaRole.Separator, new() { Name = Resources.Layout.ResourcePaneResize }).PressAsync("Home");
            }
            await pane.Locator(".resource-pane-canvas-toggle").ClickAsync();
            await page.WaitForFunctionAsync("""
                () => Math.abs(document.querySelector('.resource-pane').getBoundingClientRect().width -
                    document.querySelector('.resources-layout').getBoundingClientRect().width) < .1
                """);
            var navBox = await page.Locator(".resource-tabs-nav").BoundingBoxAsync();
            var toolbarBox = await pane.Locator(".resource-pane-head").BoundingBoxAsync();
            Assert.NotNull(navBox);
            Assert.NotNull(toolbarBox);
            await page.EvaluateAsync("""
                () => {
                    window.selectionRevealFrames = new Promise(resolve => {
                        const layout = document.querySelector('.resources-layout');
                        const observer = new MutationObserver(() => {
                            if (!layout.classList.contains('pane-selection-reveal')) return;
                            observer.disconnect();
                            const frames = [], start = performance.now();
                            window.selectionRevealTitles = [];
                            window.selectionRevealResources = [];
                            window.selectionRevealSubscriptions = [];
                            const sample = () => {
                                const pane = layout.querySelector('.resource-pane').getBoundingClientRect();
                                const content = layout.querySelector('.resource-content');
                                const details = content.getBoundingClientRect();
                                const nav = document.querySelector('.resource-tabs-nav').getBoundingClientRect();
                                const toolbar = layout.querySelector('.resource-pane-head').getBoundingClientRect();
                                frames.push([pane.width, details.width, details.x, content.inert ? 1 : 0,
                                    nav.x, nav.y, nav.width, nav.height, toolbar.y, toolbar.height]);
                                window.selectionRevealTitles.push(layout.querySelector('h1.resource-title')?.textContent.trim());
                                window.selectionRevealResources.push(layout.querySelector('.resource-overview')?.dataset.resourceName);
                                window.selectionRevealSubscriptions.push(layout.querySelector('.resource-overview')?.dataset.resourceSubscription);
                                if (layout.classList.contains('pane-selection-reveal') && performance.now() - start < 2000)
                                    requestAnimationFrame(sample);
                                else resolve(frames);
                            };
                            requestAnimationFrame(sample);
                        });
                        observer.observe(layout, {attributes:true, attributeFilter:['class']});
                    });
                }
                """);
            var row = pane.Locator("[data-resource-name='api'] .resource-row-link").First;
            if (keyboard)
            {
                await row.FocusAsync();
                await row.PressAsync("Space");
            }
            else
            {
                await row.ClickAsync();
            }
            var frames = await page.EvaluateAsync<double[][]>("() => window.selectionRevealFrames");
            await Assertions.Expect(page.Locator(".pane-selection-reveal")).ToHaveCountAsync(0);
            await Assertions.Expect(page.Locator("h1.resource-title")).ToHaveTextAsync("api");
            await Assertions.Expect(page.Locator("h1.resource-title")).ToBeFocusedAsync();
            await Assertions.Expect(page.Locator(".resource-content")).Not.ToHaveAttributeAsync("inert", "");
            var layoutBox = await page.Locator(".resources-layout").BoundingBoxAsync();
            Assert.NotNull(layoutBox);
            var expectedDetailsWidth = layoutBox.Width - (width > 768 ? 420 : 0);
            Assert.NotEmpty(frames);
            Assert.All(await page.EvaluateAsync<string[]>("() => window.selectionRevealTitles"),
                title => Assert.Equal("api", title));
            Assert.All(await page.EvaluateAsync<string[]>("() => window.selectionRevealResources"),
                resource => Assert.Equal("api", resource));
            Assert.All(await page.EvaluateAsync<string[]>("() => window.selectionRevealSubscriptions"),
                resource => Assert.Equal("api", resource));
            Assert.All(frames, frame =>
            {
                Assert.InRange(Math.Abs(frame[1] - expectedDetailsWidth), 0, .1);
                Assert.Equal(navBox.X, frame[4]);
                Assert.Equal(navBox.Y, frame[5]);
                Assert.Equal(navBox.Width, frame[6]);
                Assert.Equal(navBox.Height, frame[7]);
                if (frame[0] > 0)
                {
                    Assert.Equal(toolbarBox.Y, frame[8]);
                    Assert.Equal(toolbarBox.Height, frame[9]);
                }
            });
            if (width > 768)
            {
                var intermediate = frames.Where(frame => frame[0] > 420.1 && frame[0] < layoutBox.Width - .1).ToList();
                Assert.Equal(!reducedMotion, intermediate.Count > 0);
                Assert.All(intermediate, frame =>
                {
                    Assert.Equal(1, frame[3]);
                    Assert.InRange(Math.Abs(frame[2] - frame[0]), 0, .1);
                });
            }
            else
            {
                await Assertions.Expect(pane).Not.ToBeVisibleAsync();
            }
        });
    }
 
    [Theory]
    [InlineData("selection")]
    [InlineData("reverse")]
    [InlineData("back")]
    [InlineData("reload")]
    [InlineData("focus")]
    [InlineData("early-focus")]
    [OuterloopTest("Resource-intensive Playwright browser test")]
    public async Task SelectionReveal_InterruptionDoesNotRestoreStaleContentOrStealFocus(string interruption)
    {
        await RunTestAsync(async page =>
        {
            await page.SetViewportSizeAsync(2000, 1000);
            await page.GotoAsync("/resources/cache?pane=tags");
            var pane = page.Locator(".resource-pane");
            await pane.Locator(".resource-pane-canvas-toggle").ClickAsync();
            await page.WaitForFunctionAsync("() => document.querySelector('.resource-pane').getBoundingClientRect().width === 2000");
            await page.EvaluateAsync("""
                () => {
                    window.interruptedSelectionFrames = [];
                    const start = performance.now();
                    const sample = () => {
                        const layout = document.querySelector('.resources-layout');
                        if (!layout.classList.contains('pane-full-canvas')) {
                            const selected = layout.querySelector('.resource-row:has(.resource-row-link[aria-current="page"])');
                            window.interruptedSelectionFrames.push([
                                selected?.dataset.resourceName,
                                layout.querySelector('.resource-overview')?.dataset.resourceName,
                                selected?.querySelector('.resource-row-name')?.textContent.trim(),
                                layout.querySelector('h1.resource-title')?.textContent.trim(),
                                layout.querySelector('.resource-overview')?.dataset.resourceSubscription
                            ]);
                        }
                        if (performance.now()-start < 2000) requestAnimationFrame(sample);
                    };
                    requestAnimationFrame(sample);
                }
                """);
            if (interruption == "early-focus")
            {
                // Move focus before the server can start its reveal interop, not only
                // after the reveal class has reached the browser.
                await pane.GetByRole(AriaRole.Searchbox).EvaluateAsync("""
                    field => document.querySelector('.resources-layout').addEventListener('click',
                        () => field.focus(), { once:true })
                    """);
            }
            await pane.Locator("[data-resource-name='api'] .resource-row-link").First.ClickAsync();
            await Assertions.Expect(page.Locator(".pane-selection-reveal")).ToHaveCountAsync(1);
            switch (interruption)
            {
                case "selection":
                    await pane.Locator("[data-resource-name='frontend'] .resource-row-link").DispatchEventAsync("click");
                    break;
                case "reverse":
                    await pane.Locator(".resource-pane-canvas-toggle").DispatchEventAsync("click");
                    break;
                case "back":
                    await page.GoBackAsync();
                    break;
                case "reload":
                    await page.ReloadAsync();
                    break;
                case "focus":
                    await pane.GetByRole(AriaRole.Searchbox).FocusAsync();
                    break;
            }
            await Assertions.Expect(page.Locator(".pane-selection-reveal")).ToHaveCountAsync(0);
            var expected = interruption == "selection" ? "frontend" : interruption == "back" ? "cache" : "api";
            if (interruption == "reverse")
            {
                await Assertions.Expect(page.Locator(".resource-content")).ToHaveAttributeAsync("inert", "");
                await Assertions.Expect(pane.Locator("[data-resource-name='api'] .resource-row-link").First).ToHaveAttributeAsync("aria-current", "page");
                await pane.Locator(".resource-pane-canvas-toggle").DispatchEventAsync("click");
            }
            else
            {
                await Assertions.Expect(page.Locator(".resource-content")).Not.ToHaveAttributeAsync("inert", "");
            }
            await Assertions.Expect(page.Locator("h1.resource-title")).ToHaveTextAsync(expected);
            var interruptionFrames = await page.EvaluateAsync<string[][]>("() => window.interruptedSelectionFrames ?? []");
            if (interruption != "reload")
            {
                Assert.NotEmpty(interruptionFrames);
            }
            Assert.All(interruptionFrames, frame =>
            {
                Assert.Equal(frame[0], frame[1]);
                Assert.Equal(frame[2], frame[3]);
                Assert.Equal(frame[0], frame[4]);
            });
            if (interruption is "focus" or "early-focus")
            {
                await Assertions.Expect(pane.GetByRole(AriaRole.Searchbox)).ToBeFocusedAsync();
            }
            if (interruption is "back" or "reload")
            {
                Assert.Empty(await page.Locator(".resources-layout").EvaluateAsync<string[]>(
                    """
                    el => {
                        const transition = getComputedStyle(el).transitionProperty;
                        return el.getAnimations().map(animation =>
                            `${animation.transitionProperty}: ${animation.playState}; transition=${transition}; class=${el.className}`);
                    }
                    """));
            }
        });
    }
 
    [Theory]
    [InlineData(false, false)]
    [InlineData(false, true)]
    [InlineData(true, false)]
    [InlineData(true, true)]
    [OuterloopTest("Resource-intensive Playwright browser test")]
    public async Task CanvasReturn_AfterKindChangeRestoresLogicalWidth(bool reducedMotion, bool keyboard)
    {
        await RunTestAsync(async page =>
        {
            await page.SetViewportSizeAsync(2000, 1000);
            await page.EmulateMediaAsync(new() { ReducedMotion = reducedMotion ? ReducedMotion.Reduce : ReducedMotion.NoPreference });
            await page.GotoAsync("/resources?pane=tags");
            var pane = page.Locator(".resource-pane");
            var parameter = pane.Locator("[data-resource-name='service-key']");
            await Assertions.Expect(parameter).ToBeVisibleAsync();
            var separator = page.GetByRole(AriaRole.Separator, new() { Name = Resources.Layout.ResourcePaneResize });
            await separator.PressAsync("End");
            var expectedWidth = await separator.EvaluateAsync<double>("e => Number(e.getAttribute('aria-valuenow'))");
            var toggle = pane.Locator(".resource-pane-canvas-toggle");
            await ToggleAsync();
            await Assertions.Expect(toggle).ToHaveAttributeAsync("aria-expanded", "true");
 
            // Kind changes must not change the saved width while the resizer is unmounted.
            var parametersToggle = pane.Locator(".resource-kind-toggle").Nth(1);
            await parametersToggle.ClickAsync();
            await Assertions.Expect(parameter).ToHaveCountAsync(0);
            await parametersToggle.ClickAsync();
            await Assertions.Expect(parameter).ToHaveCountAsync(1);
            await ToggleAsync();
            await AssertReturnedAsync();
 
            await ToggleAsync();
            await Assertions.Expect(toggle).ToHaveAttributeAsync("aria-expanded", "true");
            await ToggleAsync();
            await AssertReturnedAsync();
            await page.ReloadAsync();
            await AssertReturnedAsync();
 
            async Task ToggleAsync()
            {
                if (keyboard)
                {
                    await toggle.FocusAsync();
                    await toggle.PressAsync("Space");
                }
                else
                {
                    await toggle.ClickAsync();
                }
            }
 
            async Task AssertReturnedAsync()
            {
                await Assertions.Expect(toggle).ToHaveAttributeAsync("aria-expanded", "false");
                await Assertions.Expect(page.Locator(".resource-content")).Not.ToHaveAttributeAsync("inert", "");
                await page.WaitForFunctionAsync("""
                    width => {
                        const pane = document.querySelector('.resource-pane').getBoundingClientRect();
                        const content = document.querySelector('.resource-content').getBoundingClientRect();
                        return Math.abs(pane.width - width) < 1 && Math.abs(content.x - pane.right) < 1;
                    }
                    """, expectedWidth);
                await Assertions.Expect(page.GetByRole(AriaRole.Separator, new() { Name = Resources.Layout.ResourcePaneResize }))
                    .ToHaveAttributeAsync("aria-valuenow", expectedWidth.ToString(System.Globalization.CultureInfo.InvariantCulture));
            }
        });
    }
 
    [Theory]
    [InlineData(769, 1024, false)]
    [InlineData(1440, 900, false)]
    [InlineData(769, 1024, true)]
    [InlineData(1440, 900, true)]
    [OuterloopTest("Resource-intensive Playwright browser test")]
    public async Task CanvasMotion_MovesOnlyPaneAndCanReverseOrNavigate(int width, int height, bool reducedMotion)
    {
        await RunTestAsync(async page =>
        {
            await page.SetViewportSizeAsync(width, height);
            await page.EmulateMediaAsync(new() { ColorScheme = ColorScheme.Dark, ReducedMotion = reducedMotion ? ReducedMotion.Reduce : ReducedMotion.NoPreference });
            await page.GotoAsync("/resources?pane=tags");
            await Assertions.Expect(page.Locator(".resource-header-actions").GetByRole(AriaRole.Button).First).ToBeVisibleAsync();
            if (width <= 768)
            {
                await page.Locator(".resource-picker").ClickAsync();
            }
            var layout = page.Locator(".resources-layout");
            var pane = page.Locator(".resource-pane");
            var toggle = pane.Locator(".resource-pane-canvas-toggle");
            var header = page.Locator(".resource-tabs-nav");
            var headerBox = await header.BoundingBoxAsync();
            var toolbarBox = await pane.Locator(".resource-pane-head").BoundingBoxAsync();
            Assert.NotNull(headerBox);
            Assert.NotNull(toolbarBox);
            await page.Context.Tracing.StartAsync(new() { Screenshots = true, Snapshots = true });
            try
            {
                await SampleToggleAsync(expanding: true);
                await SampleToggleAsync(expanding: false);
                await toggle.DispatchEventAsync("click");
                await Assertions.Expect(layout).ToHaveClassAsync(new System.Text.RegularExpressions.Regex("pane-full-canvas"));
                await toggle.DispatchEventAsync("click");
                await Assertions.Expect(layout).Not.ToHaveClassAsync(new System.Text.RegularExpressions.Regex("pane-full-canvas"));
                await toggle.DispatchEventAsync("click");
                await Assertions.Expect(layout).ToHaveClassAsync(new System.Text.RegularExpressions.Regex("pane-full-canvas"));
                await pane.Locator("[data-resource-name='api'] .resource-row-link").First.ClickAsync(new() { Force = true });
                await Assertions.Expect(layout).Not.ToHaveClassAsync(new System.Text.RegularExpressions.Regex("pane-full-canvas|pane-motion-enabled"));
                await page.GoBackAsync();
                await Assertions.Expect(layout).Not.ToHaveClassAsync(new System.Text.RegularExpressions.Regex("pane-full-canvas|pane-motion-enabled"));
                await page.GoForwardAsync();
                await Assertions.Expect(layout).Not.ToHaveClassAsync(new System.Text.RegularExpressions.Regex("pane-full-canvas|pane-motion-enabled"));
            }
            finally
            {
                await page.Context.Tracing.StopAsync(new() { Path = Path.Combine(AppContext.BaseDirectory, $"resource-pane-motion-{width}-{reducedMotion}.zip") });
            }
 
            async Task SampleToggleAsync(bool expanding)
            {
                var start = await pane.BoundingBoxAsync();
                Assert.NotNull(start);
                await page.EvaluateAsync("""
                    () => {
                        const pane = document.querySelector('.resource-pane');
                        const header = document.querySelector('.resource-context-header');
                        const nav = document.querySelector('.resource-tabs-nav');
                        const layout = document.querySelector('.resources-layout');
                        const before = layout.classList.contains('pane-full-canvas');
                        window.paneMotionFrames = new Promise(resolve => {
                            const frames = [];
                            const observer = new MutationObserver(() => {
                                if (layout.classList.contains('pane-full-canvas') === before) return;
                                observer.disconnect();
                                const start = performance.now();
                                function sample() {
                                    const p = pane.getBoundingClientRect(), h = header.getBoundingClientRect(), n = nav.getBoundingClientRect();
                                    const toolbar = pane.querySelector('.resource-pane-head').getBoundingClientRect();
                                    const controls = [...pane.querySelector('.resource-pane-head').children].map(el => el.getBoundingClientRect()).filter(r => r.width > 0);
                                    const ordered = controls.every((r, i) => i === 0 || r.x >= controls[i - 1].right - 1);
                                    frames.push([p.width, h.x, h.y, h.width, h.height, n.x, n.y, n.width, n.height, toolbar.y, toolbar.height, ordered ? 1 : 0]);
                                    if (performance.now() - start < 500) requestAnimationFrame(sample);
                                    else resolve(frames);
                                }
                                requestAnimationFrame(sample);
                            });
                            observer.observe(layout, {attributes:true, attributeFilter:['class']});
                        });
                    }
                    """);
                await toggle.ClickAsync(new() { Force = true });
                var frames = await page.EvaluateAsync<double[][]>("() => window.paneMotionFrames");
                var end = await pane.BoundingBoxAsync();
                Assert.NotNull(end);
                Assert.NotEqual(start.Width, end.Width);
                Assert.All(frames, frame =>
                {
                    Assert.Equal(headerBox.X, frame[5]);
                    Assert.Equal(headerBox.Y, frame[6]);
                    Assert.Equal(headerBox.Width, frame[7]);
                    Assert.Equal(headerBox.Height, frame[8]);
                    Assert.Equal(toolbarBox.Y, frame[9]);
                    Assert.Equal(toolbarBox.Height, frame[10]);
                    Assert.Equal(1, frame[11]);
                });
                await Assertions.Expect(page.GetByRole(AriaRole.Group, new() { Name = "Layout", Exact = true })).ToHaveCountAsync(expanding ? 0 : width > 768 ? 1 : 0);
                var intermediate = frames.Any(frame => frame[0] > Math.Min(start.Width, end.Width) + 0.1 && frame[0] < Math.Max(start.Width, end.Width) - 0.1);
                Assert.Equal(!reducedMotion, intermediate);
                await page.ScreenshotAsync(new() { Path = Path.Combine(AppContext.BaseDirectory, $"resource-pane-motion-{width}-{reducedMotion}-{expanding}.png") });
            }
        });
    }
}