hydration - CoasterFan5/Blazor-Playwright GitHub Wiki

Hydration

This bug is why I made this repo in the first place. Take a look at this test:

namespace App.Tests;

public class GreatTest() : PageTest
{
    [Fact]
    public async Task InteractiveTest()
    {
        await Page.GotoAsync("http://localhost:5155/Inputs");
        await Expect(Page.GetByTestId("i1")).ToBeVisibleAsync();
        await Expect(Page.GetByTestId("i2")).ToBeVisibleAsync();
        await Expect(Page.GetByTestId("b3")).ToBeVisibleAsync();
        await Expect(Page.GetByTestId("message")).ToBeHiddenAsync();

        await Page.GetByTestId("i1").FillAsync("c");
        await Page.GetByTestId("i2").FillAsync("d");
        await Page.GetByTestId("b3").ClickAsync();


        await Expect(Page.GetByTestId("message")).ToContainTextAsync("success");
    }

}

This test Should pass, especially on this code below.

@page "/Inputs"
@inject ILogger<Inputs> Logger

<PageTitle>Inputs</PageTitle>

<input data-testid="i1" @bind=c>
<input data-testid="i2" @bind=d>
<button data-testid="b3" @onclick=ClickEvent>Submit</button>
@if (!string.IsNullOrEmpty(message))
{
    <p data-testid="message">
        @message
    </p>
}
@code {
    protected string? c;
    protected string? d;
    protected string? message;

    protected async Task ClickEvent()
    {
        Logger.LogInformation(c);
        Logger.LogInformation(d);
        if (c == "c" && d == "d")
        {
            message = "success";
        }
        else
        {
            message = "fail";
        }
    }

}

However, when using Blazor server mode, it will fail in actions and on the first build locally. This is one of those bugs that no one seems to really document, but don't worry, i've found a solution.

This error occurs because of a problem within the interaction between Playwright and Blazor. Playwright goes to the page and types things very quickly, so quickly in fact that blazor has not quite finished initalizing itself. So we need a way to make Playwright wait for Blazor to be ready.

We can accomplish this with the OnAfterRenderAsync method.

// MainLayout.Razor
@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await JSRuntime.InvokeVoidAsync("blazorReady");
    }
}

JSRuntime??? In my Blazor app??? Yes, We need to use the JS Runtime here because otherwise we would have to tell blazor to re-render the component, and we probably don't want to run a full rerender of our main layout every single time.

Now, we need to declare this JS function, using App.razor file:

// App.Razor
<head>
  <script>
        window.blazorReady = () => {
            const a = document.createElement(`div`);
            a.setAttribute(`data-testid`, `blazorReady`);
            document.body.appendChild(a)
        }
    </script>
</head>

With these changes, the test will now pass in actions. Congrats!

⚠️ **GitHub.com Fallback** ⚠️