AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
26.06k stars 2.25k forks source link

Rendering of headless tests fail #15447

Open djalan opened 7 months ago

djalan commented 7 months ago

Describe the bug

Hello

The synchronization of my headless tests fail once in a while and I woule like to know how I could improve my code. I am following what is in the documentation, basically calling the following after a mouse click:

 AvaloniaHeadlessPlatform.ForceRenderTimerTick();
 Dispatcher.UIThread.RunJobs();

I observed that this is not sufficient, therefore I created this function:

public static void SyncAvalonia(int numberTimes = 5)
{
    for (int i = 0; i < numberTimes; i++)
    {
        AvaloniaHeadlessPlatform.ForceRenderTimerTick(1_000_000);
        Dispatcher.UIThread.RunJobs();
    }
}

But unfortunately, this does not work perfectly because I will sometimes end up with a partially rendered window. I know this because I am taking screenshots after every click I perform during my test.

Over time, I changed the maximum number of iterations of the sync loop from 3 to 5. I guess it is because our application is becoming more and more complex and there are more things to be processed by the Dispatcher.

Is there a way where we could be 100% sure that all the Dispatcher jobs were run? The documentation of the method already says "run ALL jobs", but I still need my loop to calll RunJobs more than once.

Moreover, the 1_000_000 argument for ForceRenderTimerTick is the maximum that works. If I increase it to 10_000_000, my code will hang at this line.

As I am writing this, I am thinking that maybe if I introduce a await Task.Delay(100ms) in my loop it would give time for the Dispatcher to be fully populated instead of calling the next RunJobs too quickly.

To Reproduce

It is not happening very often. Once every week. I don't think I could reproduce this.

Expected behavior

Next window is fully rendered after clicking an element in my UI.

Avalonia version

11.0.9

OS

Windows

Additional context

I am using headless tests a bit differently than your documentation.

I am not acting on a ViewModel, but instead I am navigating from one screen to the other by clicking on different elements. I am using Headless to perform what is often known End-to-End testing or GUI tests.

maxkatz6 commented 7 months ago

@djalan you need to run Dispatcher jobs first, and only then timer tick. As otherwise render thread won't have anything to render.

But either way, we have tests for rendering into a bitmap: https://github.com/AvaloniaUI/Avalonia/blob/a32a8726a36665282b8ee70985154c20fe8123f6/tests/Avalonia.Headless.UnitTests/RenderingTests.cs#L72

For this bug we definitely need a minimal repro, as it works for me.

Note, CaptureRenderedFrame used in our tests is just a handy extension on top of ForceRenderTimerTick: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs#L22-L24

djalan commented 7 months ago

@maxkatz6 I changed my code and most of the clicks in my application work well by simply using:

Dispatcher.UIThread.RunJobs();
AvaloniaHeadlessPlatform.ForceRenderTimerTick();

On the other hand, I experience problems when we use IPageTransition like in the below image. I tried using my loop to sync over 30 times with 10_000_000 ticks, but I still can't land on the final frame when I click the next button.

I had success in other parts of my application by calling my loop 4-5 times and with 100_000 or 1_000_000 ticks, but this one below looks like a dead end.

On two rare occassions, but it is not a constant behavior, I had success looping 20x with 10_000_000 ticks. I was able to see half of page 2, but it does not happen all the time. Increasing the numbers can end up yielding less frames.

PageTransition

I was able to sync (with a few loop calls) another part of the application that uses page transition too. I will compare the classes tomorrow.